public inbox for [email protected]
help / color / mirror / Atom feed[PATCH v5 1/3] Move callback-call from ReadPageInternal to XLogReadRecord.
199+ messages / 20 participants
[nested] [flat]
* [PATCH v5 1/3] Move callback-call from ReadPageInternal to XLogReadRecord.
@ 2019-09-05 11:21 Kyotaro Horiguchi <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Kyotaro Horiguchi @ 2019-09-05 11:21 UTC (permalink / raw)
WAL reader facility used to call given page-reader callback function
in the ReadPageInternal, which is two levels below from
ReadRecord. That makes things a bit complex, but furthermore we are
going to have additional callbacks for encryption, compression or
something like that works before or after reading pages. Just adding
them as new callback makes things messier. If the caller of the
current ReadRecord could call page fetching function directly, things
would get quite easier.
As the first step of that change, this patch moves the place where
that callbacks are called by 1 level above
ReadPageInternal. XLogPageRead uses a loop over new function
XLogNeedData and the callback directly instead of ReadPageInternal.
---
src/backend/access/transam/xlog.c | 15 +-
src/backend/access/transam/xlogreader.c | 355 ++++++++++++++++---------
src/backend/access/transam/xlogutils.c | 10 +-
src/backend/replication/logical/logicalfuncs.c | 4 +-
src/backend/replication/walsender.c | 10 +-
src/bin/pg_rewind/parsexlog.c | 17 +-
src/bin/pg_waldump/pg_waldump.c | 8 +-
src/include/access/xlogreader.h | 30 ++-
src/include/access/xlogutils.h | 2 +-
src/include/replication/logicalfuncs.h | 2 +-
10 files changed, 292 insertions(+), 161 deletions(-)
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index e651a841bb..acd4ed89c4 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -884,7 +884,7 @@ static bool InstallXLogFileSegment(XLogSegNo *segno, char *tmppath,
static int XLogFileRead(XLogSegNo segno, int emode, TimeLineID tli,
int source, bool notfoundOk);
static int XLogFileReadAnyTLI(XLogSegNo segno, int emode, int source);
-static int XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
+static bool XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *readBuf,
TimeLineID *readTLI);
static bool WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
@@ -11521,7 +11521,7 @@ CancelBackup(void)
* XLogPageRead() to try fetching the record from another source, or to
* sleep and retry.
*/
-static int
+static bool
XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetRecPtr, char *readBuf, TimeLineID *readTLI)
{
@@ -11580,7 +11580,8 @@ retry:
readLen = 0;
readSource = 0;
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
}
@@ -11675,7 +11676,8 @@ retry:
goto next_record_is_invalid;
}
- return readLen;
+ xlogreader->readLen = readLen;
+ return true;
next_record_is_invalid:
lastSourceFailed = true;
@@ -11689,8 +11691,9 @@ next_record_is_invalid:
/* In standby-mode, keep trying */
if (StandbyMode)
goto retry;
- else
- return -1;
+
+ xlogreader->readLen = -1;
+ return false;
}
/*
diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c
index c6faf48d24..1d8d470158 100644
--- a/src/backend/access/transam/xlogreader.c
+++ b/src/backend/access/transam/xlogreader.c
@@ -36,8 +36,8 @@ static bool ValidXLogRecordHeader(XLogReaderState *state, XLogRecPtr RecPtr,
XLogRecPtr PrevRecPtr, XLogRecord *record, bool randAccess);
static bool ValidXLogRecord(XLogReaderState *state, XLogRecord *record,
XLogRecPtr recptr);
-static int ReadPageInternal(XLogReaderState *state, XLogRecPtr pageptr,
- int reqLen);
+static bool XLogNeedData(XLogReaderState *state, XLogRecPtr pageptr,
+ int reqLen, bool includes_page_header);
static void report_invalid_record(XLogReaderState *state, const char *fmt,...) pg_attribute_printf(2, 3);
static void ResetDecoder(XLogReaderState *state);
@@ -100,7 +100,7 @@ XLogReaderAllocate(int wal_segment_size, XLogPageReadCB pagereadfunc,
/* system_identifier initialized to zeroes above */
state->private_data = private_data;
/* ReadRecPtr and EndRecPtr initialized to zeroes above */
- /* readSegNo, readOff, readLen, readPageTLI initialized to zeroes above */
+ /* readSegNo, readLen, readPageTLI initialized to zeroes above */
state->errormsg_buf = palloc_extended(MAX_ERRORMSG_LEN + 1,
MCXT_ALLOC_NO_OOM);
if (!state->errormsg_buf)
@@ -224,7 +224,6 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
uint32 targetRecOff;
uint32 pageHeaderSize;
bool gotheader;
- int readOff;
/*
* randAccess indicates whether to verify the previous-record pointer of
@@ -276,15 +275,21 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
* byte to cover the whole record header, or at least the part of it that
* fits on the same page.
*/
- readOff = ReadPageInternal(state,
- targetPagePtr,
- Min(targetRecOff + SizeOfXLogRecord, XLOG_BLCKSZ));
- if (readOff < 0)
+ while (XLogNeedData(state, targetPagePtr,
+ Min(targetRecOff + SizeOfXLogRecord, XLOG_BLCKSZ),
+ true))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ state->ReadRecPtr, state->readBuf,
+ &state->readPageTLI))
+ break;
+ }
+
+ if (!state->page_verified)
goto err;
/*
- * ReadPageInternal always returns at least the page header, so we can
- * examine it now.
+ * We have loaded at least the page header, so we can examine it now.
*/
pageHeaderSize = XLogPageHeaderSize((XLogPageHeader) state->readBuf);
if (targetRecOff == 0)
@@ -310,8 +315,8 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
goto err;
}
- /* ReadPageInternal has verified the page header */
- Assert(pageHeaderSize <= readOff);
+ /* XLogNeedData has verified the page header */
+ Assert(pageHeaderSize <= state->readLen);
/*
* Read the record length.
@@ -388,14 +393,21 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
targetPagePtr += XLOG_BLCKSZ;
/* Wait for the next page to become available */
- readOff = ReadPageInternal(state, targetPagePtr,
- Min(total_len - gotlen + SizeOfXLogShortPHD,
- XLOG_BLCKSZ));
+ while (XLogNeedData(state, targetPagePtr,
+ Min(total_len - gotlen + SizeOfXLogShortPHD,
+ XLOG_BLCKSZ),
+ false))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ state->ReadRecPtr, state->readBuf,
+ &state->readPageTLI))
+ break;
+ }
- if (readOff < 0)
+ if (!state->page_verified)
goto err;
- Assert(SizeOfXLogShortPHD <= readOff);
+ Assert(SizeOfXLogShortPHD <= state->readLen);
/* Check that the continuation on next page looks valid */
pageHeader = (XLogPageHeader) state->readBuf;
@@ -424,20 +436,38 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
/* Append the continuation from this page to the buffer */
pageHeaderSize = XLogPageHeaderSize(pageHeader);
- if (readOff < pageHeaderSize)
- readOff = ReadPageInternal(state, targetPagePtr,
- pageHeaderSize);
+ if (state->readLen < pageHeaderSize)
+ {
+ while (XLogNeedData(state, targetPagePtr, pageHeaderSize,
+ false))
+ {
+ if (!state->read_page(state,
+ state->readPagePtr, state->readLen,
+ state->ReadRecPtr, state->readBuf,
+ &state->readPageTLI))
+ break;
+ }
+ }
- Assert(pageHeaderSize <= readOff);
+ Assert(pageHeaderSize <= state->readLen);
contdata = (char *) state->readBuf + pageHeaderSize;
len = XLOG_BLCKSZ - pageHeaderSize;
if (pageHeader->xlp_rem_len < len)
len = pageHeader->xlp_rem_len;
- if (readOff < pageHeaderSize + len)
- readOff = ReadPageInternal(state, targetPagePtr,
- pageHeaderSize + len);
+ if (state->readLen < pageHeaderSize + len)
+ {
+ if (XLogNeedData(state, targetPagePtr, pageHeaderSize + len,
+ true))
+ {
+ if (!state->read_page(state,
+ state->readPagePtr, state->readLen,
+ state->ReadRecPtr, state->readBuf,
+ &state->readPageTLI))
+ break;
+ }
+ }
memcpy(buffer, (char *) contdata, len);
buffer += len;
@@ -468,9 +498,16 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
else
{
/* Wait for the record data to become available */
- readOff = ReadPageInternal(state, targetPagePtr,
- Min(targetRecOff + total_len, XLOG_BLCKSZ));
- if (readOff < 0)
+ while (XLogNeedData(state, targetPagePtr,
+ Min(targetRecOff + total_len, XLOG_BLCKSZ), true))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ state->ReadRecPtr, state->readBuf,
+ &state->readPageTLI))
+ break;
+ }
+
+ if (!state->page_verified)
goto err;
/* Record does not cross a page boundary */
@@ -504,7 +541,7 @@ err:
* Invalidate the read state. We might read from a different source after
* failure.
*/
- XLogReaderInvalReadState(state);
+ XLogReaderDiscardReadingPage(state);
if (state->errormsg_buf[0] != '\0')
*errormsg = state->errormsg_buf;
@@ -513,120 +550,181 @@ err:
}
/*
- * Read a single xlog page including at least [pageptr, reqLen] of valid data
- * via the read_page() callback.
+ * Checks that an xlog page loaded in state->readBuf is including at least
+ * [pageptr, reqLen] and the page is valid. includes_page_header indicates that
+ * the requested region contains page header.
*
- * Returns -1 if the required page cannot be read for some reason; errormsg_buf
- * is set in that case (unless the error occurs in the read_page callback).
+ * Returns false if the buffer already contains the requested data, or error is
+ * found. state->page_verified is set to true for the former and false for the
+ * latter.
*
- * We fetch the page from a reader-local cache if we know we have the required
- * data and if there hasn't been any error since caching the data.
+ * Otherwise returns true and requests data loaded onto state->readBuf by
+ * state->readPagePtr and state->readLen. The caller shall fill the buffer at
+ * least with that portion of data and set state->readLen to the actual length
+ * of loaded data before the next call to this function.
+ *
+ * If reqLen does not contain page header, includes_page_header should be
+ * true. This function internally adds page header to reqLen.
*/
-static int
-ReadPageInternal(XLogReaderState *state, XLogRecPtr pageptr, int reqLen)
+static bool
+XLogNeedData(XLogReaderState *state, XLogRecPtr pageptr, int reqLen,
+ bool includes_page_header)
{
- int readLen;
uint32 targetPageOff;
XLogSegNo targetSegNo;
- XLogPageHeader hdr;
-
- Assert((pageptr % XLOG_BLCKSZ) == 0);
-
- XLByteToSeg(pageptr, targetSegNo, state->wal_segment_size);
- targetPageOff = XLogSegmentOffset(pageptr, state->wal_segment_size);
+ uint32 addLen = 0;
/* check whether we have all the requested data already */
- if (targetSegNo == state->readSegNo && targetPageOff == state->readOff &&
- reqLen <= state->readLen)
- return state->readLen;
+ if (state->page_verified && pageptr == state->readPagePtr)
+ {
+ if (includes_page_header)
+ {
+ /*
+ * Include page header length in request, but the total shoudn't
+ * exceed the block size.
+ */
+ uint32 headerLen =
+ XLogPageHeaderSize((XLogPageHeader) state->readBuf);
+
+ if (reqLen + headerLen <= XLOG_BLCKSZ)
+ addLen = headerLen;
+ else
+ addLen = XLOG_BLCKSZ - reqLen;
+ }
+
+ if (reqLen + addLen <= state->readLen)
+ return false;
+ }
+
+ /* Haven't loaded any data yet? Then request it. */
+ if (XLogRecPtrIsInvalid(state->readPagePtr) || state->readLen < 0)
+ {
+ state->readPagePtr = pageptr;
+ state->readLen = Max(reqLen, SizeOfXLogShortPHD);
+ state->page_verified = false;
+ return true;
+ }
+
+ if (!state->page_verified)
+ {
+ uint32 pageHeaderSize;
+ uint32 addLen = 0;
+
+ /* just loaded new data so needs to verify page header */
+
+ /* The caller must have loaded at least page header */
+ Assert(state->readLen >= SizeOfXLogShortPHD);
+
+ /*
+ * We have enough data to check the header length. Recheck the loaded
+ * length if it is a long header if any.
+ */
+ pageHeaderSize = XLogPageHeaderSize((XLogPageHeader) state->readBuf);
+
+ /*
+ * If we have not loaded a page so far, readLen is zero, which is
+ * shorter than pageHeaderSize here.
+ */
+ if (state->readLen < pageHeaderSize)
+ {
+ state->readPagePtr = pageptr;
+ state->readLen = pageHeaderSize;
+ return true;
+ }
+
+ /*
+ * Now that we know we have the full header, validate it.
+ */
+ if (!XLogReaderValidatePageHeader(state, state->readPagePtr,
+ (char *) state->readBuf))
+ {
+ /* force reading the page again. */
+ XLogReaderDiscardReadingPage(state);
+
+ return false;
+ }
+
+ state->page_verified = true;
+
+ XLByteToSeg(state->readPagePtr, state->readSegNo,
+ state->wal_segment_size);
+
+ /*
+ * calculate additional length for page header so that the total length
+ * doesn't exceed the block size.
+ */
+ if (includes_page_header)
+ {
+ addLen = pageHeaderSize;
+ if (reqLen + pageHeaderSize <= XLOG_BLCKSZ)
+ addLen = pageHeaderSize;
+ else
+ addLen = XLOG_BLCKSZ - reqLen;
+
+ Assert(addLen >= 0);
+ }
+
+ /* Usually we have requested data loaded in buffer here. */
+ if (pageptr == state->readPagePtr && reqLen + addLen <= state->readLen)
+ return false;
+
+ /*
+ * In the case the page is requested for the first record in the page,
+ * the previous request have been made not counting page header
+ * length. Request again for the same page with the length known to be
+ * needed. Otherwise we don't know the header length of the new page.
+ */
+ if (pageptr != state->readPagePtr)
+ addLen = 0;
+ }
+
+ /* Data is not in our buffer, make a new load request to the caller. */
+ XLByteToSeg(pageptr, targetSegNo, state->wal_segment_size);
+ targetPageOff = XLogSegmentOffset(pageptr, state->wal_segment_size);
+ Assert((pageptr % XLOG_BLCKSZ) == 0);
+
+ /*
+ * Every time we request to load new data of a page to the caller, even if
+ * we looked at a part of it before, we need to do verification on the next
+ * invocation as the caller might now be rereading data from a different
+ * source.
+ */
+ state->page_verified = false;
/*
- * Data is not in our buffer.
- *
- * Every time we actually read the page, even if we looked at parts of it
- * before, we need to do verification as the read_page callback might now
- * be rereading data from a different source.
- *
* Whenever switching to a new WAL segment, we read the first page of the
* file and validate its header, even if that's not where the target
* record is. This is so that we can check the additional identification
* info that is present in the first page's "long" header.
+ * Don't do this if the caller requested the first page in the segment.
*/
if (targetSegNo != state->readSegNo && targetPageOff != 0)
{
- XLogRecPtr targetSegmentPtr = pageptr - targetPageOff;
-
- readLen = state->read_page(state, targetSegmentPtr, XLOG_BLCKSZ,
- state->currRecPtr,
- state->readBuf, &state->readPageTLI);
- if (readLen < 0)
- goto err;
-
- /* we can be sure to have enough WAL available, we scrolled back */
- Assert(readLen == XLOG_BLCKSZ);
-
- if (!XLogReaderValidatePageHeader(state, targetSegmentPtr,
- state->readBuf))
- goto err;
+ /*
+ * Then we'll see that the targetSegNo now matches the readSegNo, and
+ * will not come back here, but will request the actual target page.
+ */
+ state->readPagePtr = pageptr - targetPageOff;
+ state->readLen = XLOG_BLCKSZ;
+ return true;
}
/*
- * First, read the requested data length, but at least a short page header
- * so that we can validate it.
+ * Request the caller to load the requested page. We need at least a short
+ * page header so that we can validate it.
*/
- readLen = state->read_page(state, pageptr, Max(reqLen, SizeOfXLogShortPHD),
- state->currRecPtr,
- state->readBuf, &state->readPageTLI);
- if (readLen < 0)
- goto err;
-
- Assert(readLen <= XLOG_BLCKSZ);
-
- /* Do we have enough data to check the header length? */
- if (readLen <= SizeOfXLogShortPHD)
- goto err;
-
- Assert(readLen >= reqLen);
-
- hdr = (XLogPageHeader) state->readBuf;
-
- /* still not enough */
- if (readLen < XLogPageHeaderSize(hdr))
- {
- readLen = state->read_page(state, pageptr, XLogPageHeaderSize(hdr),
- state->currRecPtr,
- state->readBuf, &state->readPageTLI);
- if (readLen < 0)
- goto err;
- }
-
- /*
- * Now that we know we have the full header, validate it.
- */
- if (!XLogReaderValidatePageHeader(state, pageptr, (char *) hdr))
- goto err;
-
- /* update read state information */
- state->readSegNo = targetSegNo;
- state->readOff = targetPageOff;
- state->readLen = readLen;
-
- return readLen;
-
-err:
- XLogReaderInvalReadState(state);
- return -1;
+ state->readPagePtr = pageptr;
+ state->readLen = Max(reqLen + addLen, SizeOfXLogShortPHD);
+ return true;
}
/*
- * Invalidate the xlogreader's read state to force a re-read.
+ * Invalidate current reading page buffer
*/
void
-XLogReaderInvalReadState(XLogReaderState *state)
+XLogReaderDiscardReadingPage(XLogReaderState *state)
{
- state->readSegNo = 0;
- state->readOff = 0;
- state->readLen = 0;
+ state->readPagePtr = InvalidXLogRecPtr;
}
/*
@@ -904,7 +1002,6 @@ XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr)
XLogRecPtr targetPagePtr;
int targetRecOff;
uint32 pageHeaderSize;
- int readLen;
/*
* Compute targetRecOff. It should typically be equal or greater than
@@ -912,7 +1009,7 @@ XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr)
* that, except when caller has explicitly specified the offset that
* falls somewhere there or when we are skipping multi-page
* continuation record. It doesn't matter though because
- * ReadPageInternal() is prepared to handle that and will read at
+ * CheckPage() is prepared to handle that and will read at
* least short page-header worth of data
*/
targetRecOff = tmpRecPtr % XLOG_BLCKSZ;
@@ -921,8 +1018,15 @@ XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr)
targetPagePtr = tmpRecPtr - targetRecOff;
/* Read the page containing the record */
- readLen = ReadPageInternal(state, targetPagePtr, targetRecOff);
- if (readLen < 0)
+ while(XLogNeedData(state, targetPagePtr, targetRecOff, true))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ state->ReadRecPtr, state->readBuf,
+ &state->readPageTLI))
+ break;
+ }
+
+ if (!state->page_verified)
goto err;
header = (XLogPageHeader) state->readBuf;
@@ -930,8 +1034,15 @@ XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr)
pageHeaderSize = XLogPageHeaderSize(header);
/* make sure we have enough data for the page header */
- readLen = ReadPageInternal(state, targetPagePtr, pageHeaderSize);
- if (readLen < 0)
+ while (XLogNeedData(state, targetPagePtr, pageHeaderSize, false))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ state->ReadRecPtr, state->readBuf,
+ &state->readPageTLI))
+ break;
+ }
+
+ if (!state->page_verified)
goto err;
/* skip over potential continuation data */
@@ -989,7 +1100,7 @@ out:
/* Reset state to what we had before finding the record */
state->ReadRecPtr = saved_state.ReadRecPtr;
state->EndRecPtr = saved_state.EndRecPtr;
- XLogReaderInvalReadState(state);
+ XLogReaderDiscardReadingPage(state);
return found;
}
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index 1fc39333f1..dad9074b9f 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -803,7 +803,7 @@ void
XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wantLength)
{
const XLogRecPtr lastReadPage = state->readSegNo *
- state->wal_segment_size + state->readOff;
+ state->wal_segment_size + state->readLen;
Assert(wantPage != InvalidXLogRecPtr && wantPage % XLOG_BLCKSZ == 0);
Assert(wantLength <= XLOG_BLCKSZ);
@@ -907,7 +907,7 @@ XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wa
* exists for normal backends, so we have to do a check/sleep/repeat style of
* loop for now.
*/
-int
+bool
read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *cur_page,
TimeLineID *pageTLI)
@@ -1009,7 +1009,8 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
else if (targetPagePtr + reqLen > read_upto)
{
/* not enough data there */
- return -1;
+ state->readLen = -1;
+ return false;
}
else
{
@@ -1026,5 +1027,6 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
XLOG_BLCKSZ);
/* number of valid bytes in the buffer */
- return count;
+ state->readLen = count;
+ return true;
}
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index d974400d6e..7210a940bd 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -114,12 +114,12 @@ check_permissions(void)
(errmsg("must be superuser or replication role to use replication slots"))));
}
-int
+bool
logical_read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *cur_page, TimeLineID *pageTLI)
{
return read_local_xlog_page(state, targetPagePtr, reqLen,
- targetRecPtr, cur_page, pageTLI);
+ targetRecPtr, cur_page, pageTLI);
}
/*
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 23870a25a5..cc35e2a04d 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -761,7 +761,7 @@ StartReplication(StartReplicationCmd *cmd)
* which has to do a plain sleep/busy loop, because the walsender's latch gets
* set every time WAL is flushed.
*/
-static int
+static bool
logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetRecPtr, char *cur_page, TimeLineID *pageTLI)
{
@@ -779,7 +779,10 @@ logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int req
/* fail if not (implies we are going to shut down) */
if (flushptr < targetPagePtr + reqLen)
- return -1;
+ {
+ state->readLen = -1;
+ return false;
+ }
if (targetPagePtr + XLOG_BLCKSZ <= flushptr)
count = XLOG_BLCKSZ; /* more than one block available */
@@ -789,7 +792,8 @@ logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int req
/* now actually read the data, we know it's there */
XLogRead(cur_page, targetPagePtr, XLOG_BLCKSZ);
- return count;
+ state->readLen = count;
+ return true;
}
/*
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 63c3879ead..4df53964e4 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -47,7 +47,7 @@ typedef struct XLogPageReadPrivate
int tliIndex;
} XLogPageReadPrivate;
-static int SimpleXLogPageRead(XLogReaderState *xlogreader,
+static bool SimpleXLogPageRead(XLogReaderState *xlogreader,
XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *readBuf,
TimeLineID *pageTLI);
@@ -235,7 +235,7 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
}
/* XLogReader callback function, to read a WAL page */
-static int
+static bool
SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *readBuf,
TimeLineID *pageTLI)
@@ -290,7 +290,8 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
if (xlogreadfd < 0)
{
pg_log_error("could not open file \"%s\": %m", xlogfpath);
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
}
@@ -303,7 +304,8 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
if (lseek(xlogreadfd, (off_t) targetPageOff, SEEK_SET) < 0)
{
pg_log_error("could not seek in file \"%s\": %m", xlogfpath);
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
@@ -316,13 +318,16 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
pg_log_error("could not read file \"%s\": read %d of %zu",
xlogfpath, r, (Size) XLOG_BLCKSZ);
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
Assert(targetSegNo == xlogreadsegno);
*pageTLI = targetHistory[private->tliIndex].tli;
- return XLOG_BLCKSZ;
+
+ xlogreader->readLen = XLOG_BLCKSZ;
+ return true;
}
/*
diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c
index b95d467805..96d1f36ebc 100644
--- a/src/bin/pg_waldump/pg_waldump.c
+++ b/src/bin/pg_waldump/pg_waldump.c
@@ -421,7 +421,7 @@ XLogDumpXLogRead(const char *directory, TimeLineID timeline_id,
/*
* XLogReader read_page callback
*/
-static int
+static bool
XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetPtr, char *readBuff, TimeLineID *curFileTLI)
{
@@ -437,14 +437,16 @@ XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
else
{
private->endptr_reached = true;
- return -1;
+ state->readLen = -1;
+ return false;
}
}
XLogDumpXLogRead(private->inpath, private->timeline, targetPagePtr,
readBuff, count);
- return count;
+ state->readLen = count;
+ return true;
}
/*
diff --git a/src/include/access/xlogreader.h b/src/include/access/xlogreader.h
index aa9bc63725..030f56802b 100644
--- a/src/include/access/xlogreader.h
+++ b/src/include/access/xlogreader.h
@@ -34,7 +34,7 @@
typedef struct XLogReaderState XLogReaderState;
/* Function type definition for the read_page callback */
-typedef int (*XLogPageReadCB) (XLogReaderState *xlogreader,
+typedef bool (*XLogPageReadCB) (XLogReaderState *xlogreader,
XLogRecPtr targetPagePtr,
int reqLen,
XLogRecPtr targetRecPtr,
@@ -123,6 +123,18 @@ struct XLogReaderState
XLogRecPtr ReadRecPtr; /* start of last record read */
XLogRecPtr EndRecPtr; /* end+1 of last record read */
+ /* ----------------------------------------
+ * Communication with page reader
+ * readBuf is XLOG_BLCKSZ bytes, valid up to at least readLen bytes.
+ * ----------------------------------------
+ */
+ /* variables to communicate with page reader */
+ XLogRecPtr readPagePtr; /* page pointer to read */
+ int32 readLen; /* bytes requested to reader, or actual bytes
+ * read by reader, which must be larger than
+ * the request, or -1 on error */
+ TimeLineID readPageTLI; /* TLI for data currently in readBuf */
+ char *readBuf; /* buffer to store data */
/* ----------------------------------------
* Decoded representation of current record
@@ -149,17 +161,9 @@ struct XLogReaderState
* ----------------------------------------
*/
- /*
- * Buffer for currently read page (XLOG_BLCKSZ bytes, valid up to at least
- * readLen bytes)
- */
- char *readBuf;
- uint32 readLen;
-
- /* last read segment, segment offset, TLI for data currently in readBuf */
+ /* last read segment and segment offset for data currently in readBuf */
+ bool page_verified;
XLogSegNo readSegNo;
- uint32 readOff;
- TimeLineID readPageTLI;
/*
* beginning of prior page read, and its TLI. Doesn't necessarily
@@ -216,8 +220,8 @@ extern struct XLogRecord *XLogReadRecord(XLogReaderState *state,
extern bool XLogReaderValidatePageHeader(XLogReaderState *state,
XLogRecPtr recptr, char *phdr);
-/* Invalidate read state */
-extern void XLogReaderInvalReadState(XLogReaderState *state);
+/* Discard bufferd page */
+extern void XLogReaderDiscardReadingPage(XLogReaderState *state);
#ifdef FRONTEND
extern XLogRecPtr XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr);
diff --git a/src/include/access/xlogutils.h b/src/include/access/xlogutils.h
index 4105b59904..0842af9f95 100644
--- a/src/include/access/xlogutils.h
+++ b/src/include/access/xlogutils.h
@@ -47,7 +47,7 @@ extern Buffer XLogReadBufferExtended(RelFileNode rnode, ForkNumber forknum,
extern Relation CreateFakeRelcacheEntry(RelFileNode rnode);
extern void FreeFakeRelcacheEntry(Relation fakerel);
-extern int read_local_xlog_page(XLogReaderState *state,
+extern bool read_local_xlog_page(XLogReaderState *state,
XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetRecPtr, char *cur_page,
TimeLineID *pageTLI);
diff --git a/src/include/replication/logicalfuncs.h b/src/include/replication/logicalfuncs.h
index a9c178a9e6..8e52b1f4aa 100644
--- a/src/include/replication/logicalfuncs.h
+++ b/src/include/replication/logicalfuncs.h
@@ -11,7 +11,7 @@
#include "replication/logical.h"
-extern int logical_read_local_xlog_page(XLogReaderState *state,
+extern bool logical_read_local_xlog_page(XLogReaderState *state,
XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr,
char *cur_page, TimeLineID *pageTLI);
--
2.16.3
----Next_Part(Fri_Sep_06_16_33_18_2019_198)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="v5-0002-Move-page-reader-out-of-XLogReadRecord.patch"
^ permalink raw reply [nested|flat] 199+ messages in thread
* [PATCH v12 1/4] Move callback-call from ReadPageInternal to XLogReadRecord.
@ 2019-09-05 11:21 Kyotaro Horiguchi <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Kyotaro Horiguchi @ 2019-09-05 11:21 UTC (permalink / raw)
The current WAL record reader reads page data using a call back
function. Although it is not so problematic alone, it would be a
problem if we are going to do add tasks like encryption which is
performed on page data before WAL reader reads them. To avoid that the
record reader facility has to have a new code path corresponds to
every new callback, this patch separates page reader from WAL record
reading facility by modifying the current WAL record reader to a state
machine.
As the first step of that change, this patch moves the page reader
function out of ReadPageInternal, then the remaining tasks of the
function are taken over by the new function XLogNeedData. As the
result XLogPageRead directly calls the page reader callback function
according to the feedback from XLogNeedData.
---
src/backend/access/transam/xlog.c | 16 +-
src/backend/access/transam/xlogreader.c | 272 ++++++++++--------
src/backend/access/transam/xlogutils.c | 12 +-
.../replication/logical/logicalfuncs.c | 2 +-
src/backend/replication/walsender.c | 10 +-
src/bin/pg_rewind/parsexlog.c | 16 +-
src/bin/pg_waldump/pg_waldump.c | 8 +-
src/include/access/xlogreader.h | 23 +-
src/include/access/xlogutils.h | 2 +-
src/include/replication/logicalfuncs.h | 2 +-
10 files changed, 213 insertions(+), 150 deletions(-)
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 5f0ee50092..4a6bd6e002 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -884,7 +884,7 @@ static bool InstallXLogFileSegment(XLogSegNo *segno, char *tmppath,
static int XLogFileRead(XLogSegNo segno, int emode, TimeLineID tli,
int source, bool notfoundOk);
static int XLogFileReadAnyTLI(XLogSegNo segno, int emode, int source);
-static int XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
+static bool XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *readBuf);
static bool WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
bool fetching_ckpt, XLogRecPtr tliRecPtr);
@@ -4249,7 +4249,6 @@ ReadRecord(XLogReaderState *xlogreader, XLogRecPtr RecPtr, int emode,
XLogRecord *record;
XLogPageReadPrivate *private = (XLogPageReadPrivate *) xlogreader->private_data;
- /* Pass through parameters to XLogPageRead */
private->fetching_ckpt = fetching_ckpt;
private->emode = emode;
private->randAccess = (RecPtr != InvalidXLogRecPtr);
@@ -11537,7 +11536,7 @@ CancelBackup(void)
* XLogPageRead() to try fetching the record from another source, or to
* sleep and retry.
*/
-static int
+static bool
XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetRecPtr, char *readBuf)
{
@@ -11596,7 +11595,8 @@ retry:
readLen = 0;
readSource = 0;
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
}
@@ -11691,7 +11691,8 @@ retry:
goto next_record_is_invalid;
}
- return readLen;
+ xlogreader->readLen = readLen;
+ return true;
next_record_is_invalid:
lastSourceFailed = true;
@@ -11705,8 +11706,9 @@ next_record_is_invalid:
/* In standby-mode, keep trying */
if (StandbyMode)
goto retry;
- else
- return -1;
+
+ xlogreader->readLen = -1;
+ return false;
}
/*
diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c
index 67418b05f1..063223701e 100644
--- a/src/backend/access/transam/xlogreader.c
+++ b/src/backend/access/transam/xlogreader.c
@@ -36,8 +36,8 @@
static void report_invalid_record(XLogReaderState *state, const char *fmt,...)
pg_attribute_printf(2, 3);
static bool allocate_recordbuf(XLogReaderState *state, uint32 reclength);
-static int ReadPageInternal(XLogReaderState *state, XLogRecPtr pageptr,
- int reqLen);
+static bool XLogNeedData(XLogReaderState *state, XLogRecPtr pageptr,
+ int reqLen, bool header_inclusive);
static void XLogReaderInvalReadState(XLogReaderState *state);
static bool ValidXLogRecordHeader(XLogReaderState *state, XLogRecPtr RecPtr,
XLogRecPtr PrevRecPtr, XLogRecord *record, bool randAccess);
@@ -245,7 +245,6 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
uint32 targetRecOff;
uint32 pageHeaderSize;
bool gotheader;
- int readOff;
/*
* randAccess indicates whether to verify the previous-record pointer of
@@ -297,14 +296,20 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
* byte to cover the whole record header, or at least the part of it that
* fits on the same page.
*/
- readOff = ReadPageInternal(state, targetPagePtr,
- Min(targetRecOff + SizeOfXLogRecord, XLOG_BLCKSZ));
- if (readOff < 0)
+ while (XLogNeedData(state, targetPagePtr,
+ Min(targetRecOff + SizeOfXLogRecord, XLOG_BLCKSZ),
+ targetRecOff != 0))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ RecPtr, state->readBuf))
+ break;
+ }
+
+ if (!state->page_verified)
goto err;
/*
- * ReadPageInternal always returns at least the page header, so we can
- * examine it now.
+ * We have at least the page header, so we can examine it now.
*/
pageHeaderSize = XLogPageHeaderSize((XLogPageHeader) state->readBuf);
if (targetRecOff == 0)
@@ -330,8 +335,8 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
goto err;
}
- /* ReadPageInternal has verified the page header */
- Assert(pageHeaderSize <= readOff);
+ /* XLogNeedData has verified the page header */
+ Assert(pageHeaderSize <= state->readLen);
/*
* Read the record length.
@@ -404,18 +409,25 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
do
{
+ int rest_len = total_len - gotlen;
+
/* Calculate pointer to beginning of next page */
targetPagePtr += XLOG_BLCKSZ;
/* Wait for the next page to become available */
- readOff = ReadPageInternal(state, targetPagePtr,
- Min(total_len - gotlen + SizeOfXLogShortPHD,
- XLOG_BLCKSZ));
+ while (XLogNeedData(state, targetPagePtr,
+ Min(rest_len, XLOG_BLCKSZ),
+ false))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ state->ReadRecPtr, state->readBuf))
+ break;
+ }
- if (readOff < 0)
+ if (!state->page_verified)
goto err;
- Assert(SizeOfXLogShortPHD <= readOff);
+ Assert(SizeOfXLogShortPHD <= state->readLen);
/* Check that the continuation on next page looks valid */
pageHeader = (XLogPageHeader) state->readBuf;
@@ -444,21 +456,14 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
/* Append the continuation from this page to the buffer */
pageHeaderSize = XLogPageHeaderSize(pageHeader);
- if (readOff < pageHeaderSize)
- readOff = ReadPageInternal(state, targetPagePtr,
- pageHeaderSize);
-
- Assert(pageHeaderSize <= readOff);
+ Assert(pageHeaderSize <= state->readLen);
contdata = (char *) state->readBuf + pageHeaderSize;
len = XLOG_BLCKSZ - pageHeaderSize;
if (pageHeader->xlp_rem_len < len)
len = pageHeader->xlp_rem_len;
- if (readOff < pageHeaderSize + len)
- readOff = ReadPageInternal(state, targetPagePtr,
- pageHeaderSize + len);
-
+ Assert (pageHeaderSize + len <= state->readLen);
memcpy(buffer, (char *) contdata, len);
buffer += len;
gotlen += len;
@@ -488,9 +493,15 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
else
{
/* Wait for the record data to become available */
- readOff = ReadPageInternal(state, targetPagePtr,
- Min(targetRecOff + total_len, XLOG_BLCKSZ));
- if (readOff < 0)
+ while (XLogNeedData(state, targetPagePtr,
+ Min(targetRecOff + total_len, XLOG_BLCKSZ), true))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ state->ReadRecPtr, state->readBuf))
+ break;
+ }
+
+ if (!state->page_verified)
goto err;
/* Record does not cross a page boundary */
@@ -533,109 +544,139 @@ err:
}
/*
- * Read a single xlog page including at least [pageptr, reqLen] of valid data
- * via the read_page() callback.
+ * Checks that an xlog page loaded in state->readBuf is including at least
+ * [pageptr, reqLen] and the page is valid. header_inclusive indicates that
+ * reqLen is calculated including page header length.
*
- * Returns -1 if the required page cannot be read for some reason; errormsg_buf
- * is set in that case (unless the error occurs in the read_page callback).
+ * Returns false if the buffer already contains the requested data, or found
+ * error. state->page_verified is set to true for the former and false for the
+ * latter.
*
- * We fetch the page from a reader-local cache if we know we have the required
- * data and if there hasn't been any error since caching the data.
+ * Otherwise returns true and requests data loaded onto state->readBuf by
+ * state->readPagePtr and state->readLen. The caller shall call this function
+ * again after filling the buffer at least with that portion of data and set
+ * state->readLen to the length of actually loaded data.
+ *
+ * If header_inclusive is false, corrects reqLen internally by adding the
+ * actual page header length and may request caller for new data.
*/
-static int
-ReadPageInternal(XLogReaderState *state, XLogRecPtr pageptr, int reqLen)
+static bool
+XLogNeedData(XLogReaderState *state, XLogRecPtr pageptr, int reqLen,
+ bool header_inclusive)
{
- int readLen;
uint32 targetPageOff;
XLogSegNo targetSegNo;
- XLogPageHeader hdr;
+ uint32 addLen = 0;
- Assert((pageptr % XLOG_BLCKSZ) == 0);
+ /* Some data is loaded, but page header is not verified yet. */
+ if (!state->page_verified &&
+ !XLogRecPtrIsInvalid(state->readPagePtr) && state->readLen >= 0)
+ {
+ uint32 pageHeaderSize;
- XLByteToSeg(pageptr, targetSegNo, state->segcxt.ws_segsize);
- targetPageOff = XLogSegmentOffset(pageptr, state->segcxt.ws_segsize);
+ /* just loaded new data so needs to verify page header */
- /* check whether we have all the requested data already */
- if (targetSegNo == state->seg.ws_segno &&
- targetPageOff == state->segoff && reqLen <= state->readLen)
- return state->readLen;
+ /* The caller must have loaded at least page header */
+ Assert (state->readLen >= SizeOfXLogShortPHD);
- /*
- * Data is not in our buffer.
- *
- * Every time we actually read the page, even if we looked at parts of it
- * before, we need to do verification as the read_page callback might now
- * be rereading data from a different source.
- *
- * Whenever switching to a new WAL segment, we read the first page of the
- * file and validate its header, even if that's not where the target
- * record is. This is so that we can check the additional identification
- * info that is present in the first page's "long" header.
- */
- if (targetSegNo != state->seg.ws_segno && targetPageOff != 0)
- {
- XLogRecPtr targetSegmentPtr = pageptr - targetPageOff;
+ /*
+ * We have enough data to check the header length. Recheck the loaded
+ * length against the actual header length.
+ */
+ pageHeaderSize = XLogPageHeaderSize((XLogPageHeader) state->readBuf);
- readLen = state->read_page(state, targetSegmentPtr, XLOG_BLCKSZ,
- state->currRecPtr,
- state->readBuf);
- if (readLen < 0)
- goto err;
+ /* Request more data if we don't have the full header. */
+ if (state->readLen < pageHeaderSize)
+ {
+ state->readLen = pageHeaderSize;
+ return true;
+ }
+
+ /* Now that we know we have the full header, validate it. */
+ if (!XLogReaderValidatePageHeader(state, state->readPagePtr,
+ (char *) state->readBuf))
+ {
+ /* That's bad. Force reading the page again. */
+ XLogReaderInvalReadState(state);
- /* we can be sure to have enough WAL available, we scrolled back */
- Assert(readLen == XLOG_BLCKSZ);
+ return false;
+ }
- if (!XLogReaderValidatePageHeader(state, targetSegmentPtr,
- state->readBuf))
- goto err;
+ state->page_verified = true;
+
+ XLByteToSeg(state->readPagePtr, state->seg.ws_segno,
+ state->segcxt.ws_segsize);
}
/*
- * First, read the requested data length, but at least a short page header
- * so that we can validate it.
+ * The loaded page may not be the one caller is supposing to read when we
+ * are verifying the first page of new segment. In that case, skip further
+ * verification and immediately load the target page.
*/
- readLen = state->read_page(state, pageptr, Max(reqLen, SizeOfXLogShortPHD),
- state->currRecPtr,
- state->readBuf);
- if (readLen < 0)
- goto err;
-
- Assert(readLen <= XLOG_BLCKSZ);
+ if (state->page_verified && pageptr == state->readPagePtr)
+ {
- /* Do we have enough data to check the header length? */
- if (readLen <= SizeOfXLogShortPHD)
- goto err;
+ /*
+ * calculate additional length for page header keeping the total
+ * length within the block size.
+ */
+ if (!header_inclusive)
+ {
+ uint32 pageHeaderSize =
+ XLogPageHeaderSize((XLogPageHeader) state->readBuf);
- Assert(readLen >= reqLen);
+ addLen = pageHeaderSize;
+ if (reqLen + pageHeaderSize <= XLOG_BLCKSZ)
+ addLen = pageHeaderSize;
+ else
+ addLen = XLOG_BLCKSZ - reqLen;
- hdr = (XLogPageHeader) state->readBuf;
+ Assert(addLen >= 0);
+ }
- /* still not enough */
- if (readLen < XLogPageHeaderSize(hdr))
- {
- readLen = state->read_page(state, pageptr, XLogPageHeaderSize(hdr),
- state->currRecPtr,
- state->readBuf);
- if (readLen < 0)
- goto err;
+ /* Return if we already have it. */
+ if (reqLen + addLen <= state->readLen)
+ return false;
}
+ /* Data is not in our buffer, request the caller for it. */
+ XLByteToSeg(pageptr, targetSegNo, state->segcxt.ws_segsize);
+ targetPageOff = XLogSegmentOffset(pageptr, state->segcxt.ws_segsize);
+ Assert((pageptr % XLOG_BLCKSZ) == 0);
+
/*
- * Now that we know we have the full header, validate it.
+ * Every time we request to load new data of a page to the caller, even if
+ * we looked at a part of it before, we need to do verification on the next
+ * invocation as the caller might now be rereading data from a different
+ * source.
*/
- if (!XLogReaderValidatePageHeader(state, pageptr, (char *) hdr))
- goto err;
-
- /* update read state information */
- state->seg.ws_segno = targetSegNo;
- state->segoff = targetPageOff;
- state->readLen = readLen;
+ state->page_verified = false;
- return readLen;
+ /*
+ * Whenever switching to a new WAL segment, we read the first page of the
+ * file and validate its header, even if that's not where the target
+ * record is. This is so that we can check the additional identification
+ * info that is present in the first page's "long" header.
+ * Don't do this if the caller requested the first page in the segment.
+ */
+ if (targetSegNo != state->seg.ws_segno && targetPageOff != 0)
+ {
+ /*
+ * Then we'll see that the targetSegNo now matches the ws_segno, and
+ * will not come back here, but will request the actual target page.
+ */
+ state->readPagePtr = pageptr - targetPageOff;
+ state->readLen = XLOG_BLCKSZ;
+ return true;
+ }
-err:
- XLogReaderInvalReadState(state);
- return -1;
+ /*
+ * Request the caller to load the page. We need at least a short page
+ * header so that we can validate it.
+ */
+ state->readPagePtr = pageptr;
+ state->readLen = Max(reqLen + addLen, SizeOfXLogShortPHD);
+ return true;
}
/*
@@ -644,9 +685,7 @@ err:
static void
XLogReaderInvalReadState(XLogReaderState *state)
{
- state->seg.ws_segno = 0;
- state->segoff = 0;
- state->readLen = 0;
+ state->readPagePtr = InvalidXLogRecPtr;
}
/*
@@ -924,7 +963,6 @@ XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr)
XLogRecPtr targetPagePtr;
int targetRecOff;
uint32 pageHeaderSize;
- int readLen;
/*
* Compute targetRecOff. It should typically be equal or greater than
@@ -932,7 +970,7 @@ XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr)
* that, except when caller has explicitly specified the offset that
* falls somewhere there or when we are skipping multi-page
* continuation record. It doesn't matter though because
- * ReadPageInternal() is prepared to handle that and will read at
+ * XLogNeedData() is prepared to handle that and will read at
* least short page-header worth of data
*/
targetRecOff = tmpRecPtr % XLOG_BLCKSZ;
@@ -940,19 +978,23 @@ XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr)
/* scroll back to page boundary */
targetPagePtr = tmpRecPtr - targetRecOff;
- /* Read the page containing the record */
- readLen = ReadPageInternal(state, targetPagePtr, targetRecOff);
- if (readLen < 0)
+ while(XLogNeedData(state, targetPagePtr, targetRecOff,
+ targetRecOff != 0))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ state->ReadRecPtr, state->readBuf))
+ break;
+ }
+
+ if (!state->page_verified)
goto err;
header = (XLogPageHeader) state->readBuf;
pageHeaderSize = XLogPageHeaderSize(header);
- /* make sure we have enough data for the page header */
- readLen = ReadPageInternal(state, targetPagePtr, pageHeaderSize);
- if (readLen < 0)
- goto err;
+ /* we should have read the page header */
+ Assert (state->readLen >= pageHeaderSize);
/* skip over potential continuation data */
if (header->xlp_info & XLP_FIRST_IS_CONTRECORD)
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index 446760ed6e..060fbc0c4c 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -680,8 +680,8 @@ XLogTruncateRelation(RelFileNode rnode, ForkNumber forkNum,
void
XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wantLength)
{
- const XLogRecPtr lastReadPage = (state->seg.ws_segno *
- state->segcxt.ws_segsize + state->segoff);
+ const XLogRecPtr lastReadPage = state->seg.ws_segno *
+ state->segcxt.ws_segsize + state->readLen;
Assert(wantPage != InvalidXLogRecPtr && wantPage % XLOG_BLCKSZ == 0);
Assert(wantLength <= XLOG_BLCKSZ);
@@ -813,7 +813,7 @@ wal_segment_open(XLogSegNo nextSegNo, WALSegmentContext *segcxt,
* exists for normal backends, so we have to do a check/sleep/repeat style of
* loop for now.
*/
-int
+bool
read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *cur_page)
{
@@ -915,7 +915,8 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
else if (targetPagePtr + reqLen > read_upto)
{
/* not enough data there */
- return -1;
+ state->readLen = -1;
+ return false;
}
else
{
@@ -933,7 +934,8 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
WALReadRaiseError(&errinfo);
/* number of valid bytes in the buffer */
- return count;
+ state->readLen = count;
+ return true;
}
/*
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index aa2106b152..4b0e8ea773 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -106,7 +106,7 @@ check_permissions(void)
(errmsg("must be superuser or replication role to use replication slots"))));
}
-int
+bool
logical_read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *cur_page)
{
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 8bafa65e50..554c186ae1 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -762,7 +762,7 @@ StartReplication(StartReplicationCmd *cmd)
* which has to do a plain sleep/busy loop, because the walsender's latch gets
* set every time WAL is flushed.
*/
-static int
+static bool
logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetRecPtr, char *cur_page)
{
@@ -782,7 +782,10 @@ logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int req
/* fail if not (implies we are going to shut down) */
if (flushptr < targetPagePtr + reqLen)
- return -1;
+ {
+ state->readLen = -1;
+ return false;
+ }
if (targetPagePtr + XLOG_BLCKSZ <= flushptr)
count = XLOG_BLCKSZ; /* more than one block available */
@@ -812,7 +815,8 @@ logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int req
XLByteToSeg(targetPagePtr, segno, sendCxt->ws_segsize);
CheckXLogRemoved(segno, sendSeg->ws_tli);
- return count;
+ state->readLen = count;
+ return true;
}
/*
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 1d03375a3e..795e84ff9f 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -44,7 +44,7 @@ typedef struct XLogPageReadPrivate
int tliIndex;
} XLogPageReadPrivate;
-static int SimpleXLogPageRead(XLogReaderState *xlogreader,
+static bool SimpleXLogPageRead(XLogReaderState *xlogreader,
XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *readBuf);
@@ -228,7 +228,7 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
}
/* XLogReader callback function, to read a WAL page */
-static int
+static bool
SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *readBuf)
{
@@ -283,7 +283,8 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
if (xlogreadfd < 0)
{
pg_log_error("could not open file \"%s\": %m", xlogfpath);
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
}
@@ -296,7 +297,8 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
if (lseek(xlogreadfd, (off_t) targetPageOff, SEEK_SET) < 0)
{
pg_log_error("could not seek in file \"%s\": %m", xlogfpath);
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
@@ -309,13 +311,15 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
pg_log_error("could not read file \"%s\": read %d of %zu",
xlogfpath, r, (Size) XLOG_BLCKSZ);
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
Assert(targetSegNo == xlogreadsegno);
xlogreader->seg.ws_tli = targetHistory[private->tliIndex].tli;
- return XLOG_BLCKSZ;
+ xlogreader->readLen = XLOG_BLCKSZ;
+ return true;
}
/*
diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c
index 30a5851d87..df13cf3173 100644
--- a/src/bin/pg_waldump/pg_waldump.c
+++ b/src/bin/pg_waldump/pg_waldump.c
@@ -324,7 +324,7 @@ WALDumpOpenSegment(XLogSegNo nextSegNo, WALSegmentContext *segcxt,
/*
* XLogReader read_page callback
*/
-static int
+static bool
WALDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetPtr, char *readBuff)
{
@@ -341,7 +341,8 @@ WALDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
else
{
private->endptr_reached = true;
- return -1;
+ state->readLen = -1;
+ return false;
}
}
@@ -366,7 +367,8 @@ WALDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
(Size) errinfo.wre_req);
}
- return count;
+ state->readLen = count;
+ return true;
}
/*
diff --git a/src/include/access/xlogreader.h b/src/include/access/xlogreader.h
index 0193611b7f..d990432ffe 100644
--- a/src/include/access/xlogreader.h
+++ b/src/include/access/xlogreader.h
@@ -49,7 +49,7 @@ typedef struct WALSegmentContext
typedef struct XLogReaderState XLogReaderState;
/* Function type definition for the read_page callback */
-typedef int (*XLogPageReadCB) (XLogReaderState *xlogreader,
+typedef bool (*XLogPageReadCB) (XLogReaderState *xlogreader,
XLogRecPtr targetPagePtr,
int reqLen,
XLogRecPtr targetRecPtr,
@@ -131,6 +131,20 @@ struct XLogReaderState
XLogRecPtr ReadRecPtr; /* start of last record read */
XLogRecPtr EndRecPtr; /* end+1 of last record read */
+ /* ----------------------------------------
+ * Communication with page reader
+ * readBuf is XLOG_BLCKSZ bytes, valid up to at least readLen bytes.
+ * ----------------------------------------
+ */
+ /* variables to communicate with page reader */
+ XLogRecPtr readPagePtr; /* page pointer to read */
+ int32 readLen; /* bytes requested to reader, or actual bytes
+ * read by reader, which must be larger than
+ * the request, or -1 on error */
+ TimeLineID readPageTLI; /* TLI for data currently in readBuf */
+ char *readBuf; /* buffer to store data */
+ bool page_verified; /* is the page on the buffer verified? */
+
/* ----------------------------------------
* Decoded representation of current record
@@ -157,13 +171,6 @@ struct XLogReaderState
* ----------------------------------------
*/
- /*
- * Buffer for currently read page (XLOG_BLCKSZ bytes, valid up to at least
- * readLen bytes)
- */
- char *readBuf;
- uint32 readLen;
-
/* last read XLOG position for data currently in readBuf */
WALSegmentContext segcxt;
WALOpenSegment seg;
diff --git a/src/include/access/xlogutils.h b/src/include/access/xlogutils.h
index 0572b24192..0867ef0599 100644
--- a/src/include/access/xlogutils.h
+++ b/src/include/access/xlogutils.h
@@ -47,7 +47,7 @@ extern Buffer XLogReadBufferExtended(RelFileNode rnode, ForkNumber forknum,
extern Relation CreateFakeRelcacheEntry(RelFileNode rnode);
extern void FreeFakeRelcacheEntry(Relation fakerel);
-extern int read_local_xlog_page(XLogReaderState *state,
+extern bool read_local_xlog_page(XLogReaderState *state,
XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetRecPtr, char *cur_page);
diff --git a/src/include/replication/logicalfuncs.h b/src/include/replication/logicalfuncs.h
index 012096f183..54291221c3 100644
--- a/src/include/replication/logicalfuncs.h
+++ b/src/include/replication/logicalfuncs.h
@@ -11,7 +11,7 @@
#include "replication/logical.h"
-extern int logical_read_local_xlog_page(XLogReaderState *state,
+extern bool logical_read_local_xlog_page(XLogReaderState *state,
XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr,
char *cur_page);
--
2.23.0
----Next_Part(Fri_Nov_29_17_14_21_2019_330)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="v12-0002-Move-page-reader-out-of-XLogReadRecord.patch"
^ permalink raw reply [nested|flat] 199+ messages in thread
* [PATCH v7 1/4] Move callback-call from ReadPageInternal to XLogReadRecord.
@ 2019-09-05 11:21 Kyotaro Horiguchi <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Kyotaro Horiguchi @ 2019-09-05 11:21 UTC (permalink / raw)
The current WAL record reader reads page data using a call back
function. Although it is not so problematic alone, it would be a
problem if we are going to do add tasks like encryption which is
performed on page data before WAL reader reads them. To avoid that the
record reader facility has to have a new code path corresponds to
every new callback, this patch separates page reader from WAL record
reading facility by modifying the current WAL record reader to a state
machine.
As the first step of that change, this patch moves the page reader
function out of ReadPageInternal, then the remaining tasks of the
function are taken over by the new function XLogNeedData. As the
result XLogPageRead directly calls the page reader callback function
according to the feedback from XLogNeedData.
---
src/backend/access/transam/xlog.c | 16 +-
src/backend/access/transam/xlogreader.c | 272 ++++++++++++++----------
src/backend/access/transam/xlogutils.c | 12 +-
src/backend/replication/walsender.c | 10 +-
src/bin/pg_rewind/parsexlog.c | 16 +-
src/bin/pg_waldump/pg_waldump.c | 8 +-
src/include/access/xlogreader.h | 23 +-
src/include/access/xlogutils.h | 2 +-
8 files changed, 211 insertions(+), 148 deletions(-)
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 7621fc05e2..51c409d00e 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -900,7 +900,7 @@ static bool InstallXLogFileSegment(XLogSegNo *segno, char *tmppath,
static int XLogFileRead(XLogSegNo segno, int emode, TimeLineID tli,
XLogSource source, bool notfoundOk);
static int XLogFileReadAnyTLI(XLogSegNo segno, int emode, XLogSource source);
-static int XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
+static bool XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *readBuf);
static bool WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
bool fetching_ckpt, XLogRecPtr tliRecPtr);
@@ -4285,7 +4285,6 @@ ReadRecord(XLogReaderState *xlogreader, int emode,
XLogRecord *record;
XLogPageReadPrivate *private = (XLogPageReadPrivate *) xlogreader->private_data;
- /* Pass through parameters to XLogPageRead */
private->fetching_ckpt = fetching_ckpt;
private->emode = emode;
private->randAccess = (xlogreader->ReadRecPtr == InvalidXLogRecPtr);
@@ -11660,7 +11659,7 @@ CancelBackup(void)
* XLogPageRead() to try fetching the record from another source, or to
* sleep and retry.
*/
-static int
+static bool
XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetRecPtr, char *readBuf)
{
@@ -11719,7 +11718,8 @@ retry:
readLen = 0;
readSource = XLOG_FROM_ANY;
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
}
@@ -11814,7 +11814,8 @@ retry:
goto next_record_is_invalid;
}
- return readLen;
+ xlogreader->readLen = readLen;
+ return true;
next_record_is_invalid:
lastSourceFailed = true;
@@ -11828,8 +11829,9 @@ next_record_is_invalid:
/* In standby-mode, keep trying */
if (StandbyMode)
goto retry;
- else
- return -1;
+
+ xlogreader->readLen = -1;
+ return false;
}
/*
diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c
index 32f02256ed..2c1500443e 100644
--- a/src/backend/access/transam/xlogreader.c
+++ b/src/backend/access/transam/xlogreader.c
@@ -36,8 +36,8 @@
static void report_invalid_record(XLogReaderState *state, const char *fmt,...)
pg_attribute_printf(2, 3);
static bool allocate_recordbuf(XLogReaderState *state, uint32 reclength);
-static int ReadPageInternal(XLogReaderState *state, XLogRecPtr pageptr,
- int reqLen);
+static bool XLogNeedData(XLogReaderState *state, XLogRecPtr pageptr,
+ int reqLen, bool header_inclusive);
static void XLogReaderInvalReadState(XLogReaderState *state);
static bool ValidXLogRecordHeader(XLogReaderState *state, XLogRecPtr RecPtr,
XLogRecPtr PrevRecPtr, XLogRecord *record, bool randAccess);
@@ -269,7 +269,6 @@ XLogReadRecord(XLogReaderState *state, char **errormsg)
uint32 targetRecOff;
uint32 pageHeaderSize;
bool gotheader;
- int readOff;
/*
* randAccess indicates whether to verify the previous-record pointer of
@@ -319,14 +318,20 @@ XLogReadRecord(XLogReaderState *state, char **errormsg)
* byte to cover the whole record header, or at least the part of it that
* fits on the same page.
*/
- readOff = ReadPageInternal(state, targetPagePtr,
- Min(targetRecOff + SizeOfXLogRecord, XLOG_BLCKSZ));
- if (readOff < 0)
+ while (XLogNeedData(state, targetPagePtr,
+ Min(targetRecOff + SizeOfXLogRecord, XLOG_BLCKSZ),
+ targetRecOff != 0))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ RecPtr, state->readBuf))
+ break;
+ }
+
+ if (!state->page_verified)
goto err;
/*
- * ReadPageInternal always returns at least the page header, so we can
- * examine it now.
+ * We have at least the page header, so we can examine it now.
*/
pageHeaderSize = XLogPageHeaderSize((XLogPageHeader) state->readBuf);
if (targetRecOff == 0)
@@ -352,8 +357,8 @@ XLogReadRecord(XLogReaderState *state, char **errormsg)
goto err;
}
- /* ReadPageInternal has verified the page header */
- Assert(pageHeaderSize <= readOff);
+ /* XLogNeedData has verified the page header */
+ Assert(pageHeaderSize <= state->readLen);
/*
* Read the record length.
@@ -426,18 +431,25 @@ XLogReadRecord(XLogReaderState *state, char **errormsg)
do
{
+ int rest_len = total_len - gotlen;
+
/* Calculate pointer to beginning of next page */
targetPagePtr += XLOG_BLCKSZ;
/* Wait for the next page to become available */
- readOff = ReadPageInternal(state, targetPagePtr,
- Min(total_len - gotlen + SizeOfXLogShortPHD,
- XLOG_BLCKSZ));
+ while (XLogNeedData(state, targetPagePtr,
+ Min(rest_len, XLOG_BLCKSZ),
+ false))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ state->ReadRecPtr, state->readBuf))
+ break;
+ }
- if (readOff < 0)
+ if (!state->page_verified)
goto err;
- Assert(SizeOfXLogShortPHD <= readOff);
+ Assert(SizeOfXLogShortPHD <= state->readLen);
/* Check that the continuation on next page looks valid */
pageHeader = (XLogPageHeader) state->readBuf;
@@ -466,21 +478,14 @@ XLogReadRecord(XLogReaderState *state, char **errormsg)
/* Append the continuation from this page to the buffer */
pageHeaderSize = XLogPageHeaderSize(pageHeader);
- if (readOff < pageHeaderSize)
- readOff = ReadPageInternal(state, targetPagePtr,
- pageHeaderSize);
-
- Assert(pageHeaderSize <= readOff);
+ Assert(pageHeaderSize <= state->readLen);
contdata = (char *) state->readBuf + pageHeaderSize;
len = XLOG_BLCKSZ - pageHeaderSize;
if (pageHeader->xlp_rem_len < len)
len = pageHeader->xlp_rem_len;
- if (readOff < pageHeaderSize + len)
- readOff = ReadPageInternal(state, targetPagePtr,
- pageHeaderSize + len);
-
+ Assert (pageHeaderSize + len <= state->readLen);
memcpy(buffer, (char *) contdata, len);
buffer += len;
gotlen += len;
@@ -510,9 +515,15 @@ XLogReadRecord(XLogReaderState *state, char **errormsg)
else
{
/* Wait for the record data to become available */
- readOff = ReadPageInternal(state, targetPagePtr,
- Min(targetRecOff + total_len, XLOG_BLCKSZ));
- if (readOff < 0)
+ while (XLogNeedData(state, targetPagePtr,
+ Min(targetRecOff + total_len, XLOG_BLCKSZ), true))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ state->ReadRecPtr, state->readBuf))
+ break;
+ }
+
+ if (!state->page_verified)
goto err;
/* Record does not cross a page boundary */
@@ -555,109 +566,139 @@ err:
}
/*
- * Read a single xlog page including at least [pageptr, reqLen] of valid data
- * via the read_page() callback.
+ * Checks that an xlog page loaded in state->readBuf is including at least
+ * [pageptr, reqLen] and the page is valid. header_inclusive indicates that
+ * reqLen is calculated including page header length.
*
- * Returns -1 if the required page cannot be read for some reason; errormsg_buf
- * is set in that case (unless the error occurs in the read_page callback).
+ * Returns false if the buffer already contains the requested data, or found
+ * error. state->page_verified is set to true for the former and false for the
+ * latter.
*
- * We fetch the page from a reader-local cache if we know we have the required
- * data and if there hasn't been any error since caching the data.
+ * Otherwise returns true and requests data loaded onto state->readBuf by
+ * state->readPagePtr and state->readLen. The caller shall call this function
+ * again after filling the buffer at least with that portion of data and set
+ * state->readLen to the length of actually loaded data.
+ *
+ * If header_inclusive is false, corrects reqLen internally by adding the
+ * actual page header length and may request caller for new data.
*/
-static int
-ReadPageInternal(XLogReaderState *state, XLogRecPtr pageptr, int reqLen)
+static bool
+XLogNeedData(XLogReaderState *state, XLogRecPtr pageptr, int reqLen,
+ bool header_inclusive)
{
- int readLen;
uint32 targetPageOff;
XLogSegNo targetSegNo;
- XLogPageHeader hdr;
+ uint32 addLen = 0;
- Assert((pageptr % XLOG_BLCKSZ) == 0);
+ /* Some data is loaded, but page header is not verified yet. */
+ if (!state->page_verified &&
+ !XLogRecPtrIsInvalid(state->readPagePtr) && state->readLen >= 0)
+ {
+ uint32 pageHeaderSize;
- XLByteToSeg(pageptr, targetSegNo, state->segcxt.ws_segsize);
- targetPageOff = XLogSegmentOffset(pageptr, state->segcxt.ws_segsize);
+ /* just loaded new data so needs to verify page header */
- /* check whether we have all the requested data already */
- if (targetSegNo == state->seg.ws_segno &&
- targetPageOff == state->segoff && reqLen <= state->readLen)
- return state->readLen;
+ /* The caller must have loaded at least page header */
+ Assert (state->readLen >= SizeOfXLogShortPHD);
- /*
- * Data is not in our buffer.
- *
- * Every time we actually read the page, even if we looked at parts of it
- * before, we need to do verification as the read_page callback might now
- * be rereading data from a different source.
- *
- * Whenever switching to a new WAL segment, we read the first page of the
- * file and validate its header, even if that's not where the target
- * record is. This is so that we can check the additional identification
- * info that is present in the first page's "long" header.
- */
- if (targetSegNo != state->seg.ws_segno && targetPageOff != 0)
- {
- XLogRecPtr targetSegmentPtr = pageptr - targetPageOff;
+ /*
+ * We have enough data to check the header length. Recheck the loaded
+ * length against the actual header length.
+ */
+ pageHeaderSize = XLogPageHeaderSize((XLogPageHeader) state->readBuf);
- readLen = state->read_page(state, targetSegmentPtr, XLOG_BLCKSZ,
- state->currRecPtr,
- state->readBuf);
- if (readLen < 0)
- goto err;
+ /* Request more data if we don't have the full header. */
+ if (state->readLen < pageHeaderSize)
+ {
+ state->readLen = pageHeaderSize;
+ return true;
+ }
+
+ /* Now that we know we have the full header, validate it. */
+ if (!XLogReaderValidatePageHeader(state, state->readPagePtr,
+ (char *) state->readBuf))
+ {
+ /* That's bad. Force reading the page again. */
+ XLogReaderInvalReadState(state);
- /* we can be sure to have enough WAL available, we scrolled back */
- Assert(readLen == XLOG_BLCKSZ);
+ return false;
+ }
- if (!XLogReaderValidatePageHeader(state, targetSegmentPtr,
- state->readBuf))
- goto err;
+ state->page_verified = true;
+
+ XLByteToSeg(state->readPagePtr, state->seg.ws_segno,
+ state->segcxt.ws_segsize);
}
/*
- * First, read the requested data length, but at least a short page header
- * so that we can validate it.
+ * The loaded page may not be the one caller is supposing to read when we
+ * are verifying the first page of new segment. In that case, skip further
+ * verification and immediately load the target page.
*/
- readLen = state->read_page(state, pageptr, Max(reqLen, SizeOfXLogShortPHD),
- state->currRecPtr,
- state->readBuf);
- if (readLen < 0)
- goto err;
-
- Assert(readLen <= XLOG_BLCKSZ);
+ if (state->page_verified && pageptr == state->readPagePtr)
+ {
- /* Do we have enough data to check the header length? */
- if (readLen <= SizeOfXLogShortPHD)
- goto err;
+ /*
+ * calculate additional length for page header keeping the total
+ * length within the block size.
+ */
+ if (!header_inclusive)
+ {
+ uint32 pageHeaderSize =
+ XLogPageHeaderSize((XLogPageHeader) state->readBuf);
- Assert(readLen >= reqLen);
+ addLen = pageHeaderSize;
+ if (reqLen + pageHeaderSize <= XLOG_BLCKSZ)
+ addLen = pageHeaderSize;
+ else
+ addLen = XLOG_BLCKSZ - reqLen;
- hdr = (XLogPageHeader) state->readBuf;
+ Assert(addLen >= 0);
+ }
- /* still not enough */
- if (readLen < XLogPageHeaderSize(hdr))
- {
- readLen = state->read_page(state, pageptr, XLogPageHeaderSize(hdr),
- state->currRecPtr,
- state->readBuf);
- if (readLen < 0)
- goto err;
+ /* Return if we already have it. */
+ if (reqLen + addLen <= state->readLen)
+ return false;
}
+ /* Data is not in our buffer, request the caller for it. */
+ XLByteToSeg(pageptr, targetSegNo, state->segcxt.ws_segsize);
+ targetPageOff = XLogSegmentOffset(pageptr, state->segcxt.ws_segsize);
+ Assert((pageptr % XLOG_BLCKSZ) == 0);
+
/*
- * Now that we know we have the full header, validate it.
+ * Every time we request to load new data of a page to the caller, even if
+ * we looked at a part of it before, we need to do verification on the next
+ * invocation as the caller might now be rereading data from a different
+ * source.
*/
- if (!XLogReaderValidatePageHeader(state, pageptr, (char *) hdr))
- goto err;
-
- /* update read state information */
- state->seg.ws_segno = targetSegNo;
- state->segoff = targetPageOff;
- state->readLen = readLen;
+ state->page_verified = false;
- return readLen;
+ /*
+ * Whenever switching to a new WAL segment, we read the first page of the
+ * file and validate its header, even if that's not where the target
+ * record is. This is so that we can check the additional identification
+ * info that is present in the first page's "long" header.
+ * Don't do this if the caller requested the first page in the segment.
+ */
+ if (targetSegNo != state->seg.ws_segno && targetPageOff != 0)
+ {
+ /*
+ * Then we'll see that the targetSegNo now matches the ws_segno, and
+ * will not come back here, but will request the actual target page.
+ */
+ state->readPagePtr = pageptr - targetPageOff;
+ state->readLen = XLOG_BLCKSZ;
+ return true;
+ }
-err:
- XLogReaderInvalReadState(state);
- return -1;
+ /*
+ * Request the caller to load the page. We need at least a short page
+ * header so that we can validate it.
+ */
+ state->readPagePtr = pageptr;
+ state->readLen = Max(reqLen + addLen, SizeOfXLogShortPHD);
+ return true;
}
/*
@@ -666,9 +707,7 @@ err:
static void
XLogReaderInvalReadState(XLogReaderState *state)
{
- state->seg.ws_segno = 0;
- state->segoff = 0;
- state->readLen = 0;
+ state->readPagePtr = InvalidXLogRecPtr;
}
/*
@@ -949,7 +988,6 @@ XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr)
XLogRecPtr targetPagePtr;
int targetRecOff;
uint32 pageHeaderSize;
- int readLen;
/*
* Compute targetRecOff. It should typically be equal or greater than
@@ -957,7 +995,7 @@ XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr)
* that, except when caller has explicitly specified the offset that
* falls somewhere there or when we are skipping multi-page
* continuation record. It doesn't matter though because
- * ReadPageInternal() is prepared to handle that and will read at
+ * XLogNeedData() is prepared to handle that and will read at
* least short page-header worth of data
*/
targetRecOff = tmpRecPtr % XLOG_BLCKSZ;
@@ -965,19 +1003,23 @@ XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr)
/* scroll back to page boundary */
targetPagePtr = tmpRecPtr - targetRecOff;
- /* Read the page containing the record */
- readLen = ReadPageInternal(state, targetPagePtr, targetRecOff);
- if (readLen < 0)
+ while(XLogNeedData(state, targetPagePtr, targetRecOff,
+ targetRecOff != 0))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ state->ReadRecPtr, state->readBuf))
+ break;
+ }
+
+ if (!state->page_verified)
goto err;
header = (XLogPageHeader) state->readBuf;
pageHeaderSize = XLogPageHeaderSize(header);
- /* make sure we have enough data for the page header */
- readLen = ReadPageInternal(state, targetPagePtr, pageHeaderSize);
- if (readLen < 0)
- goto err;
+ /* we should have read the page header */
+ Assert (state->readLen >= pageHeaderSize);
/* skip over potential continuation data */
if (header->xlp_info & XLP_FIRST_IS_CONTRECORD)
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index b217ffa52f..47676bf800 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -685,8 +685,8 @@ XLogTruncateRelation(RelFileNode rnode, ForkNumber forkNum,
void
XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wantLength)
{
- const XLogRecPtr lastReadPage = (state->seg.ws_segno *
- state->segcxt.ws_segsize + state->segoff);
+ const XLogRecPtr lastReadPage = state->seg.ws_segno *
+ state->segcxt.ws_segsize + state->readLen;
Assert(wantPage != InvalidXLogRecPtr && wantPage % XLOG_BLCKSZ == 0);
Assert(wantLength <= XLOG_BLCKSZ);
@@ -818,7 +818,7 @@ wal_segment_open(XLogSegNo nextSegNo, WALSegmentContext * segcxt,
* exists for normal backends, so we have to do a check/sleep/repeat style of
* loop for now.
*/
-int
+bool
read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *cur_page)
{
@@ -920,7 +920,8 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
else if (targetPagePtr + reqLen > read_upto)
{
/* not enough data there */
- return -1;
+ state->readLen = -1;
+ return false;
}
else
{
@@ -938,7 +939,8 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
WALReadRaiseError(&errinfo);
/* number of valid bytes in the buffer */
- return count;
+ state->readLen = count;
+ return true;
}
/*
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 76ec3c7dd0..c66ea308d8 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -762,7 +762,7 @@ StartReplication(StartReplicationCmd *cmd)
* which has to do a plain sleep/busy loop, because the walsender's latch gets
* set every time WAL is flushed.
*/
-static int
+static bool
logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetRecPtr, char *cur_page)
{
@@ -782,7 +782,10 @@ logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int req
/* fail if not (implies we are going to shut down) */
if (flushptr < targetPagePtr + reqLen)
- return -1;
+ {
+ state->readLen = -1;
+ return false;
+ }
if (targetPagePtr + XLOG_BLCKSZ <= flushptr)
count = XLOG_BLCKSZ; /* more than one block available */
@@ -812,7 +815,8 @@ logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int req
XLByteToSeg(targetPagePtr, segno, sendCxt->ws_segsize);
CheckXLogRemoved(segno, sendSeg->ws_tli);
- return count;
+ state->readLen = count;
+ return true;
}
/*
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index eb61cb8803..78ee9f3faa 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -44,7 +44,7 @@ typedef struct XLogPageReadPrivate
int tliIndex;
} XLogPageReadPrivate;
-static int SimpleXLogPageRead(XLogReaderState *xlogreader,
+static bool SimpleXLogPageRead(XLogReaderState *xlogreader,
XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *readBuf);
@@ -227,7 +227,7 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
}
/* XLogReader callback function, to read a WAL page */
-static int
+static bool
SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *readBuf)
{
@@ -282,7 +282,8 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
if (xlogreadfd < 0)
{
pg_log_error("could not open file \"%s\": %m", xlogfpath);
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
}
@@ -295,7 +296,8 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
if (lseek(xlogreadfd, (off_t) targetPageOff, SEEK_SET) < 0)
{
pg_log_error("could not seek in file \"%s\": %m", xlogfpath);
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
@@ -308,13 +310,15 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
pg_log_error("could not read file \"%s\": read %d of %zu",
xlogfpath, r, (Size) XLOG_BLCKSZ);
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
Assert(targetSegNo == xlogreadsegno);
xlogreader->seg.ws_tli = targetHistory[private->tliIndex].tli;
- return XLOG_BLCKSZ;
+ xlogreader->readLen = XLOG_BLCKSZ;
+ return true;
}
/*
diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c
index 279acfa044..443fe33599 100644
--- a/src/bin/pg_waldump/pg_waldump.c
+++ b/src/bin/pg_waldump/pg_waldump.c
@@ -322,7 +322,7 @@ WALDumpOpenSegment(XLogSegNo nextSegNo, WALSegmentContext *segcxt,
/*
* XLogReader read_page callback
*/
-static int
+static bool
WALDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetPtr, char *readBuff)
{
@@ -339,7 +339,8 @@ WALDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
else
{
private->endptr_reached = true;
- return -1;
+ state->readLen = -1;
+ return false;
}
}
@@ -364,7 +365,8 @@ WALDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
(Size) errinfo.wre_req);
}
- return count;
+ state->readLen = count;
+ return true;
}
/*
diff --git a/src/include/access/xlogreader.h b/src/include/access/xlogreader.h
index 4582196e18..6ad953eea3 100644
--- a/src/include/access/xlogreader.h
+++ b/src/include/access/xlogreader.h
@@ -51,7 +51,7 @@ typedef struct WALSegmentContext
typedef struct XLogReaderState XLogReaderState;
/* Function type definition for the read_page callback */
-typedef int (*XLogPageReadCB) (XLogReaderState *xlogreader,
+typedef bool (*XLogPageReadCB) (XLogReaderState *xlogreader,
XLogRecPtr targetPagePtr,
int reqLen,
XLogRecPtr targetRecPtr,
@@ -134,6 +134,20 @@ struct XLogReaderState
XLogRecPtr ReadRecPtr; /* start of last record read */
XLogRecPtr EndRecPtr; /* end+1 of last record read */
+ /* ----------------------------------------
+ * Communication with page reader
+ * readBuf is XLOG_BLCKSZ bytes, valid up to at least readLen bytes.
+ * ----------------------------------------
+ */
+ /* variables to communicate with page reader */
+ XLogRecPtr readPagePtr; /* page pointer to read */
+ int32 readLen; /* bytes requested to reader, or actual bytes
+ * read by reader, which must be larger than
+ * the request, or -1 on error */
+ TimeLineID readPageTLI; /* TLI for data currently in readBuf */
+ char *readBuf; /* buffer to store data */
+ bool page_verified; /* is the page on the buffer verified? */
+
/* ----------------------------------------
* Decoded representation of current record
@@ -160,13 +174,6 @@ struct XLogReaderState
* ----------------------------------------
*/
- /*
- * Buffer for currently read page (XLOG_BLCKSZ bytes, valid up to at least
- * readLen bytes)
- */
- char *readBuf;
- uint32 readLen;
-
/* last read XLOG position for data currently in readBuf */
WALSegmentContext segcxt;
WALOpenSegment seg;
diff --git a/src/include/access/xlogutils.h b/src/include/access/xlogutils.h
index 5181a077d9..dc7d894e5d 100644
--- a/src/include/access/xlogutils.h
+++ b/src/include/access/xlogutils.h
@@ -47,7 +47,7 @@ extern Buffer XLogReadBufferExtended(RelFileNode rnode, ForkNumber forknum,
extern Relation CreateFakeRelcacheEntry(RelFileNode rnode);
extern void FreeFakeRelcacheEntry(Relation fakerel);
-extern int read_local_xlog_page(XLogReaderState *state,
+extern bool read_local_xlog_page(XLogReaderState *state,
XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetRecPtr, char *cur_page);
--
2.18.2
----Next_Part(Tue_Mar_24_18_24_13_2020_275)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="v7-0002-Move-page-reader-out-of-XLogReadRecord.patch"
^ permalink raw reply [nested|flat] 199+ messages in thread
* [PATCH v9 1/4] Move callback-call from ReadPageInternal to XLogReadRecord.
@ 2019-09-05 11:21 Kyotaro Horiguchi <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Kyotaro Horiguchi @ 2019-09-05 11:21 UTC (permalink / raw)
The current WAL record reader reads page data using a call back
function. Although it is not so problematic alone, it would be a
problem if we are going to do add tasks like encryption which is
performed on page data before WAL reader reads them. To avoid that the
record reader facility has to have a new code path corresponds to
every new callback, this patch separates page reader from WAL record
reading facility by modifying the current WAL record reader to a state
machine.
As the first step of that change, this patch moves the page reader
function out of ReadPageInternal, then the remaining tasks of the
function are taken over by the new function XLogNeedData. As the
result XLogPageRead directly calls the page reader callback function
according to the feedback from XLogNeedData.
---
src/backend/access/transam/xlog.c | 16 +-
src/backend/access/transam/xlogreader.c | 273 ++++++++++--------
src/backend/access/transam/xlogutils.c | 10 +-
.../replication/logical/logicalfuncs.c | 2 +-
src/backend/replication/walsender.c | 10 +-
src/bin/pg_rewind/parsexlog.c | 16 +-
src/bin/pg_waldump/pg_waldump.c | 8 +-
src/include/access/xlogreader.h | 23 +-
src/include/access/xlogutils.h | 2 +-
src/include/replication/logicalfuncs.h | 2 +-
src/test/recovery/t/011_crash_recovery.pl | 1 +
11 files changed, 213 insertions(+), 150 deletions(-)
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index b602e28f61..a7630c643a 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -884,7 +884,7 @@ static bool InstallXLogFileSegment(XLogSegNo *segno, char *tmppath,
static int XLogFileRead(XLogSegNo segno, int emode, TimeLineID tli,
int source, bool notfoundOk);
static int XLogFileReadAnyTLI(XLogSegNo segno, int emode, int source);
-static int XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
+static bool XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *readBuf);
static bool WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
bool fetching_ckpt, XLogRecPtr tliRecPtr);
@@ -4249,7 +4249,6 @@ ReadRecord(XLogReaderState *xlogreader, XLogRecPtr RecPtr, int emode,
XLogRecord *record;
XLogPageReadPrivate *private = (XLogPageReadPrivate *) xlogreader->private_data;
- /* Pass through parameters to XLogPageRead */
private->fetching_ckpt = fetching_ckpt;
private->emode = emode;
private->randAccess = (RecPtr != InvalidXLogRecPtr);
@@ -11540,7 +11539,7 @@ CancelBackup(void)
* XLogPageRead() to try fetching the record from another source, or to
* sleep and retry.
*/
-static int
+static bool
XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetRecPtr, char *readBuf)
{
@@ -11599,7 +11598,8 @@ retry:
readLen = 0;
readSource = 0;
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
}
@@ -11694,7 +11694,8 @@ retry:
goto next_record_is_invalid;
}
- return readLen;
+ xlogreader->readLen = readLen;
+ return true;
next_record_is_invalid:
lastSourceFailed = true;
@@ -11708,8 +11709,9 @@ next_record_is_invalid:
/* In standby-mode, keep trying */
if (StandbyMode)
goto retry;
- else
- return -1;
+
+ xlogreader->readLen = -1;
+ return false;
}
/*
diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c
index c8b0d2303d..66f3bc5597 100644
--- a/src/backend/access/transam/xlogreader.c
+++ b/src/backend/access/transam/xlogreader.c
@@ -34,8 +34,8 @@
static void report_invalid_record(XLogReaderState *state, const char *fmt,...)
pg_attribute_printf(2, 3);
static bool allocate_recordbuf(XLogReaderState *state, uint32 reclength);
-static int ReadPageInternal(XLogReaderState *state, XLogRecPtr pageptr,
- int reqLen);
+static bool XLogNeedData(XLogReaderState *state, XLogRecPtr pageptr,
+ int reqLen, bool header_inclusive);
static void XLogReaderInvalReadState(XLogReaderState *state);
static bool ValidXLogRecordHeader(XLogReaderState *state, XLogRecPtr RecPtr,
XLogRecPtr PrevRecPtr, XLogRecord *record, bool randAccess);
@@ -244,7 +244,6 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
uint32 targetRecOff;
uint32 pageHeaderSize;
bool gotheader;
- int readOff;
/*
* randAccess indicates whether to verify the previous-record pointer of
@@ -296,15 +295,20 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
* byte to cover the whole record header, or at least the part of it that
* fits on the same page.
*/
- readOff = ReadPageInternal(state,
- targetPagePtr,
- Min(targetRecOff + SizeOfXLogRecord, XLOG_BLCKSZ));
- if (readOff < 0)
+ while (XLogNeedData(state, targetPagePtr,
+ Min(targetRecOff + SizeOfXLogRecord, XLOG_BLCKSZ),
+ targetRecOff != 0))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ RecPtr, state->readBuf))
+ break;
+ }
+
+ if (!state->page_verified)
goto err;
/*
- * ReadPageInternal always returns at least the page header, so we can
- * examine it now.
+ * We have at least the page header, so we can examine it now.
*/
pageHeaderSize = XLogPageHeaderSize((XLogPageHeader) state->readBuf);
if (targetRecOff == 0)
@@ -330,8 +334,8 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
goto err;
}
- /* ReadPageInternal has verified the page header */
- Assert(pageHeaderSize <= readOff);
+ /* XLogNeedData has verified the page header */
+ Assert(pageHeaderSize <= state->readLen);
/*
* Read the record length.
@@ -404,18 +408,25 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
do
{
+ int rest_len = total_len - gotlen;
+
/* Calculate pointer to beginning of next page */
targetPagePtr += XLOG_BLCKSZ;
/* Wait for the next page to become available */
- readOff = ReadPageInternal(state, targetPagePtr,
- Min(total_len - gotlen + SizeOfXLogShortPHD,
- XLOG_BLCKSZ));
+ while (XLogNeedData(state, targetPagePtr,
+ Min(rest_len, XLOG_BLCKSZ),
+ false))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ state->ReadRecPtr, state->readBuf))
+ break;
+ }
- if (readOff < 0)
+ if (!state->page_verified)
goto err;
- Assert(SizeOfXLogShortPHD <= readOff);
+ Assert(SizeOfXLogShortPHD <= state->readLen);
/* Check that the continuation on next page looks valid */
pageHeader = (XLogPageHeader) state->readBuf;
@@ -444,21 +455,14 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
/* Append the continuation from this page to the buffer */
pageHeaderSize = XLogPageHeaderSize(pageHeader);
- if (readOff < pageHeaderSize)
- readOff = ReadPageInternal(state, targetPagePtr,
- pageHeaderSize);
-
- Assert(pageHeaderSize <= readOff);
+ Assert (pageHeaderSize <= state->readLen);
contdata = (char *) state->readBuf + pageHeaderSize;
len = XLOG_BLCKSZ - pageHeaderSize;
if (pageHeader->xlp_rem_len < len)
len = pageHeader->xlp_rem_len;
- if (readOff < pageHeaderSize + len)
- readOff = ReadPageInternal(state, targetPagePtr,
- pageHeaderSize + len);
-
+ Assert (pageHeaderSize + len <= state->readLen);
memcpy(buffer, (char *) contdata, len);
buffer += len;
gotlen += len;
@@ -488,9 +492,15 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
else
{
/* Wait for the record data to become available */
- readOff = ReadPageInternal(state, targetPagePtr,
- Min(targetRecOff + total_len, XLOG_BLCKSZ));
- if (readOff < 0)
+ while (XLogNeedData(state, targetPagePtr,
+ Min(targetRecOff + total_len, XLOG_BLCKSZ), true))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ state->ReadRecPtr, state->readBuf))
+ break;
+ }
+
+ if (!state->page_verified)
goto err;
/* Record does not cross a page boundary */
@@ -533,109 +543,139 @@ err:
}
/*
- * Read a single xlog page including at least [pageptr, reqLen] of valid data
- * via the read_page() callback.
+ * Checks that an xlog page loaded in state->readBuf is including at least
+ * [pageptr, reqLen] and the page is valid. header_inclusive indicates that
+ * reqLen is calculated including page header length.
*
- * Returns -1 if the required page cannot be read for some reason; errormsg_buf
- * is set in that case (unless the error occurs in the read_page callback).
+ * Returns false if the buffer already contains the requested data, or found
+ * error. state->page_verified is set to true for the former and false for the
+ * latter.
*
- * We fetch the page from a reader-local cache if we know we have the required
- * data and if there hasn't been any error since caching the data.
+ * Otherwise returns true and requests data loaded onto state->readBuf by
+ * state->readPagePtr and state->readLen. The caller shall call this function
+ * again after filling the buffer at least with that portion of data and set
+ * state->readLen to the length of actually loaded data.
+ *
+ * If header_inclusive is false, corrects reqLen internally by adding the
+ * actual page header length and may request caller for new data.
*/
-static int
-ReadPageInternal(XLogReaderState *state, XLogRecPtr pageptr, int reqLen)
+static bool
+XLogNeedData(XLogReaderState *state, XLogRecPtr pageptr, int reqLen,
+ bool header_inclusive)
{
- int readLen;
uint32 targetPageOff;
XLogSegNo targetSegNo;
- XLogPageHeader hdr;
+ uint32 addLen = 0;
- Assert((pageptr % XLOG_BLCKSZ) == 0);
+ /* Some data is loaded, but page header is not verified yet. */
+ if (!state->page_verified &&
+ !XLogRecPtrIsInvalid(state->readPagePtr) && state->readLen >= 0)
+ {
+ uint32 pageHeaderSize;
- XLByteToSeg(pageptr, targetSegNo, state->segcxt.ws_segsize);
- targetPageOff = XLogSegmentOffset(pageptr, state->segcxt.ws_segsize);
+ /* just loaded new data so needs to verify page header */
- /* check whether we have all the requested data already */
- if (targetSegNo == state->seg.ws_segno &&
- targetPageOff == state->seg.ws_off && reqLen <= state->readLen)
- return state->readLen;
+ /* The caller must have loaded at least page header */
+ Assert (state->readLen >= SizeOfXLogShortPHD);
- /*
- * Data is not in our buffer.
- *
- * Every time we actually read the page, even if we looked at parts of it
- * before, we need to do verification as the read_page callback might now
- * be rereading data from a different source.
- *
- * Whenever switching to a new WAL segment, we read the first page of the
- * file and validate its header, even if that's not where the target
- * record is. This is so that we can check the additional identification
- * info that is present in the first page's "long" header.
- */
- if (targetSegNo != state->seg.ws_segno && targetPageOff != 0)
- {
- XLogRecPtr targetSegmentPtr = pageptr - targetPageOff;
+ /*
+ * We have enough data to check the header length. Recheck the loaded
+ * length against the actual header length.
+ */
+ pageHeaderSize = XLogPageHeaderSize((XLogPageHeader) state->readBuf);
- readLen = state->read_page(state, targetSegmentPtr, XLOG_BLCKSZ,
- state->currRecPtr,
- state->readBuf);
- if (readLen < 0)
- goto err;
+ /* Request more data if we don't have the full header. */
+ if (state->readLen < pageHeaderSize)
+ {
+ state->readLen = pageHeaderSize;
+ return true;
+ }
+
+ /* Now that we know we have the full header, validate it. */
+ if (!XLogReaderValidatePageHeader(state, state->readPagePtr,
+ (char *) state->readBuf))
+ {
+ /* That's bad. Force reading the page again. */
+ XLogReaderInvalReadState(state);
- /* we can be sure to have enough WAL available, we scrolled back */
- Assert(readLen == XLOG_BLCKSZ);
+ return false;
+ }
- if (!XLogReaderValidatePageHeader(state, targetSegmentPtr,
- state->readBuf))
- goto err;
+ state->page_verified = true;
+
+ XLByteToSeg(state->readPagePtr, state->seg.ws_segno,
+ state->segcxt.ws_segsize);
}
/*
- * First, read the requested data length, but at least a short page header
- * so that we can validate it.
+ * The loaded page may not be the one caller is supposing to read when we
+ * are verifying the first page of new segment. In that case, skip further
+ * verification and immediately load the target page.
*/
- readLen = state->read_page(state, pageptr, Max(reqLen, SizeOfXLogShortPHD),
- state->currRecPtr,
- state->readBuf);
- if (readLen < 0)
- goto err;
-
- Assert(readLen <= XLOG_BLCKSZ);
+ if (state->page_verified && pageptr == state->readPagePtr)
+ {
- /* Do we have enough data to check the header length? */
- if (readLen <= SizeOfXLogShortPHD)
- goto err;
+ /*
+ * calculate additional length for page header keeping the total
+ * length within the block size.
+ */
+ if (!header_inclusive)
+ {
+ uint32 pageHeaderSize =
+ XLogPageHeaderSize((XLogPageHeader) state->readBuf);
- Assert(readLen >= reqLen);
+ addLen = pageHeaderSize;
+ if (reqLen + pageHeaderSize <= XLOG_BLCKSZ)
+ addLen = pageHeaderSize;
+ else
+ addLen = XLOG_BLCKSZ - reqLen;
- hdr = (XLogPageHeader) state->readBuf;
+ Assert(addLen >= 0);
+ }
- /* still not enough */
- if (readLen < XLogPageHeaderSize(hdr))
- {
- readLen = state->read_page(state, pageptr, XLogPageHeaderSize(hdr),
- state->currRecPtr,
- state->readBuf);
- if (readLen < 0)
- goto err;
+ /* Return if we already have it. */
+ if (reqLen + addLen <= state->readLen)
+ return false;
}
+ /* Data is not in our buffer, request the caller for it. */
+ XLByteToSeg(pageptr, targetSegNo, state->segcxt.ws_segsize);
+ targetPageOff = XLogSegmentOffset(pageptr, state->segcxt.ws_segsize);
+ Assert((pageptr % XLOG_BLCKSZ) == 0);
+
/*
- * Now that we know we have the full header, validate it.
+ * Every time we request to load new data of a page to the caller, even if
+ * we looked at a part of it before, we need to do verification on the next
+ * invocation as the caller might now be rereading data from a different
+ * source.
*/
- if (!XLogReaderValidatePageHeader(state, pageptr, (char *) hdr))
- goto err;
-
- /* update read state information */
- state->seg.ws_segno = targetSegNo;
- state->seg.ws_off = targetPageOff;
- state->readLen = readLen;
+ state->page_verified = false;
- return readLen;
+ /*
+ * Whenever switching to a new WAL segment, we read the first page of the
+ * file and validate its header, even if that's not where the target
+ * record is. This is so that we can check the additional identification
+ * info that is present in the first page's "long" header.
+ * Don't do this if the caller requested the first page in the segment.
+ */
+ if (targetSegNo != state->seg.ws_segno && targetPageOff != 0)
+ {
+ /*
+ * Then we'll see that the targetSegNo now matches the ws_segno, and
+ * will not come back here, but will request the actual target page.
+ */
+ state->readPagePtr = pageptr - targetPageOff;
+ state->readLen = XLOG_BLCKSZ;
+ return true;
+ }
-err:
- XLogReaderInvalReadState(state);
- return -1;
+ /*
+ * Request the caller to load the page. We need at least a short page
+ * header so that we can validate it.
+ */
+ state->readPagePtr = pageptr;
+ state->readLen = Max(reqLen + addLen, SizeOfXLogShortPHD);
+ return true;
}
/*
@@ -644,9 +684,7 @@ err:
static void
XLogReaderInvalReadState(XLogReaderState *state)
{
- state->seg.ws_segno = 0;
- state->seg.ws_off = 0;
- state->readLen = 0;
+ state->readPagePtr = InvalidXLogRecPtr;
}
/*
@@ -924,7 +962,6 @@ XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr)
XLogRecPtr targetPagePtr;
int targetRecOff;
uint32 pageHeaderSize;
- int readLen;
/*
* Compute targetRecOff. It should typically be equal or greater than
@@ -932,7 +969,7 @@ XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr)
* that, except when caller has explicitly specified the offset that
* falls somewhere there or when we are skipping multi-page
* continuation record. It doesn't matter though because
- * ReadPageInternal() is prepared to handle that and will read at
+ * CheckPage() is prepared to handle that and will read at
* least short page-header worth of data
*/
targetRecOff = tmpRecPtr % XLOG_BLCKSZ;
@@ -940,19 +977,23 @@ XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr)
/* scroll back to page boundary */
targetPagePtr = tmpRecPtr - targetRecOff;
- /* Read the page containing the record */
- readLen = ReadPageInternal(state, targetPagePtr, targetRecOff);
- if (readLen < 0)
+ while(XLogNeedData(state, targetPagePtr, targetRecOff,
+ targetRecOff != 0))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ state->ReadRecPtr, state->readBuf))
+ break;
+ }
+
+ if (!state->page_verified)
goto err;
header = (XLogPageHeader) state->readBuf;
pageHeaderSize = XLogPageHeaderSize(header);
- /* make sure we have enough data for the page header */
- readLen = ReadPageInternal(state, targetPagePtr, pageHeaderSize);
- if (readLen < 0)
- goto err;
+ /* we should have read the page header */
+ Assert (state->readLen >= pageHeaderSize);
/* skip over potential continuation data */
if (header->xlp_info & XLP_FIRST_IS_CONTRECORD)
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index 5f1e5ba75d..a19726a96e 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -803,7 +803,7 @@ void
XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wantLength)
{
const XLogRecPtr lastReadPage = state->seg.ws_segno *
- state->segcxt.ws_segsize + state->seg.ws_off;
+ state->segcxt.ws_segsize + state->readLen;
Assert(wantPage != InvalidXLogRecPtr && wantPage % XLOG_BLCKSZ == 0);
Assert(wantLength <= XLOG_BLCKSZ);
@@ -907,7 +907,7 @@ XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wa
* exists for normal backends, so we have to do a check/sleep/repeat style of
* loop for now.
*/
-int
+bool
read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *cur_page)
{
@@ -1007,7 +1007,8 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
else if (targetPagePtr + reqLen > read_upto)
{
/* not enough data there */
- return -1;
+ state->readLen = -1;
+ return false;
}
else
{
@@ -1024,5 +1025,6 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
XLOG_BLCKSZ);
/* number of valid bytes in the buffer */
- return count;
+ state->readLen = count;
+ return true;
}
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index d1cf80d441..310cd9d8cf 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -114,7 +114,7 @@ check_permissions(void)
(errmsg("must be superuser or replication role to use replication slots"))));
}
-int
+bool
logical_read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *cur_page)
{
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index b0ebe5039c..3ea71a0f84 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -760,7 +760,7 @@ StartReplication(StartReplicationCmd *cmd)
* which has to do a plain sleep/busy loop, because the walsender's latch gets
* set every time WAL is flushed.
*/
-static int
+static bool
logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetRecPtr, char *cur_page)
{
@@ -778,7 +778,10 @@ logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int req
/* fail if not (implies we are going to shut down) */
if (flushptr < targetPagePtr + reqLen)
- return -1;
+ {
+ state->readLen = -1;
+ return false;
+ }
if (targetPagePtr + XLOG_BLCKSZ <= flushptr)
count = XLOG_BLCKSZ; /* more than one block available */
@@ -788,7 +791,8 @@ logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int req
/* now actually read the data, we know it's there */
XLogRead(sendCxt, cur_page, targetPagePtr, XLOG_BLCKSZ);
- return count;
+ state->readLen = count;
+ return true;
}
/*
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 264a8f4db5..8aecd1adc7 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -46,7 +46,7 @@ typedef struct XLogPageReadPrivate
int tliIndex;
} XLogPageReadPrivate;
-static int SimpleXLogPageRead(XLogReaderState *xlogreader,
+static bool SimpleXLogPageRead(XLogReaderState *xlogreader,
XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *readBuf);
@@ -230,7 +230,7 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
}
/* XLogReader callback function, to read a WAL page */
-static int
+static bool
SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *readBuf)
{
@@ -285,7 +285,8 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
if (xlogreadfd < 0)
{
pg_log_error("could not open file \"%s\": %m", xlogfpath);
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
}
@@ -298,7 +299,8 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
if (lseek(xlogreadfd, (off_t) targetPageOff, SEEK_SET) < 0)
{
pg_log_error("could not seek in file \"%s\": %m", xlogfpath);
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
@@ -311,13 +313,15 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
pg_log_error("could not read file \"%s\": read %d of %zu",
xlogfpath, r, (Size) XLOG_BLCKSZ);
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
Assert(targetSegNo == xlogreadsegno);
xlogreader->seg.ws_tli = targetHistory[private->tliIndex].tli;
- return XLOG_BLCKSZ;
+ xlogreader->readLen = XLOG_BLCKSZ;
+ return true;
}
/*
diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c
index b79208cd73..6e424bd8e1 100644
--- a/src/bin/pg_waldump/pg_waldump.c
+++ b/src/bin/pg_waldump/pg_waldump.c
@@ -406,7 +406,7 @@ XLogDumpXLogRead(const char *directory, TimeLineID timeline_id,
/*
* XLogReader read_page callback
*/
-static int
+static bool
XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetPtr, char *readBuff)
{
@@ -422,14 +422,16 @@ XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
else
{
private->endptr_reached = true;
- return -1;
+ state->readLen = -1;
+ return false;
}
}
XLogDumpXLogRead(state->segcxt.ws_dir, private->timeline, targetPagePtr,
readBuff, count);
- return count;
+ state->readLen = count;
+ return true;
}
/*
diff --git a/src/include/access/xlogreader.h b/src/include/access/xlogreader.h
index 1bbee386e8..8b747d465f 100644
--- a/src/include/access/xlogreader.h
+++ b/src/include/access/xlogreader.h
@@ -50,7 +50,7 @@ typedef struct WALSegmentContext
typedef struct XLogReaderState XLogReaderState;
/* Function type definition for the read_page callback */
-typedef int (*XLogPageReadCB) (XLogReaderState *xlogreader,
+typedef bool (*XLogPageReadCB) (XLogReaderState *xlogreader,
XLogRecPtr targetPagePtr,
int reqLen,
XLogRecPtr targetRecPtr,
@@ -132,6 +132,20 @@ struct XLogReaderState
XLogRecPtr ReadRecPtr; /* start of last record read */
XLogRecPtr EndRecPtr; /* end+1 of last record read */
+ /* ----------------------------------------
+ * Communication with page reader
+ * readBuf is XLOG_BLCKSZ bytes, valid up to at least readLen bytes.
+ * ----------------------------------------
+ */
+ /* variables to communicate with page reader */
+ XLogRecPtr readPagePtr; /* page pointer to read */
+ int32 readLen; /* bytes requested to reader, or actual bytes
+ * read by reader, which must be larger than
+ * the request, or -1 on error */
+ TimeLineID readPageTLI; /* TLI for data currently in readBuf */
+ char *readBuf; /* buffer to store data */
+ bool page_verified; /* is the page on the buffer verified? */
+
/* ----------------------------------------
* Decoded representation of current record
@@ -158,13 +172,6 @@ struct XLogReaderState
* ----------------------------------------
*/
- /*
- * Buffer for currently read page (XLOG_BLCKSZ bytes, valid up to at least
- * readLen bytes)
- */
- char *readBuf;
- uint32 readLen;
-
/* last read XLOG position for data currently in readBuf */
WALSegmentContext segcxt;
WALOpenSegment seg;
diff --git a/src/include/access/xlogutils.h b/src/include/access/xlogutils.h
index 2df98e45b2..47b65463f9 100644
--- a/src/include/access/xlogutils.h
+++ b/src/include/access/xlogutils.h
@@ -47,7 +47,7 @@ extern Buffer XLogReadBufferExtended(RelFileNode rnode, ForkNumber forknum,
extern Relation CreateFakeRelcacheEntry(RelFileNode rnode);
extern void FreeFakeRelcacheEntry(Relation fakerel);
-extern int read_local_xlog_page(XLogReaderState *state,
+extern bool read_local_xlog_page(XLogReaderState *state,
XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetRecPtr, char *cur_page);
diff --git a/src/include/replication/logicalfuncs.h b/src/include/replication/logicalfuncs.h
index 012096f183..54291221c3 100644
--- a/src/include/replication/logicalfuncs.h
+++ b/src/include/replication/logicalfuncs.h
@@ -11,7 +11,7 @@
#include "replication/logical.h"
-extern int logical_read_local_xlog_page(XLogReaderState *state,
+extern bool logical_read_local_xlog_page(XLogReaderState *state,
XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr,
char *cur_page);
diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 526a3481fb..c78912571e 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -55,6 +55,7 @@ is($node->safe_psql('postgres', qq[SELECT txid_status('$xid');]),
# Crash and restart the postmaster
$node->stop('immediate');
+print "HOGEEEEEEEEEEEE\n";
$node->start;
# Make sure we really got a new xid
--
2.23.0
----Next_Part(Thu_Oct_24_14_51_01_2019_720)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="v9-0002-Move-page-reader-out-of-XLogReadRecord.patch"
^ permalink raw reply [nested|flat] 199+ messages in thread
* [PATCH v8 1/4] Move callback-call from ReadPageInternal to XLogReadRecord.
@ 2019-09-05 11:21 Kyotaro Horiguchi <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Kyotaro Horiguchi @ 2019-09-05 11:21 UTC (permalink / raw)
The current WAL record reader reads page data using a call back
function. Although it is not so problematic alone, it would be a
problem if we are going to do add tasks like encryption which is
performed on page data before WAL reader reads them. To avoid that the
record reader facility has to have a new code path corresponds to
every new callback, this patch separates page reader from WAL record
reading facility by modifying the current WAL record reader to a state
machine.
As the first step of that change, this patch moves the page reader
function out of ReadPageInternal, then the remaining tasks of the
function are taken over by the new function XLogNeedData. As the
result XLogPageRead directly calls the page reader callback function
according to the feedback from XLogNeedData.
---
src/backend/access/transam/xlog.c | 16 +-
src/backend/access/transam/xlogreader.c | 306 +++++++++++++++----------
src/backend/access/transam/xlogutils.c | 10 +-
src/backend/replication/logical/logicalfuncs.c | 2 +-
src/backend/replication/walsender.c | 10 +-
src/bin/pg_rewind/parsexlog.c | 16 +-
src/bin/pg_waldump/pg_waldump.c | 8 +-
src/include/access/xlogreader.h | 23 +-
src/include/access/xlogutils.h | 2 +-
src/include/replication/logicalfuncs.h | 2 +-
src/test/recovery/t/011_crash_recovery.pl | 1 +
11 files changed, 239 insertions(+), 157 deletions(-)
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 6c69eb6dd7..5dcb2e500c 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -884,7 +884,7 @@ static bool InstallXLogFileSegment(XLogSegNo *segno, char *tmppath,
static int XLogFileRead(XLogSegNo segno, int emode, TimeLineID tli,
int source, bool notfoundOk);
static int XLogFileReadAnyTLI(XLogSegNo segno, int emode, int source);
-static int XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
+static bool XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *readBuf);
static bool WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
bool fetching_ckpt, XLogRecPtr tliRecPtr);
@@ -4249,7 +4249,6 @@ ReadRecord(XLogReaderState *xlogreader, XLogRecPtr RecPtr, int emode,
XLogRecord *record;
XLogPageReadPrivate *private = (XLogPageReadPrivate *) xlogreader->private_data;
- /* Pass through parameters to XLogPageRead */
private->fetching_ckpt = fetching_ckpt;
private->emode = emode;
private->randAccess = (RecPtr != InvalidXLogRecPtr);
@@ -11522,7 +11521,7 @@ CancelBackup(void)
* XLogPageRead() to try fetching the record from another source, or to
* sleep and retry.
*/
-static int
+static bool
XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetRecPtr, char *readBuf)
{
@@ -11581,7 +11580,8 @@ retry:
readLen = 0;
readSource = 0;
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
}
@@ -11676,7 +11676,8 @@ retry:
goto next_record_is_invalid;
}
- return readLen;
+ xlogreader->readLen = readLen;
+ return true;
next_record_is_invalid:
lastSourceFailed = true;
@@ -11690,8 +11691,9 @@ next_record_is_invalid:
/* In standby-mode, keep trying */
if (StandbyMode)
goto retry;
- else
- return -1;
+
+ xlogreader->readLen = -1;
+ return false;
}
/*
diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c
index 27c27303d6..900a628752 100644
--- a/src/backend/access/transam/xlogreader.c
+++ b/src/backend/access/transam/xlogreader.c
@@ -34,8 +34,8 @@
static void report_invalid_record(XLogReaderState *state, const char *fmt,...)
pg_attribute_printf(2, 3);
static bool allocate_recordbuf(XLogReaderState *state, uint32 reclength);
-static int ReadPageInternal(XLogReaderState *state, XLogRecPtr pageptr,
- int reqLen);
+static bool XLogNeedData(XLogReaderState *state, XLogRecPtr pageptr,
+ int reqLen, bool header_inclusive);
static void XLogReaderInvalReadState(XLogReaderState *state);
static bool ValidXLogRecordHeader(XLogReaderState *state, XLogRecPtr RecPtr,
XLogRecPtr PrevRecPtr, XLogRecord *record, bool randAccess);
@@ -104,7 +104,7 @@ XLogReaderAllocate(int wal_segment_size, const char *waldir,
/* system_identifier initialized to zeroes above */
state->private_data = private_data;
/* ReadRecPtr and EndRecPtr initialized to zeroes above */
- /* readSegNo, readOff, readLen, readPageTLI initialized to zeroes above */
+ /* readSegNo, readLen, readPageTLI initialized to zeroes above */
state->errormsg_buf = palloc_extended(MAX_ERRORMSG_LEN + 1,
MCXT_ALLOC_NO_OOM);
if (!state->errormsg_buf)
@@ -245,7 +245,6 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
uint32 targetRecOff;
uint32 pageHeaderSize;
bool gotheader;
- int readOff;
/*
* randAccess indicates whether to verify the previous-record pointer of
@@ -297,15 +296,20 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
* byte to cover the whole record header, or at least the part of it that
* fits on the same page.
*/
- readOff = ReadPageInternal(state,
- targetPagePtr,
- Min(targetRecOff + SizeOfXLogRecord, XLOG_BLCKSZ));
- if (readOff < 0)
+ while (XLogNeedData(state, targetPagePtr,
+ Min(targetRecOff + SizeOfXLogRecord, XLOG_BLCKSZ),
+ targetRecOff != 0))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ RecPtr, state->readBuf))
+ break;
+ }
+
+ if (!state->page_verified)
goto err;
/*
- * ReadPageInternal always returns at least the page header, so we can
- * examine it now.
+ * We have at least the page header, so we can examine it now.
*/
pageHeaderSize = XLogPageHeaderSize((XLogPageHeader) state->readBuf);
if (targetRecOff == 0)
@@ -331,8 +335,8 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
goto err;
}
- /* ReadPageInternal has verified the page header */
- Assert(pageHeaderSize <= readOff);
+ /* XLogNeedData has verified the page header */
+ Assert(pageHeaderSize <= state->readLen);
/*
* Read the record length.
@@ -405,18 +409,25 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
do
{
+ int rest_len = total_len - gotlen;
+
/* Calculate pointer to beginning of next page */
targetPagePtr += XLOG_BLCKSZ;
/* Wait for the next page to become available */
- readOff = ReadPageInternal(state, targetPagePtr,
- Min(total_len - gotlen + SizeOfXLogShortPHD,
- XLOG_BLCKSZ));
+ while (XLogNeedData(state, targetPagePtr,
+ Min(rest_len, XLOG_BLCKSZ),
+ false))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ state->ReadRecPtr, state->readBuf))
+ break;
+ }
- if (readOff < 0)
+ if (!state->page_verified)
goto err;
- Assert(SizeOfXLogShortPHD <= readOff);
+ Assert(SizeOfXLogShortPHD <= state->readLen);
/* Check that the continuation on next page looks valid */
pageHeader = (XLogPageHeader) state->readBuf;
@@ -445,21 +456,14 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
/* Append the continuation from this page to the buffer */
pageHeaderSize = XLogPageHeaderSize(pageHeader);
- if (readOff < pageHeaderSize)
- readOff = ReadPageInternal(state, targetPagePtr,
- pageHeaderSize);
-
- Assert(pageHeaderSize <= readOff);
+ Assert (pageHeaderSize <= state->readLen);
contdata = (char *) state->readBuf + pageHeaderSize;
len = XLOG_BLCKSZ - pageHeaderSize;
if (pageHeader->xlp_rem_len < len)
len = pageHeader->xlp_rem_len;
- if (readOff < pageHeaderSize + len)
- readOff = ReadPageInternal(state, targetPagePtr,
- pageHeaderSize + len);
-
+ Assert (pageHeaderSize + len <= state->readLen);
memcpy(buffer, (char *) contdata, len);
buffer += len;
gotlen += len;
@@ -489,9 +493,15 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
else
{
/* Wait for the record data to become available */
- readOff = ReadPageInternal(state, targetPagePtr,
- Min(targetRecOff + total_len, XLOG_BLCKSZ));
- if (readOff < 0)
+ while (XLogNeedData(state, targetPagePtr,
+ Min(targetRecOff + total_len, XLOG_BLCKSZ), true))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ state->ReadRecPtr, state->readBuf))
+ break;
+ }
+
+ if (!state->page_verified)
goto err;
/* Record does not cross a page boundary */
@@ -534,109 +544,158 @@ err:
}
/*
- * Read a single xlog page including at least [pageptr, reqLen] of valid data
- * via the read_page() callback.
+ * Checks that an xlog page loaded in state->readBuf is including at least
+ * [pageptr, reqLen] and the page is valid. header_inclusive indicates that
+ * reqLen is calculated including page header length.
*
- * Returns -1 if the required page cannot be read for some reason; errormsg_buf
- * is set in that case (unless the error occurs in the read_page callback).
+ * Returns false if the buffer already contains the requested data, or found
+ * error. state->page_verified is set to true for the former and false for the
+ * latter.
*
- * We fetch the page from a reader-local cache if we know we have the required
- * data and if there hasn't been any error since caching the data.
+ * Otherwise returns true and requests data loaded onto state->readBuf by
+ * state->readPagePtr and state->readLen. The caller shall call this function
+ * again after filling the buffer at least with that portion of data and set
+ * state->readLen to the length of actually loaded data.
+ *
+ * If header_inclusive is false, corrects reqLen internally by adding the
+ * actual page header length and may request caller for new data.
*/
-static int
-ReadPageInternal(XLogReaderState *state, XLogRecPtr pageptr, int reqLen)
+static bool
+XLogNeedData(XLogReaderState *state, XLogRecPtr pageptr, int reqLen,
+ bool header_inclusive)
{
- int readLen;
uint32 targetPageOff;
XLogSegNo targetSegNo;
- XLogPageHeader hdr;
-
- Assert((pageptr % XLOG_BLCKSZ) == 0);
-
- XLByteToSeg(pageptr, targetSegNo, state->segcxt.ws_segsize);
- targetPageOff = XLogSegmentOffset(pageptr, state->segcxt.ws_segsize);
+ uint32 addLen = 0;
/* check whether we have all the requested data already */
- if (targetSegNo == state->seg.ws_segno &&
- targetPageOff == state->seg.ws_off && reqLen <= state->readLen)
- return state->readLen;
+ if (state->page_verified && pageptr == state->readPagePtr)
+ {
+ if (!header_inclusive)
+ {
+ /*
+ * calculate additional length for page header so that the total
+ * length doesn't exceed the block size.
+ */
+ uint32 pageHeaderSize =
+ XLogPageHeaderSize((XLogPageHeader) state->readBuf);
+
+ addLen = pageHeaderSize;
+ if (reqLen + pageHeaderSize <= XLOG_BLCKSZ)
+ addLen = pageHeaderSize;
+ else
+ addLen = XLOG_BLCKSZ - reqLen;
+ }
+
+ if (reqLen + addLen <= state->readLen)
+ return false;
+ }
+
+ if (!state->page_verified &&
+ !XLogRecPtrIsInvalid(state->readPagePtr) && state->readLen >= 0)
+ {
+ uint32 pageHeaderSize;
+
+ /* just loaded new data so needs to verify page header */
+
+ /* The caller must have loaded at least page header */
+ Assert (state->readLen >= SizeOfXLogShortPHD);
+
+ /*
+ * We have enough data to check the header length. Recheck the loaded
+ * length if it is a long header if any.
+ */
+ pageHeaderSize = XLogPageHeaderSize((XLogPageHeader) state->readBuf);
+
+ /* Request more data if we don't have the full header. */
+ if (state->readLen < pageHeaderSize)
+ {
+ state->readLen = pageHeaderSize;
+ return true;
+ }
+
+ /* Now that we know we have the full header, validate it. */
+ if (!XLogReaderValidatePageHeader(state, state->readPagePtr,
+ (char *) state->readBuf))
+ {
+ /* That's bad. Force reading the page again. */
+ XLogReaderInvalReadState(state);
+
+ return false;
+ }
+
+ state->page_verified = true;
+
+ XLByteToSeg(state->readPagePtr, state->seg.ws_segno,
+ state->segcxt.ws_segsize);
+
+ /*
+ * The loaded page may not be the one caller is supposing to read when
+ * we are verifying the first page of new segment. In that case, skip
+ * further verification and immediately load the target page.
+ */
+ if (pageptr == state->readPagePtr)
+ {
+
+ /*
+ * calculate additional length for page header keeping the total
+ * length within the block size.
+ */
+ if (!header_inclusive)
+ {
+ addLen = pageHeaderSize;
+ if (reqLen + pageHeaderSize <= XLOG_BLCKSZ)
+ addLen = pageHeaderSize;
+ else
+ addLen = XLOG_BLCKSZ - reqLen;
+
+ Assert(addLen >= 0);
+ }
+
+ /* Return if we already have it. */
+ if (reqLen + addLen <= state->readLen)
+ return false;
+ }
+ }
+
+ /* Data is not in our buffer, request the caller for it. */
+ XLByteToSeg(pageptr, targetSegNo, state->segcxt.ws_segsize);
+ targetPageOff = XLogSegmentOffset(pageptr, state->segcxt.ws_segsize);
+ Assert((pageptr % XLOG_BLCKSZ) == 0);
+
+ /*
+ * Every time we request to load new data of a page to the caller, even if
+ * we looked at a part of it before, we need to do verification on the next
+ * invocation as the caller might now be rereading data from a different
+ * source.
+ */
+ state->page_verified = false;
/*
- * Data is not in our buffer.
- *
- * Every time we actually read the page, even if we looked at parts of it
- * before, we need to do verification as the read_page callback might now
- * be rereading data from a different source.
- *
* Whenever switching to a new WAL segment, we read the first page of the
* file and validate its header, even if that's not where the target
* record is. This is so that we can check the additional identification
* info that is present in the first page's "long" header.
+ * Don't do this if the caller requested the first page in the segment.
*/
if (targetSegNo != state->seg.ws_segno && targetPageOff != 0)
{
- XLogRecPtr targetSegmentPtr = pageptr - targetPageOff;
-
- readLen = state->read_page(state, targetSegmentPtr, XLOG_BLCKSZ,
- state->currRecPtr,
- state->readBuf);
- if (readLen < 0)
- goto err;
-
- /* we can be sure to have enough WAL available, we scrolled back */
- Assert(readLen == XLOG_BLCKSZ);
-
- if (!XLogReaderValidatePageHeader(state, targetSegmentPtr,
- state->readBuf))
- goto err;
+ /*
+ * Then we'll see that the targetSegNo now matches the ws_segno, and
+ * will not come back here, but will request the actual target page.
+ */
+ state->readPagePtr = pageptr - targetPageOff;
+ state->readLen = XLOG_BLCKSZ;
+ return true;
}
/*
- * First, read the requested data length, but at least a short page header
- * so that we can validate it.
+ * Request the caller to load the page. We need at least a short page
+ * header so that we can validate it.
*/
- readLen = state->read_page(state, pageptr, Max(reqLen, SizeOfXLogShortPHD),
- state->currRecPtr,
- state->readBuf);
- if (readLen < 0)
- goto err;
-
- Assert(readLen <= XLOG_BLCKSZ);
-
- /* Do we have enough data to check the header length? */
- if (readLen <= SizeOfXLogShortPHD)
- goto err;
-
- Assert(readLen >= reqLen);
-
- hdr = (XLogPageHeader) state->readBuf;
-
- /* still not enough */
- if (readLen < XLogPageHeaderSize(hdr))
- {
- readLen = state->read_page(state, pageptr, XLogPageHeaderSize(hdr),
- state->currRecPtr,
- state->readBuf);
- if (readLen < 0)
- goto err;
- }
-
- /*
- * Now that we know we have the full header, validate it.
- */
- if (!XLogReaderValidatePageHeader(state, pageptr, (char *) hdr))
- goto err;
-
- /* update read state information */
- state->seg.ws_segno = targetSegNo;
- state->seg.ws_off = targetPageOff;
- state->readLen = readLen;
-
- return readLen;
-
-err:
- XLogReaderInvalReadState(state);
- return -1;
+ state->readPagePtr = pageptr;
+ state->readLen = Max(reqLen + addLen, SizeOfXLogShortPHD);
+ return true;
}
/*
@@ -645,9 +704,7 @@ err:
static void
XLogReaderInvalReadState(XLogReaderState *state)
{
- state->seg.ws_segno = 0;
- state->seg.ws_off = 0;
- state->readLen = 0;
+ state->readPagePtr = InvalidXLogRecPtr;
}
/*
@@ -925,7 +982,6 @@ XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr)
XLogRecPtr targetPagePtr;
int targetRecOff;
uint32 pageHeaderSize;
- int readLen;
/*
* Compute targetRecOff. It should typically be equal or greater than
@@ -933,7 +989,7 @@ XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr)
* that, except when caller has explicitly specified the offset that
* falls somewhere there or when we are skipping multi-page
* continuation record. It doesn't matter though because
- * ReadPageInternal() is prepared to handle that and will read at
+ * CheckPage() is prepared to handle that and will read at
* least short page-header worth of data
*/
targetRecOff = tmpRecPtr % XLOG_BLCKSZ;
@@ -941,19 +997,23 @@ XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr)
/* scroll back to page boundary */
targetPagePtr = tmpRecPtr - targetRecOff;
- /* Read the page containing the record */
- readLen = ReadPageInternal(state, targetPagePtr, targetRecOff);
- if (readLen < 0)
+ while(XLogNeedData(state, targetPagePtr, targetRecOff,
+ targetRecOff != 0))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ state->ReadRecPtr, state->readBuf))
+ break;
+ }
+
+ if (!state->page_verified)
goto err;
header = (XLogPageHeader) state->readBuf;
pageHeaderSize = XLogPageHeaderSize(header);
- /* make sure we have enough data for the page header */
- readLen = ReadPageInternal(state, targetPagePtr, pageHeaderSize);
- if (readLen < 0)
- goto err;
+ /* we should have read the page header */
+ Assert (state->readLen >= pageHeaderSize);
/* skip over potential continuation data */
if (header->xlp_info & XLP_FIRST_IS_CONTRECORD)
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index 5f1e5ba75d..a19726a96e 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -803,7 +803,7 @@ void
XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wantLength)
{
const XLogRecPtr lastReadPage = state->seg.ws_segno *
- state->segcxt.ws_segsize + state->seg.ws_off;
+ state->segcxt.ws_segsize + state->readLen;
Assert(wantPage != InvalidXLogRecPtr && wantPage % XLOG_BLCKSZ == 0);
Assert(wantLength <= XLOG_BLCKSZ);
@@ -907,7 +907,7 @@ XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wa
* exists for normal backends, so we have to do a check/sleep/repeat style of
* loop for now.
*/
-int
+bool
read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *cur_page)
{
@@ -1007,7 +1007,8 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
else if (targetPagePtr + reqLen > read_upto)
{
/* not enough data there */
- return -1;
+ state->readLen = -1;
+ return false;
}
else
{
@@ -1024,5 +1025,6 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
XLOG_BLCKSZ);
/* number of valid bytes in the buffer */
- return count;
+ state->readLen = count;
+ return true;
}
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index d1cf80d441..310cd9d8cf 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -114,7 +114,7 @@ check_permissions(void)
(errmsg("must be superuser or replication role to use replication slots"))));
}
-int
+bool
logical_read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *cur_page)
{
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index eb4a98cc91..0809ceaeb8 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -760,7 +760,7 @@ StartReplication(StartReplicationCmd *cmd)
* which has to do a plain sleep/busy loop, because the walsender's latch gets
* set every time WAL is flushed.
*/
-static int
+static bool
logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetRecPtr, char *cur_page)
{
@@ -778,7 +778,10 @@ logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int req
/* fail if not (implies we are going to shut down) */
if (flushptr < targetPagePtr + reqLen)
- return -1;
+ {
+ state->readLen = -1;
+ return false;
+ }
if (targetPagePtr + XLOG_BLCKSZ <= flushptr)
count = XLOG_BLCKSZ; /* more than one block available */
@@ -788,7 +791,8 @@ logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int req
/* now actually read the data, we know it's there */
XLogRead(sendCxt, cur_page, targetPagePtr, XLOG_BLCKSZ);
- return count;
+ state->readLen = count;
+ return true;
}
/*
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 264a8f4db5..8aecd1adc7 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -46,7 +46,7 @@ typedef struct XLogPageReadPrivate
int tliIndex;
} XLogPageReadPrivate;
-static int SimpleXLogPageRead(XLogReaderState *xlogreader,
+static bool SimpleXLogPageRead(XLogReaderState *xlogreader,
XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *readBuf);
@@ -230,7 +230,7 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
}
/* XLogReader callback function, to read a WAL page */
-static int
+static bool
SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *readBuf)
{
@@ -285,7 +285,8 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
if (xlogreadfd < 0)
{
pg_log_error("could not open file \"%s\": %m", xlogfpath);
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
}
@@ -298,7 +299,8 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
if (lseek(xlogreadfd, (off_t) targetPageOff, SEEK_SET) < 0)
{
pg_log_error("could not seek in file \"%s\": %m", xlogfpath);
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
@@ -311,13 +313,15 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
pg_log_error("could not read file \"%s\": read %d of %zu",
xlogfpath, r, (Size) XLOG_BLCKSZ);
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
Assert(targetSegNo == xlogreadsegno);
xlogreader->seg.ws_tli = targetHistory[private->tliIndex].tli;
- return XLOG_BLCKSZ;
+ xlogreader->readLen = XLOG_BLCKSZ;
+ return true;
}
/*
diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c
index b79208cd73..6e424bd8e1 100644
--- a/src/bin/pg_waldump/pg_waldump.c
+++ b/src/bin/pg_waldump/pg_waldump.c
@@ -406,7 +406,7 @@ XLogDumpXLogRead(const char *directory, TimeLineID timeline_id,
/*
* XLogReader read_page callback
*/
-static int
+static bool
XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetPtr, char *readBuff)
{
@@ -422,14 +422,16 @@ XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
else
{
private->endptr_reached = true;
- return -1;
+ state->readLen = -1;
+ return false;
}
}
XLogDumpXLogRead(state->segcxt.ws_dir, private->timeline, targetPagePtr,
readBuff, count);
- return count;
+ state->readLen = count;
+ return true;
}
/*
diff --git a/src/include/access/xlogreader.h b/src/include/access/xlogreader.h
index 1bbee386e8..8b747d465f 100644
--- a/src/include/access/xlogreader.h
+++ b/src/include/access/xlogreader.h
@@ -50,7 +50,7 @@ typedef struct WALSegmentContext
typedef struct XLogReaderState XLogReaderState;
/* Function type definition for the read_page callback */
-typedef int (*XLogPageReadCB) (XLogReaderState *xlogreader,
+typedef bool (*XLogPageReadCB) (XLogReaderState *xlogreader,
XLogRecPtr targetPagePtr,
int reqLen,
XLogRecPtr targetRecPtr,
@@ -132,6 +132,20 @@ struct XLogReaderState
XLogRecPtr ReadRecPtr; /* start of last record read */
XLogRecPtr EndRecPtr; /* end+1 of last record read */
+ /* ----------------------------------------
+ * Communication with page reader
+ * readBuf is XLOG_BLCKSZ bytes, valid up to at least readLen bytes.
+ * ----------------------------------------
+ */
+ /* variables to communicate with page reader */
+ XLogRecPtr readPagePtr; /* page pointer to read */
+ int32 readLen; /* bytes requested to reader, or actual bytes
+ * read by reader, which must be larger than
+ * the request, or -1 on error */
+ TimeLineID readPageTLI; /* TLI for data currently in readBuf */
+ char *readBuf; /* buffer to store data */
+ bool page_verified; /* is the page on the buffer verified? */
+
/* ----------------------------------------
* Decoded representation of current record
@@ -158,13 +172,6 @@ struct XLogReaderState
* ----------------------------------------
*/
- /*
- * Buffer for currently read page (XLOG_BLCKSZ bytes, valid up to at least
- * readLen bytes)
- */
- char *readBuf;
- uint32 readLen;
-
/* last read XLOG position for data currently in readBuf */
WALSegmentContext segcxt;
WALOpenSegment seg;
diff --git a/src/include/access/xlogutils.h b/src/include/access/xlogutils.h
index 2df98e45b2..47b65463f9 100644
--- a/src/include/access/xlogutils.h
+++ b/src/include/access/xlogutils.h
@@ -47,7 +47,7 @@ extern Buffer XLogReadBufferExtended(RelFileNode rnode, ForkNumber forknum,
extern Relation CreateFakeRelcacheEntry(RelFileNode rnode);
extern void FreeFakeRelcacheEntry(Relation fakerel);
-extern int read_local_xlog_page(XLogReaderState *state,
+extern bool read_local_xlog_page(XLogReaderState *state,
XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetRecPtr, char *cur_page);
diff --git a/src/include/replication/logicalfuncs.h b/src/include/replication/logicalfuncs.h
index 012096f183..54291221c3 100644
--- a/src/include/replication/logicalfuncs.h
+++ b/src/include/replication/logicalfuncs.h
@@ -11,7 +11,7 @@
#include "replication/logical.h"
-extern int logical_read_local_xlog_page(XLogReaderState *state,
+extern bool logical_read_local_xlog_page(XLogReaderState *state,
XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr,
char *cur_page);
diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 526a3481fb..c78912571e 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -55,6 +55,7 @@ is($node->safe_psql('postgres', qq[SELECT txid_status('$xid');]),
# Crash and restart the postmaster
$node->stop('immediate');
+print "HOGEEEEEEEEEEEE\n";
$node->start;
# Make sure we really got a new xid
--
2.16.3
----Next_Part(Fri_Sep_27_12_07_26_2019_308)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="v8-0002-Move-page-reader-out-of-XLogReadRecord.patch"
^ permalink raw reply [nested|flat] 199+ messages in thread
* [PATCH v7 1/4] Move callback-call from ReadPageInternal to XLogReadRecord.
@ 2019-09-05 11:21 Kyotaro Horiguchi <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Kyotaro Horiguchi @ 2019-09-05 11:21 UTC (permalink / raw)
The current WAL record reader reads page data using a call back
function. Although it is not so problematic alone, it would be a
problem if we are going to do add tasks like encryption which is
performed on page data before WAL reader reads them. To avoid that the
record reader facility has to have a new code path corresponds to
every new callback, this patch separates page reader from WAL record
reading facility by modifying the current WAL record reader to a state
machine.
As the first step of that change, this patch moves the page reader
function out of ReadPageInternal, then the remaining tasks of the
function are taken over by the new function XLogNeedData. As the
result XLogPageRead directly calls the page reader callback function
according to the feedback from XLogNeedData.
---
src/backend/access/transam/xlog.c | 16 +-
src/backend/access/transam/xlogreader.c | 332 +++++++++++++++----------
src/backend/access/transam/xlogutils.c | 10 +-
src/backend/replication/logical/logicalfuncs.c | 2 +-
src/backend/replication/walsender.c | 10 +-
src/bin/pg_rewind/parsexlog.c | 16 +-
src/bin/pg_waldump/pg_waldump.c | 8 +-
src/include/access/xlogreader.h | 23 +-
src/include/access/xlogutils.h | 2 +-
src/include/replication/logicalfuncs.h | 2 +-
10 files changed, 259 insertions(+), 162 deletions(-)
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 6c69eb6dd7..5dcb2e500c 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -884,7 +884,7 @@ static bool InstallXLogFileSegment(XLogSegNo *segno, char *tmppath,
static int XLogFileRead(XLogSegNo segno, int emode, TimeLineID tli,
int source, bool notfoundOk);
static int XLogFileReadAnyTLI(XLogSegNo segno, int emode, int source);
-static int XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
+static bool XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *readBuf);
static bool WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
bool fetching_ckpt, XLogRecPtr tliRecPtr);
@@ -4249,7 +4249,6 @@ ReadRecord(XLogReaderState *xlogreader, XLogRecPtr RecPtr, int emode,
XLogRecord *record;
XLogPageReadPrivate *private = (XLogPageReadPrivate *) xlogreader->private_data;
- /* Pass through parameters to XLogPageRead */
private->fetching_ckpt = fetching_ckpt;
private->emode = emode;
private->randAccess = (RecPtr != InvalidXLogRecPtr);
@@ -11522,7 +11521,7 @@ CancelBackup(void)
* XLogPageRead() to try fetching the record from another source, or to
* sleep and retry.
*/
-static int
+static bool
XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetRecPtr, char *readBuf)
{
@@ -11581,7 +11580,8 @@ retry:
readLen = 0;
readSource = 0;
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
}
@@ -11676,7 +11676,8 @@ retry:
goto next_record_is_invalid;
}
- return readLen;
+ xlogreader->readLen = readLen;
+ return true;
next_record_is_invalid:
lastSourceFailed = true;
@@ -11690,8 +11691,9 @@ next_record_is_invalid:
/* In standby-mode, keep trying */
if (StandbyMode)
goto retry;
- else
- return -1;
+
+ xlogreader->readLen = -1;
+ return false;
}
/*
diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c
index 27c27303d6..c2bb664f07 100644
--- a/src/backend/access/transam/xlogreader.c
+++ b/src/backend/access/transam/xlogreader.c
@@ -34,9 +34,9 @@
static void report_invalid_record(XLogReaderState *state, const char *fmt,...)
pg_attribute_printf(2, 3);
static bool allocate_recordbuf(XLogReaderState *state, uint32 reclength);
-static int ReadPageInternal(XLogReaderState *state, XLogRecPtr pageptr,
- int reqLen);
-static void XLogReaderInvalReadState(XLogReaderState *state);
+static bool XLogNeedData(XLogReaderState *state, XLogRecPtr pageptr,
+ int reqLen, bool header_inclusive);
+static void XLogReaderDiscardReadingPage(XLogReaderState *state);
static bool ValidXLogRecordHeader(XLogReaderState *state, XLogRecPtr RecPtr,
XLogRecPtr PrevRecPtr, XLogRecord *record, bool randAccess);
static bool ValidXLogRecord(XLogReaderState *state, XLogRecord *record,
@@ -104,7 +104,7 @@ XLogReaderAllocate(int wal_segment_size, const char *waldir,
/* system_identifier initialized to zeroes above */
state->private_data = private_data;
/* ReadRecPtr and EndRecPtr initialized to zeroes above */
- /* readSegNo, readOff, readLen, readPageTLI initialized to zeroes above */
+ /* readSegNo, readLen, readPageTLI initialized to zeroes above */
state->errormsg_buf = palloc_extended(MAX_ERRORMSG_LEN + 1,
MCXT_ALLOC_NO_OOM);
if (!state->errormsg_buf)
@@ -245,7 +245,6 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
uint32 targetRecOff;
uint32 pageHeaderSize;
bool gotheader;
- int readOff;
/*
* randAccess indicates whether to verify the previous-record pointer of
@@ -297,15 +296,20 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
* byte to cover the whole record header, or at least the part of it that
* fits on the same page.
*/
- readOff = ReadPageInternal(state,
- targetPagePtr,
- Min(targetRecOff + SizeOfXLogRecord, XLOG_BLCKSZ));
- if (readOff < 0)
+ while (XLogNeedData(state, targetPagePtr,
+ Min(targetRecOff + SizeOfXLogRecord, XLOG_BLCKSZ),
+ targetRecOff != 0))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ RecPtr, state->readBuf))
+ break;
+ }
+
+ if (!state->page_verified)
goto err;
/*
- * ReadPageInternal always returns at least the page header, so we can
- * examine it now.
+ * We have loaded at least the page header, so we can examine it now.
*/
pageHeaderSize = XLogPageHeaderSize((XLogPageHeader) state->readBuf);
if (targetRecOff == 0)
@@ -331,8 +335,8 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
goto err;
}
- /* ReadPageInternal has verified the page header */
- Assert(pageHeaderSize <= readOff);
+ /* XLogNeedData has verified the page header */
+ Assert(pageHeaderSize <= state->readLen);
/*
* Read the record length.
@@ -405,18 +409,25 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
do
{
+ int rest_len = total_len - gotlen;
+
/* Calculate pointer to beginning of next page */
targetPagePtr += XLOG_BLCKSZ;
/* Wait for the next page to become available */
- readOff = ReadPageInternal(state, targetPagePtr,
- Min(total_len - gotlen + SizeOfXLogShortPHD,
- XLOG_BLCKSZ));
+ while (XLogNeedData(state, targetPagePtr,
+ Min(rest_len, XLOG_BLCKSZ),
+ false))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ state->ReadRecPtr, state->readBuf))
+ break;
+ }
- if (readOff < 0)
+ if (!state->page_verified)
goto err;
- Assert(SizeOfXLogShortPHD <= readOff);
+ Assert(SizeOfXLogShortPHD <= state->readLen);
/* Check that the continuation on next page looks valid */
pageHeader = (XLogPageHeader) state->readBuf;
@@ -445,21 +456,14 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
/* Append the continuation from this page to the buffer */
pageHeaderSize = XLogPageHeaderSize(pageHeader);
- if (readOff < pageHeaderSize)
- readOff = ReadPageInternal(state, targetPagePtr,
- pageHeaderSize);
-
- Assert(pageHeaderSize <= readOff);
+ Assert (pageHeaderSize <= state->readLen);
contdata = (char *) state->readBuf + pageHeaderSize;
len = XLOG_BLCKSZ - pageHeaderSize;
if (pageHeader->xlp_rem_len < len)
len = pageHeader->xlp_rem_len;
- if (readOff < pageHeaderSize + len)
- readOff = ReadPageInternal(state, targetPagePtr,
- pageHeaderSize + len);
-
+ Assert (pageHeaderSize + len <= state->readLen);
memcpy(buffer, (char *) contdata, len);
buffer += len;
gotlen += len;
@@ -489,9 +493,15 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
else
{
/* Wait for the record data to become available */
- readOff = ReadPageInternal(state, targetPagePtr,
- Min(targetRecOff + total_len, XLOG_BLCKSZ));
- if (readOff < 0)
+ while (XLogNeedData(state, targetPagePtr,
+ Min(targetRecOff + total_len, XLOG_BLCKSZ), true))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ state->ReadRecPtr, state->readBuf))
+ break;
+ }
+
+ if (!state->page_verified)
goto err;
/* Record does not cross a page boundary */
@@ -525,7 +535,7 @@ err:
* Invalidate the read state. We might read from a different source after
* failure.
*/
- XLogReaderInvalReadState(state);
+ XLogReaderDiscardReadingPage(state);
if (state->errormsg_buf[0] != '\0')
*errormsg = state->errormsg_buf;
@@ -534,120 +544,183 @@ err:
}
/*
- * Read a single xlog page including at least [pageptr, reqLen] of valid data
- * via the read_page() callback.
+ * Checks that an xlog page loaded in state->readBuf is including at least
+ * [pageptr, reqLen] and the page is valid. header_inclusive indicates that
+ * reqLen is calculated including page header length.
*
- * Returns -1 if the required page cannot be read for some reason; errormsg_buf
- * is set in that case (unless the error occurs in the read_page callback).
+ * Returns false if the buffer already contains the requested data, or found
+ * error. state->page_verified is set to true for the former and false for the
+ * latter.
*
- * We fetch the page from a reader-local cache if we know we have the required
- * data and if there hasn't been any error since caching the data.
+ * Otherwise returns true and requests data loaded onto state->readBuf by
+ * state->readPagePtr and state->readLen. The caller shall call this function
+ * again after filling the buffer at least with that portion of data and set
+ * state->readLen to the length of actually loaded data.
+ *
+ * If header_inclusive is false, corrects reqLen internally by adding the
+ * actual page header length and may request caller for new data.
*/
-static int
-ReadPageInternal(XLogReaderState *state, XLogRecPtr pageptr, int reqLen)
+static bool
+XLogNeedData(XLogReaderState *state, XLogRecPtr pageptr, int reqLen,
+ bool header_inclusive)
{
- int readLen;
uint32 targetPageOff;
XLogSegNo targetSegNo;
- XLogPageHeader hdr;
-
- Assert((pageptr % XLOG_BLCKSZ) == 0);
-
- XLByteToSeg(pageptr, targetSegNo, state->segcxt.ws_segsize);
- targetPageOff = XLogSegmentOffset(pageptr, state->segcxt.ws_segsize);
+ uint32 addLen = 0;
/* check whether we have all the requested data already */
- if (targetSegNo == state->seg.ws_segno &&
- targetPageOff == state->seg.ws_off && reqLen <= state->readLen)
- return state->readLen;
+ if (state->page_verified && pageptr == state->readPagePtr)
+ {
+ if (!header_inclusive)
+ {
+ /*
+ * calculate additional length for page header so that the total
+ * length doesn't exceed the block size.
+ */
+ uint32 pageHeaderSize =
+ XLogPageHeaderSize((XLogPageHeader) state->readBuf);
+
+ addLen = pageHeaderSize;
+ if (reqLen + pageHeaderSize <= XLOG_BLCKSZ)
+ addLen = pageHeaderSize;
+ else
+ addLen = XLOG_BLCKSZ - reqLen;
+ }
+
+ if (reqLen + addLen <= state->readLen)
+ return false;
+ }
+
+ /* Haven't loaded any data yet? Then request it. */
+ if (XLogRecPtrIsInvalid(state->readPagePtr) ||
+ state->readPagePtr != pageptr || state->readLen < 0)
+ {
+ state->readPagePtr = pageptr;
+ state->readLen = Max(reqLen, SizeOfXLogShortPHD);
+ state->page_verified = false;
+ return true;
+ }
+
+ if (!state->page_verified)
+ {
+ uint32 pageHeaderSize;
+ uint32 addLen = 0;
+
+ /* just loaded new data so needs to verify page header */
+
+ /* The caller must have loaded at least page header */
+ Assert(state->readLen >= SizeOfXLogShortPHD);
+
+ /*
+ * We have enough data to check the header length. Recheck the loaded
+ * length if it is a long header if any.
+ */
+ pageHeaderSize = XLogPageHeaderSize((XLogPageHeader) state->readBuf);
+
+ /*
+ * If we have not loaded a page so far, readLen is zero, which is
+ * shorter than pageHeaderSize here.
+ */
+ if (state->readLen < pageHeaderSize)
+ {
+ state->readPagePtr = pageptr;
+ state->readLen = pageHeaderSize;
+ return true;
+ }
+
+ /*
+ * Now that we know we have the full header, validate it.
+ */
+ if (!XLogReaderValidatePageHeader(state, state->readPagePtr,
+ (char *) state->readBuf))
+ {
+ /* force reading the page again. */
+ XLogReaderDiscardReadingPage(state);
+
+ return false;
+ }
+
+ state->page_verified = true;
+
+ XLByteToSeg(state->readPagePtr, state->seg.ws_segno,
+ state->segcxt.ws_segsize);
+
+ /*
+ * calculate additional length for page header so that the total
+ * length doesn't exceed the block size.
+ */
+ if (!header_inclusive)
+ {
+ addLen = pageHeaderSize;
+ if (reqLen + pageHeaderSize <= XLOG_BLCKSZ)
+ addLen = pageHeaderSize;
+ else
+ addLen = XLOG_BLCKSZ - reqLen;
+
+ Assert(addLen >= 0);
+ }
+
+ /* Usually we have requested data loaded in buffer here. */
+ if (pageptr == state->readPagePtr && reqLen + addLen <= state->readLen)
+ return false;
+
+ /*
+ * In the case the page is requested for the first record in the page,
+ * the previous request have been made not counting page header
+ * length. Request again for the same page with the length known to be
+ * needed. Otherwise we don't know the header length of the new page.
+ */
+ if (pageptr != state->readPagePtr)
+ addLen = 0;
+ }
+
+ /* Data is not in our buffer, make a new load request to the caller. */
+ XLByteToSeg(pageptr, targetSegNo, state->segcxt.ws_segsize);
+ targetPageOff = XLogSegmentOffset(pageptr, state->segcxt.ws_segsize);
+ Assert((pageptr % XLOG_BLCKSZ) == 0);
+
+ /*
+ * Every time we request to load new data of a page to the caller, even if
+ * we looked at a part of it before, we need to do verification on the next
+ * invocation as the caller might now be rereading data from a different
+ * source.
+ */
+ state->page_verified = false;
/*
- * Data is not in our buffer.
- *
- * Every time we actually read the page, even if we looked at parts of it
- * before, we need to do verification as the read_page callback might now
- * be rereading data from a different source.
- *
* Whenever switching to a new WAL segment, we read the first page of the
* file and validate its header, even if that's not where the target
* record is. This is so that we can check the additional identification
* info that is present in the first page's "long" header.
+ * Don't do this if the caller requested the first page in the segment.
*/
if (targetSegNo != state->seg.ws_segno && targetPageOff != 0)
{
- XLogRecPtr targetSegmentPtr = pageptr - targetPageOff;
-
- readLen = state->read_page(state, targetSegmentPtr, XLOG_BLCKSZ,
- state->currRecPtr,
- state->readBuf);
- if (readLen < 0)
- goto err;
-
- /* we can be sure to have enough WAL available, we scrolled back */
- Assert(readLen == XLOG_BLCKSZ);
-
- if (!XLogReaderValidatePageHeader(state, targetSegmentPtr,
- state->readBuf))
- goto err;
+ /*
+ * Then we'll see that the targetSegNo now matches the ws_segno, and
+ * will not come back here, but will request the actual target page.
+ */
+ state->readPagePtr = pageptr - targetPageOff;
+ state->readLen = XLOG_BLCKSZ;
+ return true;
}
/*
- * First, read the requested data length, but at least a short page header
- * so that we can validate it.
+ * Request the caller to load the requested page. We need at least a short
+ * page header so that we can validate it.
*/
- readLen = state->read_page(state, pageptr, Max(reqLen, SizeOfXLogShortPHD),
- state->currRecPtr,
- state->readBuf);
- if (readLen < 0)
- goto err;
-
- Assert(readLen <= XLOG_BLCKSZ);
-
- /* Do we have enough data to check the header length? */
- if (readLen <= SizeOfXLogShortPHD)
- goto err;
-
- Assert(readLen >= reqLen);
-
- hdr = (XLogPageHeader) state->readBuf;
-
- /* still not enough */
- if (readLen < XLogPageHeaderSize(hdr))
- {
- readLen = state->read_page(state, pageptr, XLogPageHeaderSize(hdr),
- state->currRecPtr,
- state->readBuf);
- if (readLen < 0)
- goto err;
- }
-
- /*
- * Now that we know we have the full header, validate it.
- */
- if (!XLogReaderValidatePageHeader(state, pageptr, (char *) hdr))
- goto err;
-
- /* update read state information */
- state->seg.ws_segno = targetSegNo;
- state->seg.ws_off = targetPageOff;
- state->readLen = readLen;
-
- return readLen;
-
-err:
- XLogReaderInvalReadState(state);
- return -1;
+ state->readPagePtr = pageptr;
+ state->readLen = Max(reqLen + addLen, SizeOfXLogShortPHD);
+ return true;
}
/*
- * Invalidate the xlogreader's read state to force a re-read.
+ * Invalidate current reading page buffer
*/
static void
-XLogReaderInvalReadState(XLogReaderState *state)
+XLogReaderDiscardReadingPage(XLogReaderState *state)
{
- state->seg.ws_segno = 0;
- state->seg.ws_off = 0;
- state->readLen = 0;
+ state->readPagePtr = InvalidXLogRecPtr;
}
/*
@@ -925,7 +998,6 @@ XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr)
XLogRecPtr targetPagePtr;
int targetRecOff;
uint32 pageHeaderSize;
- int readLen;
/*
* Compute targetRecOff. It should typically be equal or greater than
@@ -933,7 +1005,7 @@ XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr)
* that, except when caller has explicitly specified the offset that
* falls somewhere there or when we are skipping multi-page
* continuation record. It doesn't matter though because
- * ReadPageInternal() is prepared to handle that and will read at
+ * CheckPage() is prepared to handle that and will read at
* least short page-header worth of data
*/
targetRecOff = tmpRecPtr % XLOG_BLCKSZ;
@@ -941,19 +1013,23 @@ XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr)
/* scroll back to page boundary */
targetPagePtr = tmpRecPtr - targetRecOff;
- /* Read the page containing the record */
- readLen = ReadPageInternal(state, targetPagePtr, targetRecOff);
- if (readLen < 0)
+ while(XLogNeedData(state, targetPagePtr, targetRecOff,
+ targetRecOff != 0))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ state->ReadRecPtr, state->readBuf))
+ break;
+ }
+
+ if (!state->page_verified)
goto err;
header = (XLogPageHeader) state->readBuf;
pageHeaderSize = XLogPageHeaderSize(header);
- /* make sure we have enough data for the page header */
- readLen = ReadPageInternal(state, targetPagePtr, pageHeaderSize);
- if (readLen < 0)
- goto err;
+ /* we should have read the page header */
+ Assert (state->readLen >= pageHeaderSize);
/* skip over potential continuation data */
if (header->xlp_info & XLP_FIRST_IS_CONTRECORD)
@@ -1010,7 +1086,7 @@ out:
/* Reset state to what we had before finding the record */
state->ReadRecPtr = saved_state.ReadRecPtr;
state->EndRecPtr = saved_state.EndRecPtr;
- XLogReaderInvalReadState(state);
+ XLogReaderDiscardReadingPage(state);
return found;
}
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index 5f1e5ba75d..a19726a96e 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -803,7 +803,7 @@ void
XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wantLength)
{
const XLogRecPtr lastReadPage = state->seg.ws_segno *
- state->segcxt.ws_segsize + state->seg.ws_off;
+ state->segcxt.ws_segsize + state->readLen;
Assert(wantPage != InvalidXLogRecPtr && wantPage % XLOG_BLCKSZ == 0);
Assert(wantLength <= XLOG_BLCKSZ);
@@ -907,7 +907,7 @@ XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wa
* exists for normal backends, so we have to do a check/sleep/repeat style of
* loop for now.
*/
-int
+bool
read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *cur_page)
{
@@ -1007,7 +1007,8 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
else if (targetPagePtr + reqLen > read_upto)
{
/* not enough data there */
- return -1;
+ state->readLen = -1;
+ return false;
}
else
{
@@ -1024,5 +1025,6 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
XLOG_BLCKSZ);
/* number of valid bytes in the buffer */
- return count;
+ state->readLen = count;
+ return true;
}
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index d1cf80d441..310cd9d8cf 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -114,7 +114,7 @@ check_permissions(void)
(errmsg("must be superuser or replication role to use replication slots"))));
}
-int
+bool
logical_read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *cur_page)
{
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index eb4a98cc91..0809ceaeb8 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -760,7 +760,7 @@ StartReplication(StartReplicationCmd *cmd)
* which has to do a plain sleep/busy loop, because the walsender's latch gets
* set every time WAL is flushed.
*/
-static int
+static bool
logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetRecPtr, char *cur_page)
{
@@ -778,7 +778,10 @@ logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int req
/* fail if not (implies we are going to shut down) */
if (flushptr < targetPagePtr + reqLen)
- return -1;
+ {
+ state->readLen = -1;
+ return false;
+ }
if (targetPagePtr + XLOG_BLCKSZ <= flushptr)
count = XLOG_BLCKSZ; /* more than one block available */
@@ -788,7 +791,8 @@ logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int req
/* now actually read the data, we know it's there */
XLogRead(sendCxt, cur_page, targetPagePtr, XLOG_BLCKSZ);
- return count;
+ state->readLen = count;
+ return true;
}
/*
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 264a8f4db5..8aecd1adc7 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -46,7 +46,7 @@ typedef struct XLogPageReadPrivate
int tliIndex;
} XLogPageReadPrivate;
-static int SimpleXLogPageRead(XLogReaderState *xlogreader,
+static bool SimpleXLogPageRead(XLogReaderState *xlogreader,
XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *readBuf);
@@ -230,7 +230,7 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
}
/* XLogReader callback function, to read a WAL page */
-static int
+static bool
SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *readBuf)
{
@@ -285,7 +285,8 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
if (xlogreadfd < 0)
{
pg_log_error("could not open file \"%s\": %m", xlogfpath);
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
}
@@ -298,7 +299,8 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
if (lseek(xlogreadfd, (off_t) targetPageOff, SEEK_SET) < 0)
{
pg_log_error("could not seek in file \"%s\": %m", xlogfpath);
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
@@ -311,13 +313,15 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
pg_log_error("could not read file \"%s\": read %d of %zu",
xlogfpath, r, (Size) XLOG_BLCKSZ);
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
Assert(targetSegNo == xlogreadsegno);
xlogreader->seg.ws_tli = targetHistory[private->tliIndex].tli;
- return XLOG_BLCKSZ;
+ xlogreader->readLen = XLOG_BLCKSZ;
+ return true;
}
/*
diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c
index b79208cd73..6e424bd8e1 100644
--- a/src/bin/pg_waldump/pg_waldump.c
+++ b/src/bin/pg_waldump/pg_waldump.c
@@ -406,7 +406,7 @@ XLogDumpXLogRead(const char *directory, TimeLineID timeline_id,
/*
* XLogReader read_page callback
*/
-static int
+static bool
XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetPtr, char *readBuff)
{
@@ -422,14 +422,16 @@ XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
else
{
private->endptr_reached = true;
- return -1;
+ state->readLen = -1;
+ return false;
}
}
XLogDumpXLogRead(state->segcxt.ws_dir, private->timeline, targetPagePtr,
readBuff, count);
- return count;
+ state->readLen = count;
+ return true;
}
/*
diff --git a/src/include/access/xlogreader.h b/src/include/access/xlogreader.h
index 1bbee386e8..8b747d465f 100644
--- a/src/include/access/xlogreader.h
+++ b/src/include/access/xlogreader.h
@@ -50,7 +50,7 @@ typedef struct WALSegmentContext
typedef struct XLogReaderState XLogReaderState;
/* Function type definition for the read_page callback */
-typedef int (*XLogPageReadCB) (XLogReaderState *xlogreader,
+typedef bool (*XLogPageReadCB) (XLogReaderState *xlogreader,
XLogRecPtr targetPagePtr,
int reqLen,
XLogRecPtr targetRecPtr,
@@ -132,6 +132,20 @@ struct XLogReaderState
XLogRecPtr ReadRecPtr; /* start of last record read */
XLogRecPtr EndRecPtr; /* end+1 of last record read */
+ /* ----------------------------------------
+ * Communication with page reader
+ * readBuf is XLOG_BLCKSZ bytes, valid up to at least readLen bytes.
+ * ----------------------------------------
+ */
+ /* variables to communicate with page reader */
+ XLogRecPtr readPagePtr; /* page pointer to read */
+ int32 readLen; /* bytes requested to reader, or actual bytes
+ * read by reader, which must be larger than
+ * the request, or -1 on error */
+ TimeLineID readPageTLI; /* TLI for data currently in readBuf */
+ char *readBuf; /* buffer to store data */
+ bool page_verified; /* is the page on the buffer verified? */
+
/* ----------------------------------------
* Decoded representation of current record
@@ -158,13 +172,6 @@ struct XLogReaderState
* ----------------------------------------
*/
- /*
- * Buffer for currently read page (XLOG_BLCKSZ bytes, valid up to at least
- * readLen bytes)
- */
- char *readBuf;
- uint32 readLen;
-
/* last read XLOG position for data currently in readBuf */
WALSegmentContext segcxt;
WALOpenSegment seg;
diff --git a/src/include/access/xlogutils.h b/src/include/access/xlogutils.h
index 2df98e45b2..47b65463f9 100644
--- a/src/include/access/xlogutils.h
+++ b/src/include/access/xlogutils.h
@@ -47,7 +47,7 @@ extern Buffer XLogReadBufferExtended(RelFileNode rnode, ForkNumber forknum,
extern Relation CreateFakeRelcacheEntry(RelFileNode rnode);
extern void FreeFakeRelcacheEntry(Relation fakerel);
-extern int read_local_xlog_page(XLogReaderState *state,
+extern bool read_local_xlog_page(XLogReaderState *state,
XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetRecPtr, char *cur_page);
diff --git a/src/include/replication/logicalfuncs.h b/src/include/replication/logicalfuncs.h
index 012096f183..54291221c3 100644
--- a/src/include/replication/logicalfuncs.h
+++ b/src/include/replication/logicalfuncs.h
@@ -11,7 +11,7 @@
#include "replication/logical.h"
-extern int logical_read_local_xlog_page(XLogReaderState *state,
+extern bool logical_read_local_xlog_page(XLogReaderState *state,
XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr,
char *cur_page);
--
2.16.3
----Next_Part(Wed_Sep_25_15_50_32_2019_576)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="v7-0002-Move-page-reader-out-of-XLogReadRecord.patch"
^ permalink raw reply [nested|flat] 199+ messages in thread
* [PATCH v6 1/4] Move callback-call from ReadPageInternal to XLogReadRecord.
@ 2019-09-05 11:21 Kyotaro Horiguchi <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Kyotaro Horiguchi @ 2019-09-05 11:21 UTC (permalink / raw)
The current WAL record reader reads page data using a call back
function. Although it is not so problematic alone, it would be a
problem if we are going to do add tasks like encryption which is
performed on page data before WAL reader reads them. To avoid that the
record reader facility has to have a new code path corresponds to
every new callback, this patch separates page reader from WAL record
reading facility by modifying the current WAL record reader to a state
machine.
As the first step of that change, this patch moves the page reader
function out of ReadPageInternal, then the remaining tasks of the
function are taken over by the new function XLogNeedData. As the
result XLogPageRead directly calls the page reader callback function
according to the feedback from XLogNeedData.
---
src/backend/access/transam/xlog.c | 16 +-
src/backend/access/transam/xlogreader.c | 336 +++++++++++++++----------
src/backend/access/transam/xlogutils.c | 10 +-
src/backend/replication/logical/logicalfuncs.c | 4 +-
src/backend/replication/walsender.c | 10 +-
src/bin/pg_rewind/parsexlog.c | 17 +-
src/bin/pg_waldump/pg_waldump.c | 8 +-
src/include/access/xlogreader.h | 26 +-
src/include/access/xlogutils.h | 2 +-
src/include/replication/logicalfuncs.h | 2 +-
10 files changed, 265 insertions(+), 166 deletions(-)
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 6876537b62..d7f899e738 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -884,7 +884,7 @@ static bool InstallXLogFileSegment(XLogSegNo *segno, char *tmppath,
static int XLogFileRead(XLogSegNo segno, int emode, TimeLineID tli,
int source, bool notfoundOk);
static int XLogFileReadAnyTLI(XLogSegNo segno, int emode, int source);
-static int XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
+static bool XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *readBuf,
TimeLineID *readTLI);
static bool WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
@@ -4249,7 +4249,6 @@ ReadRecord(XLogReaderState *xlogreader, XLogRecPtr RecPtr, int emode,
XLogRecord *record;
XLogPageReadPrivate *private = (XLogPageReadPrivate *) xlogreader->private_data;
- /* Pass through parameters to XLogPageRead */
private->fetching_ckpt = fetching_ckpt;
private->emode = emode;
private->randAccess = (RecPtr != InvalidXLogRecPtr);
@@ -11521,7 +11520,7 @@ CancelBackup(void)
* XLogPageRead() to try fetching the record from another source, or to
* sleep and retry.
*/
-static int
+static bool
XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetRecPtr, char *readBuf, TimeLineID *readTLI)
{
@@ -11580,7 +11579,8 @@ retry:
readLen = 0;
readSource = 0;
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
}
@@ -11675,7 +11675,8 @@ retry:
goto next_record_is_invalid;
}
- return readLen;
+ xlogreader->readLen = readLen;
+ return true;
next_record_is_invalid:
lastSourceFailed = true;
@@ -11689,8 +11690,9 @@ next_record_is_invalid:
/* In standby-mode, keep trying */
if (StandbyMode)
goto retry;
- else
- return -1;
+
+ xlogreader->readLen = -1;
+ return false;
}
/*
diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c
index a66e3324b1..12a52159a9 100644
--- a/src/backend/access/transam/xlogreader.c
+++ b/src/backend/access/transam/xlogreader.c
@@ -34,9 +34,9 @@
static void report_invalid_record(XLogReaderState *state, const char *fmt,...)
pg_attribute_printf(2, 3);
static bool allocate_recordbuf(XLogReaderState *state, uint32 reclength);
-static int ReadPageInternal(XLogReaderState *state, XLogRecPtr pageptr,
- int reqLen);
-static void XLogReaderInvalReadState(XLogReaderState *state);
+static bool XLogNeedData(XLogReaderState *state, XLogRecPtr pageptr,
+ int reqLen, bool header_inclusive);
+static void XLogReaderDiscardReadingPage(XLogReaderState *state);
static bool ValidXLogRecordHeader(XLogReaderState *state, XLogRecPtr RecPtr,
XLogRecPtr PrevRecPtr, XLogRecord *record, bool randAccess);
static bool ValidXLogRecord(XLogReaderState *state, XLogRecord *record,
@@ -101,7 +101,7 @@ XLogReaderAllocate(int wal_segment_size, XLogPageReadCB pagereadfunc,
/* system_identifier initialized to zeroes above */
state->private_data = private_data;
/* ReadRecPtr and EndRecPtr initialized to zeroes above */
- /* readSegNo, readOff, readLen, readPageTLI initialized to zeroes above */
+ /* readSegNo, readLen, readPageTLI initialized to zeroes above */
state->errormsg_buf = palloc_extended(MAX_ERRORMSG_LEN + 1,
MCXT_ALLOC_NO_OOM);
if (!state->errormsg_buf)
@@ -225,7 +225,6 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
uint32 targetRecOff;
uint32 pageHeaderSize;
bool gotheader;
- int readOff;
/*
* randAccess indicates whether to verify the previous-record pointer of
@@ -277,15 +276,21 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
* byte to cover the whole record header, or at least the part of it that
* fits on the same page.
*/
- readOff = ReadPageInternal(state,
- targetPagePtr,
- Min(targetRecOff + SizeOfXLogRecord, XLOG_BLCKSZ));
- if (readOff < 0)
+ while (XLogNeedData(state, targetPagePtr,
+ Min(targetRecOff + SizeOfXLogRecord, XLOG_BLCKSZ),
+ targetRecOff != 0))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ RecPtr, state->readBuf,
+ &state->readPageTLI))
+ break;
+ }
+
+ if (!state->page_verified)
goto err;
/*
- * ReadPageInternal always returns at least the page header, so we can
- * examine it now.
+ * We have loaded at least the page header, so we can examine it now.
*/
pageHeaderSize = XLogPageHeaderSize((XLogPageHeader) state->readBuf);
if (targetRecOff == 0)
@@ -311,8 +316,8 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
goto err;
}
- /* ReadPageInternal has verified the page header */
- Assert(pageHeaderSize <= readOff);
+ /* XLogNeedData has verified the page header */
+ Assert(pageHeaderSize <= state->readLen);
/*
* Read the record length.
@@ -385,18 +390,26 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
do
{
+ int rest_len = total_len - gotlen;
+
/* Calculate pointer to beginning of next page */
targetPagePtr += XLOG_BLCKSZ;
/* Wait for the next page to become available */
- readOff = ReadPageInternal(state, targetPagePtr,
- Min(total_len - gotlen + SizeOfXLogShortPHD,
- XLOG_BLCKSZ));
+ while (XLogNeedData(state, targetPagePtr,
+ Min(rest_len, XLOG_BLCKSZ),
+ false))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ state->ReadRecPtr, state->readBuf,
+ &state->readPageTLI))
+ break;
+ }
- if (readOff < 0)
+ if (!state->page_verified)
goto err;
- Assert(SizeOfXLogShortPHD <= readOff);
+ Assert(SizeOfXLogShortPHD <= state->readLen);
/* Check that the continuation on next page looks valid */
pageHeader = (XLogPageHeader) state->readBuf;
@@ -425,21 +438,14 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
/* Append the continuation from this page to the buffer */
pageHeaderSize = XLogPageHeaderSize(pageHeader);
- if (readOff < pageHeaderSize)
- readOff = ReadPageInternal(state, targetPagePtr,
- pageHeaderSize);
-
- Assert(pageHeaderSize <= readOff);
+ Assert (pageHeaderSize <= state->readLen);
contdata = (char *) state->readBuf + pageHeaderSize;
len = XLOG_BLCKSZ - pageHeaderSize;
if (pageHeader->xlp_rem_len < len)
len = pageHeader->xlp_rem_len;
- if (readOff < pageHeaderSize + len)
- readOff = ReadPageInternal(state, targetPagePtr,
- pageHeaderSize + len);
-
+ Assert (pageHeaderSize + len <= state->readLen);
memcpy(buffer, (char *) contdata, len);
buffer += len;
gotlen += len;
@@ -469,9 +475,16 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
else
{
/* Wait for the record data to become available */
- readOff = ReadPageInternal(state, targetPagePtr,
- Min(targetRecOff + total_len, XLOG_BLCKSZ));
- if (readOff < 0)
+ while (XLogNeedData(state, targetPagePtr,
+ Min(targetRecOff + total_len, XLOG_BLCKSZ), true))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ state->ReadRecPtr, state->readBuf,
+ &state->readPageTLI))
+ break;
+ }
+
+ if (!state->page_verified)
goto err;
/* Record does not cross a page boundary */
@@ -505,7 +518,7 @@ err:
* Invalidate the read state. We might read from a different source after
* failure.
*/
- XLogReaderInvalReadState(state);
+ XLogReaderDiscardReadingPage(state);
if (state->errormsg_buf[0] != '\0')
*errormsg = state->errormsg_buf;
@@ -514,120 +527,183 @@ err:
}
/*
- * Read a single xlog page including at least [pageptr, reqLen] of valid data
- * via the read_page() callback.
+ * Checks that an xlog page loaded in state->readBuf is including at least
+ * [pageptr, reqLen] and the page is valid. header_inclusive indicates that
+ * reqLen is calculated including page header length.
*
- * Returns -1 if the required page cannot be read for some reason; errormsg_buf
- * is set in that case (unless the error occurs in the read_page callback).
+ * Returns false if the buffer already contains the requested data, or found
+ * error. state->page_verified is set to true for the former and false for the
+ * latter.
*
- * We fetch the page from a reader-local cache if we know we have the required
- * data and if there hasn't been any error since caching the data.
+ * Otherwise returns true and requests data loaded onto state->readBuf by
+ * state->readPagePtr and state->readLen. The caller shall call this function
+ * again after filling the buffer at least with that portion of data and set
+ * state->readLen to the length of actually loaded data.
+ *
+ * If header_inclusive is false, corrects reqLen internally by adding the
+ * actual page header length and may request caller for new data.
*/
-static int
-ReadPageInternal(XLogReaderState *state, XLogRecPtr pageptr, int reqLen)
+static bool
+XLogNeedData(XLogReaderState *state, XLogRecPtr pageptr, int reqLen,
+ bool header_inclusive)
{
- int readLen;
uint32 targetPageOff;
XLogSegNo targetSegNo;
- XLogPageHeader hdr;
-
- Assert((pageptr % XLOG_BLCKSZ) == 0);
-
- XLByteToSeg(pageptr, targetSegNo, state->wal_segment_size);
- targetPageOff = XLogSegmentOffset(pageptr, state->wal_segment_size);
+ uint32 addLen = 0;
/* check whether we have all the requested data already */
- if (targetSegNo == state->readSegNo && targetPageOff == state->readOff &&
- reqLen <= state->readLen)
- return state->readLen;
+ if (state->page_verified && pageptr == state->readPagePtr)
+ {
+ if (!header_inclusive)
+ {
+ /*
+ * calculate additional length for page header so that the total
+ * length doesn't exceed the block size.
+ */
+ uint32 pageHeaderSize =
+ XLogPageHeaderSize((XLogPageHeader) state->readBuf);
+
+ addLen = pageHeaderSize;
+ if (reqLen + pageHeaderSize <= XLOG_BLCKSZ)
+ addLen = pageHeaderSize;
+ else
+ addLen = XLOG_BLCKSZ - reqLen;
+ }
+
+ if (reqLen + addLen <= state->readLen)
+ return false;
+ }
+
+ /* Haven't loaded any data yet? Then request it. */
+ if (XLogRecPtrIsInvalid(state->readPagePtr) ||
+ state->readPagePtr != pageptr || state->readLen < 0)
+ {
+ state->readPagePtr = pageptr;
+ state->readLen = Max(reqLen, SizeOfXLogShortPHD);
+ state->page_verified = false;
+ return true;
+ }
+
+ if (!state->page_verified)
+ {
+ uint32 pageHeaderSize;
+ uint32 addLen = 0;
+
+ /* just loaded new data so needs to verify page header */
+
+ /* The caller must have loaded at least page header */
+ Assert(state->readLen >= SizeOfXLogShortPHD);
+
+ /*
+ * We have enough data to check the header length. Recheck the loaded
+ * length if it is a long header if any.
+ */
+ pageHeaderSize = XLogPageHeaderSize((XLogPageHeader) state->readBuf);
+
+ /*
+ * If we have not loaded a page so far, readLen is zero, which is
+ * shorter than pageHeaderSize here.
+ */
+ if (state->readLen < pageHeaderSize)
+ {
+ state->readPagePtr = pageptr;
+ state->readLen = pageHeaderSize;
+ return true;
+ }
+
+ /*
+ * Now that we know we have the full header, validate it.
+ */
+ if (!XLogReaderValidatePageHeader(state, state->readPagePtr,
+ (char *) state->readBuf))
+ {
+ /* force reading the page again. */
+ XLogReaderDiscardReadingPage(state);
+
+ return false;
+ }
+
+ state->page_verified = true;
+
+ XLByteToSeg(state->readPagePtr, state->readSegNo,
+ state->wal_segment_size);
+
+ /*
+ * calculate additional length for page header so that the total
+ * length doesn't exceed the block size.
+ */
+ if (!header_inclusive)
+ {
+ addLen = pageHeaderSize;
+ if (reqLen + pageHeaderSize <= XLOG_BLCKSZ)
+ addLen = pageHeaderSize;
+ else
+ addLen = XLOG_BLCKSZ - reqLen;
+
+ Assert(addLen >= 0);
+ }
+
+ /* Usually we have requested data loaded in buffer here. */
+ if (pageptr == state->readPagePtr && reqLen + addLen <= state->readLen)
+ return false;
+
+ /*
+ * In the case the page is requested for the first record in the page,
+ * the previous request have been made not counting page header
+ * length. Request again for the same page with the length known to be
+ * needed. Otherwise we don't know the header length of the new page.
+ */
+ if (pageptr != state->readPagePtr)
+ addLen = 0;
+ }
+
+ /* Data is not in our buffer, make a new load request to the caller. */
+ XLByteToSeg(pageptr, targetSegNo, state->wal_segment_size);
+ targetPageOff = XLogSegmentOffset(pageptr, state->wal_segment_size);
+ Assert((pageptr % XLOG_BLCKSZ) == 0);
+
+ /*
+ * Every time we request to load new data of a page to the caller, even if
+ * we looked at a part of it before, we need to do verification on the next
+ * invocation as the caller might now be rereading data from a different
+ * source.
+ */
+ state->page_verified = false;
/*
- * Data is not in our buffer.
- *
- * Every time we actually read the page, even if we looked at parts of it
- * before, we need to do verification as the read_page callback might now
- * be rereading data from a different source.
- *
* Whenever switching to a new WAL segment, we read the first page of the
* file and validate its header, even if that's not where the target
* record is. This is so that we can check the additional identification
* info that is present in the first page's "long" header.
+ * Don't do this if the caller requested the first page in the segment.
*/
if (targetSegNo != state->readSegNo && targetPageOff != 0)
{
- XLogRecPtr targetSegmentPtr = pageptr - targetPageOff;
-
- readLen = state->read_page(state, targetSegmentPtr, XLOG_BLCKSZ,
- state->currRecPtr,
- state->readBuf, &state->readPageTLI);
- if (readLen < 0)
- goto err;
-
- /* we can be sure to have enough WAL available, we scrolled back */
- Assert(readLen == XLOG_BLCKSZ);
-
- if (!XLogReaderValidatePageHeader(state, targetSegmentPtr,
- state->readBuf))
- goto err;
+ /*
+ * Then we'll see that the targetSegNo now matches the readSegNo, and
+ * will not come back here, but will request the actual target page.
+ */
+ state->readPagePtr = pageptr - targetPageOff;
+ state->readLen = XLOG_BLCKSZ;
+ return true;
}
/*
- * First, read the requested data length, but at least a short page header
- * so that we can validate it.
+ * Request the caller to load the requested page. We need at least a short
+ * page header so that we can validate it.
*/
- readLen = state->read_page(state, pageptr, Max(reqLen, SizeOfXLogShortPHD),
- state->currRecPtr,
- state->readBuf, &state->readPageTLI);
- if (readLen < 0)
- goto err;
-
- Assert(readLen <= XLOG_BLCKSZ);
-
- /* Do we have enough data to check the header length? */
- if (readLen <= SizeOfXLogShortPHD)
- goto err;
-
- Assert(readLen >= reqLen);
-
- hdr = (XLogPageHeader) state->readBuf;
-
- /* still not enough */
- if (readLen < XLogPageHeaderSize(hdr))
- {
- readLen = state->read_page(state, pageptr, XLogPageHeaderSize(hdr),
- state->currRecPtr,
- state->readBuf, &state->readPageTLI);
- if (readLen < 0)
- goto err;
- }
-
- /*
- * Now that we know we have the full header, validate it.
- */
- if (!XLogReaderValidatePageHeader(state, pageptr, (char *) hdr))
- goto err;
-
- /* update read state information */
- state->readSegNo = targetSegNo;
- state->readOff = targetPageOff;
- state->readLen = readLen;
-
- return readLen;
-
-err:
- XLogReaderInvalReadState(state);
- return -1;
+ state->readPagePtr = pageptr;
+ state->readLen = Max(reqLen + addLen, SizeOfXLogShortPHD);
+ return true;
}
/*
- * Invalidate the xlogreader's read state to force a re-read.
+ * Invalidate current reading page buffer
*/
static void
-XLogReaderInvalReadState(XLogReaderState *state)
+XLogReaderDiscardReadingPage(XLogReaderState *state)
{
- state->readSegNo = 0;
- state->readOff = 0;
- state->readLen = 0;
+ state->readPagePtr = InvalidXLogRecPtr;
}
/*
@@ -905,7 +981,6 @@ XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr)
XLogRecPtr targetPagePtr;
int targetRecOff;
uint32 pageHeaderSize;
- int readLen;
/*
* Compute targetRecOff. It should typically be equal or greater than
@@ -913,7 +988,7 @@ XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr)
* that, except when caller has explicitly specified the offset that
* falls somewhere there or when we are skipping multi-page
* continuation record. It doesn't matter though because
- * ReadPageInternal() is prepared to handle that and will read at
+ * CheckPage() is prepared to handle that and will read at
* least short page-header worth of data
*/
targetRecOff = tmpRecPtr % XLOG_BLCKSZ;
@@ -921,19 +996,24 @@ XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr)
/* scroll back to page boundary */
targetPagePtr = tmpRecPtr - targetRecOff;
- /* Read the page containing the record */
- readLen = ReadPageInternal(state, targetPagePtr, targetRecOff);
- if (readLen < 0)
+ while(XLogNeedData(state, targetPagePtr, targetRecOff,
+ targetRecOff != 0))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ state->ReadRecPtr, state->readBuf,
+ &state->readPageTLI))
+ break;
+ }
+
+ if (!state->page_verified)
goto err;
header = (XLogPageHeader) state->readBuf;
pageHeaderSize = XLogPageHeaderSize(header);
- /* make sure we have enough data for the page header */
- readLen = ReadPageInternal(state, targetPagePtr, pageHeaderSize);
- if (readLen < 0)
- goto err;
+ /* we should have read the page header */
+ Assert (state->readLen >= pageHeaderSize);
/* skip over potential continuation data */
if (header->xlp_info & XLP_FIRST_IS_CONTRECORD)
@@ -990,7 +1070,7 @@ out:
/* Reset state to what we had before finding the record */
state->ReadRecPtr = saved_state.ReadRecPtr;
state->EndRecPtr = saved_state.EndRecPtr;
- XLogReaderInvalReadState(state);
+ XLogReaderDiscardReadingPage(state);
return found;
}
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index 1fc39333f1..dad9074b9f 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -803,7 +803,7 @@ void
XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wantLength)
{
const XLogRecPtr lastReadPage = state->readSegNo *
- state->wal_segment_size + state->readOff;
+ state->wal_segment_size + state->readLen;
Assert(wantPage != InvalidXLogRecPtr && wantPage % XLOG_BLCKSZ == 0);
Assert(wantLength <= XLOG_BLCKSZ);
@@ -907,7 +907,7 @@ XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wa
* exists for normal backends, so we have to do a check/sleep/repeat style of
* loop for now.
*/
-int
+bool
read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *cur_page,
TimeLineID *pageTLI)
@@ -1009,7 +1009,8 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
else if (targetPagePtr + reqLen > read_upto)
{
/* not enough data there */
- return -1;
+ state->readLen = -1;
+ return false;
}
else
{
@@ -1026,5 +1027,6 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
XLOG_BLCKSZ);
/* number of valid bytes in the buffer */
- return count;
+ state->readLen = count;
+ return true;
}
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index d974400d6e..7210a940bd 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -114,12 +114,12 @@ check_permissions(void)
(errmsg("must be superuser or replication role to use replication slots"))));
}
-int
+bool
logical_read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *cur_page, TimeLineID *pageTLI)
{
return read_local_xlog_page(state, targetPagePtr, reqLen,
- targetRecPtr, cur_page, pageTLI);
+ targetRecPtr, cur_page, pageTLI);
}
/*
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 23870a25a5..cc35e2a04d 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -761,7 +761,7 @@ StartReplication(StartReplicationCmd *cmd)
* which has to do a plain sleep/busy loop, because the walsender's latch gets
* set every time WAL is flushed.
*/
-static int
+static bool
logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetRecPtr, char *cur_page, TimeLineID *pageTLI)
{
@@ -779,7 +779,10 @@ logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int req
/* fail if not (implies we are going to shut down) */
if (flushptr < targetPagePtr + reqLen)
- return -1;
+ {
+ state->readLen = -1;
+ return false;
+ }
if (targetPagePtr + XLOG_BLCKSZ <= flushptr)
count = XLOG_BLCKSZ; /* more than one block available */
@@ -789,7 +792,8 @@ logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int req
/* now actually read the data, we know it's there */
XLogRead(cur_page, targetPagePtr, XLOG_BLCKSZ);
- return count;
+ state->readLen = count;
+ return true;
}
/*
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 63c3879ead..4df53964e4 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -47,7 +47,7 @@ typedef struct XLogPageReadPrivate
int tliIndex;
} XLogPageReadPrivate;
-static int SimpleXLogPageRead(XLogReaderState *xlogreader,
+static bool SimpleXLogPageRead(XLogReaderState *xlogreader,
XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *readBuf,
TimeLineID *pageTLI);
@@ -235,7 +235,7 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
}
/* XLogReader callback function, to read a WAL page */
-static int
+static bool
SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *readBuf,
TimeLineID *pageTLI)
@@ -290,7 +290,8 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
if (xlogreadfd < 0)
{
pg_log_error("could not open file \"%s\": %m", xlogfpath);
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
}
@@ -303,7 +304,8 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
if (lseek(xlogreadfd, (off_t) targetPageOff, SEEK_SET) < 0)
{
pg_log_error("could not seek in file \"%s\": %m", xlogfpath);
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
@@ -316,13 +318,16 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
pg_log_error("could not read file \"%s\": read %d of %zu",
xlogfpath, r, (Size) XLOG_BLCKSZ);
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
Assert(targetSegNo == xlogreadsegno);
*pageTLI = targetHistory[private->tliIndex].tli;
- return XLOG_BLCKSZ;
+
+ xlogreader->readLen = XLOG_BLCKSZ;
+ return true;
}
/*
diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c
index b95d467805..96d1f36ebc 100644
--- a/src/bin/pg_waldump/pg_waldump.c
+++ b/src/bin/pg_waldump/pg_waldump.c
@@ -421,7 +421,7 @@ XLogDumpXLogRead(const char *directory, TimeLineID timeline_id,
/*
* XLogReader read_page callback
*/
-static int
+static bool
XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetPtr, char *readBuff, TimeLineID *curFileTLI)
{
@@ -437,14 +437,16 @@ XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
else
{
private->endptr_reached = true;
- return -1;
+ state->readLen = -1;
+ return false;
}
}
XLogDumpXLogRead(private->inpath, private->timeline, targetPagePtr,
readBuff, count);
- return count;
+ state->readLen = count;
+ return true;
}
/*
diff --git a/src/include/access/xlogreader.h b/src/include/access/xlogreader.h
index 735b1bd2fd..6de7c19a2a 100644
--- a/src/include/access/xlogreader.h
+++ b/src/include/access/xlogreader.h
@@ -34,7 +34,7 @@
typedef struct XLogReaderState XLogReaderState;
/* Function type definition for the read_page callback */
-typedef int (*XLogPageReadCB) (XLogReaderState *xlogreader,
+typedef bool (*XLogPageReadCB) (XLogReaderState *xlogreader,
XLogRecPtr targetPagePtr,
int reqLen,
XLogRecPtr targetRecPtr,
@@ -123,6 +123,18 @@ struct XLogReaderState
XLogRecPtr ReadRecPtr; /* start of last record read */
XLogRecPtr EndRecPtr; /* end+1 of last record read */
+ /* ----------------------------------------
+ * Communication with page reader
+ * readBuf is XLOG_BLCKSZ bytes, valid up to at least readLen bytes.
+ * ----------------------------------------
+ */
+ /* variables to communicate with page reader */
+ XLogRecPtr readPagePtr; /* page pointer to read */
+ int32 readLen; /* bytes requested to reader, or actual bytes
+ * read by reader, which must be larger than
+ * the request, or -1 on error */
+ TimeLineID readPageTLI; /* TLI for data currently in readBuf */
+ char *readBuf; /* buffer to store data */
/* ----------------------------------------
* Decoded representation of current record
@@ -149,17 +161,9 @@ struct XLogReaderState
* ----------------------------------------
*/
- /*
- * Buffer for currently read page (XLOG_BLCKSZ bytes, valid up to at least
- * readLen bytes)
- */
- char *readBuf;
- uint32 readLen;
-
- /* last read segment, segment offset, TLI for data currently in readBuf */
+ /* last read segment and segment offset for data currently in readBuf */
+ bool page_verified;
XLogSegNo readSegNo;
- uint32 readOff;
- TimeLineID readPageTLI;
/*
* beginning of prior page read, and its TLI. Doesn't necessarily
diff --git a/src/include/access/xlogutils.h b/src/include/access/xlogutils.h
index 4105b59904..0842af9f95 100644
--- a/src/include/access/xlogutils.h
+++ b/src/include/access/xlogutils.h
@@ -47,7 +47,7 @@ extern Buffer XLogReadBufferExtended(RelFileNode rnode, ForkNumber forknum,
extern Relation CreateFakeRelcacheEntry(RelFileNode rnode);
extern void FreeFakeRelcacheEntry(Relation fakerel);
-extern int read_local_xlog_page(XLogReaderState *state,
+extern bool read_local_xlog_page(XLogReaderState *state,
XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetRecPtr, char *cur_page,
TimeLineID *pageTLI);
diff --git a/src/include/replication/logicalfuncs.h b/src/include/replication/logicalfuncs.h
index a9c178a9e6..8e52b1f4aa 100644
--- a/src/include/replication/logicalfuncs.h
+++ b/src/include/replication/logicalfuncs.h
@@ -11,7 +11,7 @@
#include "replication/logical.h"
-extern int logical_read_local_xlog_page(XLogReaderState *state,
+extern bool logical_read_local_xlog_page(XLogReaderState *state,
XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr,
char *cur_page, TimeLineID *pageTLI);
--
2.16.3
----Next_Part(Tue_Sep_10_17_40_54_2019_177)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="v6-0002-Move-page-reader-out-of-XLogReadRecord.patch"
^ permalink raw reply [nested|flat] 199+ messages in thread
* [PATCH v11 1/4] Move callback-call from ReadPageInternal to XLogReadRecord.
@ 2019-09-05 11:21 Kyotaro Horiguchi <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Kyotaro Horiguchi @ 2019-09-05 11:21 UTC (permalink / raw)
The current WAL record reader reads page data using a call back
function. Although it is not so problematic alone, it would be a
problem if we are going to do add tasks like encryption which is
performed on page data before WAL reader reads them. To avoid that the
record reader facility has to have a new code path corresponds to
every new callback, this patch separates page reader from WAL record
reading facility by modifying the current WAL record reader to a state
machine.
As the first step of that change, this patch moves the page reader
function out of ReadPageInternal, then the remaining tasks of the
function are taken over by the new function XLogNeedData. As the
result XLogPageRead directly calls the page reader callback function
according to the feedback from XLogNeedData.
---
src/backend/access/transam/xlog.c | 16 +-
src/backend/access/transam/xlogreader.c | 272 ++++++++++--------
src/backend/access/transam/xlogutils.c | 12 +-
.../replication/logical/logicalfuncs.c | 2 +-
src/backend/replication/walsender.c | 10 +-
src/bin/pg_rewind/parsexlog.c | 16 +-
src/bin/pg_waldump/pg_waldump.c | 14 +-
src/include/access/xlogreader.h | 23 +-
src/include/access/xlogutils.h | 2 +-
src/include/replication/logicalfuncs.h | 2 +-
10 files changed, 216 insertions(+), 153 deletions(-)
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 5f0ee50092..4a6bd6e002 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -884,7 +884,7 @@ static bool InstallXLogFileSegment(XLogSegNo *segno, char *tmppath,
static int XLogFileRead(XLogSegNo segno, int emode, TimeLineID tli,
int source, bool notfoundOk);
static int XLogFileReadAnyTLI(XLogSegNo segno, int emode, int source);
-static int XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
+static bool XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *readBuf);
static bool WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
bool fetching_ckpt, XLogRecPtr tliRecPtr);
@@ -4249,7 +4249,6 @@ ReadRecord(XLogReaderState *xlogreader, XLogRecPtr RecPtr, int emode,
XLogRecord *record;
XLogPageReadPrivate *private = (XLogPageReadPrivate *) xlogreader->private_data;
- /* Pass through parameters to XLogPageRead */
private->fetching_ckpt = fetching_ckpt;
private->emode = emode;
private->randAccess = (RecPtr != InvalidXLogRecPtr);
@@ -11537,7 +11536,7 @@ CancelBackup(void)
* XLogPageRead() to try fetching the record from another source, or to
* sleep and retry.
*/
-static int
+static bool
XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetRecPtr, char *readBuf)
{
@@ -11596,7 +11595,8 @@ retry:
readLen = 0;
readSource = 0;
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
}
@@ -11691,7 +11691,8 @@ retry:
goto next_record_is_invalid;
}
- return readLen;
+ xlogreader->readLen = readLen;
+ return true;
next_record_is_invalid:
lastSourceFailed = true;
@@ -11705,8 +11706,9 @@ next_record_is_invalid:
/* In standby-mode, keep trying */
if (StandbyMode)
goto retry;
- else
- return -1;
+
+ xlogreader->readLen = -1;
+ return false;
}
/*
diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c
index 67418b05f1..388662ade4 100644
--- a/src/backend/access/transam/xlogreader.c
+++ b/src/backend/access/transam/xlogreader.c
@@ -36,8 +36,8 @@
static void report_invalid_record(XLogReaderState *state, const char *fmt,...)
pg_attribute_printf(2, 3);
static bool allocate_recordbuf(XLogReaderState *state, uint32 reclength);
-static int ReadPageInternal(XLogReaderState *state, XLogRecPtr pageptr,
- int reqLen);
+static bool XLogNeedData(XLogReaderState *state, XLogRecPtr pageptr,
+ int reqLen, bool header_inclusive);
static void XLogReaderInvalReadState(XLogReaderState *state);
static bool ValidXLogRecordHeader(XLogReaderState *state, XLogRecPtr RecPtr,
XLogRecPtr PrevRecPtr, XLogRecord *record, bool randAccess);
@@ -245,7 +245,6 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
uint32 targetRecOff;
uint32 pageHeaderSize;
bool gotheader;
- int readOff;
/*
* randAccess indicates whether to verify the previous-record pointer of
@@ -297,14 +296,20 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
* byte to cover the whole record header, or at least the part of it that
* fits on the same page.
*/
- readOff = ReadPageInternal(state, targetPagePtr,
- Min(targetRecOff + SizeOfXLogRecord, XLOG_BLCKSZ));
- if (readOff < 0)
+ while (XLogNeedData(state, targetPagePtr,
+ Min(targetRecOff + SizeOfXLogRecord, XLOG_BLCKSZ),
+ targetRecOff != 0))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ RecPtr, state->readBuf))
+ break;
+ }
+
+ if (!state->page_verified)
goto err;
/*
- * ReadPageInternal always returns at least the page header, so we can
- * examine it now.
+ * We have at least the page header, so we can examine it now.
*/
pageHeaderSize = XLogPageHeaderSize((XLogPageHeader) state->readBuf);
if (targetRecOff == 0)
@@ -330,8 +335,8 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
goto err;
}
- /* ReadPageInternal has verified the page header */
- Assert(pageHeaderSize <= readOff);
+ /* XLogNeedData has verified the page header */
+ Assert(pageHeaderSize <= state->readLen);
/*
* Read the record length.
@@ -404,18 +409,25 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
do
{
+ int rest_len = total_len - gotlen;
+
/* Calculate pointer to beginning of next page */
targetPagePtr += XLOG_BLCKSZ;
/* Wait for the next page to become available */
- readOff = ReadPageInternal(state, targetPagePtr,
- Min(total_len - gotlen + SizeOfXLogShortPHD,
- XLOG_BLCKSZ));
+ while (XLogNeedData(state, targetPagePtr,
+ Min(rest_len, XLOG_BLCKSZ),
+ false))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ state->ReadRecPtr, state->readBuf))
+ break;
+ }
- if (readOff < 0)
+ if (!state->page_verified)
goto err;
- Assert(SizeOfXLogShortPHD <= readOff);
+ Assert(SizeOfXLogShortPHD <= state->readLen);
/* Check that the continuation on next page looks valid */
pageHeader = (XLogPageHeader) state->readBuf;
@@ -444,21 +456,14 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
/* Append the continuation from this page to the buffer */
pageHeaderSize = XLogPageHeaderSize(pageHeader);
- if (readOff < pageHeaderSize)
- readOff = ReadPageInternal(state, targetPagePtr,
- pageHeaderSize);
-
- Assert(pageHeaderSize <= readOff);
+ Assert (pageHeaderSize <= state->readLen);
contdata = (char *) state->readBuf + pageHeaderSize;
len = XLOG_BLCKSZ - pageHeaderSize;
if (pageHeader->xlp_rem_len < len)
len = pageHeader->xlp_rem_len;
- if (readOff < pageHeaderSize + len)
- readOff = ReadPageInternal(state, targetPagePtr,
- pageHeaderSize + len);
-
+ Assert (pageHeaderSize + len <= state->readLen);
memcpy(buffer, (char *) contdata, len);
buffer += len;
gotlen += len;
@@ -488,9 +493,15 @@ XLogReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, char **errormsg)
else
{
/* Wait for the record data to become available */
- readOff = ReadPageInternal(state, targetPagePtr,
- Min(targetRecOff + total_len, XLOG_BLCKSZ));
- if (readOff < 0)
+ while (XLogNeedData(state, targetPagePtr,
+ Min(targetRecOff + total_len, XLOG_BLCKSZ), true))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ state->ReadRecPtr, state->readBuf))
+ break;
+ }
+
+ if (!state->page_verified)
goto err;
/* Record does not cross a page boundary */
@@ -533,109 +544,139 @@ err:
}
/*
- * Read a single xlog page including at least [pageptr, reqLen] of valid data
- * via the read_page() callback.
+ * Checks that an xlog page loaded in state->readBuf is including at least
+ * [pageptr, reqLen] and the page is valid. header_inclusive indicates that
+ * reqLen is calculated including page header length.
*
- * Returns -1 if the required page cannot be read for some reason; errormsg_buf
- * is set in that case (unless the error occurs in the read_page callback).
+ * Returns false if the buffer already contains the requested data, or found
+ * error. state->page_verified is set to true for the former and false for the
+ * latter.
*
- * We fetch the page from a reader-local cache if we know we have the required
- * data and if there hasn't been any error since caching the data.
+ * Otherwise returns true and requests data loaded onto state->readBuf by
+ * state->readPagePtr and state->readLen. The caller shall call this function
+ * again after filling the buffer at least with that portion of data and set
+ * state->readLen to the length of actually loaded data.
+ *
+ * If header_inclusive is false, corrects reqLen internally by adding the
+ * actual page header length and may request caller for new data.
*/
-static int
-ReadPageInternal(XLogReaderState *state, XLogRecPtr pageptr, int reqLen)
+static bool
+XLogNeedData(XLogReaderState *state, XLogRecPtr pageptr, int reqLen,
+ bool header_inclusive)
{
- int readLen;
uint32 targetPageOff;
XLogSegNo targetSegNo;
- XLogPageHeader hdr;
+ uint32 addLen = 0;
- Assert((pageptr % XLOG_BLCKSZ) == 0);
+ /* Some data is loaded, but page header is not verified yet. */
+ if (!state->page_verified &&
+ !XLogRecPtrIsInvalid(state->readPagePtr) && state->readLen >= 0)
+ {
+ uint32 pageHeaderSize;
- XLByteToSeg(pageptr, targetSegNo, state->segcxt.ws_segsize);
- targetPageOff = XLogSegmentOffset(pageptr, state->segcxt.ws_segsize);
+ /* just loaded new data so needs to verify page header */
- /* check whether we have all the requested data already */
- if (targetSegNo == state->seg.ws_segno &&
- targetPageOff == state->segoff && reqLen <= state->readLen)
- return state->readLen;
+ /* The caller must have loaded at least page header */
+ Assert (state->readLen >= SizeOfXLogShortPHD);
- /*
- * Data is not in our buffer.
- *
- * Every time we actually read the page, even if we looked at parts of it
- * before, we need to do verification as the read_page callback might now
- * be rereading data from a different source.
- *
- * Whenever switching to a new WAL segment, we read the first page of the
- * file and validate its header, even if that's not where the target
- * record is. This is so that we can check the additional identification
- * info that is present in the first page's "long" header.
- */
- if (targetSegNo != state->seg.ws_segno && targetPageOff != 0)
- {
- XLogRecPtr targetSegmentPtr = pageptr - targetPageOff;
+ /*
+ * We have enough data to check the header length. Recheck the loaded
+ * length against the actual header length.
+ */
+ pageHeaderSize = XLogPageHeaderSize((XLogPageHeader) state->readBuf);
- readLen = state->read_page(state, targetSegmentPtr, XLOG_BLCKSZ,
- state->currRecPtr,
- state->readBuf);
- if (readLen < 0)
- goto err;
+ /* Request more data if we don't have the full header. */
+ if (state->readLen < pageHeaderSize)
+ {
+ state->readLen = pageHeaderSize;
+ return true;
+ }
+
+ /* Now that we know we have the full header, validate it. */
+ if (!XLogReaderValidatePageHeader(state, state->readPagePtr,
+ (char *) state->readBuf))
+ {
+ /* That's bad. Force reading the page again. */
+ XLogReaderInvalReadState(state);
- /* we can be sure to have enough WAL available, we scrolled back */
- Assert(readLen == XLOG_BLCKSZ);
+ return false;
+ }
- if (!XLogReaderValidatePageHeader(state, targetSegmentPtr,
- state->readBuf))
- goto err;
+ state->page_verified = true;
+
+ XLByteToSeg(state->readPagePtr, state->seg.ws_segno,
+ state->segcxt.ws_segsize);
}
/*
- * First, read the requested data length, but at least a short page header
- * so that we can validate it.
+ * The loaded page may not be the one caller is supposing to read when we
+ * are verifying the first page of new segment. In that case, skip further
+ * verification and immediately load the target page.
*/
- readLen = state->read_page(state, pageptr, Max(reqLen, SizeOfXLogShortPHD),
- state->currRecPtr,
- state->readBuf);
- if (readLen < 0)
- goto err;
-
- Assert(readLen <= XLOG_BLCKSZ);
+ if (state->page_verified && pageptr == state->readPagePtr)
+ {
- /* Do we have enough data to check the header length? */
- if (readLen <= SizeOfXLogShortPHD)
- goto err;
+ /*
+ * calculate additional length for page header keeping the total
+ * length within the block size.
+ */
+ if (!header_inclusive)
+ {
+ uint32 pageHeaderSize =
+ XLogPageHeaderSize((XLogPageHeader) state->readBuf);
- Assert(readLen >= reqLen);
+ addLen = pageHeaderSize;
+ if (reqLen + pageHeaderSize <= XLOG_BLCKSZ)
+ addLen = pageHeaderSize;
+ else
+ addLen = XLOG_BLCKSZ - reqLen;
- hdr = (XLogPageHeader) state->readBuf;
+ Assert(addLen >= 0);
+ }
- /* still not enough */
- if (readLen < XLogPageHeaderSize(hdr))
- {
- readLen = state->read_page(state, pageptr, XLogPageHeaderSize(hdr),
- state->currRecPtr,
- state->readBuf);
- if (readLen < 0)
- goto err;
+ /* Return if we already have it. */
+ if (reqLen + addLen <= state->readLen)
+ return false;
}
+ /* Data is not in our buffer, request the caller for it. */
+ XLByteToSeg(pageptr, targetSegNo, state->segcxt.ws_segsize);
+ targetPageOff = XLogSegmentOffset(pageptr, state->segcxt.ws_segsize);
+ Assert((pageptr % XLOG_BLCKSZ) == 0);
+
/*
- * Now that we know we have the full header, validate it.
+ * Every time we request to load new data of a page to the caller, even if
+ * we looked at a part of it before, we need to do verification on the next
+ * invocation as the caller might now be rereading data from a different
+ * source.
*/
- if (!XLogReaderValidatePageHeader(state, pageptr, (char *) hdr))
- goto err;
-
- /* update read state information */
- state->seg.ws_segno = targetSegNo;
- state->segoff = targetPageOff;
- state->readLen = readLen;
+ state->page_verified = false;
- return readLen;
+ /*
+ * Whenever switching to a new WAL segment, we read the first page of the
+ * file and validate its header, even if that's not where the target
+ * record is. This is so that we can check the additional identification
+ * info that is present in the first page's "long" header.
+ * Don't do this if the caller requested the first page in the segment.
+ */
+ if (targetSegNo != state->seg.ws_segno && targetPageOff != 0)
+ {
+ /*
+ * Then we'll see that the targetSegNo now matches the ws_segno, and
+ * will not come back here, but will request the actual target page.
+ */
+ state->readPagePtr = pageptr - targetPageOff;
+ state->readLen = XLOG_BLCKSZ;
+ return true;
+ }
-err:
- XLogReaderInvalReadState(state);
- return -1;
+ /*
+ * Request the caller to load the page. We need at least a short page
+ * header so that we can validate it.
+ */
+ state->readPagePtr = pageptr;
+ state->readLen = Max(reqLen + addLen, SizeOfXLogShortPHD);
+ return true;
}
/*
@@ -644,9 +685,7 @@ err:
static void
XLogReaderInvalReadState(XLogReaderState *state)
{
- state->seg.ws_segno = 0;
- state->segoff = 0;
- state->readLen = 0;
+ state->readPagePtr = InvalidXLogRecPtr;
}
/*
@@ -924,7 +963,6 @@ XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr)
XLogRecPtr targetPagePtr;
int targetRecOff;
uint32 pageHeaderSize;
- int readLen;
/*
* Compute targetRecOff. It should typically be equal or greater than
@@ -932,7 +970,7 @@ XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr)
* that, except when caller has explicitly specified the offset that
* falls somewhere there or when we are skipping multi-page
* continuation record. It doesn't matter though because
- * ReadPageInternal() is prepared to handle that and will read at
+ * CheckPage() is prepared to handle that and will read at
* least short page-header worth of data
*/
targetRecOff = tmpRecPtr % XLOG_BLCKSZ;
@@ -940,19 +978,23 @@ XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr)
/* scroll back to page boundary */
targetPagePtr = tmpRecPtr - targetRecOff;
- /* Read the page containing the record */
- readLen = ReadPageInternal(state, targetPagePtr, targetRecOff);
- if (readLen < 0)
+ while(XLogNeedData(state, targetPagePtr, targetRecOff,
+ targetRecOff != 0))
+ {
+ if (!state->read_page(state, state->readPagePtr, state->readLen,
+ state->ReadRecPtr, state->readBuf))
+ break;
+ }
+
+ if (!state->page_verified)
goto err;
header = (XLogPageHeader) state->readBuf;
pageHeaderSize = XLogPageHeaderSize(header);
- /* make sure we have enough data for the page header */
- readLen = ReadPageInternal(state, targetPagePtr, pageHeaderSize);
- if (readLen < 0)
- goto err;
+ /* we should have read the page header */
+ Assert (state->readLen >= pageHeaderSize);
/* skip over potential continuation data */
if (header->xlp_info & XLP_FIRST_IS_CONTRECORD)
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index 446760ed6e..060fbc0c4c 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -680,8 +680,8 @@ XLogTruncateRelation(RelFileNode rnode, ForkNumber forkNum,
void
XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wantLength)
{
- const XLogRecPtr lastReadPage = (state->seg.ws_segno *
- state->segcxt.ws_segsize + state->segoff);
+ const XLogRecPtr lastReadPage = state->seg.ws_segno *
+ state->segcxt.ws_segsize + state->readLen;
Assert(wantPage != InvalidXLogRecPtr && wantPage % XLOG_BLCKSZ == 0);
Assert(wantLength <= XLOG_BLCKSZ);
@@ -813,7 +813,7 @@ wal_segment_open(XLogSegNo nextSegNo, WALSegmentContext *segcxt,
* exists for normal backends, so we have to do a check/sleep/repeat style of
* loop for now.
*/
-int
+bool
read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *cur_page)
{
@@ -915,7 +915,8 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
else if (targetPagePtr + reqLen > read_upto)
{
/* not enough data there */
- return -1;
+ state->readLen = -1;
+ return false;
}
else
{
@@ -933,7 +934,8 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
WALReadRaiseError(&errinfo);
/* number of valid bytes in the buffer */
- return count;
+ state->readLen = count;
+ return true;
}
/*
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index aa2106b152..4b0e8ea773 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -106,7 +106,7 @@ check_permissions(void)
(errmsg("must be superuser or replication role to use replication slots"))));
}
-int
+bool
logical_read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *cur_page)
{
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index ac9209747a..e2d64cc2e5 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -762,7 +762,7 @@ StartReplication(StartReplicationCmd *cmd)
* which has to do a plain sleep/busy loop, because the walsender's latch gets
* set every time WAL is flushed.
*/
-static int
+static bool
logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetRecPtr, char *cur_page)
{
@@ -782,7 +782,10 @@ logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int req
/* fail if not (implies we are going to shut down) */
if (flushptr < targetPagePtr + reqLen)
- return -1;
+ {
+ state->readLen = -1;
+ return false;
+ }
if (targetPagePtr + XLOG_BLCKSZ <= flushptr)
count = XLOG_BLCKSZ; /* more than one block available */
@@ -812,7 +815,8 @@ logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int req
XLByteToSeg(targetPagePtr, segno, sendCxt->ws_segsize);
CheckXLogRemoved(segno, sendSeg->ws_tli);
- return count;
+ state->readLen = count;
+ return true;
}
/*
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 1d03375a3e..795e84ff9f 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -44,7 +44,7 @@ typedef struct XLogPageReadPrivate
int tliIndex;
} XLogPageReadPrivate;
-static int SimpleXLogPageRead(XLogReaderState *xlogreader,
+static bool SimpleXLogPageRead(XLogReaderState *xlogreader,
XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *readBuf);
@@ -228,7 +228,7 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
}
/* XLogReader callback function, to read a WAL page */
-static int
+static bool
SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr, char *readBuf)
{
@@ -283,7 +283,8 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
if (xlogreadfd < 0)
{
pg_log_error("could not open file \"%s\": %m", xlogfpath);
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
}
@@ -296,7 +297,8 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
if (lseek(xlogreadfd, (off_t) targetPageOff, SEEK_SET) < 0)
{
pg_log_error("could not seek in file \"%s\": %m", xlogfpath);
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
@@ -309,13 +311,15 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
pg_log_error("could not read file \"%s\": read %d of %zu",
xlogfpath, r, (Size) XLOG_BLCKSZ);
- return -1;
+ xlogreader->readLen = -1;
+ return false;
}
Assert(targetSegNo == xlogreadsegno);
xlogreader->seg.ws_tli = targetHistory[private->tliIndex].tli;
- return XLOG_BLCKSZ;
+ xlogreader->readLen = XLOG_BLCKSZ;
+ return true;
}
/*
diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c
index 30a5851d87..9eece44626 100644
--- a/src/bin/pg_waldump/pg_waldump.c
+++ b/src/bin/pg_waldump/pg_waldump.c
@@ -324,9 +324,9 @@ WALDumpOpenSegment(XLogSegNo nextSegNo, WALSegmentContext *segcxt,
/*
* XLogReader read_page callback
*/
-static int
-WALDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
- XLogRecPtr targetPtr, char *readBuff)
+static bool
+XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
+ XLogRecPtr targetPtr, char *readBuff)
{
XLogDumpPrivate *private = state->private_data;
int count = XLOG_BLCKSZ;
@@ -341,7 +341,8 @@ WALDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
else
{
private->endptr_reached = true;
- return -1;
+ state->readLen = -1;
+ return false;
}
}
@@ -366,7 +367,8 @@ WALDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
(Size) errinfo.wre_req);
}
- return count;
+ state->readLen = count;
+ return true;
}
/*
@@ -1027,7 +1029,7 @@ main(int argc, char **argv)
/* done with argument parsing, do the actual work */
/* we have everything we need, start reading */
- xlogreader_state = XLogReaderAllocate(WalSegSz, waldir, WALDumpReadPage,
+ xlogreader_state = XLogReaderAllocate(WalSegSz, waldir, XLogDumpReadPage,
&private);
if (!xlogreader_state)
fatal_error("out of memory");
diff --git a/src/include/access/xlogreader.h b/src/include/access/xlogreader.h
index 0193611b7f..d990432ffe 100644
--- a/src/include/access/xlogreader.h
+++ b/src/include/access/xlogreader.h
@@ -49,7 +49,7 @@ typedef struct WALSegmentContext
typedef struct XLogReaderState XLogReaderState;
/* Function type definition for the read_page callback */
-typedef int (*XLogPageReadCB) (XLogReaderState *xlogreader,
+typedef bool (*XLogPageReadCB) (XLogReaderState *xlogreader,
XLogRecPtr targetPagePtr,
int reqLen,
XLogRecPtr targetRecPtr,
@@ -131,6 +131,20 @@ struct XLogReaderState
XLogRecPtr ReadRecPtr; /* start of last record read */
XLogRecPtr EndRecPtr; /* end+1 of last record read */
+ /* ----------------------------------------
+ * Communication with page reader
+ * readBuf is XLOG_BLCKSZ bytes, valid up to at least readLen bytes.
+ * ----------------------------------------
+ */
+ /* variables to communicate with page reader */
+ XLogRecPtr readPagePtr; /* page pointer to read */
+ int32 readLen; /* bytes requested to reader, or actual bytes
+ * read by reader, which must be larger than
+ * the request, or -1 on error */
+ TimeLineID readPageTLI; /* TLI for data currently in readBuf */
+ char *readBuf; /* buffer to store data */
+ bool page_verified; /* is the page on the buffer verified? */
+
/* ----------------------------------------
* Decoded representation of current record
@@ -157,13 +171,6 @@ struct XLogReaderState
* ----------------------------------------
*/
- /*
- * Buffer for currently read page (XLOG_BLCKSZ bytes, valid up to at least
- * readLen bytes)
- */
- char *readBuf;
- uint32 readLen;
-
/* last read XLOG position for data currently in readBuf */
WALSegmentContext segcxt;
WALOpenSegment seg;
diff --git a/src/include/access/xlogutils.h b/src/include/access/xlogutils.h
index 0572b24192..0867ef0599 100644
--- a/src/include/access/xlogutils.h
+++ b/src/include/access/xlogutils.h
@@ -47,7 +47,7 @@ extern Buffer XLogReadBufferExtended(RelFileNode rnode, ForkNumber forknum,
extern Relation CreateFakeRelcacheEntry(RelFileNode rnode);
extern void FreeFakeRelcacheEntry(Relation fakerel);
-extern int read_local_xlog_page(XLogReaderState *state,
+extern bool read_local_xlog_page(XLogReaderState *state,
XLogRecPtr targetPagePtr, int reqLen,
XLogRecPtr targetRecPtr, char *cur_page);
diff --git a/src/include/replication/logicalfuncs.h b/src/include/replication/logicalfuncs.h
index 012096f183..54291221c3 100644
--- a/src/include/replication/logicalfuncs.h
+++ b/src/include/replication/logicalfuncs.h
@@ -11,7 +11,7 @@
#include "replication/logical.h"
-extern int logical_read_local_xlog_page(XLogReaderState *state,
+extern bool logical_read_local_xlog_page(XLogReaderState *state,
XLogRecPtr targetPagePtr,
int reqLen, XLogRecPtr targetRecPtr,
char *cur_page);
--
2.23.0
----Next_Part(Wed_Nov_27_12_09_23_2019_240)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="v11-0002-Move-page-reader-out-of-XLogReadRecord.patch"
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs)
@ 2022-04-05 09:45 Michael Paquier <[email protected]>
2 siblings, 1 reply; 199+ messages in thread
From: Michael Paquier @ 2022-04-05 09:45 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Nitin Jadhav <[email protected]>; Bharath Rupireddy <[email protected]>; Matthias van de Meent <[email protected]>; Ashutosh Sharma <[email protected]>; Julien Rouhaud <[email protected]>; Bruce Momjian <[email protected]>; Tom Lane <[email protected]>; Magnus Hagander <[email protected]>; PostgreSQL Hackers <[email protected]>
On Fri, Mar 18, 2022 at 05:15:56PM -0700, Andres Freund wrote:
> Have you measured the performance effects of this? On fast storage with large
> shared_buffers I've seen these loops in profiles. It's probably fine, but it'd
> be good to verify that.
I am wondering if we could make the function inlined at some point.
We could also play it safe and only update the counters every N loops
instead.
> This view is depressingly complicated. Added up the view definitions for
> the already existing pg_stat_progress* views add up to a measurable part of
> the size of an empty database:
Yeah. I think that what's proposed could be simplified, and we had
better remove the fields that are not that useful. First, do we have
any need for next_flags? Second, is the start LSN really necessary
for monitoring purposes? Not all the information in the first
parameter is useful, as well. For example "shutdown" will never be
seen as it is not possible to use a session at this stage, no? There
is also no gain in having "immediate", "flush-all", "force" and "wait"
(for this one if the checkpoint is requested the session doing the
work knows this information already).
A last thing is that we may gain in visibility by having more
attributes as an effect of splitting param2. On thing that would make
sense is to track the reason why the checkpoint was triggered
separately (aka wal and time). Should we use a text[] instead to list
all the parameters instead? Using a space-separated list of items is
not intuitive IMO, and callers of this routine will likely parse
that.
Shouldn't we also track the number of files flushed in each sub-step?
In some deployments you could have a large number of 2PC files and
such. We may want more information on such matters.
+ WHEN 3 THEN 'checkpointing replication slots'
+ WHEN 4 THEN 'checkpointing logical replication snapshot files'
+ WHEN 5 THEN 'checkpointing logical rewrite mapping files'
+ WHEN 6 THEN 'checkpointing replication origin'
+ WHEN 7 THEN 'checkpointing commit log pages'
+ WHEN 8 THEN 'checkpointing commit time stamp pages'
There is a lot of "checkpointing" here. All those terms could be
shorter without losing their meaning.
This patch still needs some work, so I am marking it as RwF for now.
--
Michael
Attachments:
[application/pgp-signature] signature.asc (833B, ../../YkwPyAoTq%[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs)
@ 2022-06-06 06:03 Nitin Jadhav <[email protected]>
2 siblings, 0 replies; 199+ messages in thread
From: Nitin Jadhav @ 2022-06-06 06:03 UTC (permalink / raw)
To: Julien Rouhaud <[email protected]>; +Cc: Matthias van de Meent <[email protected]>; Bharath Rupireddy <[email protected]>; Ashutosh Sharma <[email protected]>; Bruce Momjian <[email protected]>; Tom Lane <[email protected]>; Magnus Hagander <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi,
Here is the update patch which fixes the previous comments discussed
in this thread. I am sorry for the long gap in the discussion. Kindly
let me know if I have missed any of the comments or anything new.
Thanks & Regards,
Nitin Jadhav
On Fri, Mar 18, 2022 at 4:52 PM Nitin Jadhav
<[email protected]> wrote:
>
> > > > I don't get it. The checkpoint flags and the view flags (set by
> > > > pgstat_progrss_update*) are different, so why can't we add this flag to the
> > > > view flags? The fact that checkpointer.c doesn't update the passed flag and
> > > > instead look in the shmem to see if CHECKPOINT_IMMEDIATE has been set since is
> > > > an implementation detail, and the view shouldn't focus on which flags were
> > > > initially passed to the checkpointer but instead which flags the checkpointer
> > > > is actually enforcing, as that's what the user should be interested in. If you
> > > > want to store it in another field internally but display it in the view with
> > > > the rest of the flags, I'm fine with it.
> > >
> > > Just to be in sync with the way code behaves, it is better not to
> > > update the next checkpoint request's CHECKPOINT_IMMEDIATE with the
> > > current checkpoint 'flags' field. Because the current checkpoint
> > > starts with a different set of flags and when there is a new request
> > > (with CHECKPOINT_IMMEDIATE), it just processes the pending operations
> > > quickly to take up next requests. If we update this information in the
> > > 'flags' field of the view, it says that the current checkpoint is
> > > started with CHECKPOINT_IMMEDIATE which is not true.
> >
> > Which is why I suggested to only take into account CHECKPOINT_REQUESTED (to
> > be able to display that a new checkpoint was requested)
>
> I will take care in the next patch.
>
> > > Hence I had
> > > thought of adding a new field ('next flags' or 'upcoming flags') which
> > > contain all the flag values of new checkpoint requests. This field
> > > indicates whether the current checkpoint is throttled or not and also
> > > it indicates there are new requests.
> >
> > I'm not opposed to having such a field, I'm opposed to having a view with "the
> > current checkpoint is throttled but if there are some flags in the next
> > checkpoint flags and those flags contain checkpoint immediate then the current
> > checkpoint isn't actually throttled anymore" behavior.
>
> I understand your point and I also agree that it becomes difficult for
> the user to understand the context.
>
> > and
> > CHECKPOINT_IMMEDIATE, to be able to display that the current checkpoint isn't
> > throttled anymore if it were.
> >
> > I still don't understand why you want so much to display "how the checkpoint
> > was initially started" rather than "how the checkpoint is really behaving right
> > now". The whole point of having a progress view is to have something dynamic
> > that reflects the current activity.
>
> As of now I will not consider adding this information to the view. If
> required and nobody opposes having this included in the 'flags' field
> of the view, then I will consider adding.
>
> Thanks & Regards,
> Nitin Jadhav
>
> On Mon, Mar 14, 2022 at 5:16 PM Julien Rouhaud <[email protected]> wrote:
> >
> > On Mon, Mar 14, 2022 at 03:16:50PM +0530, Nitin Jadhav wrote:
> > > > > I am not suggesting
> > > > > removing the existing 'flags' field of pg_stat_progress_checkpoint
> > > > > view and adding a new field 'throttled'. The content of the 'flags'
> > > > > field remains the same. I was suggesting replacing the 'next_flags'
> > > > > field with 'throttled' field since the new request with
> > > > > CHECKPOINT_IMMEDIATE flag enabled will affect the current checkpoint.
> > > >
> > > > Are you saying that this new throttled flag will only be set by the overloaded
> > > > flags in ckpt_flags?
> > >
> > > Yes. you are right.
> > >
> > > > So you can have a checkpoint with a CHECKPOINT_IMMEDIATE
> > > > flags that's throttled, and a checkpoint without the CHECKPOINT_IMMEDIATE flag
> > > > that's not throttled?
> > >
> > > I think it's the reverse. A checkpoint with a CHECKPOINT_IMMEDIATE
> > > flags that's not throttled (disables delays between writes) and a
> > > checkpoint without the CHECKPOINT_IMMEDIATE flag that's throttled
> > > (enables delays between writes)
> >
> > Yes that's how it's supposed to work, but my point was that your suggested
> > 'throttled' flag could say the opposite, which is bad.
> >
> > > > I don't get it. The checkpoint flags and the view flags (set by
> > > > pgstat_progrss_update*) are different, so why can't we add this flag to the
> > > > view flags? The fact that checkpointer.c doesn't update the passed flag and
> > > > instead look in the shmem to see if CHECKPOINT_IMMEDIATE has been set since is
> > > > an implementation detail, and the view shouldn't focus on which flags were
> > > > initially passed to the checkpointer but instead which flags the checkpointer
> > > > is actually enforcing, as that's what the user should be interested in. If you
> > > > want to store it in another field internally but display it in the view with
> > > > the rest of the flags, I'm fine with it.
> > >
> > > Just to be in sync with the way code behaves, it is better not to
> > > update the next checkpoint request's CHECKPOINT_IMMEDIATE with the
> > > current checkpoint 'flags' field. Because the current checkpoint
> > > starts with a different set of flags and when there is a new request
> > > (with CHECKPOINT_IMMEDIATE), it just processes the pending operations
> > > quickly to take up next requests. If we update this information in the
> > > 'flags' field of the view, it says that the current checkpoint is
> > > started with CHECKPOINT_IMMEDIATE which is not true.
> >
> > Which is why I suggested to only take into account CHECKPOINT_REQUESTED (to
> > be able to display that a new checkpoint was requested) and
> > CHECKPOINT_IMMEDIATE, to be able to display that the current checkpoint isn't
> > throttled anymore if it were.
> >
> > I still don't understand why you want so much to display "how the checkpoint
> > was initially started" rather than "how the checkpoint is really behaving right
> > now". The whole point of having a progress view is to have something dynamic
> > that reflects the current activity.
> >
> > > Hence I had
> > > thought of adding a new field ('next flags' or 'upcoming flags') which
> > > contain all the flag values of new checkpoint requests. This field
> > > indicates whether the current checkpoint is throttled or not and also
> > > it indicates there are new requests.
> >
> > I'm not opposed to having such a field, I'm opposed to having a view with "the
> > current checkpoint is throttled but if there are some flags in the next
> > checkpoint flags and those flags contain checkpoint immediate then the current
> > checkpoint isn't actually throttled anymore" behavior.
Attachments:
[application/octet-stream] v6-0001-pg_stat_progress_checkpoint-view.patch (38.0K, ../../CAMm1aWZ9eP==mk_UcO_LkYTx9oJLd98KYBbbsxp6r9DeDPbZ4g@mail.gmail.com/2-v6-0001-pg_stat_progress_checkpoint-view.patch)
download | inline diff:
From 104184f6cba7dfe33eb710de8158b21e6937bf51 Mon Sep 17 00:00:00 2001
From: Nitin Jadhav <[email protected]>
Date: Mon, 6 Jun 2022 05:55:06 +0000
Subject: [PATCH] pg_stat_progress_checkpoint-view
---
doc/src/sgml/monitoring.sgml | 405 +++++++++++++++++++++++++-
doc/src/sgml/ref/checkpoint.sgml | 7 +
doc/src/sgml/wal.sgml | 6 +-
src/backend/access/transam/xlog.c | 102 +++++++
src/backend/catalog/system_views.sql | 51 ++++
src/backend/postmaster/checkpointer.c | 15 +-
src/backend/storage/buffer/bufmgr.c | 7 +
src/backend/storage/sync/sync.c | 6 +
src/backend/utils/adt/pgstatfuncs.c | 2 +
src/include/commands/progress.h | 38 +++
src/include/utils/backend_progress.h | 3 +-
src/test/regress/expected/rules.out | 70 +++++
12 files changed, 706 insertions(+), 6 deletions(-)
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 4549c2560e..5fe0ba4492 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -414,6 +414,13 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser
See <xref linkend='copy-progress-reporting'/>.
</entry>
</row>
+
+ <row>
+ <entry><structname>pg_stat_progress_checkpoint</structname><indexterm><primary>pg_stat_progress_checkpoint</primary></indexterm></entry>
+ <entry>One row only, showing the progress of the checkpoint.
+ See <xref linkend='checkpoint-progress-reporting'/>.
+ </entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -5736,7 +5743,7 @@ SELECT pg_stat_get_backend_pid(s.backendid) AS pid,
which support progress reporting are <command>ANALYZE</command>,
<command>CLUSTER</command>,
<command>CREATE INDEX</command>, <command>VACUUM</command>,
- <command>COPY</command>,
+ <command>COPY</command>, <command>CHECKPOINT</command>
and <xref linkend="protocol-replication-base-backup"/> (i.e., replication
command that <xref linkend="app-pgbasebackup"/> issues to take
a base backup).
@@ -7024,6 +7031,402 @@ SELECT pg_stat_get_backend_pid(s.backendid) AS pid,
</table>
</sect2>
+ <sect2 id="checkpoint-progress-reporting">
+ <title>Checkpoint Progress Reporting</title>
+
+ <indexterm>
+ <primary>pg_stat_progress_checkpoint</primary>
+ </indexterm>
+
+ <para>
+ Whenever the checkpoint operation is running, the
+ <structname>pg_stat_progress_checkpoint</structname> view will contain a
+ single row indicating the progress of the checkpoint. The tables below
+ describe the information that will be reported and provide information about
+ how to interpret it.
+ </para>
+
+ <table id="pg-stat-progress-checkpoint-view" xreflabel="pg_stat_progress_checkpoint">
+ <title><structname>pg_stat_progress_checkpoint</structname> View</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pid</structfield> <type>integer</type>
+ </para>
+ <para>
+ Process ID of the checkpointer process.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>type</structfield> <type>text</type>
+ </para>
+ <para>
+ Type of the checkpoint. See <xref linkend="checkpoint-types"/>.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>flags</structfield> <type>text</type>
+ </para>
+ <para>
+ Flags of the checkpoint. See <xref linkend="checkpoint-flags"/>.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>start_lsn</structfield> <type>text</type>
+ </para>
+ <para>
+ The checkpoint start location.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>start_time</structfield> <type>timestamp with time zone</type>
+ </para>
+ <para>
+ Start time of the checkpoint.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>phase</structfield> <type>text</type>
+ </para>
+ <para>
+ Current processing phase. See <xref linkend="checkpoint-phases"/>.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>buffers_total</structfield> <type>bigint</type>
+ </para>
+ <para>
+ Total number of buffers to be written. This is estimated and reported
+ as of the beginning of buffer write operation.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>buffers_processed</structfield> <type>bigint</type>
+ </para>
+ <para>
+ Number of buffers processed. This counter increases when the targeted
+ buffer is processed. This number will eventually become equal to
+ <literal>buffers_total</literal> when the checkpoint is
+ complete.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>buffers_written</structfield> <type>bigint</type>
+ </para>
+ <para>
+ Number of buffers written. This counter only advances when the targeted
+ buffers is written. Note that some of the buffers are processed but may
+ not required to be written. So this count will always be less than or
+ equal to <literal>buffers_total</literal>.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>files_total</structfield> <type>bigint</type>
+ </para>
+ <para>
+ Total number of files to be synced. This is estimated and reported as of
+ the beginning of sync operation.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>files_synced</structfield> <type>bigint</type>
+ </para>
+ <para>
+ Number of files synced. This counter advances when the targeted file is
+ synced. This number will eventually become equal to
+ <literal>files_total</literal> when the checkpoint is complete.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>new_requests</structfield> <type>text</type>
+ </para>
+ <para>
+ True if any of the backend is requested for a checkpoint while the
+ current checkpoint is in progress, False otherwise.
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <table id="checkpoint-types">
+ <title>Checkpoint Types</title>
+ <tgroup cols="2">
+ <colspec colname="col1" colwidth="1*"/>
+ <colspec colname="col2" colwidth="2*"/>
+ <thead>
+ <row>
+ <entry>Types</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>checkpoint</literal></entry>
+ <entry>
+ The current operation is checkpoint.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>restartpoint</literal></entry>
+ <entry>
+ The current operation is restartpoint.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <table id="checkpoint-flags">
+ <title>Checkpoint Flags</title>
+ <tgroup cols="2">
+ <colspec colname="col1" colwidth="1*"/>
+ <colspec colname="col2" colwidth="2*"/>
+ <thead>
+ <row>
+ <entry>Flags</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>shutdown</literal></entry>
+ <entry>
+ The checkpoint is for shutdown.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>end-of-recovery</literal></entry>
+ <entry>
+ The checkpoint is for end-of-recovery.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>immediate</literal></entry>
+ <entry>
+ The checkpoint happens without delays.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>force</literal></entry>
+ <entry>
+ The checkpoint is started because some operation (for which the
+ checkpoint is necessary) forced a checkpoint.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>flush all</literal></entry>
+ <entry>
+ The checkpoint flushes all pages, including those belonging to unlogged
+ tables.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>wait</literal></entry>
+ <entry>
+ The operations which requested the checkpoint waits for completion
+ before returning.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>requested</literal></entry>
+ <entry>
+ The checkpoint request has been made.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>wal</literal></entry>
+ <entry>
+ The checkpoint is started because <literal>max_wal_size</literal> is
+ reached.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>time</literal></entry>
+ <entry>
+ The checkpoint is started because <literal>checkpoint_timeout</literal>
+ expired.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <table id="checkpoint-phases">
+ <title>Checkpoint Phases</title>
+ <tgroup cols="2">
+ <colspec colname="col1" colwidth="1*"/>
+ <colspec colname="col2" colwidth="2*"/>
+ <thead>
+ <row>
+ <entry>Phase</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>initializing</literal></entry>
+ <entry>
+ The checkpointer process is preparing to begin the checkpoint operation.
+ This phase is expected to be very brief.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>getting virtual transaction IDs</literal></entry>
+ <entry>
+ The checkpointer process is getting the virtual transaction IDs that
+ are delaying the checkpoint.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>checkpointing replication slots</literal></entry>
+ <entry>
+ The checkpointer process is currently flushing all the replication slots
+ to disk.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>checkpointing logical replication snapshot files</literal></entry>
+ <entry>
+ The checkpointer process is currently removing all the serialized
+ snapshot files that are not required anymore.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>checkpointing logical rewrite mapping files</literal></entry>
+ <entry>
+ The checkpointer process is currently removing unwanted or flushing
+ required logical rewrite mapping files.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>checkpointing replication origin</literal></entry>
+ <entry>
+ The checkpointer process is currently performing a checkpoint of each
+ replication origin's progress with respect to the replayed remote LSN.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>checkpointing commit log pages</literal></entry>
+ <entry>
+ The checkpointer process is currently writing commit log pages to disk.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>checkpointing commit time stamp pages</literal></entry>
+ <entry>
+ The checkpointer process is currently writing commit time stamp pages to
+ disk.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>checkpointing subtransaction pages</literal></entry>
+ <entry>
+ The checkpointer process is currently writing subtransaction pages to disk.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>checkpointing multixact pages</literal></entry>
+ <entry>
+ The checkpointer process is currently writing multixact pages to disk.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>checkpointing predicate lock pages</literal></entry>
+ <entry>
+ The checkpointer process is currently writing predicate lock pages to disk.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>checkpointing buffers</literal></entry>
+ <entry>
+ The checkpointer process is currently writing buffers to disk.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>processing file sync requests</literal></entry>
+ <entry>
+ The checkpointer process is currently processing file sync requests.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>performing two phase checkpoint</literal></entry>
+ <entry>
+ The checkpointer process is currently performing two phase checkpoint.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>performing post checkpoint cleanup</literal></entry>
+ <entry>
+ The checkpointer process is currently performing post checkpoint cleanup.
+ It removes any lingering files that can be safely removed.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>invalidating replication slots</literal></entry>
+ <entry>
+ The checkpointer process is currently invalidating replication slots.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>recycling old WAL files</literal></entry>
+ <entry>
+ The checkpointer process is currently recycling old WAL files.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>truncating subtransactions</literal></entry>
+ <entry>
+ The checkpointer process is currently removing the subtransaction
+ segments.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>finalizing</literal></entry>
+ <entry>
+ The checkpointer process is finalizing the checkpoint operation.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ </sect2>
+
</sect1>
<sect1 id="dynamic-trace">
diff --git a/doc/src/sgml/ref/checkpoint.sgml b/doc/src/sgml/ref/checkpoint.sgml
index 1cebc03d15..f33db50cfc 100644
--- a/doc/src/sgml/ref/checkpoint.sgml
+++ b/doc/src/sgml/ref/checkpoint.sgml
@@ -56,6 +56,13 @@ CHECKPOINT
the <link linkend="predefined-roles-table"><literal>pg_checkpointer</literal></link>
role can call <command>CHECKPOINT</command>.
</para>
+
+ <para>
+ The checkpointer process running the checkpoint will report its progress
+ in the <structname>pg_stat_progress_checkpoint</structname> view except for
+ the shutdown and end-of-recovery cases. See
+ <xref linkend="checkpoint-progress-reporting"/> for details.
+ </para>
</refsect1>
<refsect1>
diff --git a/doc/src/sgml/wal.sgml b/doc/src/sgml/wal.sgml
index 4b6ef283c1..607f21dfd4 100644
--- a/doc/src/sgml/wal.sgml
+++ b/doc/src/sgml/wal.sgml
@@ -530,7 +530,11 @@
adjust the <xref linkend="guc-archive-timeout"/> parameter rather than the
checkpoint parameters.)
It is also possible to force a checkpoint by using the SQL
- command <command>CHECKPOINT</command>.
+ command <command>CHECKPOINT</command>. The checkpointer process running the
+ checkpoint will report its progress in the
+ <structname>pg_stat_progress_checkpoint</structname> view except for the
+ shutdown and end-of-recovery cases. See
+ <xref linkend="checkpoint-progress-reporting"/> for details.
</para>
<para>
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 71136b11a2..8272d02b1e 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -66,6 +66,7 @@
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
#include "catalog/pg_database.h"
+#include "commands/progress.h"
#include "common/controldata_utils.h"
#include "common/file_utils.h"
#include "executor/instrument.h"
@@ -695,6 +696,8 @@ static void WALInsertLockAcquireExclusive(void);
static void WALInsertLockRelease(void);
static void WALInsertLockUpdateInsertingAt(XLogRecPtr insertingAt);
+static void checkpoint_progress_start(int flags, int type);
+
/*
* Insert an XLOG record represented by an already-constructed chain of data
* chunks. This is a low-level routine; to construct the WAL record header
@@ -6429,6 +6432,9 @@ CreateCheckPoint(int flags)
XLogCtl->RedoRecPtr = checkPoint.redo;
SpinLockRelease(&XLogCtl->info_lck);
+ /* Prepare to report progress of the checkpoint. */
+ checkpoint_progress_start(flags, PROGRESS_CHECKPOINT_TYPE_CHECKPOINT);
+
/*
* If enabled, log checkpoint start. We postpone this until now so as not
* to log anything if we decided to skip the checkpoint.
@@ -6511,6 +6517,8 @@ CreateCheckPoint(int flags)
* clog and we will correctly flush the update below. So we cannot miss
* any xacts we need to wait for.
*/
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_GET_VIRTUAL_TRANSACTION_IDS);
vxids = GetVirtualXIDsDelayingChkpt(&nvxids, DELAY_CHKPT_START);
if (nvxids > 0)
{
@@ -6626,6 +6634,8 @@ CreateCheckPoint(int flags)
/*
* Let smgr do post-checkpoint cleanup (eg, deleting old files).
*/
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_POST_CHECKPOINT_CLEANUP);
SyncPostCheckpoint();
/*
@@ -6641,6 +6651,9 @@ CreateCheckPoint(int flags)
*/
XLByteToSeg(RedoRecPtr, _logSegNo, wal_segment_size);
KeepLogSeg(recptr, &_logSegNo);
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_INVALIDATE_REPLI_SLOTS);
+
if (InvalidateObsoleteReplicationSlots(_logSegNo))
{
/*
@@ -6651,6 +6664,8 @@ CreateCheckPoint(int flags)
KeepLogSeg(recptr, &_logSegNo);
}
_logSegNo--;
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_RECYCLE_OLD_XLOG);
RemoveOldXlogFiles(_logSegNo, RedoRecPtr, recptr,
checkPoint.ThisTimeLineID);
@@ -6669,11 +6684,21 @@ CreateCheckPoint(int flags)
* StartupSUBTRANS hasn't been called yet.
*/
if (!RecoveryInProgress())
+ {
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_TRUNCATE_SUBTRANS);
TruncateSUBTRANS(GetOldestTransactionIdConsideredRunning());
+ }
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_FINALIZE);
/* Real work is done; log and update stats. */
LogCheckpointEnd(false);
+ /* Stop reporting progress of the checkpoint. */
+ pgstat_progress_end_command();
+
/* Reset the process title */
update_checkpoint_display(flags, false, true);
@@ -6830,29 +6855,63 @@ static void
CheckPointGuts(XLogRecPtr checkPointRedo, int flags)
{
CheckPointRelationMap();
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_REPLI_SLOTS);
CheckPointReplicationSlots();
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_SNAPSHOTS);
CheckPointSnapBuild();
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_LOGICAL_REWRITE_MAPPINGS);
CheckPointLogicalRewriteHeap();
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_REPLI_ORIGIN);
CheckPointReplicationOrigin();
/* Write out all dirty data in SLRUs and the main buffer pool */
TRACE_POSTGRESQL_BUFFER_CHECKPOINT_START(flags);
CheckpointStats.ckpt_write_t = GetCurrentTimestamp();
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_CLOG_PAGES);
CheckPointCLOG();
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_COMMITTS_PAGES);
CheckPointCommitTs();
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_SUBTRANS_PAGES);
CheckPointSUBTRANS();
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_MULTIXACT_PAGES);
CheckPointMultiXact();
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_PREDICATE_LOCK_PAGES);
CheckPointPredicate();
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_BUFFERS);
CheckPointBuffers(flags);
/* Perform all queued up fsyncs */
TRACE_POSTGRESQL_BUFFER_CHECKPOINT_SYNC_START();
CheckpointStats.ckpt_sync_t = GetCurrentTimestamp();
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_SYNC_FILES);
ProcessSyncRequests();
CheckpointStats.ckpt_sync_end_t = GetCurrentTimestamp();
TRACE_POSTGRESQL_BUFFER_CHECKPOINT_DONE();
/* We deliberately delay 2PC checkpointing as long as possible */
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_TWO_PHASE);
CheckPointTwoPhase(checkPointRedo);
}
@@ -7002,6 +7061,9 @@ CreateRestartPoint(int flags)
MemSet(&CheckpointStats, 0, sizeof(CheckpointStats));
CheckpointStats.ckpt_start_t = GetCurrentTimestamp();
+ /* Prepare to report progress of the restartpoint. */
+ checkpoint_progress_start(flags, PROGRESS_CHECKPOINT_TYPE_RESTARTPOINT);
+
if (log_checkpoints)
LogCheckpointStart(flags, true);
@@ -7085,6 +7147,9 @@ CreateRestartPoint(int flags)
replayPtr = GetXLogReplayRecPtr(&replayTLI);
endptr = (receivePtr < replayPtr) ? replayPtr : receivePtr;
KeepLogSeg(endptr, &_logSegNo);
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_INVALIDATE_REPLI_SLOTS);
+
if (InvalidateObsoleteReplicationSlots(_logSegNo))
{
/*
@@ -7111,6 +7176,8 @@ CreateRestartPoint(int flags)
if (!RecoveryInProgress())
replayTLI = XLogCtl->InsertTimeLineID;
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_RECYCLE_OLD_XLOG);
RemoveOldXlogFiles(_logSegNo, RedoRecPtr, endptr, replayTLI);
/*
@@ -7127,11 +7194,20 @@ CreateRestartPoint(int flags)
* this because StartupSUBTRANS hasn't been called yet.
*/
if (EnableHotStandby)
+ {
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_TRUNCATE_SUBTRANS);
TruncateSUBTRANS(GetOldestTransactionIdConsideredRunning());
+ }
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_FINALIZE);
/* Real work is done; log and update stats. */
LogCheckpointEnd(true);
+ /* Stop reporting progress of the restartpoint. */
+ pgstat_progress_end_command();
+
/* Reset the process title */
update_checkpoint_display(flags, true, true);
@@ -8880,3 +8956,29 @@ SetWalWriterSleeping(bool sleeping)
XLogCtl->WalWriterSleeping = sleeping;
SpinLockRelease(&XLogCtl->info_lck);
}
+
+/*
+ * Start reporting progress of the checkpoint.
+ */
+static void
+checkpoint_progress_start(int flags, int type)
+{
+ const int index[] = {
+ PROGRESS_CHECKPOINT_TYPE,
+ PROGRESS_CHECKPOINT_FLAGS,
+ PROGRESS_CHECKPOINT_LSN,
+ PROGRESS_CHECKPOINT_START_TIMESTAMP,
+ PROGRESS_CHECKPOINT_PHASE
+ };
+ int64 val[5];
+
+ pgstat_progress_start_command(PROGRESS_COMMAND_CHECKPOINT, InvalidOid);
+
+ val[0] = type;
+ val[1] = flags;
+ val[2] = RedoRecPtr;
+ val[3] = CheckpointStats.ckpt_start_t;
+ val[4] = PROGRESS_CHECKPOINT_PHASE_INIT;
+
+ pgstat_progress_update_multi_param(5, index, val);
+}
\ No newline at end of file
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index fedaed533b..20d029a547 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1265,6 +1265,57 @@ CREATE VIEW pg_stat_progress_copy AS
FROM pg_stat_get_progress_info('COPY') AS S
LEFT JOIN pg_database D ON S.datid = D.oid;
+CREATE VIEW pg_stat_progress_checkpoint AS
+ SELECT
+ S.pid AS pid,
+ CASE S.param1 WHEN 1 THEN 'checkpoint'
+ WHEN 2 THEN 'restartpoint'
+ END AS type,
+ ( CASE WHEN (S.param2 & 4) > 0 THEN 'immediate ' ELSE '' END ||
+ CASE WHEN (S.param2 & 8) > 0 THEN 'force ' ELSE '' END ||
+ CASE WHEN (S.param2 & 16) > 0 THEN 'flush-all ' ELSE '' END ||
+ CASE WHEN (S.param2 & 32) > 0 THEN 'wait ' ELSE '' END ||
+ CASE WHEN (S.param2 & 128) > 0 THEN 'wal ' ELSE '' END ||
+ CASE WHEN (S.param2 & 256) > 0 THEN 'time ' ELSE '' END
+ ) AS flags,
+ ( '0/0'::pg_lsn +
+ ((CASE
+ WHEN S.param3 < 0 THEN pow(2::numeric, 64::numeric)::numeric
+ ELSE 0::numeric
+ END) +
+ S.param3::numeric)
+ ) AS start_lsn,
+ to_timestamp(946684800 + (S.param4::float8 / 1000000)) AS start_time,
+ CASE S.param5 WHEN 1 THEN 'initializing'
+ WHEN 2 THEN 'getting virtual transaction IDs'
+ WHEN 3 THEN 'checkpointing replication slots'
+ WHEN 4 THEN 'checkpointing logical replication snapshot files'
+ WHEN 5 THEN 'checkpointing logical rewrite mapping files'
+ WHEN 6 THEN 'checkpointing replication origin'
+ WHEN 7 THEN 'checkpointing commit log pages'
+ WHEN 8 THEN 'checkpointing commit time stamp pages'
+ WHEN 9 THEN 'checkpointing subtransaction pages'
+ WHEN 10 THEN 'checkpointing multixact pages'
+ WHEN 11 THEN 'checkpointing predicate lock pages'
+ WHEN 12 THEN 'checkpointing buffers'
+ WHEN 13 THEN 'processing file sync requests'
+ WHEN 14 THEN 'performing two phase checkpoint'
+ WHEN 15 THEN 'performing post checkpoint cleanup'
+ WHEN 16 THEN 'invalidating replication slots'
+ WHEN 17 THEN 'recycling old WAL files'
+ WHEN 18 THEN 'truncating subtransactions'
+ WHEN 19 THEN 'finalizing'
+ END AS phase,
+ S.param6 AS buffers_total,
+ S.param7 AS buffers_processed,
+ S.param8 AS buffers_written,
+ S.param9 AS files_total,
+ S.param10 AS files_synced,
+ CASE S.param11 WHEN 0 THEN 'false'
+ WHEN 1 THEN 'true'
+ END AS new_requests
+ FROM pg_stat_get_progress_info('CHECKPOINT') AS S;
+
CREATE VIEW pg_user_mappings AS
SELECT
U.oid AS umid,
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index c937c39f50..79cae7e9ca 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -39,6 +39,7 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
#include "access/xlogrecovery.h"
+#include "commands/progress.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
@@ -163,7 +164,7 @@ static pg_time_t last_xlog_switch_time;
static void HandleCheckpointerInterrupts(void);
static void CheckArchiveTimeout(void);
static bool IsCheckpointOnSchedule(double progress);
-static bool ImmediateCheckpointRequested(void);
+static bool ImmediateCheckpointRequested(int flags);
static bool CompactCheckpointerRequestQueue(void);
static void UpdateSharedMemoryConfig(void);
@@ -667,16 +668,24 @@ CheckArchiveTimeout(void)
* there is one pending behind it.)
*/
static bool
-ImmediateCheckpointRequested(void)
+ImmediateCheckpointRequested(int flags)
{
volatile CheckpointerShmemStruct *cps = CheckpointerShmem;
+ if (cps->ckpt_flags & CHECKPOINT_REQUESTED)
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_NEW_REQUESTS, true);
+
/*
* We don't need to acquire the ckpt_lck in this case because we're only
* looking at a single flag bit.
*/
if (cps->ckpt_flags & CHECKPOINT_IMMEDIATE)
+ {
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_FLAGS,
+ (flags | CHECKPOINT_IMMEDIATE));
return true;
+ }
+
return false;
}
@@ -708,7 +717,7 @@ CheckpointWriteDelay(int flags, double progress)
*/
if (!(flags & CHECKPOINT_IMMEDIATE) &&
!ShutdownRequestPending &&
- !ImmediateCheckpointRequested() &&
+ !ImmediateCheckpointRequested(flags) &&
IsCheckpointOnSchedule(progress))
{
if (ConfigReloadPending)
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index ae13011d27..55f03c1301 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -39,6 +39,7 @@
#include "catalog/catalog.h"
#include "catalog/storage.h"
#include "catalog/storage_xlog.h"
+#include "commands/progress.h"
#include "executor/instrument.h"
#include "lib/binaryheap.h"
#include "miscadmin.h"
@@ -2019,6 +2020,8 @@ BufferSync(int flags)
WritebackContextInit(&wb_context, &checkpoint_flush_after);
TRACE_POSTGRESQL_BUFFER_SYNC_START(NBuffers, num_to_scan);
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_BUFFERS_TOTAL,
+ num_to_scan);
/*
* Sort buffers that need to be written to reduce the likelihood of random
@@ -2136,6 +2139,8 @@ BufferSync(int flags)
bufHdr = GetBufferDescriptor(buf_id);
num_processed++;
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_BUFFERS_PROCESSED,
+ num_processed);
/*
* We don't need to acquire the lock here, because we're only looking
@@ -2156,6 +2161,8 @@ BufferSync(int flags)
TRACE_POSTGRESQL_BUFFER_SYNC_WRITTEN(buf_id);
PendingCheckpointerStats.buf_written_checkpoints++;
num_written++;
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_BUFFERS_WRITTEN,
+ num_written);
}
}
diff --git a/src/backend/storage/sync/sync.c b/src/backend/storage/sync/sync.c
index e1fb631003..3acbf94c5e 100644
--- a/src/backend/storage/sync/sync.c
+++ b/src/backend/storage/sync/sync.c
@@ -23,6 +23,7 @@
#include "access/multixact.h"
#include "access/xlog.h"
#include "access/xlogutils.h"
+#include "commands/progress.h"
#include "commands/tablespace.h"
#include "miscadmin.h"
#include "pgstat.h"
@@ -368,6 +369,9 @@ ProcessSyncRequests(void)
/* Now scan the hashtable for fsync requests to process */
absorb_counter = FSYNCS_PER_ABSORB;
hash_seq_init(&hstat, pendingOps);
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_FILES_TOTAL,
+ hash_get_num_entries(pendingOps));
+
while ((entry = (PendingFsyncEntry *) hash_seq_search(&hstat)) != NULL)
{
int failures;
@@ -431,6 +435,8 @@ ProcessSyncRequests(void)
longest = elapsed;
total_elapsed += elapsed;
processed++;
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_FILES_SYNCED,
+ processed);
if (log_checkpoints)
elog(DEBUG1, "checkpoint sync: number=%d file=%s time=%.3f ms",
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 893690dad5..c0de766fde 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -476,6 +476,8 @@ pg_stat_get_progress_info(PG_FUNCTION_ARGS)
cmdtype = PROGRESS_COMMAND_BASEBACKUP;
else if (pg_strcasecmp(cmd, "COPY") == 0)
cmdtype = PROGRESS_COMMAND_COPY;
+ else if (pg_strcasecmp(cmd, "CHECKPOINT") == 0)
+ cmdtype = PROGRESS_COMMAND_CHECKPOINT;
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/include/commands/progress.h b/src/include/commands/progress.h
index a28938caf4..33a64d2f0b 100644
--- a/src/include/commands/progress.h
+++ b/src/include/commands/progress.h
@@ -151,4 +151,42 @@
#define PROGRESS_COPY_TYPE_PIPE 3
#define PROGRESS_COPY_TYPE_CALLBACK 4
+/* Progress parameters for checkpoint */
+#define PROGRESS_CHECKPOINT_TYPE 0
+#define PROGRESS_CHECKPOINT_FLAGS 1
+#define PROGRESS_CHECKPOINT_LSN 2
+#define PROGRESS_CHECKPOINT_START_TIMESTAMP 3
+#define PROGRESS_CHECKPOINT_PHASE 4
+#define PROGRESS_CHECKPOINT_BUFFERS_TOTAL 5
+#define PROGRESS_CHECKPOINT_BUFFERS_PROCESSED 6
+#define PROGRESS_CHECKPOINT_BUFFERS_WRITTEN 7
+#define PROGRESS_CHECKPOINT_FILES_TOTAL 8
+#define PROGRESS_CHECKPOINT_FILES_SYNCED 9
+#define PROGRESS_CHECKPOINT_NEW_REQUESTS 10
+
+/* Types of checkpoint (as advertised via PROGRESS_CHECKPOINT_TYPE) */
+#define PROGRESS_CHECKPOINT_TYPE_CHECKPOINT 1
+#define PROGRESS_CHECKPOINT_TYPE_RESTARTPOINT 2
+
+/* Phases of checkpoint (as advertised via PROGRESS_CHECKPOINT_PHASE) */
+#define PROGRESS_CHECKPOINT_PHASE_INIT 1
+#define PROGRESS_CHECKPOINT_PHASE_GET_VIRTUAL_TRANSACTION_IDS 2
+#define PROGRESS_CHECKPOINT_PHASE_REPLI_SLOTS 3
+#define PROGRESS_CHECKPOINT_PHASE_SNAPSHOTS 4
+#define PROGRESS_CHECKPOINT_PHASE_LOGICAL_REWRITE_MAPPINGS 5
+#define PROGRESS_CHECKPOINT_PHASE_REPLI_ORIGIN 6
+#define PROGRESS_CHECKPOINT_PHASE_CLOG_PAGES 7
+#define PROGRESS_CHECKPOINT_PHASE_COMMITTS_PAGES 8
+#define PROGRESS_CHECKPOINT_PHASE_SUBTRANS_PAGES 9
+#define PROGRESS_CHECKPOINT_PHASE_MULTIXACT_PAGES 10
+#define PROGRESS_CHECKPOINT_PHASE_PREDICATE_LOCK_PAGES 11
+#define PROGRESS_CHECKPOINT_PHASE_BUFFERS 12
+#define PROGRESS_CHECKPOINT_PHASE_SYNC_FILES 13
+#define PROGRESS_CHECKPOINT_PHASE_TWO_PHASE 14
+#define PROGRESS_CHECKPOINT_PHASE_POST_CHECKPOINT_CLEANUP 15
+#define PROGRESS_CHECKPOINT_PHASE_INVALIDATE_REPLI_SLOTS 16
+#define PROGRESS_CHECKPOINT_PHASE_RECYCLE_OLD_XLOG 17
+#define PROGRESS_CHECKPOINT_PHASE_TRUNCATE_SUBTRANS 18
+#define PROGRESS_CHECKPOINT_PHASE_FINALIZE 19
+
#endif
diff --git a/src/include/utils/backend_progress.h b/src/include/utils/backend_progress.h
index 47bf8029b0..02d51fb948 100644
--- a/src/include/utils/backend_progress.h
+++ b/src/include/utils/backend_progress.h
@@ -27,7 +27,8 @@ typedef enum ProgressCommandType
PROGRESS_COMMAND_CLUSTER,
PROGRESS_COMMAND_CREATE_INDEX,
PROGRESS_COMMAND_BASEBACKUP,
- PROGRESS_COMMAND_COPY
+ PROGRESS_COMMAND_COPY,
+ PROGRESS_COMMAND_CHECKPOINT
} ProgressCommandType;
#define PGSTAT_NUM_PROGRESS_PARAM 20
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index fc3cde3226..ec130dad2a 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1912,6 +1912,76 @@ pg_stat_progress_basebackup| SELECT s.pid,
s.param4 AS tablespaces_total,
s.param5 AS tablespaces_streamed
FROM pg_stat_get_progress_info('BASEBACKUP'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20);
+pg_stat_progress_checkpoint| SELECT s.pid,
+ CASE s.param1
+ WHEN 1 THEN 'checkpoint'::text
+ WHEN 2 THEN 'restartpoint'::text
+ ELSE NULL::text
+ END AS type,
+ (((((
+ CASE
+ WHEN ((s.param2 & (4)::bigint) > 0) THEN 'immediate '::text
+ ELSE ''::text
+ END ||
+ CASE
+ WHEN ((s.param2 & (8)::bigint) > 0) THEN 'force '::text
+ ELSE ''::text
+ END) ||
+ CASE
+ WHEN ((s.param2 & (16)::bigint) > 0) THEN 'flush-all '::text
+ ELSE ''::text
+ END) ||
+ CASE
+ WHEN ((s.param2 & (32)::bigint) > 0) THEN 'wait '::text
+ ELSE ''::text
+ END) ||
+ CASE
+ WHEN ((s.param2 & (128)::bigint) > 0) THEN 'wal '::text
+ ELSE ''::text
+ END) ||
+ CASE
+ WHEN ((s.param2 & (256)::bigint) > 0) THEN 'time '::text
+ ELSE ''::text
+ END) AS flags,
+ ('0/0'::pg_lsn + (
+ CASE
+ WHEN (s.param3 < 0) THEN pow((2)::numeric, (64)::numeric)
+ ELSE (0)::numeric
+ END + (s.param3)::numeric)) AS start_lsn,
+ to_timestamp(((946684800)::double precision + ((s.param4)::double precision / (1000000)::double precision))) AS start_time,
+ CASE s.param5
+ WHEN 1 THEN 'initializing'::text
+ WHEN 2 THEN 'getting virtual transaction IDs'::text
+ WHEN 3 THEN 'checkpointing replication slots'::text
+ WHEN 4 THEN 'checkpointing logical replication snapshot files'::text
+ WHEN 5 THEN 'checkpointing logical rewrite mapping files'::text
+ WHEN 6 THEN 'checkpointing replication origin'::text
+ WHEN 7 THEN 'checkpointing commit log pages'::text
+ WHEN 8 THEN 'checkpointing commit time stamp pages'::text
+ WHEN 9 THEN 'checkpointing subtransaction pages'::text
+ WHEN 10 THEN 'checkpointing multixact pages'::text
+ WHEN 11 THEN 'checkpointing predicate lock pages'::text
+ WHEN 12 THEN 'checkpointing buffers'::text
+ WHEN 13 THEN 'processing file sync requests'::text
+ WHEN 14 THEN 'performing two phase checkpoint'::text
+ WHEN 15 THEN 'performing post checkpoint cleanup'::text
+ WHEN 16 THEN 'invalidating replication slots'::text
+ WHEN 17 THEN 'recycling old WAL files'::text
+ WHEN 18 THEN 'truncating subtransactions'::text
+ WHEN 19 THEN 'finalizing'::text
+ ELSE NULL::text
+ END AS phase,
+ s.param6 AS buffers_total,
+ s.param7 AS buffers_processed,
+ s.param8 AS buffers_written,
+ s.param9 AS files_total,
+ s.param10 AS files_synced,
+ CASE s.param11
+ WHEN 0 THEN 'false'::text
+ WHEN 1 THEN 'true'::text
+ ELSE NULL::text
+ END AS new_requests
+ FROM pg_stat_get_progress_info('CHECKPOINT'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20);
pg_stat_progress_cluster| SELECT s.pid,
s.datid,
d.datname,
--
2.25.1
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs)
@ 2022-06-13 13:38 Nitin Jadhav <[email protected]>
2 siblings, 1 reply; 199+ messages in thread
From: Nitin Jadhav @ 2022-06-13 13:38 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Bharath Rupireddy <[email protected]>; Matthias van de Meent <[email protected]>; Ashutosh Sharma <[email protected]>; Julien Rouhaud <[email protected]>; Bruce Momjian <[email protected]>; Tom Lane <[email protected]>; Magnus Hagander <[email protected]>; PostgreSQL Hackers <[email protected]>
> Have you measured the performance effects of this? On fast storage with large
> shared_buffers I've seen these loops in profiles. It's probably fine, but it'd
> be good to verify that.
To understand the performance effects of the above, I have taken the
average of five checkpoints with the patch and without the patch in my
environment. Here are the results.
With patch: 269.65 s
Without patch: 269.60 s
It looks fine. Please share your views.
> This view is depressingly complicated. Added up the view definitions for
> the already existing pg_stat_progress* views add up to a measurable part of
> the size of an empty database:
Thank you so much for sharing the detailed analysis. We can remove a
few fields which are not so important to make it simple.
Thanks & Regards,
Nitin Jadhav
On Sat, Mar 19, 2022 at 5:45 AM Andres Freund <[email protected]> wrote:
>
> Hi,
>
> This is a long thread, sorry for asking if this has been asked before.
>
> On 2022-03-08 20:25:28 +0530, Nitin Jadhav wrote:
> > * Sort buffers that need to be written to reduce the likelihood of random
> > @@ -2129,6 +2132,8 @@ BufferSync(int flags)
> > bufHdr = GetBufferDescriptor(buf_id);
> >
> > num_processed++;
> > + pgstat_progress_update_param(PROGRESS_CHECKPOINT_BUFFERS_PROCESSED,
> > + num_processed);
> >
> > /*
> > * We don't need to acquire the lock here, because we're only looking
> > @@ -2149,6 +2154,8 @@ BufferSync(int flags)
> > TRACE_POSTGRESQL_BUFFER_SYNC_WRITTEN(buf_id);
> > PendingCheckpointerStats.m_buf_written_checkpoints++;
> > num_written++;
> > + pgstat_progress_update_param(PROGRESS_CHECKPOINT_BUFFERS_WRITTEN,
> > + num_written);
> > }
> > }
>
> Have you measured the performance effects of this? On fast storage with large
> shared_buffers I've seen these loops in profiles. It's probably fine, but it'd
> be good to verify that.
>
>
> > @@ -1897,6 +1897,112 @@ pg_stat_progress_basebackup| SELECT s.pid,
> > s.param4 AS tablespaces_total,
> > s.param5 AS tablespaces_streamed
> > FROM pg_stat_get_progress_info('BASEBACKUP'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20);
> > +pg_stat_progress_checkpoint| SELECT s.pid,
> > + CASE s.param1
> > + WHEN 1 THEN 'checkpoint'::text
> > + WHEN 2 THEN 'restartpoint'::text
> > + ELSE NULL::text
> > + END AS type,
> > + (((((((
> > + CASE
> > + WHEN ((s.param2 & (1)::bigint) > 0) THEN 'shutdown '::text
> > + ELSE ''::text
> > + END ||
> > + CASE
> > + WHEN ((s.param2 & (2)::bigint) > 0) THEN 'end-of-recovery '::text
> > + ELSE ''::text
> > + END) ||
> > + CASE
> > + WHEN ((s.param2 & (4)::bigint) > 0) THEN 'immediate '::text
> > + ELSE ''::text
> > + END) ||
> > + CASE
> > + WHEN ((s.param2 & (8)::bigint) > 0) THEN 'force '::text
> > + ELSE ''::text
> > + END) ||
> > + CASE
> > + WHEN ((s.param2 & (16)::bigint) > 0) THEN 'flush-all '::text
> > + ELSE ''::text
> > + END) ||
> > + CASE
> > + WHEN ((s.param2 & (32)::bigint) > 0) THEN 'wait '::text
> > + ELSE ''::text
> > + END) ||
> > + CASE
> > + WHEN ((s.param2 & (128)::bigint) > 0) THEN 'wal '::text
> > + ELSE ''::text
> > + END) ||
> > + CASE
> > + WHEN ((s.param2 & (256)::bigint) > 0) THEN 'time '::text
> > + ELSE ''::text
> > + END) AS flags,
> > + (((((((
> > + CASE
> > + WHEN ((s.param3 & (1)::bigint) > 0) THEN 'shutdown '::text
> > + ELSE ''::text
> > + END ||
> > + CASE
> > + WHEN ((s.param3 & (2)::bigint) > 0) THEN 'end-of-recovery '::text
> > + ELSE ''::text
> > + END) ||
> > + CASE
> > + WHEN ((s.param3 & (4)::bigint) > 0) THEN 'immediate '::text
> > + ELSE ''::text
> > + END) ||
> > + CASE
> > + WHEN ((s.param3 & (8)::bigint) > 0) THEN 'force '::text
> > + ELSE ''::text
> > + END) ||
> > + CASE
> > + WHEN ((s.param3 & (16)::bigint) > 0) THEN 'flush-all '::text
> > + ELSE ''::text
> > + END) ||
> > + CASE
> > + WHEN ((s.param3 & (32)::bigint) > 0) THEN 'wait '::text
> > + ELSE ''::text
> > + END) ||
> > + CASE
> > + WHEN ((s.param3 & (128)::bigint) > 0) THEN 'wal '::text
> > + ELSE ''::text
> > + END) ||
> > + CASE
> > + WHEN ((s.param3 & (256)::bigint) > 0) THEN 'time '::text
> > + ELSE ''::text
> > + END) AS next_flags,
> > + ('0/0'::pg_lsn + (
> > + CASE
> > + WHEN (s.param4 < 0) THEN pow((2)::numeric, (64)::numeric)
> > + ELSE (0)::numeric
> > + END + (s.param4)::numeric)) AS start_lsn,
> > + to_timestamp(((946684800)::double precision + ((s.param5)::double precision / (1000000)::double precision))) AS start_time,
> > + CASE s.param6
> > + WHEN 1 THEN 'initializing'::text
> > + WHEN 2 THEN 'getting virtual transaction IDs'::text
> > + WHEN 3 THEN 'checkpointing replication slots'::text
> > + WHEN 4 THEN 'checkpointing logical replication snapshot files'::text
> > + WHEN 5 THEN 'checkpointing logical rewrite mapping files'::text
> > + WHEN 6 THEN 'checkpointing replication origin'::text
> > + WHEN 7 THEN 'checkpointing commit log pages'::text
> > + WHEN 8 THEN 'checkpointing commit time stamp pages'::text
> > + WHEN 9 THEN 'checkpointing subtransaction pages'::text
> > + WHEN 10 THEN 'checkpointing multixact pages'::text
> > + WHEN 11 THEN 'checkpointing predicate lock pages'::text
> > + WHEN 12 THEN 'checkpointing buffers'::text
> > + WHEN 13 THEN 'processing file sync requests'::text
> > + WHEN 14 THEN 'performing two phase checkpoint'::text
> > + WHEN 15 THEN 'performing post checkpoint cleanup'::text
> > + WHEN 16 THEN 'invalidating replication slots'::text
> > + WHEN 17 THEN 'recycling old WAL files'::text
> > + WHEN 18 THEN 'truncating subtransactions'::text
> > + WHEN 19 THEN 'finalizing'::text
> > + ELSE NULL::text
> > + END AS phase,
> > + s.param7 AS buffers_total,
> > + s.param8 AS buffers_processed,
> > + s.param9 AS buffers_written,
> > + s.param10 AS files_total,
> > + s.param11 AS files_synced
> > + FROM pg_stat_get_progress_info('CHECKPOINT'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20);
> > pg_stat_progress_cluster| SELECT s.pid,
> > s.datid,
> > d.datname,
>
> This view is depressingly complicated. Added up the view definitions for
> the already existing pg_stat_progress* views add up to a measurable part of
> the size of an empty database:
>
> postgres[1160866][1]=# SELECT sum(octet_length(ev_action)), SUM(pg_column_size(ev_action)) FROM pg_rewrite WHERE ev_class::regclass::text LIKE '%progress%';
> ┌───────┬───────┐
> │ sum │ sum │
> ├───────┼───────┤
> │ 97410 │ 19786 │
> └───────┴───────┘
> (1 row)
>
> and this view looks to be a good bit more complicated than the existing
> pg_stat_progress* views.
>
> Indeed:
> template1[1165473][1]=# SELECT ev_class::regclass, length(ev_action), pg_column_size(ev_action) FROM pg_rewrite WHERE ev_class::regclass::text LIKE '%progress%' ORDER BY length(ev_action) DESC;
> ┌───────────────────────────────┬────────┬────────────────┐
> │ ev_class │ length │ pg_column_size │
> ├───────────────────────────────┼────────┼────────────────┤
> │ pg_stat_progress_checkpoint │ 43290 │ 5409 │
> │ pg_stat_progress_create_index │ 23293 │ 4177 │
> │ pg_stat_progress_cluster │ 18390 │ 3704 │
> │ pg_stat_progress_analyze │ 16121 │ 3339 │
> │ pg_stat_progress_vacuum │ 16076 │ 3392 │
> │ pg_stat_progress_copy │ 15124 │ 3080 │
> │ pg_stat_progress_basebackup │ 8406 │ 2094 │
> └───────────────────────────────┴────────┴────────────────┘
> (7 rows)
>
> pg_rewrite without pg_stat_progress_checkpoint: 745472, with: 753664
>
>
> pg_rewrite is the second biggest relation in an empty database already...
>
> template1[1164827][1]=# SELECT relname, pg_total_relation_size(oid) FROM pg_class WHERE relkind = 'r' ORDER BY 2 DESC LIMIT 5;
> ┌────────────────┬────────────────────────┐
> │ relname │ pg_total_relation_size │
> ├────────────────┼────────────────────────┤
> │ pg_proc │ 1212416 │
> │ pg_rewrite │ 745472 │
> │ pg_attribute │ 704512 │
> │ pg_description │ 630784 │
> │ pg_collation │ 409600 │
> └────────────────┴────────────────────────┘
> (5 rows)
>
> Greetings,
>
> Andres Freund
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs)
@ 2022-06-13 13:56 Nitin Jadhav <[email protected]>
parent: Michael Paquier <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Nitin Jadhav @ 2022-06-13 13:56 UTC (permalink / raw)
To: Michael Paquier <[email protected]>; +Cc: Andres Freund <[email protected]>; Bharath Rupireddy <[email protected]>; Matthias van de Meent <[email protected]>; Ashutosh Sharma <[email protected]>; Julien Rouhaud <[email protected]>; Bruce Momjian <[email protected]>; Tom Lane <[email protected]>; Magnus Hagander <[email protected]>; PostgreSQL Hackers <[email protected]>
> > Have you measured the performance effects of this? On fast storage with large
> > shared_buffers I've seen these loops in profiles. It's probably fine, but it'd
> > be good to verify that.
>
> I am wondering if we could make the function inlined at some point.
> We could also play it safe and only update the counters every N loops
> instead.
The idea looks good but based on the performance numbers shared above,
it is not affecting the performance. So we can use the current
approach as it gives more accurate progress.
---
> > This view is depressingly complicated. Added up the view definitions for
> > the already existing pg_stat_progress* views add up to a measurable part of
> > the size of an empty database:
>
> Yeah. I think that what's proposed could be simplified, and we had
> better remove the fields that are not that useful. First, do we have
> any need for next_flags?
"next_flags" is removed in the v6 patch. Added a "new_requests" field
to get to know whether the current checkpoint is being throttled or
not. Please share your views on this.
---
> Second, is the start LSN really necessary
> for monitoring purposes?
IMO, start LSN is necessary to debug if the checkpoint is taking longer.
---
> Not all the information in the first
> parameter is useful, as well. For example "shutdown" will never be
> seen as it is not possible to use a session at this stage, no?
I understand that "shutdown" and "end-of-recovery" will never be seen
and I have removed it in the v6 patch.
---
> There
> is also no gain in having "immediate", "flush-all", "force" and "wait"
> (for this one if the checkpoint is requested the session doing the
> work knows this information already).
"immediate" is required to understand whether the current checkpoint
is throttled or not. I am not sure about other flags "flush-all",
"force" and "wait". I have just supported all the flags to match the
'checkpoint start' log message. Please share your views. If it is not
really required, I will remove it in the next patch.
---
> A last thing is that we may gain in visibility by having more
> attributes as an effect of splitting param2. On thing that would make
> sense is to track the reason why the checkpoint was triggered
> separately (aka wal and time). Should we use a text[] instead to list
> all the parameters instead? Using a space-separated list of items is
> not intuitive IMO, and callers of this routine will likely parse
> that.
If I understand the above comment correctly, you are saying to
introduce a new field, say "reason" ( possible values are either wal
or time) and the "flags" field will continue to represent the other
flags like "immediate", etc. The idea looks good here. We can
introduce new field "reason" and "flags" field can be renamed to
"throttled" (true/false) if we decide to not support other flags
"flush-all", "force" and "wait".
---
> + WHEN 3 THEN 'checkpointing replication slots'
> + WHEN 4 THEN 'checkpointing logical replication snapshot files'
> + WHEN 5 THEN 'checkpointing logical rewrite mapping files'
> + WHEN 6 THEN 'checkpointing replication origin'
> + WHEN 7 THEN 'checkpointing commit log pages'
> + WHEN 8 THEN 'checkpointing commit time stamp pages'
> There is a lot of "checkpointing" here. All those terms could be
> shorter without losing their meaning.
I will try to make it short in the next patch.
---
Please share your thoughts.
Thanks & Regards,
Nitin Jadhav
On Tue, Apr 5, 2022 at 3:15 PM Michael Paquier <[email protected]> wrote:
>
> On Fri, Mar 18, 2022 at 05:15:56PM -0700, Andres Freund wrote:
> > Have you measured the performance effects of this? On fast storage with large
> > shared_buffers I've seen these loops in profiles. It's probably fine, but it'd
> > be good to verify that.
>
> I am wondering if we could make the function inlined at some point.
> We could also play it safe and only update the counters every N loops
> instead.
>
> > This view is depressingly complicated. Added up the view definitions for
> > the already existing pg_stat_progress* views add up to a measurable part of
> > the size of an empty database:
>
> Yeah. I think that what's proposed could be simplified, and we had
> better remove the fields that are not that useful. First, do we have
> any need for next_flags? Second, is the start LSN really necessary
> for monitoring purposes? Not all the information in the first
> parameter is useful, as well. For example "shutdown" will never be
> seen as it is not possible to use a session at this stage, no? There
> is also no gain in having "immediate", "flush-all", "force" and "wait"
> (for this one if the checkpoint is requested the session doing the
> work knows this information already).
>
> A last thing is that we may gain in visibility by having more
> attributes as an effect of splitting param2. On thing that would make
> sense is to track the reason why the checkpoint was triggered
> separately (aka wal and time). Should we use a text[] instead to list
> all the parameters instead? Using a space-separated list of items is
> not intuitive IMO, and callers of this routine will likely parse
> that.
>
> Shouldn't we also track the number of files flushed in each sub-step?
> In some deployments you could have a large number of 2PC files and
> such. We may want more information on such matters.
>
> + WHEN 3 THEN 'checkpointing replication slots'
> + WHEN 4 THEN 'checkpointing logical replication snapshot files'
> + WHEN 5 THEN 'checkpointing logical rewrite mapping files'
> + WHEN 6 THEN 'checkpointing replication origin'
> + WHEN 7 THEN 'checkpointing commit log pages'
> + WHEN 8 THEN 'checkpointing commit time stamp pages'
> There is a lot of "checkpointing" here. All those terms could be
> shorter without losing their meaning.
>
> This patch still needs some work, so I am marking it as RwF for now.
> --
> Michael
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs)
@ 2022-07-07 00:04 Andres Freund <[email protected]>
parent: Nitin Jadhav <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Andres Freund @ 2022-07-07 00:04 UTC (permalink / raw)
To: Nitin Jadhav <[email protected]>; +Cc: Bharath Rupireddy <[email protected]>; Matthias van de Meent <[email protected]>; Ashutosh Sharma <[email protected]>; Julien Rouhaud <[email protected]>; Bruce Momjian <[email protected]>; Tom Lane <[email protected]>; Magnus Hagander <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi,
On 2022-06-13 19:08:35 +0530, Nitin Jadhav wrote:
> > Have you measured the performance effects of this? On fast storage with large
> > shared_buffers I've seen these loops in profiles. It's probably fine, but it'd
> > be good to verify that.
>
> To understand the performance effects of the above, I have taken the
> average of five checkpoints with the patch and without the patch in my
> environment. Here are the results.
> With patch: 269.65 s
> Without patch: 269.60 s
Those look like timed checkpoints - if the checkpoints are sleeping a
part of the time, you're not going to see any potential overhead.
To see whether this has an effect you'd have to make sure there's a
certain number of dirty buffers (e.g. by doing CREATE TABLE AS
some_query) and then do a manual checkpoint and time how long that
times.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs)
@ 2022-07-28 09:38 Nitin Jadhav <[email protected]>
parent: Andres Freund <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Nitin Jadhav @ 2022-07-28 09:38 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Bharath Rupireddy <[email protected]>; Matthias van de Meent <[email protected]>; Ashutosh Sharma <[email protected]>; Julien Rouhaud <[email protected]>; Bruce Momjian <[email protected]>; Tom Lane <[email protected]>; Magnus Hagander <[email protected]>; PostgreSQL Hackers <[email protected]>
> > To understand the performance effects of the above, I have taken the
> > average of five checkpoints with the patch and without the patch in my
> > environment. Here are the results.
> > With patch: 269.65 s
> > Without patch: 269.60 s
>
> Those look like timed checkpoints - if the checkpoints are sleeping a
> part of the time, you're not going to see any potential overhead.
Yes. The above data is collected from timed checkpoints.
create table t1(a int);
insert into t1 select * from generate_series(1,10000000);
I generated a lot of data by using the above queries which would in
turn trigger the checkpoint (wal).
---
> To see whether this has an effect you'd have to make sure there's a
> certain number of dirty buffers (e.g. by doing CREATE TABLE AS
> some_query) and then do a manual checkpoint and time how long that
> times.
For this case I have generated data by using below queries.
create table t1(a int);
insert into t1 select * from generate_series(1,8000000);
This does not trigger the checkpoint automatically. I have issued the
CHECKPOINT manually and measured the performance by considering an
average of 5 checkpoints. Here are the details.
With patch: 2.457 s
Without patch: 2.334 s
Please share your thoughts.
Thanks & Regards,
Nitin Jadhav
On Thu, Jul 7, 2022 at 5:34 AM Andres Freund <[email protected]> wrote:
>
> Hi,
>
> On 2022-06-13 19:08:35 +0530, Nitin Jadhav wrote:
> > > Have you measured the performance effects of this? On fast storage with large
> > > shared_buffers I've seen these loops in profiles. It's probably fine, but it'd
> > > be good to verify that.
> >
> > To understand the performance effects of the above, I have taken the
> > average of five checkpoints with the patch and without the patch in my
> > environment. Here are the results.
> > With patch: 269.65 s
> > Without patch: 269.60 s
>
> Those look like timed checkpoints - if the checkpoints are sleeping a
> part of the time, you're not going to see any potential overhead.
>
> To see whether this has an effect you'd have to make sure there's a
> certain number of dirty buffers (e.g. by doing CREATE TABLE AS
> some_query) and then do a manual checkpoint and time how long that
> times.
>
> Greetings,
>
> Andres Freund
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs)
@ 2022-11-04 08:25 Drouvot, Bertrand <[email protected]>
parent: Nitin Jadhav <[email protected]>
0 siblings, 4 replies; 199+ messages in thread
From: Drouvot, Bertrand @ 2022-11-04 08:25 UTC (permalink / raw)
To: Nitin Jadhav <[email protected]>; Andres Freund <[email protected]>; +Cc: Bharath Rupireddy <[email protected]>; Matthias van de Meent <[email protected]>; Ashutosh Sharma <[email protected]>; Julien Rouhaud <[email protected]>; Bruce Momjian <[email protected]>; Tom Lane <[email protected]>; Magnus Hagander <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi,
On 7/28/22 11:38 AM, Nitin Jadhav wrote:
>>> To understand the performance effects of the above, I have taken the
>>> average of five checkpoints with the patch and without the patch in my
>>> environment. Here are the results.
>>> With patch: 269.65 s
>>> Without patch: 269.60 s
>>
>> Those look like timed checkpoints - if the checkpoints are sleeping a
>> part of the time, you're not going to see any potential overhead.
>
> Yes. The above data is collected from timed checkpoints.
>
> create table t1(a int);
> insert into t1 select * from generate_series(1,10000000);
>
> I generated a lot of data by using the above queries which would in
> turn trigger the checkpoint (wal).
> ---
>
>> To see whether this has an effect you'd have to make sure there's a
>> certain number of dirty buffers (e.g. by doing CREATE TABLE AS
>> some_query) and then do a manual checkpoint and time how long that
>> times.
>
> For this case I have generated data by using below queries.
>
> create table t1(a int);
> insert into t1 select * from generate_series(1,8000000);
>
> This does not trigger the checkpoint automatically. I have issued the
> CHECKPOINT manually and measured the performance by considering an
> average of 5 checkpoints. Here are the details.
>
> With patch: 2.457 s
> Without patch: 2.334 s
>
> Please share your thoughts.
>
v6 was not applying anymore, due to a change in
doc/src/sgml/ref/checkpoint.sgml done by b9eb0ff09e (Rename
pg_checkpointer predefined role to pg_checkpoint).
Please find attached a rebase in v7.
While working on this rebase, I also noticed that "pg_checkpointer" is
still mentioned in some translation files:
"
$ git grep pg_checkpointer
src/backend/po/de.po:msgid "must be superuser or have privileges of
pg_checkpointer to do CHECKPOINT"
src/backend/po/ja.po:msgid "must be superuser or have privileges of
pg_checkpointer to do CHECKPOINT"
src/backend/po/ja.po:msgstr
"CHECKPOINTを実行するにはスーパーユーザーであるか、またはpg_checkpointerの権限を持つ必要があります"
src/backend/po/sv.po:msgid "must be superuser or have privileges of
pg_checkpointer to do CHECKPOINT"
"
I'm not familiar with how the translation files are handled (looks like
they have their own set of commits, see 3c0bcdbc66 for example) but
wanted to mention that "pg_checkpointer" is still mentioned (even if
that may be expected as the last commit related to translation files
(aka 3c0bcdbc66) is older than the one that renamed pg_checkpointer to
pg_checkpoint (aka b9eb0ff09e)).
That said, back to this patch: I did not look closely but noticed that
the buffers_total reported by pg_stat_progress_checkpoint:
postgres=# select type,flags,start_lsn,phase,buffers_total,new_requests
from pg_stat_progress_checkpoint;
type | flags | start_lsn | phase
| buffers_total | new_requests
------------+-----------------------+------------+-----------------------+---------------+--------------
checkpoint | immediate force wait | 1/E6C523A8 | checkpointing
buffers | 1024275 | false
(1 row)
is a little bit different from what is logged once completed:
2022-11-04 08:18:50.806 UTC [3488442] LOG: checkpoint complete: wrote
1024278 buffers (97.7%);
Regards,
--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
commit 5198f5010be0febd019b1888817e2d78b8e42b21
Author: bdrouvot <[email protected]>
Date: Thu Nov 3 12:59:10 2022 +0000
v7-0001-pg_stat_progress_checkpoint-view.patch
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index e5d622d514..ceb7d60ffa 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -414,6 +414,13 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser
See <xref linkend='copy-progress-reporting'/>.
</entry>
</row>
+
+ <row>
+ <entry><structname>pg_stat_progress_checkpoint</structname><indexterm><primary>pg_stat_progress_checkpoint</primary></indexterm></entry>
+ <entry>One row only, showing the progress of the checkpoint.
+ See <xref linkend='checkpoint-progress-reporting'/>.
+ </entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -5784,7 +5791,7 @@ FROM pg_stat_get_backend_idset() AS backendid;
which support progress reporting are <command>ANALYZE</command>,
<command>CLUSTER</command>,
<command>CREATE INDEX</command>, <command>VACUUM</command>,
- <command>COPY</command>,
+ <command>COPY</command>, <command>CHECKPOINT</command>
and <xref linkend="protocol-replication-base-backup"/> (i.e., replication
command that <xref linkend="app-pgbasebackup"/> issues to take
a base backup).
@@ -7072,6 +7079,402 @@ FROM pg_stat_get_backend_idset() AS backendid;
</table>
</sect2>
+ <sect2 id="checkpoint-progress-reporting">
+ <title>Checkpoint Progress Reporting</title>
+
+ <indexterm>
+ <primary>pg_stat_progress_checkpoint</primary>
+ </indexterm>
+
+ <para>
+ Whenever the checkpoint operation is running, the
+ <structname>pg_stat_progress_checkpoint</structname> view will contain a
+ single row indicating the progress of the checkpoint. The tables below
+ describe the information that will be reported and provide information about
+ how to interpret it.
+ </para>
+
+ <table id="pg-stat-progress-checkpoint-view" xreflabel="pg_stat_progress_checkpoint">
+ <title><structname>pg_stat_progress_checkpoint</structname> View</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pid</structfield> <type>integer</type>
+ </para>
+ <para>
+ Process ID of the checkpointer process.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>type</structfield> <type>text</type>
+ </para>
+ <para>
+ Type of the checkpoint. See <xref linkend="checkpoint-types"/>.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>flags</structfield> <type>text</type>
+ </para>
+ <para>
+ Flags of the checkpoint. See <xref linkend="checkpoint-flags"/>.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>start_lsn</structfield> <type>text</type>
+ </para>
+ <para>
+ The checkpoint start location.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>start_time</structfield> <type>timestamp with time zone</type>
+ </para>
+ <para>
+ Start time of the checkpoint.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>phase</structfield> <type>text</type>
+ </para>
+ <para>
+ Current processing phase. See <xref linkend="checkpoint-phases"/>.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>buffers_total</structfield> <type>bigint</type>
+ </para>
+ <para>
+ Total number of buffers to be written. This is estimated and reported
+ as of the beginning of buffer write operation.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>buffers_processed</structfield> <type>bigint</type>
+ </para>
+ <para>
+ Number of buffers processed. This counter increases when the targeted
+ buffer is processed. This number will eventually become equal to
+ <literal>buffers_total</literal> when the checkpoint is
+ complete.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>buffers_written</structfield> <type>bigint</type>
+ </para>
+ <para>
+ Number of buffers written. This counter only advances when the targeted
+ buffers is written. Note that some of the buffers are processed but may
+ not required to be written. So this count will always be less than or
+ equal to <literal>buffers_total</literal>.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>files_total</structfield> <type>bigint</type>
+ </para>
+ <para>
+ Total number of files to be synced. This is estimated and reported as of
+ the beginning of sync operation.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>files_synced</structfield> <type>bigint</type>
+ </para>
+ <para>
+ Number of files synced. This counter advances when the targeted file is
+ synced. This number will eventually become equal to
+ <literal>files_total</literal> when the checkpoint is complete.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>new_requests</structfield> <type>text</type>
+ </para>
+ <para>
+ True if any of the backend is requested for a checkpoint while the
+ current checkpoint is in progress, False otherwise.
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <table id="checkpoint-types">
+ <title>Checkpoint Types</title>
+ <tgroup cols="2">
+ <colspec colname="col1" colwidth="1*"/>
+ <colspec colname="col2" colwidth="2*"/>
+ <thead>
+ <row>
+ <entry>Types</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>checkpoint</literal></entry>
+ <entry>
+ The current operation is checkpoint.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>restartpoint</literal></entry>
+ <entry>
+ The current operation is restartpoint.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <table id="checkpoint-flags">
+ <title>Checkpoint Flags</title>
+ <tgroup cols="2">
+ <colspec colname="col1" colwidth="1*"/>
+ <colspec colname="col2" colwidth="2*"/>
+ <thead>
+ <row>
+ <entry>Flags</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>shutdown</literal></entry>
+ <entry>
+ The checkpoint is for shutdown.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>end-of-recovery</literal></entry>
+ <entry>
+ The checkpoint is for end-of-recovery.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>immediate</literal></entry>
+ <entry>
+ The checkpoint happens without delays.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>force</literal></entry>
+ <entry>
+ The checkpoint is started because some operation (for which the
+ checkpoint is necessary) forced a checkpoint.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>flush all</literal></entry>
+ <entry>
+ The checkpoint flushes all pages, including those belonging to unlogged
+ tables.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>wait</literal></entry>
+ <entry>
+ The operations which requested the checkpoint waits for completion
+ before returning.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>requested</literal></entry>
+ <entry>
+ The checkpoint request has been made.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>wal</literal></entry>
+ <entry>
+ The checkpoint is started because <literal>max_wal_size</literal> is
+ reached.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>time</literal></entry>
+ <entry>
+ The checkpoint is started because <literal>checkpoint_timeout</literal>
+ expired.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <table id="checkpoint-phases">
+ <title>Checkpoint Phases</title>
+ <tgroup cols="2">
+ <colspec colname="col1" colwidth="1*"/>
+ <colspec colname="col2" colwidth="2*"/>
+ <thead>
+ <row>
+ <entry>Phase</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>initializing</literal></entry>
+ <entry>
+ The checkpointer process is preparing to begin the checkpoint operation.
+ This phase is expected to be very brief.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>getting virtual transaction IDs</literal></entry>
+ <entry>
+ The checkpointer process is getting the virtual transaction IDs that
+ are delaying the checkpoint.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>checkpointing replication slots</literal></entry>
+ <entry>
+ The checkpointer process is currently flushing all the replication slots
+ to disk.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>checkpointing logical replication snapshot files</literal></entry>
+ <entry>
+ The checkpointer process is currently removing all the serialized
+ snapshot files that are not required anymore.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>checkpointing logical rewrite mapping files</literal></entry>
+ <entry>
+ The checkpointer process is currently removing unwanted or flushing
+ required logical rewrite mapping files.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>checkpointing replication origin</literal></entry>
+ <entry>
+ The checkpointer process is currently performing a checkpoint of each
+ replication origin's progress with respect to the replayed remote LSN.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>checkpointing commit log pages</literal></entry>
+ <entry>
+ The checkpointer process is currently writing commit log pages to disk.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>checkpointing commit time stamp pages</literal></entry>
+ <entry>
+ The checkpointer process is currently writing commit time stamp pages to
+ disk.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>checkpointing subtransaction pages</literal></entry>
+ <entry>
+ The checkpointer process is currently writing subtransaction pages to disk.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>checkpointing multixact pages</literal></entry>
+ <entry>
+ The checkpointer process is currently writing multixact pages to disk.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>checkpointing predicate lock pages</literal></entry>
+ <entry>
+ The checkpointer process is currently writing predicate lock pages to disk.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>checkpointing buffers</literal></entry>
+ <entry>
+ The checkpointer process is currently writing buffers to disk.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>processing file sync requests</literal></entry>
+ <entry>
+ The checkpointer process is currently processing file sync requests.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>performing two phase checkpoint</literal></entry>
+ <entry>
+ The checkpointer process is currently performing two phase checkpoint.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>performing post checkpoint cleanup</literal></entry>
+ <entry>
+ The checkpointer process is currently performing post checkpoint cleanup.
+ It removes any lingering files that can be safely removed.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>invalidating replication slots</literal></entry>
+ <entry>
+ The checkpointer process is currently invalidating replication slots.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>recycling old WAL files</literal></entry>
+ <entry>
+ The checkpointer process is currently recycling old WAL files.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>truncating subtransactions</literal></entry>
+ <entry>
+ The checkpointer process is currently removing the subtransaction
+ segments.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>finalizing</literal></entry>
+ <entry>
+ The checkpointer process is finalizing the checkpoint operation.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ </sect2>
+
</sect1>
<sect1 id="dynamic-trace">
diff --git a/doc/src/sgml/ref/checkpoint.sgml b/doc/src/sgml/ref/checkpoint.sgml
index 28a1d717b8..906883336d 100644
--- a/doc/src/sgml/ref/checkpoint.sgml
+++ b/doc/src/sgml/ref/checkpoint.sgml
@@ -55,6 +55,14 @@ CHECKPOINT
Only superusers or users with the privileges of
the <link linkend="predefined-roles-table"><literal>pg_checkpoint</literal></link>
role can call <command>CHECKPOINT</command>.
+
+ <para>
+ The checkpointer process running the checkpoint will report its progress
+ in the <structname>pg_stat_progress_checkpoint</structname> view except for
+ the shutdown and end-of-recovery cases. See
+ <xref linkend="checkpoint-progress-reporting"/> for details.
+ </para>
+
</para>
</refsect1>
diff --git a/doc/src/sgml/wal.sgml b/doc/src/sgml/wal.sgml
index 6a38b53744..733a8d2837 100644
--- a/doc/src/sgml/wal.sgml
+++ b/doc/src/sgml/wal.sgml
@@ -531,7 +531,11 @@
adjust the <xref linkend="guc-archive-timeout"/> parameter rather than the
checkpoint parameters.)
It is also possible to force a checkpoint by using the SQL
- command <command>CHECKPOINT</command>.
+ command <command>CHECKPOINT</command>. The checkpointer process running the
+ checkpoint will report its progress in the
+ <structname>pg_stat_progress_checkpoint</structname> view except for the
+ shutdown and end-of-recovery cases. See
+ <xref linkend="checkpoint-progress-reporting"/> for details.
</para>
<para>
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index be54c23187..f2cebe0973 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -67,6 +67,7 @@
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
#include "catalog/pg_database.h"
+#include "commands/progress.h"
#include "common/controldata_utils.h"
#include "common/file_utils.h"
#include "executor/instrument.h"
@@ -698,6 +699,8 @@ static void WALInsertLockAcquireExclusive(void);
static void WALInsertLockRelease(void);
static void WALInsertLockUpdateInsertingAt(XLogRecPtr insertingAt);
+static void checkpoint_progress_start(int flags, int type);
+
/*
* Insert an XLOG record represented by an already-constructed chain of data
* chunks. This is a low-level routine; to construct the WAL record header
@@ -6622,6 +6625,9 @@ CreateCheckPoint(int flags)
XLogCtl->RedoRecPtr = checkPoint.redo;
SpinLockRelease(&XLogCtl->info_lck);
+ /* Prepare to report progress of the checkpoint. */
+ checkpoint_progress_start(flags, PROGRESS_CHECKPOINT_TYPE_CHECKPOINT);
+
/*
* If enabled, log checkpoint start. We postpone this until now so as not
* to log anything if we decided to skip the checkpoint.
@@ -6704,6 +6710,8 @@ CreateCheckPoint(int flags)
* clog and we will correctly flush the update below. So we cannot miss
* any xacts we need to wait for.
*/
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_GET_VIRTUAL_TRANSACTION_IDS);
vxids = GetVirtualXIDsDelayingChkpt(&nvxids, DELAY_CHKPT_START);
if (nvxids > 0)
{
@@ -6819,6 +6827,8 @@ CreateCheckPoint(int flags)
/*
* Let smgr do post-checkpoint cleanup (eg, deleting old files).
*/
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_POST_CHECKPOINT_CLEANUP);
SyncPostCheckpoint();
/*
@@ -6834,6 +6844,9 @@ CreateCheckPoint(int flags)
*/
XLByteToSeg(RedoRecPtr, _logSegNo, wal_segment_size);
KeepLogSeg(recptr, &_logSegNo);
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_INVALIDATE_REPLI_SLOTS);
+
if (InvalidateObsoleteReplicationSlots(_logSegNo))
{
/*
@@ -6844,6 +6857,8 @@ CreateCheckPoint(int flags)
KeepLogSeg(recptr, &_logSegNo);
}
_logSegNo--;
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_RECYCLE_OLD_XLOG);
RemoveOldXlogFiles(_logSegNo, RedoRecPtr, recptr,
checkPoint.ThisTimeLineID);
@@ -6862,11 +6877,21 @@ CreateCheckPoint(int flags)
* StartupSUBTRANS hasn't been called yet.
*/
if (!RecoveryInProgress())
+ {
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_TRUNCATE_SUBTRANS);
TruncateSUBTRANS(GetOldestTransactionIdConsideredRunning());
+ }
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_FINALIZE);
/* Real work is done; log and update stats. */
LogCheckpointEnd(false);
+ /* Stop reporting progress of the checkpoint. */
+ pgstat_progress_end_command();
+
/* Reset the process title */
update_checkpoint_display(flags, false, true);
@@ -7023,29 +7048,63 @@ static void
CheckPointGuts(XLogRecPtr checkPointRedo, int flags)
{
CheckPointRelationMap();
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_REPLI_SLOTS);
CheckPointReplicationSlots();
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_SNAPSHOTS);
CheckPointSnapBuild();
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_LOGICAL_REWRITE_MAPPINGS);
CheckPointLogicalRewriteHeap();
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_REPLI_ORIGIN);
CheckPointReplicationOrigin();
/* Write out all dirty data in SLRUs and the main buffer pool */
TRACE_POSTGRESQL_BUFFER_CHECKPOINT_START(flags);
CheckpointStats.ckpt_write_t = GetCurrentTimestamp();
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_CLOG_PAGES);
CheckPointCLOG();
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_COMMITTS_PAGES);
CheckPointCommitTs();
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_SUBTRANS_PAGES);
CheckPointSUBTRANS();
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_MULTIXACT_PAGES);
CheckPointMultiXact();
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_PREDICATE_LOCK_PAGES);
CheckPointPredicate();
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_BUFFERS);
CheckPointBuffers(flags);
/* Perform all queued up fsyncs */
TRACE_POSTGRESQL_BUFFER_CHECKPOINT_SYNC_START();
CheckpointStats.ckpt_sync_t = GetCurrentTimestamp();
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_SYNC_FILES);
ProcessSyncRequests();
CheckpointStats.ckpt_sync_end_t = GetCurrentTimestamp();
TRACE_POSTGRESQL_BUFFER_CHECKPOINT_DONE();
/* We deliberately delay 2PC checkpointing as long as possible */
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_TWO_PHASE);
CheckPointTwoPhase(checkPointRedo);
}
@@ -7195,6 +7254,9 @@ CreateRestartPoint(int flags)
MemSet(&CheckpointStats, 0, sizeof(CheckpointStats));
CheckpointStats.ckpt_start_t = GetCurrentTimestamp();
+ /* Prepare to report progress of the restartpoint. */
+ checkpoint_progress_start(flags, PROGRESS_CHECKPOINT_TYPE_RESTARTPOINT);
+
if (log_checkpoints)
LogCheckpointStart(flags, true);
@@ -7278,6 +7340,9 @@ CreateRestartPoint(int flags)
replayPtr = GetXLogReplayRecPtr(&replayTLI);
endptr = (receivePtr < replayPtr) ? replayPtr : receivePtr;
KeepLogSeg(endptr, &_logSegNo);
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_INVALIDATE_REPLI_SLOTS);
+
if (InvalidateObsoleteReplicationSlots(_logSegNo))
{
/*
@@ -7304,6 +7369,8 @@ CreateRestartPoint(int flags)
if (!RecoveryInProgress())
replayTLI = XLogCtl->InsertTimeLineID;
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_RECYCLE_OLD_XLOG);
RemoveOldXlogFiles(_logSegNo, RedoRecPtr, endptr, replayTLI);
/*
@@ -7320,11 +7387,20 @@ CreateRestartPoint(int flags)
* this because StartupSUBTRANS hasn't been called yet.
*/
if (EnableHotStandby)
+ {
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_TRUNCATE_SUBTRANS);
TruncateSUBTRANS(GetOldestTransactionIdConsideredRunning());
+ }
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_FINALIZE);
/* Real work is done; log and update stats. */
LogCheckpointEnd(true);
+ /* Stop reporting progress of the restartpoint. */
+ pgstat_progress_end_command();
+
/* Reset the process title */
update_checkpoint_display(flags, true, true);
@@ -8958,3 +9034,29 @@ SetWalWriterSleeping(bool sleeping)
XLogCtl->WalWriterSleeping = sleeping;
SpinLockRelease(&XLogCtl->info_lck);
}
+
+/*
+ * Start reporting progress of the checkpoint.
+ */
+static void
+checkpoint_progress_start(int flags, int type)
+{
+ const int index[] = {
+ PROGRESS_CHECKPOINT_TYPE,
+ PROGRESS_CHECKPOINT_FLAGS,
+ PROGRESS_CHECKPOINT_LSN,
+ PROGRESS_CHECKPOINT_START_TIMESTAMP,
+ PROGRESS_CHECKPOINT_PHASE
+ };
+ int64 val[5];
+
+ pgstat_progress_start_command(PROGRESS_COMMAND_CHECKPOINT, InvalidOid);
+
+ val[0] = type;
+ val[1] = flags;
+ val[2] = RedoRecPtr;
+ val[3] = CheckpointStats.ckpt_start_t;
+ val[4] = PROGRESS_CHECKPOINT_PHASE_INIT;
+
+ pgstat_progress_update_multi_param(5, index, val);
+}
\ No newline at end of file
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2d8104b090..384ca35833 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1267,6 +1267,57 @@ CREATE VIEW pg_stat_progress_copy AS
FROM pg_stat_get_progress_info('COPY') AS S
LEFT JOIN pg_database D ON S.datid = D.oid;
+CREATE VIEW pg_stat_progress_checkpoint AS
+ SELECT
+ S.pid AS pid,
+ CASE S.param1 WHEN 1 THEN 'checkpoint'
+ WHEN 2 THEN 'restartpoint'
+ END AS type,
+ ( CASE WHEN (S.param2 & 4) > 0 THEN 'immediate ' ELSE '' END ||
+ CASE WHEN (S.param2 & 8) > 0 THEN 'force ' ELSE '' END ||
+ CASE WHEN (S.param2 & 16) > 0 THEN 'flush-all ' ELSE '' END ||
+ CASE WHEN (S.param2 & 32) > 0 THEN 'wait ' ELSE '' END ||
+ CASE WHEN (S.param2 & 128) > 0 THEN 'wal ' ELSE '' END ||
+ CASE WHEN (S.param2 & 256) > 0 THEN 'time ' ELSE '' END
+ ) AS flags,
+ ( '0/0'::pg_lsn +
+ ((CASE
+ WHEN S.param3 < 0 THEN pow(2::numeric, 64::numeric)::numeric
+ ELSE 0::numeric
+ END) +
+ S.param3::numeric)
+ ) AS start_lsn,
+ to_timestamp(946684800 + (S.param4::float8 / 1000000)) AS start_time,
+ CASE S.param5 WHEN 1 THEN 'initializing'
+ WHEN 2 THEN 'getting virtual transaction IDs'
+ WHEN 3 THEN 'checkpointing replication slots'
+ WHEN 4 THEN 'checkpointing logical replication snapshot files'
+ WHEN 5 THEN 'checkpointing logical rewrite mapping files'
+ WHEN 6 THEN 'checkpointing replication origin'
+ WHEN 7 THEN 'checkpointing commit log pages'
+ WHEN 8 THEN 'checkpointing commit time stamp pages'
+ WHEN 9 THEN 'checkpointing subtransaction pages'
+ WHEN 10 THEN 'checkpointing multixact pages'
+ WHEN 11 THEN 'checkpointing predicate lock pages'
+ WHEN 12 THEN 'checkpointing buffers'
+ WHEN 13 THEN 'processing file sync requests'
+ WHEN 14 THEN 'performing two phase checkpoint'
+ WHEN 15 THEN 'performing post checkpoint cleanup'
+ WHEN 16 THEN 'invalidating replication slots'
+ WHEN 17 THEN 'recycling old WAL files'
+ WHEN 18 THEN 'truncating subtransactions'
+ WHEN 19 THEN 'finalizing'
+ END AS phase,
+ S.param6 AS buffers_total,
+ S.param7 AS buffers_processed,
+ S.param8 AS buffers_written,
+ S.param9 AS files_total,
+ S.param10 AS files_synced,
+ CASE S.param11 WHEN 0 THEN 'false'
+ WHEN 1 THEN 'true'
+ END AS new_requests
+ FROM pg_stat_get_progress_info('CHECKPOINT') AS S;
+
CREATE VIEW pg_user_mappings AS
SELECT
U.oid AS umid,
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 5fc076fc14..21bf75b058 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -39,6 +39,7 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
#include "access/xlogrecovery.h"
+#include "commands/progress.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
@@ -163,7 +164,7 @@ static pg_time_t last_xlog_switch_time;
static void HandleCheckpointerInterrupts(void);
static void CheckArchiveTimeout(void);
static bool IsCheckpointOnSchedule(double progress);
-static bool ImmediateCheckpointRequested(void);
+static bool ImmediateCheckpointRequested(int flags);
static bool CompactCheckpointerRequestQueue(void);
static void UpdateSharedMemoryConfig(void);
@@ -667,16 +668,24 @@ CheckArchiveTimeout(void)
* there is one pending behind it.)
*/
static bool
-ImmediateCheckpointRequested(void)
+ImmediateCheckpointRequested(int flags)
{
volatile CheckpointerShmemStruct *cps = CheckpointerShmem;
+ if (cps->ckpt_flags & CHECKPOINT_REQUESTED)
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_NEW_REQUESTS, true);
+
/*
* We don't need to acquire the ckpt_lck in this case because we're only
* looking at a single flag bit.
*/
if (cps->ckpt_flags & CHECKPOINT_IMMEDIATE)
+ {
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_FLAGS,
+ (flags | CHECKPOINT_IMMEDIATE));
return true;
+ }
+
return false;
}
@@ -708,7 +717,7 @@ CheckpointWriteDelay(int flags, double progress)
*/
if (!(flags & CHECKPOINT_IMMEDIATE) &&
!ShutdownRequestPending &&
- !ImmediateCheckpointRequested() &&
+ !ImmediateCheckpointRequested(flags) &&
IsCheckpointOnSchedule(progress))
{
if (ConfigReloadPending)
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 73d30bf619..6d69255667 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -39,6 +39,7 @@
#include "catalog/catalog.h"
#include "catalog/storage.h"
#include "catalog/storage_xlog.h"
+#include "commands/progress.h"
#include "executor/instrument.h"
#include "lib/binaryheap.h"
#include "miscadmin.h"
@@ -2026,6 +2027,8 @@ BufferSync(int flags)
WritebackContextInit(&wb_context, &checkpoint_flush_after);
TRACE_POSTGRESQL_BUFFER_SYNC_START(NBuffers, num_to_scan);
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_BUFFERS_TOTAL,
+ num_to_scan);
/*
* Sort buffers that need to be written to reduce the likelihood of random
@@ -2143,6 +2146,8 @@ BufferSync(int flags)
bufHdr = GetBufferDescriptor(buf_id);
num_processed++;
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_BUFFERS_PROCESSED,
+ num_processed);
/*
* We don't need to acquire the lock here, because we're only looking
@@ -2163,6 +2168,8 @@ BufferSync(int flags)
TRACE_POSTGRESQL_BUFFER_SYNC_WRITTEN(buf_id);
PendingCheckpointerStats.buf_written_checkpoints++;
num_written++;
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_BUFFERS_WRITTEN,
+ num_written);
}
}
diff --git a/src/backend/storage/sync/sync.c b/src/backend/storage/sync/sync.c
index 9d6a9e9109..aa25215910 100644
--- a/src/backend/storage/sync/sync.c
+++ b/src/backend/storage/sync/sync.c
@@ -23,6 +23,7 @@
#include "access/multixact.h"
#include "access/xlog.h"
#include "access/xlogutils.h"
+#include "commands/progress.h"
#include "commands/tablespace.h"
#include "miscadmin.h"
#include "pgstat.h"
@@ -368,6 +369,9 @@ ProcessSyncRequests(void)
/* Now scan the hashtable for fsync requests to process */
absorb_counter = FSYNCS_PER_ABSORB;
hash_seq_init(&hstat, pendingOps);
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_FILES_TOTAL,
+ hash_get_num_entries(pendingOps));
+
while ((entry = (PendingFsyncEntry *) hash_seq_search(&hstat)) != NULL)
{
int failures;
@@ -431,6 +435,8 @@ ProcessSyncRequests(void)
longest = elapsed;
total_elapsed += elapsed;
processed++;
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_FILES_SYNCED,
+ processed);
if (log_checkpoints)
elog(DEBUG1, "checkpoint sync: number=%d file=%s time=%.3f ms",
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 96bffc0f2a..8281d961bf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -497,6 +497,8 @@ pg_stat_get_progress_info(PG_FUNCTION_ARGS)
cmdtype = PROGRESS_COMMAND_BASEBACKUP;
else if (pg_strcasecmp(cmd, "COPY") == 0)
cmdtype = PROGRESS_COMMAND_COPY;
+ else if (pg_strcasecmp(cmd, "CHECKPOINT") == 0)
+ cmdtype = PROGRESS_COMMAND_CHECKPOINT;
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/include/commands/progress.h b/src/include/commands/progress.h
index a28938caf4..33a64d2f0b 100644
--- a/src/include/commands/progress.h
+++ b/src/include/commands/progress.h
@@ -151,4 +151,42 @@
#define PROGRESS_COPY_TYPE_PIPE 3
#define PROGRESS_COPY_TYPE_CALLBACK 4
+/* Progress parameters for checkpoint */
+#define PROGRESS_CHECKPOINT_TYPE 0
+#define PROGRESS_CHECKPOINT_FLAGS 1
+#define PROGRESS_CHECKPOINT_LSN 2
+#define PROGRESS_CHECKPOINT_START_TIMESTAMP 3
+#define PROGRESS_CHECKPOINT_PHASE 4
+#define PROGRESS_CHECKPOINT_BUFFERS_TOTAL 5
+#define PROGRESS_CHECKPOINT_BUFFERS_PROCESSED 6
+#define PROGRESS_CHECKPOINT_BUFFERS_WRITTEN 7
+#define PROGRESS_CHECKPOINT_FILES_TOTAL 8
+#define PROGRESS_CHECKPOINT_FILES_SYNCED 9
+#define PROGRESS_CHECKPOINT_NEW_REQUESTS 10
+
+/* Types of checkpoint (as advertised via PROGRESS_CHECKPOINT_TYPE) */
+#define PROGRESS_CHECKPOINT_TYPE_CHECKPOINT 1
+#define PROGRESS_CHECKPOINT_TYPE_RESTARTPOINT 2
+
+/* Phases of checkpoint (as advertised via PROGRESS_CHECKPOINT_PHASE) */
+#define PROGRESS_CHECKPOINT_PHASE_INIT 1
+#define PROGRESS_CHECKPOINT_PHASE_GET_VIRTUAL_TRANSACTION_IDS 2
+#define PROGRESS_CHECKPOINT_PHASE_REPLI_SLOTS 3
+#define PROGRESS_CHECKPOINT_PHASE_SNAPSHOTS 4
+#define PROGRESS_CHECKPOINT_PHASE_LOGICAL_REWRITE_MAPPINGS 5
+#define PROGRESS_CHECKPOINT_PHASE_REPLI_ORIGIN 6
+#define PROGRESS_CHECKPOINT_PHASE_CLOG_PAGES 7
+#define PROGRESS_CHECKPOINT_PHASE_COMMITTS_PAGES 8
+#define PROGRESS_CHECKPOINT_PHASE_SUBTRANS_PAGES 9
+#define PROGRESS_CHECKPOINT_PHASE_MULTIXACT_PAGES 10
+#define PROGRESS_CHECKPOINT_PHASE_PREDICATE_LOCK_PAGES 11
+#define PROGRESS_CHECKPOINT_PHASE_BUFFERS 12
+#define PROGRESS_CHECKPOINT_PHASE_SYNC_FILES 13
+#define PROGRESS_CHECKPOINT_PHASE_TWO_PHASE 14
+#define PROGRESS_CHECKPOINT_PHASE_POST_CHECKPOINT_CLEANUP 15
+#define PROGRESS_CHECKPOINT_PHASE_INVALIDATE_REPLI_SLOTS 16
+#define PROGRESS_CHECKPOINT_PHASE_RECYCLE_OLD_XLOG 17
+#define PROGRESS_CHECKPOINT_PHASE_TRUNCATE_SUBTRANS 18
+#define PROGRESS_CHECKPOINT_PHASE_FINALIZE 19
+
#endif
diff --git a/src/include/utils/backend_progress.h b/src/include/utils/backend_progress.h
index 47bf8029b0..02d51fb948 100644
--- a/src/include/utils/backend_progress.h
+++ b/src/include/utils/backend_progress.h
@@ -27,7 +27,8 @@ typedef enum ProgressCommandType
PROGRESS_COMMAND_CLUSTER,
PROGRESS_COMMAND_CREATE_INDEX,
PROGRESS_COMMAND_BASEBACKUP,
- PROGRESS_COMMAND_COPY
+ PROGRESS_COMMAND_COPY,
+ PROGRESS_COMMAND_CHECKPOINT
} ProgressCommandType;
#define PGSTAT_NUM_PROGRESS_PARAM 20
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 624d0e5aae..eab68d7f14 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1913,6 +1913,76 @@ pg_stat_progress_basebackup| SELECT s.pid,
s.param4 AS tablespaces_total,
s.param5 AS tablespaces_streamed
FROM pg_stat_get_progress_info('BASEBACKUP'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20);
+pg_stat_progress_checkpoint| SELECT s.pid,
+ CASE s.param1
+ WHEN 1 THEN 'checkpoint'::text
+ WHEN 2 THEN 'restartpoint'::text
+ ELSE NULL::text
+ END AS type,
+ (((((
+ CASE
+ WHEN ((s.param2 & (4)::bigint) > 0) THEN 'immediate '::text
+ ELSE ''::text
+ END ||
+ CASE
+ WHEN ((s.param2 & (8)::bigint) > 0) THEN 'force '::text
+ ELSE ''::text
+ END) ||
+ CASE
+ WHEN ((s.param2 & (16)::bigint) > 0) THEN 'flush-all '::text
+ ELSE ''::text
+ END) ||
+ CASE
+ WHEN ((s.param2 & (32)::bigint) > 0) THEN 'wait '::text
+ ELSE ''::text
+ END) ||
+ CASE
+ WHEN ((s.param2 & (128)::bigint) > 0) THEN 'wal '::text
+ ELSE ''::text
+ END) ||
+ CASE
+ WHEN ((s.param2 & (256)::bigint) > 0) THEN 'time '::text
+ ELSE ''::text
+ END) AS flags,
+ ('0/0'::pg_lsn + (
+ CASE
+ WHEN (s.param3 < 0) THEN pow((2)::numeric, (64)::numeric)
+ ELSE (0)::numeric
+ END + (s.param3)::numeric)) AS start_lsn,
+ to_timestamp(((946684800)::double precision + ((s.param4)::double precision / (1000000)::double precision))) AS start_time,
+ CASE s.param5
+ WHEN 1 THEN 'initializing'::text
+ WHEN 2 THEN 'getting virtual transaction IDs'::text
+ WHEN 3 THEN 'checkpointing replication slots'::text
+ WHEN 4 THEN 'checkpointing logical replication snapshot files'::text
+ WHEN 5 THEN 'checkpointing logical rewrite mapping files'::text
+ WHEN 6 THEN 'checkpointing replication origin'::text
+ WHEN 7 THEN 'checkpointing commit log pages'::text
+ WHEN 8 THEN 'checkpointing commit time stamp pages'::text
+ WHEN 9 THEN 'checkpointing subtransaction pages'::text
+ WHEN 10 THEN 'checkpointing multixact pages'::text
+ WHEN 11 THEN 'checkpointing predicate lock pages'::text
+ WHEN 12 THEN 'checkpointing buffers'::text
+ WHEN 13 THEN 'processing file sync requests'::text
+ WHEN 14 THEN 'performing two phase checkpoint'::text
+ WHEN 15 THEN 'performing post checkpoint cleanup'::text
+ WHEN 16 THEN 'invalidating replication slots'::text
+ WHEN 17 THEN 'recycling old WAL files'::text
+ WHEN 18 THEN 'truncating subtransactions'::text
+ WHEN 19 THEN 'finalizing'::text
+ ELSE NULL::text
+ END AS phase,
+ s.param6 AS buffers_total,
+ s.param7 AS buffers_processed,
+ s.param8 AS buffers_written,
+ s.param9 AS files_total,
+ s.param10 AS files_synced,
+ CASE s.param11
+ WHEN 0 THEN 'false'::text
+ WHEN 1 THEN 'true'::text
+ ELSE NULL::text
+ END AS new_requests
+ FROM pg_stat_get_progress_info('CHECKPOINT'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20);
pg_stat_progress_cluster| SELECT s.pid,
s.datid,
d.datname,
Attachments:
[text/plain] v7-0001-pg_stat_progress_checkpoint-view.patch (37.3K, ../../[email protected]/2-v7-0001-pg_stat_progress_checkpoint-view.patch)
download | inline diff:
commit 5198f5010be0febd019b1888817e2d78b8e42b21
Author: bdrouvot <[email protected]>
Date: Thu Nov 3 12:59:10 2022 +0000
v7-0001-pg_stat_progress_checkpoint-view.patch
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index e5d622d514..ceb7d60ffa 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -414,6 +414,13 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser
See <xref linkend='copy-progress-reporting'/>.
</entry>
</row>
+
+ <row>
+ <entry><structname>pg_stat_progress_checkpoint</structname><indexterm><primary>pg_stat_progress_checkpoint</primary></indexterm></entry>
+ <entry>One row only, showing the progress of the checkpoint.
+ See <xref linkend='checkpoint-progress-reporting'/>.
+ </entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -5784,7 +5791,7 @@ FROM pg_stat_get_backend_idset() AS backendid;
which support progress reporting are <command>ANALYZE</command>,
<command>CLUSTER</command>,
<command>CREATE INDEX</command>, <command>VACUUM</command>,
- <command>COPY</command>,
+ <command>COPY</command>, <command>CHECKPOINT</command>
and <xref linkend="protocol-replication-base-backup"/> (i.e., replication
command that <xref linkend="app-pgbasebackup"/> issues to take
a base backup).
@@ -7072,6 +7079,402 @@ FROM pg_stat_get_backend_idset() AS backendid;
</table>
</sect2>
+ <sect2 id="checkpoint-progress-reporting">
+ <title>Checkpoint Progress Reporting</title>
+
+ <indexterm>
+ <primary>pg_stat_progress_checkpoint</primary>
+ </indexterm>
+
+ <para>
+ Whenever the checkpoint operation is running, the
+ <structname>pg_stat_progress_checkpoint</structname> view will contain a
+ single row indicating the progress of the checkpoint. The tables below
+ describe the information that will be reported and provide information about
+ how to interpret it.
+ </para>
+
+ <table id="pg-stat-progress-checkpoint-view" xreflabel="pg_stat_progress_checkpoint">
+ <title><structname>pg_stat_progress_checkpoint</structname> View</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pid</structfield> <type>integer</type>
+ </para>
+ <para>
+ Process ID of the checkpointer process.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>type</structfield> <type>text</type>
+ </para>
+ <para>
+ Type of the checkpoint. See <xref linkend="checkpoint-types"/>.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>flags</structfield> <type>text</type>
+ </para>
+ <para>
+ Flags of the checkpoint. See <xref linkend="checkpoint-flags"/>.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>start_lsn</structfield> <type>text</type>
+ </para>
+ <para>
+ The checkpoint start location.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>start_time</structfield> <type>timestamp with time zone</type>
+ </para>
+ <para>
+ Start time of the checkpoint.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>phase</structfield> <type>text</type>
+ </para>
+ <para>
+ Current processing phase. See <xref linkend="checkpoint-phases"/>.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>buffers_total</structfield> <type>bigint</type>
+ </para>
+ <para>
+ Total number of buffers to be written. This is estimated and reported
+ as of the beginning of buffer write operation.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>buffers_processed</structfield> <type>bigint</type>
+ </para>
+ <para>
+ Number of buffers processed. This counter increases when the targeted
+ buffer is processed. This number will eventually become equal to
+ <literal>buffers_total</literal> when the checkpoint is
+ complete.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>buffers_written</structfield> <type>bigint</type>
+ </para>
+ <para>
+ Number of buffers written. This counter only advances when the targeted
+ buffers is written. Note that some of the buffers are processed but may
+ not required to be written. So this count will always be less than or
+ equal to <literal>buffers_total</literal>.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>files_total</structfield> <type>bigint</type>
+ </para>
+ <para>
+ Total number of files to be synced. This is estimated and reported as of
+ the beginning of sync operation.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>files_synced</structfield> <type>bigint</type>
+ </para>
+ <para>
+ Number of files synced. This counter advances when the targeted file is
+ synced. This number will eventually become equal to
+ <literal>files_total</literal> when the checkpoint is complete.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>new_requests</structfield> <type>text</type>
+ </para>
+ <para>
+ True if any of the backend is requested for a checkpoint while the
+ current checkpoint is in progress, False otherwise.
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <table id="checkpoint-types">
+ <title>Checkpoint Types</title>
+ <tgroup cols="2">
+ <colspec colname="col1" colwidth="1*"/>
+ <colspec colname="col2" colwidth="2*"/>
+ <thead>
+ <row>
+ <entry>Types</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>checkpoint</literal></entry>
+ <entry>
+ The current operation is checkpoint.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>restartpoint</literal></entry>
+ <entry>
+ The current operation is restartpoint.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <table id="checkpoint-flags">
+ <title>Checkpoint Flags</title>
+ <tgroup cols="2">
+ <colspec colname="col1" colwidth="1*"/>
+ <colspec colname="col2" colwidth="2*"/>
+ <thead>
+ <row>
+ <entry>Flags</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>shutdown</literal></entry>
+ <entry>
+ The checkpoint is for shutdown.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>end-of-recovery</literal></entry>
+ <entry>
+ The checkpoint is for end-of-recovery.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>immediate</literal></entry>
+ <entry>
+ The checkpoint happens without delays.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>force</literal></entry>
+ <entry>
+ The checkpoint is started because some operation (for which the
+ checkpoint is necessary) forced a checkpoint.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>flush all</literal></entry>
+ <entry>
+ The checkpoint flushes all pages, including those belonging to unlogged
+ tables.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>wait</literal></entry>
+ <entry>
+ The operations which requested the checkpoint waits for completion
+ before returning.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>requested</literal></entry>
+ <entry>
+ The checkpoint request has been made.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>wal</literal></entry>
+ <entry>
+ The checkpoint is started because <literal>max_wal_size</literal> is
+ reached.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>time</literal></entry>
+ <entry>
+ The checkpoint is started because <literal>checkpoint_timeout</literal>
+ expired.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <table id="checkpoint-phases">
+ <title>Checkpoint Phases</title>
+ <tgroup cols="2">
+ <colspec colname="col1" colwidth="1*"/>
+ <colspec colname="col2" colwidth="2*"/>
+ <thead>
+ <row>
+ <entry>Phase</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>initializing</literal></entry>
+ <entry>
+ The checkpointer process is preparing to begin the checkpoint operation.
+ This phase is expected to be very brief.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>getting virtual transaction IDs</literal></entry>
+ <entry>
+ The checkpointer process is getting the virtual transaction IDs that
+ are delaying the checkpoint.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>checkpointing replication slots</literal></entry>
+ <entry>
+ The checkpointer process is currently flushing all the replication slots
+ to disk.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>checkpointing logical replication snapshot files</literal></entry>
+ <entry>
+ The checkpointer process is currently removing all the serialized
+ snapshot files that are not required anymore.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>checkpointing logical rewrite mapping files</literal></entry>
+ <entry>
+ The checkpointer process is currently removing unwanted or flushing
+ required logical rewrite mapping files.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>checkpointing replication origin</literal></entry>
+ <entry>
+ The checkpointer process is currently performing a checkpoint of each
+ replication origin's progress with respect to the replayed remote LSN.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>checkpointing commit log pages</literal></entry>
+ <entry>
+ The checkpointer process is currently writing commit log pages to disk.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>checkpointing commit time stamp pages</literal></entry>
+ <entry>
+ The checkpointer process is currently writing commit time stamp pages to
+ disk.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>checkpointing subtransaction pages</literal></entry>
+ <entry>
+ The checkpointer process is currently writing subtransaction pages to disk.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>checkpointing multixact pages</literal></entry>
+ <entry>
+ The checkpointer process is currently writing multixact pages to disk.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>checkpointing predicate lock pages</literal></entry>
+ <entry>
+ The checkpointer process is currently writing predicate lock pages to disk.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>checkpointing buffers</literal></entry>
+ <entry>
+ The checkpointer process is currently writing buffers to disk.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>processing file sync requests</literal></entry>
+ <entry>
+ The checkpointer process is currently processing file sync requests.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>performing two phase checkpoint</literal></entry>
+ <entry>
+ The checkpointer process is currently performing two phase checkpoint.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>performing post checkpoint cleanup</literal></entry>
+ <entry>
+ The checkpointer process is currently performing post checkpoint cleanup.
+ It removes any lingering files that can be safely removed.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>invalidating replication slots</literal></entry>
+ <entry>
+ The checkpointer process is currently invalidating replication slots.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>recycling old WAL files</literal></entry>
+ <entry>
+ The checkpointer process is currently recycling old WAL files.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>truncating subtransactions</literal></entry>
+ <entry>
+ The checkpointer process is currently removing the subtransaction
+ segments.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>finalizing</literal></entry>
+ <entry>
+ The checkpointer process is finalizing the checkpoint operation.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ </sect2>
+
</sect1>
<sect1 id="dynamic-trace">
diff --git a/doc/src/sgml/ref/checkpoint.sgml b/doc/src/sgml/ref/checkpoint.sgml
index 28a1d717b8..906883336d 100644
--- a/doc/src/sgml/ref/checkpoint.sgml
+++ b/doc/src/sgml/ref/checkpoint.sgml
@@ -55,6 +55,14 @@ CHECKPOINT
Only superusers or users with the privileges of
the <link linkend="predefined-roles-table"><literal>pg_checkpoint</literal></link>
role can call <command>CHECKPOINT</command>.
+
+ <para>
+ The checkpointer process running the checkpoint will report its progress
+ in the <structname>pg_stat_progress_checkpoint</structname> view except for
+ the shutdown and end-of-recovery cases. See
+ <xref linkend="checkpoint-progress-reporting"/> for details.
+ </para>
+
</para>
</refsect1>
diff --git a/doc/src/sgml/wal.sgml b/doc/src/sgml/wal.sgml
index 6a38b53744..733a8d2837 100644
--- a/doc/src/sgml/wal.sgml
+++ b/doc/src/sgml/wal.sgml
@@ -531,7 +531,11 @@
adjust the <xref linkend="guc-archive-timeout"/> parameter rather than the
checkpoint parameters.)
It is also possible to force a checkpoint by using the SQL
- command <command>CHECKPOINT</command>.
+ command <command>CHECKPOINT</command>. The checkpointer process running the
+ checkpoint will report its progress in the
+ <structname>pg_stat_progress_checkpoint</structname> view except for the
+ shutdown and end-of-recovery cases. See
+ <xref linkend="checkpoint-progress-reporting"/> for details.
</para>
<para>
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index be54c23187..f2cebe0973 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -67,6 +67,7 @@
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
#include "catalog/pg_database.h"
+#include "commands/progress.h"
#include "common/controldata_utils.h"
#include "common/file_utils.h"
#include "executor/instrument.h"
@@ -698,6 +699,8 @@ static void WALInsertLockAcquireExclusive(void);
static void WALInsertLockRelease(void);
static void WALInsertLockUpdateInsertingAt(XLogRecPtr insertingAt);
+static void checkpoint_progress_start(int flags, int type);
+
/*
* Insert an XLOG record represented by an already-constructed chain of data
* chunks. This is a low-level routine; to construct the WAL record header
@@ -6622,6 +6625,9 @@ CreateCheckPoint(int flags)
XLogCtl->RedoRecPtr = checkPoint.redo;
SpinLockRelease(&XLogCtl->info_lck);
+ /* Prepare to report progress of the checkpoint. */
+ checkpoint_progress_start(flags, PROGRESS_CHECKPOINT_TYPE_CHECKPOINT);
+
/*
* If enabled, log checkpoint start. We postpone this until now so as not
* to log anything if we decided to skip the checkpoint.
@@ -6704,6 +6710,8 @@ CreateCheckPoint(int flags)
* clog and we will correctly flush the update below. So we cannot miss
* any xacts we need to wait for.
*/
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_GET_VIRTUAL_TRANSACTION_IDS);
vxids = GetVirtualXIDsDelayingChkpt(&nvxids, DELAY_CHKPT_START);
if (nvxids > 0)
{
@@ -6819,6 +6827,8 @@ CreateCheckPoint(int flags)
/*
* Let smgr do post-checkpoint cleanup (eg, deleting old files).
*/
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_POST_CHECKPOINT_CLEANUP);
SyncPostCheckpoint();
/*
@@ -6834,6 +6844,9 @@ CreateCheckPoint(int flags)
*/
XLByteToSeg(RedoRecPtr, _logSegNo, wal_segment_size);
KeepLogSeg(recptr, &_logSegNo);
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_INVALIDATE_REPLI_SLOTS);
+
if (InvalidateObsoleteReplicationSlots(_logSegNo))
{
/*
@@ -6844,6 +6857,8 @@ CreateCheckPoint(int flags)
KeepLogSeg(recptr, &_logSegNo);
}
_logSegNo--;
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_RECYCLE_OLD_XLOG);
RemoveOldXlogFiles(_logSegNo, RedoRecPtr, recptr,
checkPoint.ThisTimeLineID);
@@ -6862,11 +6877,21 @@ CreateCheckPoint(int flags)
* StartupSUBTRANS hasn't been called yet.
*/
if (!RecoveryInProgress())
+ {
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_TRUNCATE_SUBTRANS);
TruncateSUBTRANS(GetOldestTransactionIdConsideredRunning());
+ }
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_FINALIZE);
/* Real work is done; log and update stats. */
LogCheckpointEnd(false);
+ /* Stop reporting progress of the checkpoint. */
+ pgstat_progress_end_command();
+
/* Reset the process title */
update_checkpoint_display(flags, false, true);
@@ -7023,29 +7048,63 @@ static void
CheckPointGuts(XLogRecPtr checkPointRedo, int flags)
{
CheckPointRelationMap();
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_REPLI_SLOTS);
CheckPointReplicationSlots();
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_SNAPSHOTS);
CheckPointSnapBuild();
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_LOGICAL_REWRITE_MAPPINGS);
CheckPointLogicalRewriteHeap();
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_REPLI_ORIGIN);
CheckPointReplicationOrigin();
/* Write out all dirty data in SLRUs and the main buffer pool */
TRACE_POSTGRESQL_BUFFER_CHECKPOINT_START(flags);
CheckpointStats.ckpt_write_t = GetCurrentTimestamp();
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_CLOG_PAGES);
CheckPointCLOG();
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_COMMITTS_PAGES);
CheckPointCommitTs();
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_SUBTRANS_PAGES);
CheckPointSUBTRANS();
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_MULTIXACT_PAGES);
CheckPointMultiXact();
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_PREDICATE_LOCK_PAGES);
CheckPointPredicate();
+
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_BUFFERS);
CheckPointBuffers(flags);
/* Perform all queued up fsyncs */
TRACE_POSTGRESQL_BUFFER_CHECKPOINT_SYNC_START();
CheckpointStats.ckpt_sync_t = GetCurrentTimestamp();
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_SYNC_FILES);
ProcessSyncRequests();
CheckpointStats.ckpt_sync_end_t = GetCurrentTimestamp();
TRACE_POSTGRESQL_BUFFER_CHECKPOINT_DONE();
/* We deliberately delay 2PC checkpointing as long as possible */
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_TWO_PHASE);
CheckPointTwoPhase(checkPointRedo);
}
@@ -7195,6 +7254,9 @@ CreateRestartPoint(int flags)
MemSet(&CheckpointStats, 0, sizeof(CheckpointStats));
CheckpointStats.ckpt_start_t = GetCurrentTimestamp();
+ /* Prepare to report progress of the restartpoint. */
+ checkpoint_progress_start(flags, PROGRESS_CHECKPOINT_TYPE_RESTARTPOINT);
+
if (log_checkpoints)
LogCheckpointStart(flags, true);
@@ -7278,6 +7340,9 @@ CreateRestartPoint(int flags)
replayPtr = GetXLogReplayRecPtr(&replayTLI);
endptr = (receivePtr < replayPtr) ? replayPtr : receivePtr;
KeepLogSeg(endptr, &_logSegNo);
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_INVALIDATE_REPLI_SLOTS);
+
if (InvalidateObsoleteReplicationSlots(_logSegNo))
{
/*
@@ -7304,6 +7369,8 @@ CreateRestartPoint(int flags)
if (!RecoveryInProgress())
replayTLI = XLogCtl->InsertTimeLineID;
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_RECYCLE_OLD_XLOG);
RemoveOldXlogFiles(_logSegNo, RedoRecPtr, endptr, replayTLI);
/*
@@ -7320,11 +7387,20 @@ CreateRestartPoint(int flags)
* this because StartupSUBTRANS hasn't been called yet.
*/
if (EnableHotStandby)
+ {
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_TRUNCATE_SUBTRANS);
TruncateSUBTRANS(GetOldestTransactionIdConsideredRunning());
+ }
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
+ PROGRESS_CHECKPOINT_PHASE_FINALIZE);
/* Real work is done; log and update stats. */
LogCheckpointEnd(true);
+ /* Stop reporting progress of the restartpoint. */
+ pgstat_progress_end_command();
+
/* Reset the process title */
update_checkpoint_display(flags, true, true);
@@ -8958,3 +9034,29 @@ SetWalWriterSleeping(bool sleeping)
XLogCtl->WalWriterSleeping = sleeping;
SpinLockRelease(&XLogCtl->info_lck);
}
+
+/*
+ * Start reporting progress of the checkpoint.
+ */
+static void
+checkpoint_progress_start(int flags, int type)
+{
+ const int index[] = {
+ PROGRESS_CHECKPOINT_TYPE,
+ PROGRESS_CHECKPOINT_FLAGS,
+ PROGRESS_CHECKPOINT_LSN,
+ PROGRESS_CHECKPOINT_START_TIMESTAMP,
+ PROGRESS_CHECKPOINT_PHASE
+ };
+ int64 val[5];
+
+ pgstat_progress_start_command(PROGRESS_COMMAND_CHECKPOINT, InvalidOid);
+
+ val[0] = type;
+ val[1] = flags;
+ val[2] = RedoRecPtr;
+ val[3] = CheckpointStats.ckpt_start_t;
+ val[4] = PROGRESS_CHECKPOINT_PHASE_INIT;
+
+ pgstat_progress_update_multi_param(5, index, val);
+}
\ No newline at end of file
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2d8104b090..384ca35833 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1267,6 +1267,57 @@ CREATE VIEW pg_stat_progress_copy AS
FROM pg_stat_get_progress_info('COPY') AS S
LEFT JOIN pg_database D ON S.datid = D.oid;
+CREATE VIEW pg_stat_progress_checkpoint AS
+ SELECT
+ S.pid AS pid,
+ CASE S.param1 WHEN 1 THEN 'checkpoint'
+ WHEN 2 THEN 'restartpoint'
+ END AS type,
+ ( CASE WHEN (S.param2 & 4) > 0 THEN 'immediate ' ELSE '' END ||
+ CASE WHEN (S.param2 & 8) > 0 THEN 'force ' ELSE '' END ||
+ CASE WHEN (S.param2 & 16) > 0 THEN 'flush-all ' ELSE '' END ||
+ CASE WHEN (S.param2 & 32) > 0 THEN 'wait ' ELSE '' END ||
+ CASE WHEN (S.param2 & 128) > 0 THEN 'wal ' ELSE '' END ||
+ CASE WHEN (S.param2 & 256) > 0 THEN 'time ' ELSE '' END
+ ) AS flags,
+ ( '0/0'::pg_lsn +
+ ((CASE
+ WHEN S.param3 < 0 THEN pow(2::numeric, 64::numeric)::numeric
+ ELSE 0::numeric
+ END) +
+ S.param3::numeric)
+ ) AS start_lsn,
+ to_timestamp(946684800 + (S.param4::float8 / 1000000)) AS start_time,
+ CASE S.param5 WHEN 1 THEN 'initializing'
+ WHEN 2 THEN 'getting virtual transaction IDs'
+ WHEN 3 THEN 'checkpointing replication slots'
+ WHEN 4 THEN 'checkpointing logical replication snapshot files'
+ WHEN 5 THEN 'checkpointing logical rewrite mapping files'
+ WHEN 6 THEN 'checkpointing replication origin'
+ WHEN 7 THEN 'checkpointing commit log pages'
+ WHEN 8 THEN 'checkpointing commit time stamp pages'
+ WHEN 9 THEN 'checkpointing subtransaction pages'
+ WHEN 10 THEN 'checkpointing multixact pages'
+ WHEN 11 THEN 'checkpointing predicate lock pages'
+ WHEN 12 THEN 'checkpointing buffers'
+ WHEN 13 THEN 'processing file sync requests'
+ WHEN 14 THEN 'performing two phase checkpoint'
+ WHEN 15 THEN 'performing post checkpoint cleanup'
+ WHEN 16 THEN 'invalidating replication slots'
+ WHEN 17 THEN 'recycling old WAL files'
+ WHEN 18 THEN 'truncating subtransactions'
+ WHEN 19 THEN 'finalizing'
+ END AS phase,
+ S.param6 AS buffers_total,
+ S.param7 AS buffers_processed,
+ S.param8 AS buffers_written,
+ S.param9 AS files_total,
+ S.param10 AS files_synced,
+ CASE S.param11 WHEN 0 THEN 'false'
+ WHEN 1 THEN 'true'
+ END AS new_requests
+ FROM pg_stat_get_progress_info('CHECKPOINT') AS S;
+
CREATE VIEW pg_user_mappings AS
SELECT
U.oid AS umid,
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 5fc076fc14..21bf75b058 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -39,6 +39,7 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
#include "access/xlogrecovery.h"
+#include "commands/progress.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
@@ -163,7 +164,7 @@ static pg_time_t last_xlog_switch_time;
static void HandleCheckpointerInterrupts(void);
static void CheckArchiveTimeout(void);
static bool IsCheckpointOnSchedule(double progress);
-static bool ImmediateCheckpointRequested(void);
+static bool ImmediateCheckpointRequested(int flags);
static bool CompactCheckpointerRequestQueue(void);
static void UpdateSharedMemoryConfig(void);
@@ -667,16 +668,24 @@ CheckArchiveTimeout(void)
* there is one pending behind it.)
*/
static bool
-ImmediateCheckpointRequested(void)
+ImmediateCheckpointRequested(int flags)
{
volatile CheckpointerShmemStruct *cps = CheckpointerShmem;
+ if (cps->ckpt_flags & CHECKPOINT_REQUESTED)
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_NEW_REQUESTS, true);
+
/*
* We don't need to acquire the ckpt_lck in this case because we're only
* looking at a single flag bit.
*/
if (cps->ckpt_flags & CHECKPOINT_IMMEDIATE)
+ {
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_FLAGS,
+ (flags | CHECKPOINT_IMMEDIATE));
return true;
+ }
+
return false;
}
@@ -708,7 +717,7 @@ CheckpointWriteDelay(int flags, double progress)
*/
if (!(flags & CHECKPOINT_IMMEDIATE) &&
!ShutdownRequestPending &&
- !ImmediateCheckpointRequested() &&
+ !ImmediateCheckpointRequested(flags) &&
IsCheckpointOnSchedule(progress))
{
if (ConfigReloadPending)
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 73d30bf619..6d69255667 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -39,6 +39,7 @@
#include "catalog/catalog.h"
#include "catalog/storage.h"
#include "catalog/storage_xlog.h"
+#include "commands/progress.h"
#include "executor/instrument.h"
#include "lib/binaryheap.h"
#include "miscadmin.h"
@@ -2026,6 +2027,8 @@ BufferSync(int flags)
WritebackContextInit(&wb_context, &checkpoint_flush_after);
TRACE_POSTGRESQL_BUFFER_SYNC_START(NBuffers, num_to_scan);
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_BUFFERS_TOTAL,
+ num_to_scan);
/*
* Sort buffers that need to be written to reduce the likelihood of random
@@ -2143,6 +2146,8 @@ BufferSync(int flags)
bufHdr = GetBufferDescriptor(buf_id);
num_processed++;
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_BUFFERS_PROCESSED,
+ num_processed);
/*
* We don't need to acquire the lock here, because we're only looking
@@ -2163,6 +2168,8 @@ BufferSync(int flags)
TRACE_POSTGRESQL_BUFFER_SYNC_WRITTEN(buf_id);
PendingCheckpointerStats.buf_written_checkpoints++;
num_written++;
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_BUFFERS_WRITTEN,
+ num_written);
}
}
diff --git a/src/backend/storage/sync/sync.c b/src/backend/storage/sync/sync.c
index 9d6a9e9109..aa25215910 100644
--- a/src/backend/storage/sync/sync.c
+++ b/src/backend/storage/sync/sync.c
@@ -23,6 +23,7 @@
#include "access/multixact.h"
#include "access/xlog.h"
#include "access/xlogutils.h"
+#include "commands/progress.h"
#include "commands/tablespace.h"
#include "miscadmin.h"
#include "pgstat.h"
@@ -368,6 +369,9 @@ ProcessSyncRequests(void)
/* Now scan the hashtable for fsync requests to process */
absorb_counter = FSYNCS_PER_ABSORB;
hash_seq_init(&hstat, pendingOps);
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_FILES_TOTAL,
+ hash_get_num_entries(pendingOps));
+
while ((entry = (PendingFsyncEntry *) hash_seq_search(&hstat)) != NULL)
{
int failures;
@@ -431,6 +435,8 @@ ProcessSyncRequests(void)
longest = elapsed;
total_elapsed += elapsed;
processed++;
+ pgstat_progress_update_param(PROGRESS_CHECKPOINT_FILES_SYNCED,
+ processed);
if (log_checkpoints)
elog(DEBUG1, "checkpoint sync: number=%d file=%s time=%.3f ms",
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 96bffc0f2a..8281d961bf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -497,6 +497,8 @@ pg_stat_get_progress_info(PG_FUNCTION_ARGS)
cmdtype = PROGRESS_COMMAND_BASEBACKUP;
else if (pg_strcasecmp(cmd, "COPY") == 0)
cmdtype = PROGRESS_COMMAND_COPY;
+ else if (pg_strcasecmp(cmd, "CHECKPOINT") == 0)
+ cmdtype = PROGRESS_COMMAND_CHECKPOINT;
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/include/commands/progress.h b/src/include/commands/progress.h
index a28938caf4..33a64d2f0b 100644
--- a/src/include/commands/progress.h
+++ b/src/include/commands/progress.h
@@ -151,4 +151,42 @@
#define PROGRESS_COPY_TYPE_PIPE 3
#define PROGRESS_COPY_TYPE_CALLBACK 4
+/* Progress parameters for checkpoint */
+#define PROGRESS_CHECKPOINT_TYPE 0
+#define PROGRESS_CHECKPOINT_FLAGS 1
+#define PROGRESS_CHECKPOINT_LSN 2
+#define PROGRESS_CHECKPOINT_START_TIMESTAMP 3
+#define PROGRESS_CHECKPOINT_PHASE 4
+#define PROGRESS_CHECKPOINT_BUFFERS_TOTAL 5
+#define PROGRESS_CHECKPOINT_BUFFERS_PROCESSED 6
+#define PROGRESS_CHECKPOINT_BUFFERS_WRITTEN 7
+#define PROGRESS_CHECKPOINT_FILES_TOTAL 8
+#define PROGRESS_CHECKPOINT_FILES_SYNCED 9
+#define PROGRESS_CHECKPOINT_NEW_REQUESTS 10
+
+/* Types of checkpoint (as advertised via PROGRESS_CHECKPOINT_TYPE) */
+#define PROGRESS_CHECKPOINT_TYPE_CHECKPOINT 1
+#define PROGRESS_CHECKPOINT_TYPE_RESTARTPOINT 2
+
+/* Phases of checkpoint (as advertised via PROGRESS_CHECKPOINT_PHASE) */
+#define PROGRESS_CHECKPOINT_PHASE_INIT 1
+#define PROGRESS_CHECKPOINT_PHASE_GET_VIRTUAL_TRANSACTION_IDS 2
+#define PROGRESS_CHECKPOINT_PHASE_REPLI_SLOTS 3
+#define PROGRESS_CHECKPOINT_PHASE_SNAPSHOTS 4
+#define PROGRESS_CHECKPOINT_PHASE_LOGICAL_REWRITE_MAPPINGS 5
+#define PROGRESS_CHECKPOINT_PHASE_REPLI_ORIGIN 6
+#define PROGRESS_CHECKPOINT_PHASE_CLOG_PAGES 7
+#define PROGRESS_CHECKPOINT_PHASE_COMMITTS_PAGES 8
+#define PROGRESS_CHECKPOINT_PHASE_SUBTRANS_PAGES 9
+#define PROGRESS_CHECKPOINT_PHASE_MULTIXACT_PAGES 10
+#define PROGRESS_CHECKPOINT_PHASE_PREDICATE_LOCK_PAGES 11
+#define PROGRESS_CHECKPOINT_PHASE_BUFFERS 12
+#define PROGRESS_CHECKPOINT_PHASE_SYNC_FILES 13
+#define PROGRESS_CHECKPOINT_PHASE_TWO_PHASE 14
+#define PROGRESS_CHECKPOINT_PHASE_POST_CHECKPOINT_CLEANUP 15
+#define PROGRESS_CHECKPOINT_PHASE_INVALIDATE_REPLI_SLOTS 16
+#define PROGRESS_CHECKPOINT_PHASE_RECYCLE_OLD_XLOG 17
+#define PROGRESS_CHECKPOINT_PHASE_TRUNCATE_SUBTRANS 18
+#define PROGRESS_CHECKPOINT_PHASE_FINALIZE 19
+
#endif
diff --git a/src/include/utils/backend_progress.h b/src/include/utils/backend_progress.h
index 47bf8029b0..02d51fb948 100644
--- a/src/include/utils/backend_progress.h
+++ b/src/include/utils/backend_progress.h
@@ -27,7 +27,8 @@ typedef enum ProgressCommandType
PROGRESS_COMMAND_CLUSTER,
PROGRESS_COMMAND_CREATE_INDEX,
PROGRESS_COMMAND_BASEBACKUP,
- PROGRESS_COMMAND_COPY
+ PROGRESS_COMMAND_COPY,
+ PROGRESS_COMMAND_CHECKPOINT
} ProgressCommandType;
#define PGSTAT_NUM_PROGRESS_PARAM 20
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 624d0e5aae..eab68d7f14 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1913,6 +1913,76 @@ pg_stat_progress_basebackup| SELECT s.pid,
s.param4 AS tablespaces_total,
s.param5 AS tablespaces_streamed
FROM pg_stat_get_progress_info('BASEBACKUP'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20);
+pg_stat_progress_checkpoint| SELECT s.pid,
+ CASE s.param1
+ WHEN 1 THEN 'checkpoint'::text
+ WHEN 2 THEN 'restartpoint'::text
+ ELSE NULL::text
+ END AS type,
+ (((((
+ CASE
+ WHEN ((s.param2 & (4)::bigint) > 0) THEN 'immediate '::text
+ ELSE ''::text
+ END ||
+ CASE
+ WHEN ((s.param2 & (8)::bigint) > 0) THEN 'force '::text
+ ELSE ''::text
+ END) ||
+ CASE
+ WHEN ((s.param2 & (16)::bigint) > 0) THEN 'flush-all '::text
+ ELSE ''::text
+ END) ||
+ CASE
+ WHEN ((s.param2 & (32)::bigint) > 0) THEN 'wait '::text
+ ELSE ''::text
+ END) ||
+ CASE
+ WHEN ((s.param2 & (128)::bigint) > 0) THEN 'wal '::text
+ ELSE ''::text
+ END) ||
+ CASE
+ WHEN ((s.param2 & (256)::bigint) > 0) THEN 'time '::text
+ ELSE ''::text
+ END) AS flags,
+ ('0/0'::pg_lsn + (
+ CASE
+ WHEN (s.param3 < 0) THEN pow((2)::numeric, (64)::numeric)
+ ELSE (0)::numeric
+ END + (s.param3)::numeric)) AS start_lsn,
+ to_timestamp(((946684800)::double precision + ((s.param4)::double precision / (1000000)::double precision))) AS start_time,
+ CASE s.param5
+ WHEN 1 THEN 'initializing'::text
+ WHEN 2 THEN 'getting virtual transaction IDs'::text
+ WHEN 3 THEN 'checkpointing replication slots'::text
+ WHEN 4 THEN 'checkpointing logical replication snapshot files'::text
+ WHEN 5 THEN 'checkpointing logical rewrite mapping files'::text
+ WHEN 6 THEN 'checkpointing replication origin'::text
+ WHEN 7 THEN 'checkpointing commit log pages'::text
+ WHEN 8 THEN 'checkpointing commit time stamp pages'::text
+ WHEN 9 THEN 'checkpointing subtransaction pages'::text
+ WHEN 10 THEN 'checkpointing multixact pages'::text
+ WHEN 11 THEN 'checkpointing predicate lock pages'::text
+ WHEN 12 THEN 'checkpointing buffers'::text
+ WHEN 13 THEN 'processing file sync requests'::text
+ WHEN 14 THEN 'performing two phase checkpoint'::text
+ WHEN 15 THEN 'performing post checkpoint cleanup'::text
+ WHEN 16 THEN 'invalidating replication slots'::text
+ WHEN 17 THEN 'recycling old WAL files'::text
+ WHEN 18 THEN 'truncating subtransactions'::text
+ WHEN 19 THEN 'finalizing'::text
+ ELSE NULL::text
+ END AS phase,
+ s.param6 AS buffers_total,
+ s.param7 AS buffers_processed,
+ s.param8 AS buffers_written,
+ s.param9 AS files_total,
+ s.param10 AS files_synced,
+ CASE s.param11
+ WHEN 0 THEN 'false'::text
+ WHEN 1 THEN 'true'::text
+ ELSE NULL::text
+ END AS new_requests
+ FROM pg_stat_get_progress_info('CHECKPOINT'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20);
pg_stat_progress_cluster| SELECT s.pid,
s.datid,
d.datname,
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs)
@ 2022-11-15 11:41 Nitin Jadhav <[email protected]>
parent: Drouvot, Bertrand <[email protected]>
3 siblings, 0 replies; 199+ messages in thread
From: Nitin Jadhav @ 2022-11-15 11:41 UTC (permalink / raw)
To: Drouvot, Bertrand <[email protected]>; +Cc: Andres Freund <[email protected]>; Bharath Rupireddy <[email protected]>; Matthias van de Meent <[email protected]>; Ashutosh Sharma <[email protected]>; Julien Rouhaud <[email protected]>; Bruce Momjian <[email protected]>; Tom Lane <[email protected]>; Magnus Hagander <[email protected]>; PostgreSQL Hackers <[email protected]>
> v6 was not applying anymore, due to a change in
> doc/src/sgml/ref/checkpoint.sgml done by b9eb0ff09e (Rename
> pg_checkpointer predefined role to pg_checkpoint).
>
> Please find attached a rebase in v7.
>
> While working on this rebase, I also noticed that "pg_checkpointer" is
> still mentioned in some translation files:
Thanks for rebasing the patch and sharing the information.
---
> That said, back to this patch: I did not look closely but noticed that
> the buffers_total reported by pg_stat_progress_checkpoint:
>
> postgres=# select type,flags,start_lsn,phase,buffers_total,new_requests
> from pg_stat_progress_checkpoint;
> type | flags | start_lsn | phase
> | buffers_total | new_requests
> ------------+-----------------------+------------+-----------------------+---------------+--------------
> checkpoint | immediate force wait | 1/E6C523A8 | checkpointing
> buffers | 1024275 | false
> (1 row)
>
> is a little bit different from what is logged once completed:
>
> 2022-11-04 08:18:50.806 UTC [3488442] LOG: checkpoint complete: wrote
> 1024278 buffers (97.7%);
This is because the count shown in the checkpoint complete message
includes the additional increment done during SlruInternalWritePage().
We are not sure of this increment until it really happens. Hence it
was not considered in the patch. To make it compatible with the
checkpoint complete message, we should increment all three here,
buffers_total, buffers_processed and buffers_written. So the total
number of buffers calculated earlier may not always be the same. If
this looks good, I will update this in the next patch.
Thanks & Regards,
Nitin Jadhav
On Fri, Nov 4, 2022 at 1:57 PM Drouvot, Bertrand
<[email protected]> wrote:
>
> Hi,
>
> On 7/28/22 11:38 AM, Nitin Jadhav wrote:
> >>> To understand the performance effects of the above, I have taken the
> >>> average of five checkpoints with the patch and without the patch in my
> >>> environment. Here are the results.
> >>> With patch: 269.65 s
> >>> Without patch: 269.60 s
> >>
> >> Those look like timed checkpoints - if the checkpoints are sleeping a
> >> part of the time, you're not going to see any potential overhead.
> >
> > Yes. The above data is collected from timed checkpoints.
> >
> > create table t1(a int);
> > insert into t1 select * from generate_series(1,10000000);
> >
> > I generated a lot of data by using the above queries which would in
> > turn trigger the checkpoint (wal).
> > ---
> >
> >> To see whether this has an effect you'd have to make sure there's a
> >> certain number of dirty buffers (e.g. by doing CREATE TABLE AS
> >> some_query) and then do a manual checkpoint and time how long that
> >> times.
> >
> > For this case I have generated data by using below queries.
> >
> > create table t1(a int);
> > insert into t1 select * from generate_series(1,8000000);
> >
> > This does not trigger the checkpoint automatically. I have issued the
> > CHECKPOINT manually and measured the performance by considering an
> > average of 5 checkpoints. Here are the details.
> >
> > With patch: 2.457 s
> > Without patch: 2.334 s
> >
> > Please share your thoughts.
> >
>
> v6 was not applying anymore, due to a change in
> doc/src/sgml/ref/checkpoint.sgml done by b9eb0ff09e (Rename
> pg_checkpointer predefined role to pg_checkpoint).
>
> Please find attached a rebase in v7.
>
> While working on this rebase, I also noticed that "pg_checkpointer" is
> still mentioned in some translation files:
> "
> $ git grep pg_checkpointer
> src/backend/po/de.po:msgid "must be superuser or have privileges of
> pg_checkpointer to do CHECKPOINT"
> src/backend/po/ja.po:msgid "must be superuser or have privileges of
> pg_checkpointer to do CHECKPOINT"
> src/backend/po/ja.po:msgstr
> "CHECKPOINTを実行するにはスーパーユーザーであるか、またはpg_checkpointerの権限を持つ必要があります"
> src/backend/po/sv.po:msgid "must be superuser or have privileges of
> pg_checkpointer to do CHECKPOINT"
> "
>
> I'm not familiar with how the translation files are handled (looks like
> they have their own set of commits, see 3c0bcdbc66 for example) but
> wanted to mention that "pg_checkpointer" is still mentioned (even if
> that may be expected as the last commit related to translation files
> (aka 3c0bcdbc66) is older than the one that renamed pg_checkpointer to
> pg_checkpoint (aka b9eb0ff09e)).
>
> That said, back to this patch: I did not look closely but noticed that
> the buffers_total reported by pg_stat_progress_checkpoint:
>
> postgres=# select type,flags,start_lsn,phase,buffers_total,new_requests
> from pg_stat_progress_checkpoint;
> type | flags | start_lsn | phase
> | buffers_total | new_requests
> ------------+-----------------------+------------+-----------------------+---------------+--------------
> checkpoint | immediate force wait | 1/E6C523A8 | checkpointing
> buffers | 1024275 | false
> (1 row)
>
> is a little bit different from what is logged once completed:
>
> 2022-11-04 08:18:50.806 UTC [3488442] LOG: checkpoint complete: wrote
> 1024278 buffers (97.7%);
>
> Regards,
>
> --
> Bertrand Drouvot
> PostgreSQL Contributors Team
> RDS Open Source Databases
> Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs)
@ 2022-11-15 20:04 Robert Haas <[email protected]>
parent: Drouvot, Bertrand <[email protected]>
3 siblings, 1 reply; 199+ messages in thread
From: Robert Haas @ 2022-11-15 20:04 UTC (permalink / raw)
To: Drouvot, Bertrand <[email protected]>; +Cc: Nitin Jadhav <[email protected]>; Andres Freund <[email protected]>; Bharath Rupireddy <[email protected]>; Matthias van de Meent <[email protected]>; Ashutosh Sharma <[email protected]>; Julien Rouhaud <[email protected]>; Bruce Momjian <[email protected]>; Tom Lane <[email protected]>; Magnus Hagander <[email protected]>; PostgreSQL Hackers <[email protected]>
On Fri, Nov 4, 2022 at 4:27 AM Drouvot, Bertrand
<[email protected]> wrote:
> Please find attached a rebase in v7.
I don't think it's a good thing that this patch is using the
progress-reporting machinery. The point of that machinery is that we
want any backend to be able to report progress for any command it
happens to be running, and we don't know which command that will be at
any given point in time, or how many backends will be running any
given command at once. So we need some generic set of counters that
can be repurposed for whatever any particular backend happens to be
doing right at the moment.
But none of that applies to the checkpointer. Any information about
the checkpointer that we want to expose can just be advertised in a
dedicated chunk of shared memory, perhaps even by simply adding it to
CheckpointerShmemStruct. Then you can give the fields whatever names,
types, and sizes you like, and you don't have to do all of this stuff
with mapping down to integers and back. The only real disadvantage
that I can see is then you have to think a bit harder about what the
concurrency model is here, and maybe you end up reimplementing
something similar to what the progress-reporting stuff does for you,
and *maybe* that is a sufficient reason to do it this way.
But I'm doubtful. This feels like a square-peg-round-hole situation.
--
Robert Haas
EDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs)
@ 2022-11-15 20:18 Andres Freund <[email protected]>
parent: Drouvot, Bertrand <[email protected]>
3 siblings, 0 replies; 199+ messages in thread
From: Andres Freund @ 2022-11-15 20:18 UTC (permalink / raw)
To: Drouvot, Bertrand <[email protected]>; +Cc: Nitin Jadhav <[email protected]>; Bharath Rupireddy <[email protected]>; Matthias van de Meent <[email protected]>; Ashutosh Sharma <[email protected]>; Julien Rouhaud <[email protected]>; Bruce Momjian <[email protected]>; Tom Lane <[email protected]>; Magnus Hagander <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi,
On 2022-11-04 09:25:52 +0100, Drouvot, Bertrand wrote:
>
> @@ -7023,29 +7048,63 @@ static void
> CheckPointGuts(XLogRecPtr checkPointRedo, int flags)
> {
> CheckPointRelationMap();
> +
> + pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
> + PROGRESS_CHECKPOINT_PHASE_REPLI_SLOTS);
> CheckPointReplicationSlots();
> +
> + pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
> + PROGRESS_CHECKPOINT_PHASE_SNAPSHOTS);
> CheckPointSnapBuild();
> +
> + pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
> + PROGRESS_CHECKPOINT_PHASE_LOGICAL_REWRITE_MAPPINGS);
> CheckPointLogicalRewriteHeap();
> +
> + pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
> + PROGRESS_CHECKPOINT_PHASE_REPLI_ORIGIN);
> CheckPointReplicationOrigin();
>
> /* Write out all dirty data in SLRUs and the main buffer pool */
> TRACE_POSTGRESQL_BUFFER_CHECKPOINT_START(flags);
> CheckpointStats.ckpt_write_t = GetCurrentTimestamp();
> +
> + pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
> + PROGRESS_CHECKPOINT_PHASE_CLOG_PAGES);
> CheckPointCLOG();
> +
> + pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
> + PROGRESS_CHECKPOINT_PHASE_COMMITTS_PAGES);
> CheckPointCommitTs();
> +
> + pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
> + PROGRESS_CHECKPOINT_PHASE_SUBTRANS_PAGES);
> CheckPointSUBTRANS();
> +
> + pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
> + PROGRESS_CHECKPOINT_PHASE_MULTIXACT_PAGES);
> CheckPointMultiXact();
> +
> + pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
> + PROGRESS_CHECKPOINT_PHASE_PREDICATE_LOCK_PAGES);
> CheckPointPredicate();
> +
> + pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
> + PROGRESS_CHECKPOINT_PHASE_BUFFERS);
> CheckPointBuffers(flags);
>
> /* Perform all queued up fsyncs */
> TRACE_POSTGRESQL_BUFFER_CHECKPOINT_SYNC_START();
> CheckpointStats.ckpt_sync_t = GetCurrentTimestamp();
> + pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
> + PROGRESS_CHECKPOINT_PHASE_SYNC_FILES);
> ProcessSyncRequests();
> CheckpointStats.ckpt_sync_end_t = GetCurrentTimestamp();
> TRACE_POSTGRESQL_BUFFER_CHECKPOINT_DONE();
>
> /* We deliberately delay 2PC checkpointing as long as possible */
> + pgstat_progress_update_param(PROGRESS_CHECKPOINT_PHASE,
> + PROGRESS_CHECKPOINT_PHASE_TWO_PHASE);
> CheckPointTwoPhase(checkPointRedo);
> }
This is quite the code bloat. Can we make this less duplicative?
> +CREATE VIEW pg_stat_progress_checkpoint AS
> + SELECT
> + S.pid AS pid,
> + CASE S.param1 WHEN 1 THEN 'checkpoint'
> + WHEN 2 THEN 'restartpoint'
> + END AS type,
> + ( CASE WHEN (S.param2 & 4) > 0 THEN 'immediate ' ELSE '' END ||
> + CASE WHEN (S.param2 & 8) > 0 THEN 'force ' ELSE '' END ||
> + CASE WHEN (S.param2 & 16) > 0 THEN 'flush-all ' ELSE '' END ||
> + CASE WHEN (S.param2 & 32) > 0 THEN 'wait ' ELSE '' END ||
> + CASE WHEN (S.param2 & 128) > 0 THEN 'wal ' ELSE '' END ||
> + CASE WHEN (S.param2 & 256) > 0 THEN 'time ' ELSE '' END
> + ) AS flags,
> + ( '0/0'::pg_lsn +
> + ((CASE
> + WHEN S.param3 < 0 THEN pow(2::numeric, 64::numeric)::numeric
> + ELSE 0::numeric
> + END) +
> + S.param3::numeric)
> + ) AS start_lsn,
I don't think we should embed this much complexity in the view
defintions. It's hard to read, bloats the catalog, we can't fix them once
released. This stuff seems like it should be in a helper function.
I don't have any iea what that pow stuff is supposed to be doing.
> + to_timestamp(946684800 + (S.param4::float8 / 1000000)) AS start_time,
I don't think this is a reasonable path - embedding way too much low-level
details about the timestamp format in the view definition. Why do we need to
do this?
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs)
@ 2022-11-16 10:31 Bharath Rupireddy <[email protected]>
parent: Robert Haas <[email protected]>
0 siblings, 2 replies; 199+ messages in thread
From: Bharath Rupireddy @ 2022-11-16 10:31 UTC (permalink / raw)
To: Robert Haas <[email protected]>; +Cc: Drouvot, Bertrand <[email protected]>; Nitin Jadhav <[email protected]>; Andres Freund <[email protected]>; Matthias van de Meent <[email protected]>; Ashutosh Sharma <[email protected]>; Julien Rouhaud <[email protected]>; Bruce Momjian <[email protected]>; Tom Lane <[email protected]>; Magnus Hagander <[email protected]>; PostgreSQL Hackers <[email protected]>
On Wed, Nov 16, 2022 at 1:35 AM Robert Haas <[email protected]> wrote:
>
> On Fri, Nov 4, 2022 at 4:27 AM Drouvot, Bertrand
> <[email protected]> wrote:
> > Please find attached a rebase in v7.
>
> I don't think it's a good thing that this patch is using the
> progress-reporting machinery. The point of that machinery is that we
> want any backend to be able to report progress for any command it
> happens to be running, and we don't know which command that will be at
> any given point in time, or how many backends will be running any
> given command at once. So we need some generic set of counters that
> can be repurposed for whatever any particular backend happens to be
> doing right at the moment.
Hm.
> But none of that applies to the checkpointer. Any information about
> the checkpointer that we want to expose can just be advertised in a
> dedicated chunk of shared memory, perhaps even by simply adding it to
> CheckpointerShmemStruct. Then you can give the fields whatever names,
> types, and sizes you like, and you don't have to do all of this stuff
> with mapping down to integers and back. The only real disadvantage
> that I can see is then you have to think a bit harder about what the
> concurrency model is here, and maybe you end up reimplementing
> something similar to what the progress-reporting stuff does for you,
> and *maybe* that is a sufficient reason to do it this way.
-1 for CheckpointerShmemStruct as it is being used for running
checkpoints and I don't think adding stats to it is a great idea.
Instead, extending PgStat_CheckpointerStats and using shared memory
stats for reporting progress/last checkpoint related stats is a good
idea IMO. I also think that a new pg_stat_checkpoint view is needed
because, right now, the PgStat_CheckpointerStats stats are exposed via
the pg_stat_bgwriter view, having a separate view for checkpoint stats
is good here. Also, removing CheckpointStatsData and moving all of
those members to PgStat_CheckpointerStats, of course, by being careful
about the amount of shared memory required, is also a good idea IMO.
Going forward, PgStat_CheckpointerStats and pg_stat_checkpoint view
can be a single point of location for all the checkpoint related
stats.
Thoughts?
In fact, I was recently having an off-list chat with Bertrand Drouvot
about the above idea.
--
Bharath Rupireddy
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs)
@ 2022-11-16 18:14 Andres Freund <[email protected]>
parent: Bharath Rupireddy <[email protected]>
1 sibling, 0 replies; 199+ messages in thread
From: Andres Freund @ 2022-11-16 18:14 UTC (permalink / raw)
To: Bharath Rupireddy <[email protected]>; +Cc: Robert Haas <[email protected]>; Drouvot, Bertrand <[email protected]>; Nitin Jadhav <[email protected]>; Matthias van de Meent <[email protected]>; Ashutosh Sharma <[email protected]>; Julien Rouhaud <[email protected]>; Bruce Momjian <[email protected]>; Tom Lane <[email protected]>; Magnus Hagander <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi,
On 2022-11-16 16:01:55 +0530, Bharath Rupireddy wrote:
> -1 for CheckpointerShmemStruct as it is being used for running
> checkpoints and I don't think adding stats to it is a great idea.
Why? Imo the data needed for progress reporting aren't really "stats". We'd
not accumulate counters over time, just for the current checkpoint.
I think it might even be useful for other parts of the system to know what the
checkpointer is doing, e.g. bgwriter or autovacuum could adapt the behaviour
if checkpointer can't keep up. Somehow it'd feel wrong to use the stats system
as the source of such adjustments - but perhaps my gut feeling on that isn't
right.
The best argument for combining progress reporting with accumulating stats is
that we could likely share some of the code. Having accumulated stats for all
the checkpoint phases would e.g. be quite valuable.
> Instead, extending PgStat_CheckpointerStats and using shared memory
> stats for reporting progress/last checkpoint related stats is a good
> idea IMO
There's certainly some potential for deduplicating state and to make stats
updated more frequently. But that doesn't necessarily mean that putting the
checkpoint progress into PgStat_CheckpointerStats is a good idea (nor the
opposite).
> I also think that a new pg_stat_checkpoint view is needed
> because, right now, the PgStat_CheckpointerStats stats are exposed via
> the pg_stat_bgwriter view, having a separate view for checkpoint stats
> is good here.
I agree that we should do that, but largely independent of the architectural
question at hand.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs)
@ 2022-11-16 19:19 Robert Haas <[email protected]>
parent: Bharath Rupireddy <[email protected]>
1 sibling, 2 replies; 199+ messages in thread
From: Robert Haas @ 2022-11-16 19:19 UTC (permalink / raw)
To: Bharath Rupireddy <[email protected]>; +Cc: Drouvot, Bertrand <[email protected]>; Nitin Jadhav <[email protected]>; Andres Freund <[email protected]>; Matthias van de Meent <[email protected]>; Ashutosh Sharma <[email protected]>; Julien Rouhaud <[email protected]>; Bruce Momjian <[email protected]>; Tom Lane <[email protected]>; Magnus Hagander <[email protected]>; PostgreSQL Hackers <[email protected]>
On Wed, Nov 16, 2022 at 5:32 AM Bharath Rupireddy
<[email protected]> wrote:
> -1 for CheckpointerShmemStruct as it is being used for running
> checkpoints and I don't think adding stats to it is a great idea.
> Instead, extending PgStat_CheckpointerStats and using shared memory
> stats for reporting progress/last checkpoint related stats is a good
> idea IMO.
I agree with Andres: progress reporting isn't really quite the same
thing as stats, and either place seems like it could be reasonable. I
don't presently have an opinion on which is a better fit, but I don't
think the fact that CheckpointerShmemStruct is used for running
checkpoints rules anything out. Progress reporting is *also* about
running checkpoints. Any historical data you want to expose might not
be about running checkpoints, but, uh, so what? I don't really see
that as a strong argument against it fitting into this struct.
> I also think that a new pg_stat_checkpoint view is needed
> because, right now, the PgStat_CheckpointerStats stats are exposed via
> the pg_stat_bgwriter view, having a separate view for checkpoint stats
> is good here.
Yep.
> Also, removing CheckpointStatsData and moving all of
> those members to PgStat_CheckpointerStats, of course, by being careful
> about the amount of shared memory required, is also a good idea IMO.
> Going forward, PgStat_CheckpointerStats and pg_stat_checkpoint view
> can be a single point of location for all the checkpoint related
> stats.
I'm not sure that I completely follow this part, or that I agree with
it. I have never really understood why we drive background writer or
checkpointer statistics through the statistics collector. Here again,
for things like table statistics, there is no choice, because we could
have an unbounded number of tables and need to keep statistics about
all of them. The statistics collector can handle that by allocating
more memory as required. But there is only one background writer and
only one checkpointer, so that is not needed in those cases. Why not
just have them expose anything they want to expose through shared
memory directly?
If the statistics collector provides services that we care about, like
persisting data across restarts or making snapshots for transactional
behavior, then those might be reasons to go through it even for the
background writer or checkpointer. But if so, we should be explicit
about what the reasons are, both in the mailing list discussion and in
code comments. Otherwise I fear that we'll just end up doing something
in a more complicated way than is really necessary.
--
Robert Haas
EDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs)
@ 2022-11-16 19:52 Andres Freund <[email protected]>
parent: Robert Haas <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Andres Freund @ 2022-11-16 19:52 UTC (permalink / raw)
To: Robert Haas <[email protected]>; Melanie Plageman <[email protected]>; +Cc: Bharath Rupireddy <[email protected]>; Drouvot, Bertrand <[email protected]>; Nitin Jadhav <[email protected]>; Matthias van de Meent <[email protected]>; Ashutosh Sharma <[email protected]>; Julien Rouhaud <[email protected]>; Bruce Momjian <[email protected]>; Tom Lane <[email protected]>; Magnus Hagander <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi,
On 2022-11-16 14:19:32 -0500, Robert Haas wrote:
> I have never really understood why we drive background writer or
> checkpointer statistics through the statistics collector.
To some degree it is required for durability - the stats system needs to know
how to write out those stats. But that wasn't ever a good reason to send
messages to the stats collector - it could just read the stats from shared
memory after all.
There's also integration with snapshots of the stats, resetting them, etc.
There's also the complexity that some of the stats e.g. for checkpointer
aren't about work the checkpointer did, but just have ended up there for
historical raisins. E.g. the number of fsyncs and writes done by backends.
See below:
> Here again, for things like table statistics, there is no choice, because we
> could have an unbounded number of tables and need to keep statistics about
> all of them. The statistics collector can handle that by allocating more
> memory as required. But there is only one background writer and only one
> checkpointer, so that is not needed in those cases. Why not just have them
> expose anything they want to expose through shared memory directly?
That's how it is in 15+. The memory for "fixed-numbered" or "global"
statistics are maintained by the stats system, but in plain shared memory,
allocated at server start. Not via the hash table.
Right now stats updates for the checkpointer use the "changecount" approach to
updates. For now that makes sense, because we update the stats only
occasionally (after a checkpoint or when writing in CheckpointWriteDelay()) -
a stats viewer seeing the checkpoint count go up, without yet seeing the
corresponding buffers written would be misleading.
I don't think we'd want every buffer write or whatnot go through the
changecount mechanism, on some non-x86 platforms that could be noticable. But
if we didn't stage the stats updates locally I think we could make most of the
stats changes without that overhead. For updates that just increment a single
counter there's simply no benefit in the changecount mechanism afaict.
I didn't want to do that change during the initial shared memory stats work,
it already was bigger than I could handle...
It's not quite clear to me what the best path forward is for
buf_written_backend / buf_fsync_backend, which currently are reported via the
checkpointer stats. I think the best path might be to stop counting them via
the CheckpointerShmem->num_backend_writes etc and just populate the fields in
the view (for backward compat) via the proposed [1] pg_stat_io patch. Doing
that accounting with CheckpointerCommLock held exclusively isn't free.
> If the statistics collector provides services that we care about, like
> persisting data across restarts or making snapshots for transactional
> behavior, then those might be reasons to go through it even for the
> background writer or checkpointer. But if so, we should be explicit
> about what the reasons are, both in the mailing list discussion and in
> code comments. Otherwise I fear that we'll just end up doing something
> in a more complicated way than is really necessary.
I tried to provide at least some of that in the comments at the start of
pgstat.c in 15+. There's very likely more that should be added, but I think
it's a decent start.
Greetings,
Andres Freund
[1] https://www.postgresql.org/message-id/CAOtHd0ApHna7_p6mvHoO%2BgLZdxjaQPRemg3_o0a4ytCPijLytQ%40mail.g...
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs)
@ 2022-11-17 13:31 Bharath Rupireddy <[email protected]>
parent: Robert Haas <[email protected]>
1 sibling, 0 replies; 199+ messages in thread
From: Bharath Rupireddy @ 2022-11-17 13:31 UTC (permalink / raw)
To: Robert Haas <[email protected]>; +Cc: Drouvot, Bertrand <[email protected]>; Nitin Jadhav <[email protected]>; Andres Freund <[email protected]>; Matthias van de Meent <[email protected]>; Ashutosh Sharma <[email protected]>; Julien Rouhaud <[email protected]>; Bruce Momjian <[email protected]>; Tom Lane <[email protected]>; Magnus Hagander <[email protected]>; PostgreSQL Hackers <[email protected]>
On Thu, Nov 17, 2022 at 12:49 AM Robert Haas <[email protected]> wrote:
>
> > I also think that a new pg_stat_checkpoint view is needed
> > because, right now, the PgStat_CheckpointerStats stats are exposed via
> > the pg_stat_bgwriter view, having a separate view for checkpoint stats
> > is good here.
>
> Yep.
On Wed, Nov 16, 2022 at 11:44 PM Andres Freund <[email protected]> wrote:
>
> > I also think that a new pg_stat_checkpoint view is needed
> > because, right now, the PgStat_CheckpointerStats stats are exposed via
> > the pg_stat_bgwriter view, having a separate view for checkpoint stats
> > is good here.
>
> I agree that we should do that, but largely independent of the architectural
> question at hand.
Thanks. I quickly prepared a patch introducing pg_stat_checkpointer
view and posted it here -
https://www.postgresql.org/message-id/CALj2ACVxX2ii%3D66RypXRweZe2EsBRiPMj0aHfRfHUeXJcC7kHg%40mail.g....
--
Bharath Rupireddy
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs)
@ 2022-11-17 14:03 Robert Haas <[email protected]>
parent: Andres Freund <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Robert Haas @ 2022-11-17 14:03 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Melanie Plageman <[email protected]>; Bharath Rupireddy <[email protected]>; Drouvot, Bertrand <[email protected]>; Nitin Jadhav <[email protected]>; Matthias van de Meent <[email protected]>; Ashutosh Sharma <[email protected]>; Julien Rouhaud <[email protected]>; Bruce Momjian <[email protected]>; Tom Lane <[email protected]>; Magnus Hagander <[email protected]>; PostgreSQL Hackers <[email protected]>
On Wed, Nov 16, 2022 at 2:52 PM Andres Freund <[email protected]> wrote:
> I don't think we'd want every buffer write or whatnot go through the
> changecount mechanism, on some non-x86 platforms that could be noticable. But
> if we didn't stage the stats updates locally I think we could make most of the
> stats changes without that overhead. For updates that just increment a single
> counter there's simply no benefit in the changecount mechanism afaict.
You might be right, but I'm not sure whether it's worth stressing
about. The progress reporting mechanism uses the st_changecount
mechanism, too, and as far as I know nobody's complained about that
having too much overhead. Maybe they have, though, and I've just
missed it.
--
Robert Haas
EDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs)
@ 2022-11-17 16:24 Andres Freund <[email protected]>
parent: Robert Haas <[email protected]>
0 siblings, 2 replies; 199+ messages in thread
From: Andres Freund @ 2022-11-17 16:24 UTC (permalink / raw)
To: Robert Haas <[email protected]>; +Cc: Melanie Plageman <[email protected]>; Bharath Rupireddy <[email protected]>; Drouvot, Bertrand <[email protected]>; Nitin Jadhav <[email protected]>; Matthias van de Meent <[email protected]>; Ashutosh Sharma <[email protected]>; Julien Rouhaud <[email protected]>; Bruce Momjian <[email protected]>; Tom Lane <[email protected]>; Magnus Hagander <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi,
On 2022-11-17 09:03:32 -0500, Robert Haas wrote:
> On Wed, Nov 16, 2022 at 2:52 PM Andres Freund <[email protected]> wrote:
> > I don't think we'd want every buffer write or whatnot go through the
> > changecount mechanism, on some non-x86 platforms that could be noticable. But
> > if we didn't stage the stats updates locally I think we could make most of the
> > stats changes without that overhead. For updates that just increment a single
> > counter there's simply no benefit in the changecount mechanism afaict.
>
> You might be right, but I'm not sure whether it's worth stressing
> about. The progress reporting mechanism uses the st_changecount
> mechanism, too, and as far as I know nobody's complained about that
> having too much overhead. Maybe they have, though, and I've just
> missed it.
I've seen it in profiles, although not as the major contributor. Most places
do a reasonable amount of work between calls though.
As an experiment, I added a progress report to BufferSync()'s first loop
(i.e. where it checks all buffers). On a 128GB shared_buffers cluster that
increases the time for a do-nothing checkpoint from ~235ms to ~280ms. If I
remove the changecount stuff and use a single write + write barrier, it ends
up as 250ms. Inlining brings it down a bit further, to 247ms.
Obviously this is a very extreme case - we only do very little work between
the progress report calls. But it does seem to show that the overhead is not
entirely neglegible.
I think pgstat_progress_start_command() needs the changecount stuff, as does
pgstat_progress_update_multi_param(). But for anything updating a single
parameter at a time it really doesn't do anything useful on a platform that
doesn't tear 64bit writes (so it could be #ifdef
PG_HAVE_8BYTE_SINGLE_COPY_ATOMICITY).
Out of further curiosity I wanted to test the impact when the loop doesn't
even do a LockBufHdr() and added an unlocked pre-check. 109ms without
progress. 138ms with. 114ms with the simplified
pgstat_progress_update_param(). 108ms after inlining the simplified
pgstat_progress_update_param().
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs)
@ 2022-11-17 17:18 Tom Lane <[email protected]>
parent: Andres Freund <[email protected]>
1 sibling, 0 replies; 199+ messages in thread
From: Tom Lane @ 2022-11-17 17:18 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Robert Haas <[email protected]>; Melanie Plageman <[email protected]>; Bharath Rupireddy <[email protected]>; Drouvot, Bertrand <[email protected]>; Nitin Jadhav <[email protected]>; Matthias van de Meent <[email protected]>; Ashutosh Sharma <[email protected]>; Julien Rouhaud <[email protected]>; Bruce Momjian <[email protected]>; Magnus Hagander <[email protected]>; PostgreSQL Hackers <[email protected]>
Andres Freund <[email protected]> writes:
> I think pgstat_progress_start_command() needs the changecount stuff, as does
> pgstat_progress_update_multi_param(). But for anything updating a single
> parameter at a time it really doesn't do anything useful on a platform that
> doesn't tear 64bit writes (so it could be #ifdef
> PG_HAVE_8BYTE_SINGLE_COPY_ATOMICITY).
Seems safe to restrict it to that case.
regards, tom lane
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs)
@ 2022-11-17 17:21 Robert Haas <[email protected]>
parent: Andres Freund <[email protected]>
1 sibling, 0 replies; 199+ messages in thread
From: Robert Haas @ 2022-11-17 17:21 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Melanie Plageman <[email protected]>; Bharath Rupireddy <[email protected]>; Drouvot, Bertrand <[email protected]>; Nitin Jadhav <[email protected]>; Matthias van de Meent <[email protected]>; Ashutosh Sharma <[email protected]>; Julien Rouhaud <[email protected]>; Bruce Momjian <[email protected]>; Tom Lane <[email protected]>; Magnus Hagander <[email protected]>; PostgreSQL Hackers <[email protected]>
On Thu, Nov 17, 2022 at 11:24 AM Andres Freund <[email protected]> wrote:
> As an experiment, I added a progress report to BufferSync()'s first loop
> (i.e. where it checks all buffers). On a 128GB shared_buffers cluster that
> increases the time for a do-nothing checkpoint from ~235ms to ~280ms. If I
> remove the changecount stuff and use a single write + write barrier, it ends
> up as 250ms. Inlining brings it down a bit further, to 247ms.
OK, I'd say that's pretty good evidence that we can't totally
disregard the issue.
--
Robert Haas
EDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs)
@ 2022-12-07 19:03 Andres Freund <[email protected]>
parent: Drouvot, Bertrand <[email protected]>
3 siblings, 0 replies; 199+ messages in thread
From: Andres Freund @ 2022-12-07 19:03 UTC (permalink / raw)
To: Drouvot, Bertrand <[email protected]>; +Cc: Nitin Jadhav <[email protected]>; Bharath Rupireddy <[email protected]>; Matthias van de Meent <[email protected]>; Ashutosh Sharma <[email protected]>; Julien Rouhaud <[email protected]>; Bruce Momjian <[email protected]>; Tom Lane <[email protected]>; Magnus Hagander <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi,
On 2022-11-04 09:25:52 +0100, Drouvot, Bertrand wrote:
> Please find attached a rebase in v7.
cfbot complains that the docs don't build:
https://cirrus-ci.com/task/6694349031866368?logs=docs_build#L296
[03:24:27.317] ref/checkpoint.sgml:66: element para: validity error : Element para is not declared in para list of possible children
I've marked the patch as waitin-on-author for now.
There's been a bunch of architectural feedback too, but tbh, I don't know if
we came to any conclusion on that front...
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-24 10:11 Corey Huinker <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Corey Huinker @ 2025-02-24 10:11 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Tom Lane <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
>
> That's a good point but let's avoid excessive redundancy in the
> structures. Adding a few fields to RelStatsInfo should be enough.
>
> Regards,
> Jeff Davis
>
Incorporating most of the feedback (I kept a few of
the appendNamedArgument() calls) presented over the weekend.
* removeVersionNumStr is gone
* relpages/reltuples/relallvisible are now char[32] buffers in RelStatsInfo
and nowhere else (existing relpages conversion remains, however)
* attribute stats export query is now prepared, and queries pg_stats with
no joins
* version parameter moved to end of both queries for consistency.
Attachments:
[text/x-patch] v2-0001-Leverage-existing-functions-for-relation-stats.patch (9.5K, ../../CADkLM=dRMC6t8gp9GVf6y6E_r5EChQjMAAh_vPyih_zMiq0zvA@mail.gmail.com/3-v2-0001-Leverage-existing-functions-for-relation-stats.patch)
download | inline diff:
From 95127f6fd82bde843e5840de93678ff784750c8a Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Mon, 24 Feb 2025 02:38:22 -0500
Subject: [PATCH v2 1/3] Leverage existing functions for relation stats.
Rather than quer pg_class once per relation in order to fetch relation
stats (reltuples, relpages, relallvisible), instead just add those
fields to the existing queries in getTables() and getIndexes(), then
then store their string representations in RelStatsInfo.
---
src/bin/pg_dump/pg_dump.c | 117 ++++++++++++++++----------------------
src/bin/pg_dump/pg_dump.h | 3 +
2 files changed, 52 insertions(+), 68 deletions(-)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index afd79287177..a5e7aa73671 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6814,7 +6814,8 @@ getFuncs(Archive *fout)
*
*/
static RelStatsInfo *
-getRelationStatistics(Archive *fout, DumpableObject *rel, char relkind)
+getRelationStatistics(Archive *fout, DumpableObject *rel, const char *relpages,
+ const char *reltuples, const char *relallvisible, char relkind)
{
if (!fout->dopt->dumpStatistics)
return NULL;
@@ -6839,6 +6840,9 @@ getRelationStatistics(Archive *fout, DumpableObject *rel, char relkind)
dobj->components |= DUMP_COMPONENT_STATISTICS;
dobj->name = pg_strdup(rel->name);
dobj->namespace = rel->namespace;
+ strncpy(info->relpages, relpages,sizeof(info->relpages));
+ strncpy(info->reltuples, reltuples, sizeof(info->reltuples));
+ strncpy(info->relallvisible, relallvisible, sizeof(info->relallvisible));
info->relkind = relkind;
info->postponed_def = false;
@@ -6874,6 +6878,8 @@ getTables(Archive *fout, int *numTables)
int i_relhasindex;
int i_relhasrules;
int i_relpages;
+ int i_reltuples;
+ int i_relallvisible;
int i_toastpages;
int i_owning_tab;
int i_owning_col;
@@ -6921,6 +6927,7 @@ getTables(Archive *fout, int *numTables)
appendPQExpBufferStr(query,
"SELECT c.tableoid, c.oid, c.relname, "
"c.relnamespace, c.relkind, c.reltype, "
+ "c.relpages, c.reltuples, c.relallvisible, "
"c.relowner, "
"c.relchecks, "
"c.relhasindex, c.relhasrules, c.relpages, "
@@ -7088,6 +7095,8 @@ getTables(Archive *fout, int *numTables)
i_relhasindex = PQfnumber(res, "relhasindex");
i_relhasrules = PQfnumber(res, "relhasrules");
i_relpages = PQfnumber(res, "relpages");
+ i_reltuples = PQfnumber(res, "reltuples");
+ i_relallvisible = PQfnumber(res, "relallvisible");
i_toastpages = PQfnumber(res, "toastpages");
i_owning_tab = PQfnumber(res, "owning_tab");
i_owning_col = PQfnumber(res, "owning_col");
@@ -7233,7 +7242,14 @@ getTables(Archive *fout, int *numTables)
/* Add statistics */
if (tblinfo[i].interesting)
- getRelationStatistics(fout, &tblinfo[i].dobj, tblinfo[i].relkind);
+ {
+ char *relpages = PQgetvalue(res, i, i_relpages);
+ char *reltuples = PQgetvalue(res, i, i_reltuples);
+ char *relallvisible = PQgetvalue(res, i, i_relallvisible);
+
+ getRelationStatistics(fout, &tblinfo[i].dobj, relpages, reltuples,
+ relallvisible, tblinfo[i].relkind);
+ }
/*
* Read-lock target tables to make sure they aren't DROPPED or altered
@@ -7499,6 +7515,9 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
i_oid,
i_indrelid,
i_indexname,
+ i_relpages,
+ i_reltuples,
+ i_relallvisible,
i_parentidx,
i_indexdef,
i_indnkeyatts,
@@ -7552,6 +7571,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
appendPQExpBufferStr(query,
"SELECT t.tableoid, t.oid, i.indrelid, "
"t.relname AS indexname, "
+ "t.relpages, t.reltuples, t.relallvisible, "
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
"i.indkey, i.indisclustered, "
"c.contype, c.conname, "
@@ -7659,6 +7679,9 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
i_oid = PQfnumber(res, "oid");
i_indrelid = PQfnumber(res, "indrelid");
i_indexname = PQfnumber(res, "indexname");
+ i_relpages = PQfnumber(res, "relpages");
+ i_reltuples = PQfnumber(res, "reltuples");
+ i_relallvisible = PQfnumber(res, "relallvisible");
i_parentidx = PQfnumber(res, "parentidx");
i_indexdef = PQfnumber(res, "indexdef");
i_indnkeyatts = PQfnumber(res, "indnkeyatts");
@@ -7725,6 +7748,9 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
char contype;
char indexkind;
RelStatsInfo *relstats;
+ char *relpages;
+ char *reltuples;
+ char *relallvisible;
indxinfo[j].dobj.objType = DO_INDEX;
indxinfo[j].dobj.catId.tableoid = atooid(PQgetvalue(res, j, i_tableoid));
@@ -7758,8 +7784,13 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
else
indexkind = RELKIND_PARTITIONED_INDEX;
+ relpages = PQgetvalue(res, j, i_relpages);
+ reltuples = PQgetvalue(res, j, i_reltuples);
+ relallvisible = PQgetvalue(res, j, i_relallvisible);
+
contype = *(PQgetvalue(res, j, i_contype));
- relstats = getRelationStatistics(fout, &indxinfo[j].dobj, indexkind);
+ relstats = getRelationStatistics(fout, &indxinfo[j].dobj, relpages,
+ reltuples, relallvisible, indexkind);
if (contype == 'p' || contype == 'u' || contype == 'x')
{
@@ -10383,18 +10414,6 @@ dumpComment(Archive *fout, const char *type,
catalogId, subid, dumpId, NULL);
}
-/*
- * Tabular description of the parameters to pg_restore_relation_stats()
- * param_name, param_type
- */
-static const char *rel_stats_arginfo[][2] = {
- {"relation", "regclass"},
- {"version", "integer"},
- {"relpages", "integer"},
- {"reltuples", "real"},
- {"relallvisible", "integer"},
-};
-
/*
* Tabular description of the parameters to pg_restore_attribute_stats()
* param_name, param_type
@@ -10419,30 +10438,6 @@ static const char *att_stats_arginfo[][2] = {
{"range_bounds_histogram", "text"},
};
-/*
- * getRelStatsExportQuery --
- *
- * Generate a query that will fetch all relation (e.g. pg_class)
- * stats for a given relation.
- */
-static void
-getRelStatsExportQuery(PQExpBuffer query, Archive *fout,
- const char *schemaname, const char *relname)
-{
- resetPQExpBuffer(query);
- appendPQExpBufferStr(query,
- "SELECT c.oid::regclass AS relation, "
- "current_setting('server_version_num') AS version, "
- "c.relpages, c.reltuples, c.relallvisible "
- "FROM pg_class c "
- "JOIN pg_namespace n "
- "ON n.oid = c.relnamespace "
- "WHERE n.nspname = ");
- appendStringLiteralAH(query, schemaname, fout);
- appendPQExpBufferStr(query, " AND c.relname = ");
- appendStringLiteralAH(query, relname, fout);
-}
-
/*
* getAttStatsExportQuery --
*
@@ -10521,33 +10516,23 @@ appendNamedArgument(PQExpBuffer out, Archive *fout, const char *argname,
* Append a formatted pg_restore_relation_stats statement.
*/
static void
-appendRelStatsImport(PQExpBuffer out, Archive *fout, PGresult *res)
+appendRelStatsImport(PQExpBuffer out, Archive *fout, const RelStatsInfo *rsinfo)
{
- const char *sep = "";
+ const char *qualname = fmtQualifiedId(rsinfo->dobj.namespace->dobj.name, rsinfo->dobj.name);
+ char version[32];
- if (PQntuples(res) == 0)
- return;
+ snprintf(version, sizeof(version), "%d", fout->remoteVersion);
appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
-
- for (int argno = 0; argno < lengthof(rel_stats_arginfo); argno++)
- {
- const char *argname = rel_stats_arginfo[argno][0];
- const char *argtype = rel_stats_arginfo[argno][1];
- int fieldno = PQfnumber(res, argname);
-
- if (fieldno < 0)
- pg_fatal("relation stats export query missing field '%s'",
- argname);
-
- if (PQgetisnull(res, 0, fieldno))
- continue;
-
- appendPQExpBufferStr(out, sep);
- appendNamedArgument(out, fout, argname, PQgetvalue(res, 0, fieldno), argtype);
-
- sep = ",\n";
- }
+ appendNamedArgument(out, fout, "relation", qualname, "regclass");
+ appendPQExpBufferStr(out, ",\n");
+ appendNamedArgument(out, fout, "version", version, "integer");
+ appendPQExpBufferStr(out, ",\n");
+ appendNamedArgument(out, fout, "relpages", rsinfo->relpages, "integer");
+ appendPQExpBufferStr(out, ",\n");
+ appendNamedArgument(out, fout, "reltuples", rsinfo->reltuples, "real");
+ appendPQExpBufferStr(out, ",\n");
+ appendNamedArgument(out, fout, "relallvisible", rsinfo->relallvisible, "integer");
appendPQExpBufferStr(out, "\n);\n");
}
@@ -10643,15 +10628,11 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
tag = createPQExpBuffer();
appendPQExpBufferStr(tag, fmtId(dobj->name));
- query = createPQExpBuffer();
out = createPQExpBuffer();
- getRelStatsExportQuery(query, fout, dobj->namespace->dobj.name,
- dobj->name);
- res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
- appendRelStatsImport(out, fout, res);
- PQclear(res);
+ appendRelStatsImport(out, fout, rsinfo);
+ query = createPQExpBuffer();
getAttStatsExportQuery(query, fout, dobj->namespace->dobj.name,
dobj->name);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f08f5905aa3..1ab134ae414 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -438,6 +438,9 @@ typedef struct _indexAttachInfo
typedef struct _relStatsInfo
{
DumpableObject dobj;
+ char relpages[32];
+ char reltuples[32];
+ char relallvisible[32];
char relkind; /* 'r', 'm', 'i', etc */
bool postponed_def; /* stats must be postponed into post-data */
} RelStatsInfo;
base-commit: 2421e9a51d20bb83154e54a16ce628f9249fa907
--
2.48.1
[text/x-patch] v2-0002-Move-attribute-statistics-fetching-to-a-prepared-.patch (9.1K, ../../CADkLM=dRMC6t8gp9GVf6y6E_r5EChQjMAAh_vPyih_zMiq0zvA@mail.gmail.com/4-v2-0002-Move-attribute-statistics-fetching-to-a-prepared-.patch)
download | inline diff:
From 5a03d2935999cac037daeaac566e7709f1824f5e Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Mon, 24 Feb 2025 04:36:16 -0500
Subject: [PATCH v2 2/3] Move attribute statistics fetching to a prepared
statement.
Simplify the query to pg_stats by removing the joins to namespace and
pg_class, these were only needed because we wanted to get the relation
oid, but that's information that we already have.
Also, create a new prepared statement getAttributeStats for this query,
as it will be run once per relation.
Additionally, pull the server_version_num out of the export query, as we
already have that information. However, because version appeared in the
middle of the arginfo array, it has to move to either before the arginfo
loop, which would mix it in with the grain of the call (attname and
inherited would follow it) or to the last argument pair, which is what
was done.
Make the same move for the version parameter in the
pg_set_relation_stats() calls for consistency.
---
src/bin/pg_dump/pg_backup.h | 3 +-
src/bin/pg_dump/pg_dump.c | 137 +++++++++++++++++-------------------
2 files changed, 68 insertions(+), 72 deletions(-)
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index 350cf659c41..b78724671c5 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -74,9 +74,10 @@ enum _dumpPreparedQueries
PREPQUERY_DUMPTABLEATTACH,
PREPQUERY_GETCOLUMNACLS,
PREPQUERY_GETDOMAINCONSTRAINTS,
+ PREPQUERY_ATTRIBUTESTATS,
};
-#define NUM_PREP_QUERIES (PREPQUERY_GETDOMAINCONSTRAINTS + 1)
+#define NUM_PREP_QUERIES (PREPQUERY_ATTRIBUTESTATS + 1)
/* Parameters needed by ConnectDatabase; same for dump and restore */
typedef struct _connParams
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5e7aa73671..bbd415d5477 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -10419,10 +10419,8 @@ dumpComment(Archive *fout, const char *type,
* param_name, param_type
*/
static const char *att_stats_arginfo[][2] = {
- {"relation", "regclass"},
{"attname", "name"},
{"inherited", "boolean"},
- {"version", "integer"},
{"null_frac", "float4"},
{"avg_width", "integer"},
{"n_distinct", "float4"},
@@ -10438,59 +10436,6 @@ static const char *att_stats_arginfo[][2] = {
{"range_bounds_histogram", "text"},
};
-/*
- * getAttStatsExportQuery --
- *
- * Generate a query that will fetch all attribute (e.g. pg_statistic)
- * stats for a given relation.
- */
-static void
-getAttStatsExportQuery(PQExpBuffer query, Archive *fout,
- const char *schemaname, const char *relname)
-{
- resetPQExpBuffer(query);
- appendPQExpBufferStr(query,
- "SELECT c.oid::regclass AS relation, "
- "s.attname,"
- "s.inherited,"
- "current_setting('server_version_num') AS version, "
- "s.null_frac,"
- "s.avg_width,"
- "s.n_distinct,"
- "s.most_common_vals,"
- "s.most_common_freqs,"
- "s.histogram_bounds,"
- "s.correlation,"
- "s.most_common_elems,"
- "s.most_common_elem_freqs,"
- "s.elem_count_histogram,");
-
- if (fout->remoteVersion >= 170000)
- appendPQExpBufferStr(query,
- "s.range_length_histogram,"
- "s.range_empty_frac,"
- "s.range_bounds_histogram ");
- else
- appendPQExpBufferStr(query,
- "NULL AS range_length_histogram,"
- "NULL AS range_empty_frac,"
- "NULL AS range_bounds_histogram ");
-
- appendPQExpBufferStr(query,
- "FROM pg_stats s "
- "JOIN pg_namespace n "
- "ON n.nspname = s.schemaname "
- "JOIN pg_class c "
- "ON c.relname = s.tablename "
- "AND c.relnamespace = n.oid "
- "WHERE s.schemaname = ");
- appendStringLiteralAH(query, schemaname, fout);
- appendPQExpBufferStr(query, " AND s.tablename = ");
- appendStringLiteralAH(query, relname, fout);
- appendPQExpBufferStr(query, " ORDER BY s.attname, s.inherited");
-}
-
-
/*
* appendNamedArgument --
*
@@ -10516,23 +10461,20 @@ appendNamedArgument(PQExpBuffer out, Archive *fout, const char *argname,
* Append a formatted pg_restore_relation_stats statement.
*/
static void
-appendRelStatsImport(PQExpBuffer out, Archive *fout, const RelStatsInfo *rsinfo)
+appendRelStatsImport(PQExpBuffer out, Archive *fout,
+ const RelStatsInfo *rsinfo, const char *qualname,
+ const char *version)
{
- const char *qualname = fmtQualifiedId(rsinfo->dobj.namespace->dobj.name, rsinfo->dobj.name);
- char version[32];
-
- snprintf(version, sizeof(version), "%d", fout->remoteVersion);
-
appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
appendNamedArgument(out, fout, "relation", qualname, "regclass");
appendPQExpBufferStr(out, ",\n");
- appendNamedArgument(out, fout, "version", version, "integer");
- appendPQExpBufferStr(out, ",\n");
appendNamedArgument(out, fout, "relpages", rsinfo->relpages, "integer");
appendPQExpBufferStr(out, ",\n");
appendNamedArgument(out, fout, "reltuples", rsinfo->reltuples, "real");
appendPQExpBufferStr(out, ",\n");
appendNamedArgument(out, fout, "relallvisible", rsinfo->relallvisible, "integer");
+ appendPQExpBufferStr(out, ",\n");
+ appendNamedArgument(out, fout, "version", version, "integer");
appendPQExpBufferStr(out, "\n);\n");
}
@@ -10542,13 +10484,16 @@ appendRelStatsImport(PQExpBuffer out, Archive *fout, const RelStatsInfo *rsinfo)
* Append a series of formatted pg_restore_attribute_stats statements.
*/
static void
-appendAttStatsImport(PQExpBuffer out, Archive *fout, PGresult *res)
+appendAttStatsImport(PQExpBuffer out, Archive *fout, PGresult *res,
+ const char *qualname, const char *version)
{
+ const char *sep = ",\n";
+
for (int rownum = 0; rownum < PQntuples(res); rownum++)
{
- const char *sep = "";
-
appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
+ appendNamedArgument(out, fout, "relation", qualname, "regclass");
+
for (int argno = 0; argno < lengthof(att_stats_arginfo); argno++)
{
const char *argname = att_stats_arginfo[argno][0];
@@ -10564,8 +10509,9 @@ appendAttStatsImport(PQExpBuffer out, Archive *fout, PGresult *res)
appendPQExpBufferStr(out, sep);
appendNamedArgument(out, fout, argname, PQgetvalue(res, rownum, fieldno), argtype);
- sep = ",\n";
}
+ appendPQExpBufferStr(out, sep);
+ appendNamedArgument(out, fout, "version", version, "integer");
appendPQExpBufferStr(out, "\n);\n");
}
}
@@ -10613,6 +10559,8 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
DumpableObject *dobj = (DumpableObject *) &rsinfo->dobj;
DumpId *deps = NULL;
int ndeps = 0;
+ const char *qualname;
+ char version[32];
/* nothing to do if we are not dumping statistics */
if (!fout->dopt->dumpStatistics)
@@ -10625,18 +10573,64 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
ndeps = dobj->nDeps;
}
+ qualname = pg_strdup(fmtQualifiedId(rsinfo->dobj.namespace->dobj.name, rsinfo->dobj.name));
+ snprintf(version, sizeof(version), "%d", fout->remoteVersion);
+
tag = createPQExpBuffer();
appendPQExpBufferStr(tag, fmtId(dobj->name));
out = createPQExpBuffer();
- appendRelStatsImport(out, fout, rsinfo);
+ appendRelStatsImport(out, fout, rsinfo, qualname, version);
query = createPQExpBuffer();
- getAttStatsExportQuery(query, fout, dobj->namespace->dobj.name,
- dobj->name);
+ if (!fout->is_prepared[PREPQUERY_ATTRIBUTESTATS])
+ {
+ appendPQExpBufferStr(query,
+ "PREPARE getAttributeStats(pg_catalog.name, pg_catalog.name) AS\n"
+ "SELECT "
+ "s.attname, "
+ "s.inherited, "
+ "s.null_frac, "
+ "s.avg_width, "
+ "s.n_distinct, "
+ "s.most_common_vals, "
+ "s.most_common_freqs, "
+ "s.histogram_bounds, "
+ "s.correlation, "
+ "s.most_common_elems, "
+ "s.most_common_elem_freqs, "
+ "s.elem_count_histogram, ");
+
+ if (fout->remoteVersion >= 170000)
+ appendPQExpBufferStr(query,
+ "s.range_length_histogram,"
+ "s.range_empty_frac,"
+ "s.range_bounds_histogram ");
+ else
+ appendPQExpBufferStr(query,
+ "NULL AS range_length_histogram,"
+ "NULL AS range_empty_frac,"
+ "NULL AS range_bounds_histogram ");
+
+ appendPQExpBufferStr(query,
+ "FROM pg_stats s "
+ "WHERE s.schemaname = $1 "
+ "AND s.tablename = $2 "
+ "ORDER BY s.attname, s.inherited");
+
+ ExecuteSqlStatement(fout, query->data);
+
+ fout->is_prepared[PREPQUERY_ATTRIBUTESTATS] = true;
+ }
+
+ printfPQExpBuffer(query, "EXECUTE getAttributeStats(");
+ appendStringLiteralAH(query, dobj->namespace->dobj.name, fout);
+ appendPQExpBufferStr(query, ", ");
+ appendStringLiteralAH(query, dobj->name, fout);
+ appendPQExpBufferStr(query, "); ");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
- appendAttStatsImport(out, fout, res);
+ appendAttStatsImport(out, fout, res, qualname, version);
PQclear(res);
ArchiveEntry(fout, nilCatalogId, createDumpId(),
@@ -10652,6 +10646,7 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
destroyPQExpBuffer(query);
destroyPQExpBuffer(out);
destroyPQExpBuffer(tag);
+ pg_free((void *) qualname);
}
/*
--
2.48.1
[text/x-patch] v2-0003-Remove-nonsense-bounds-checking-of-relpages-relal.patch (2.6K, ../../CADkLM=dRMC6t8gp9GVf6y6E_r5EChQjMAAh_vPyih_zMiq0zvA@mail.gmail.com/5-v2-0003-Remove-nonsense-bounds-checking-of-relpages-relal.patch)
download | inline diff:
From 02bb2a98e7e290c53e3e130b330073d56af93f53 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Mon, 24 Feb 2025 05:01:59 -0500
Subject: [PATCH v2 3/3] Remove nonsense bounds checking of relpages,
relallvisible.
Change both to type BlockNumber and fetch as PG_GETARG_UINT32 while
we're at it.
---
src/backend/statistics/relation_stats.c | 49 ++++---------------------
1 file changed, 8 insertions(+), 41 deletions(-)
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index 046661d7c3f..e532c1ef6c7 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -62,62 +62,29 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel, bool inplace)
{
Oid reloid;
Relation crel;
- int32 relpages = DEFAULT_RELPAGES;
+ BlockNumber relpages = DEFAULT_RELPAGES;
bool update_relpages = false;
float reltuples = DEFAULT_RELTUPLES;
bool update_reltuples = false;
- int32 relallvisible = DEFAULT_RELALLVISIBLE;
+ BlockNumber relallvisible = DEFAULT_RELALLVISIBLE;
bool update_relallvisible = false;
- bool result = true;
if (!PG_ARGISNULL(RELPAGES_ARG))
{
- relpages = PG_GETARG_INT32(RELPAGES_ARG);
-
- /*
- * Partitioned tables may have relpages=-1. Note: for relations with
- * no storage, relpages=-1 is not used consistently, but must be
- * supported here.
- */
- if (relpages < -1)
- {
- ereport(elevel,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("relpages cannot be < -1")));
- result = false;
- }
- else
- update_relpages = true;
+ relpages = PG_GETARG_UINT32(RELPAGES_ARG);
+ update_relpages = true;
}
if (!PG_ARGISNULL(RELTUPLES_ARG))
{
reltuples = PG_GETARG_FLOAT4(RELTUPLES_ARG);
-
- if (reltuples < -1.0)
- {
- ereport(elevel,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("reltuples cannot be < -1.0")));
- result = false;
- }
- else
- update_reltuples = true;
+ update_reltuples = true;
}
if (!PG_ARGISNULL(RELALLVISIBLE_ARG))
{
- relallvisible = PG_GETARG_INT32(RELALLVISIBLE_ARG);
-
- if (relallvisible < 0)
- {
- ereport(elevel,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("relallvisible cannot be < 0")));
- result = false;
- }
- else
- update_relallvisible = true;
+ relallvisible = PG_GETARG_UINT32(RELALLVISIBLE_ARG);
+ update_relallvisible = true;
}
stats_check_required_arg(fcinfo, relarginfo, RELATION_ARG);
@@ -237,7 +204,7 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel, bool inplace)
CommandCounterIncrement();
- return result;
+ return true;
}
/*
--
2.48.1
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-24 14:54 Andres Freund <[email protected]>
parent: Corey Huinker <[email protected]>
0 siblings, 4 replies; 199+ messages in thread
From: Andres Freund @ 2025-02-24 14:54 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Jeff Davis <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Hi,
On 2025-02-24 05:11:48 -0500, Corey Huinker wrote:
> Incorporating most of the feedback (I kept a few of
> the appendNamedArgument() calls) presented over the weekend.
>
> * removeVersionNumStr is gone
> * relpages/reltuples/relallvisible are now char[32] buffers in RelStatsInfo
> and nowhere else (existing relpages conversion remains, however)
I don't see the point. This will use more memory and if we can't get
conversions between integers and strings right we have much bigger
problems. The same code was used in the backend too!
And it leads to storing relpages in two places, with different
transformations, which doesn't seem great.
> @@ -6921,6 +6927,7 @@ getTables(Archive *fout, int *numTables)
> appendPQExpBufferStr(query,
> "SELECT c.tableoid, c.oid, c.relname, "
> "c.relnamespace, c.relkind, c.reltype, "
> + "c.relpages, c.reltuples, c.relallvisible, "
> "c.relowner, "
> "c.relchecks, "
> "c.relhasindex, c.relhasrules, c.relpages, "
That query is already querying relpages a bit later in the query, so we'd
query the column twice.
> + printfPQExpBuffer(query, "EXECUTE getAttributeStats(");
> + appendStringLiteralAH(query, dobj->namespace->dobj.name, fout);
> + appendPQExpBufferStr(query, ", ");
> + appendStringLiteralAH(query, dobj->name, fout);
> + appendPQExpBufferStr(query, "); ");
> res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
It seems somewhat ugly that we're building an SQL string with non-trivial
constants. It'd be better to use PQexecParams() - but I guess we don't have
any uses of it yet in pg_dump.
ISTM that we ought to expose the relation oid in pg_stats. This query would be
simpler and faster if we could just use the oid as the predicate. Will take a
while till we can rely on that, but still.
Have you compared performance of with/without stats after these optimizations?
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-24 16:15 Jeff Davis <[email protected]>
parent: Andres Freund <[email protected]>
3 siblings, 0 replies; 199+ messages in thread
From: Jeff Davis @ 2025-02-24 16:15 UTC (permalink / raw)
To: Andres Freund <[email protected]>; Corey Huinker <[email protected]>; +Cc: Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Mon, 2025-02-24 at 09:54 -0500, Andres Freund wrote:
> ISTM that we ought to expose the relation oid in pg_stats. This query
> would be
> simpler and faster if we could just use the oid as the predicate.
> Will take a
> while till we can rely on that, but still.
+1. Maybe an internal view that exposes only starelid/staattnum, and
pg_stats could just be a simple join on top of that?
There's another annoyance, which is that pg_stats doesn't expose any
custom stakinds, so we lose those, but I'm not sure if that's worth
trying to fix.
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-24 17:50 Tom Lane <[email protected]>
parent: Andres Freund <[email protected]>
3 siblings, 3 replies; 199+ messages in thread
From: Tom Lane @ 2025-02-24 17:50 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Corey Huinker <[email protected]>; Jeff Davis <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Andres Freund <[email protected]> writes:
> On 2025-02-24 05:11:48 -0500, Corey Huinker wrote:
>> * relpages/reltuples/relallvisible are now char[32] buffers in RelStatsInfo
>> and nowhere else (existing relpages conversion remains, however)
> I don't see the point. This will use more memory and if we can't get
> conversions between integers and strings right we have much bigger
> problems. The same code was used in the backend too!
I don't like that either. But there's a bigger problem with 0002:
it's still got mostly table-driven output. I've been working on
fixing the problem discussed over in the -committers thread about how
we need to identify index-expression columns by number not name [1].
It's not too awful in the backend (WIP patch attached), but
getting appendAttStatsImport to do it seems like a complete disaster,
and this patch fails to make that any easier. It'd be much better
if you gave up on that table-driven business and just open-coded the
handling of the successive output values as was discussed upthread.
I don't think the table-driven approach has anything to recommend it
anyway. It requires keeping att_stats_arginfo[] in sync with the
query in getAttStatsExportQuery, an extremely nonobvious (and
undocumented) connection. Personally I would nuke the separate
getAttStatsExportQuery and appendAttStatsImport functions altogether,
and have one function that executes a query and immediately interprets
the PGresult.
Also, while working on the attached, I couldn't help forming the
opinion that we'd be better off to nuke pg_set_attribute_stats()
from orbit and require people to use pg_restore_attribute_stats().
pg_set_attribute_stats() would be fine if we had a way to force
people to call it with only named-argument notation, but we don't.
So I'm afraid that its existence will encourage people to rely
on a specific parameter order, and then they'll whine if we
add/remove/reorder parameters, as indeed I had to do below.
BTW, I pushed the 0003 patch with minor adjustments.
regards, tom lane
[1] https://www.postgresql.org/message-id/816167.1740278884%40sss.pgh.pa.us
Attachments:
[text/x-diff] allow-stats-att-name-or-number-wip.patch (11.4K, ../../[email protected]/2-allow-stats-att-name-or-number-wip.patch)
download | inline diff:
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9f60a476eb..ad59e3be9d 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -30302,6 +30302,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<function>pg_set_attribute_stats</function> (
<parameter>relation</parameter> <type>regclass</type>,
<parameter>attname</parameter> <type>name</type>,
+ <parameter>attnum</parameter> <type>integer</type>,
<parameter>inherited</parameter> <type>boolean</type>
<optional>, <parameter>null_frac</parameter> <type>real</type></optional>
<optional>, <parameter>avg_width</parameter> <type>integer</type></optional>
@@ -30318,14 +30319,17 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
</para>
<para>
Creates or updates attribute-level statistics for the given relation
- and attribute name to the specified values. The parameters correspond
+ and attribute name (or number) to the specified values. The
+ parameters correspond
to attributes of the same name found in the <link
linkend="view-pg-stats"><structname>pg_stats</structname></link>
view.
</para>
<para>
Optional parameters default to <literal>NULL</literal>, which leave
- the corresponding statistic unchanged.
+ the corresponding statistic unchanged. Exactly one
+ of <parameter>attname</parameter> and <parameter>attnum</parameter>
+ must be non-<literal>NULL</literal>.
</para>
<para>
Ordinarily, these statistics are collected automatically or updated
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 591157b1d1..876500824e 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -648,8 +648,9 @@ AS 'pg_set_relation_stats';
CREATE OR REPLACE FUNCTION
pg_set_attribute_stats(relation regclass,
- attname name,
- inherited bool,
+ attname name DEFAULT NULL,
+ attnum integer DEFAULT NULL,
+ inherited bool DEFAULT NULL,
null_frac real DEFAULT NULL,
avg_width integer DEFAULT NULL,
n_distinct real DEFAULT NULL,
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index c0c398a4bb..4886f79611 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -38,6 +38,7 @@ enum attribute_stats_argnum
{
ATTRELATION_ARG = 0,
ATTNAME_ARG,
+ ATTNUM_ARG,
INHERITED_ARG,
NULL_FRAC_ARG,
AVG_WIDTH_ARG,
@@ -59,6 +60,7 @@ static struct StatsArgInfo attarginfo[] =
{
[ATTRELATION_ARG] = {"relation", REGCLASSOID},
[ATTNAME_ARG] = {"attname", NAMEOID},
+ [ATTNUM_ARG] = {"attnum", INT4OID},
[INHERITED_ARG] = {"inherited", BOOLOID},
[NULL_FRAC_ARG] = {"null_frac", FLOAT4OID},
[AVG_WIDTH_ARG] = {"avg_width", INT4OID},
@@ -76,6 +78,22 @@ static struct StatsArgInfo attarginfo[] =
[NUM_ATTRIBUTE_STATS_ARGS] = {0}
};
+enum clear_attribute_stats_argnum
+{
+ C_ATTRELATION_ARG = 0,
+ C_ATTNAME_ARG,
+ C_INHERITED_ARG,
+ C_NUM_ATTRIBUTE_STATS_ARGS
+};
+
+static struct StatsArgInfo cleararginfo[] =
+{
+ [C_ATTRELATION_ARG] = {"relation", REGCLASSOID},
+ [C_ATTNAME_ARG] = {"attname", NAMEOID},
+ [C_INHERITED_ARG] = {"inherited", BOOLOID},
+ [C_NUM_ATTRIBUTE_STATS_ARGS] = {0}
+};
+
static bool attribute_statistics_update(FunctionCallInfo fcinfo, int elevel);
static Node *get_attr_expr(Relation rel, int attnum);
static void get_attr_stat_type(Oid reloid, AttrNumber attnum, int elevel,
@@ -116,9 +134,9 @@ static bool
attribute_statistics_update(FunctionCallInfo fcinfo, int elevel)
{
Oid reloid;
- Name attname;
- bool inherited;
+ char *attname;
AttrNumber attnum;
+ bool inherited;
Relation starel;
HeapTuple statup;
@@ -164,21 +182,51 @@ attribute_statistics_update(FunctionCallInfo fcinfo, int elevel)
/* lock before looking up attribute */
stats_lock_check_privileges(reloid);
- stats_check_required_arg(fcinfo, attarginfo, ATTNAME_ARG);
- attname = PG_GETARG_NAME(ATTNAME_ARG);
- attnum = get_attnum(reloid, NameStr(*attname));
+ /* user can specify either attname or attnum, but not both */
+ if (!PG_ARGISNULL(ATTNAME_ARG))
+ {
+ Name attnamename;
+
+ if (!PG_ARGISNULL(ATTNUM_ARG))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("must specify one of attname and attnum")));
+ attnamename = PG_GETARG_NAME(ATTNAME_ARG);
+ attname = NameStr(*attnamename);
+ attnum = get_attnum(reloid, attname);
+ /* note that this test covers attisdropped cases too: */
+ if (attnum == InvalidAttrNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ attname, get_rel_name(reloid))));
+ }
+ else if (!PG_ARGISNULL(ATTNUM_ARG))
+ {
+ attnum = PG_GETARG_INT32(ATTNUM_ARG);
+ attname = get_attname(reloid, attnum, true);
+ /* Annoyingly, get_attname doesn't check attisdropped */
+ if (attname == NULL ||
+ !SearchSysCacheExistsAttName(reloid, attname))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column %d of relation \"%s\" does not exist",
+ attnum, get_rel_name(reloid))));
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("must specify one of attname and attnum")));
+ attname = NULL; /* keep compiler quiet */
+ attnum = 0;
+ }
if (attnum < 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot modify statistics on system column \"%s\"",
- NameStr(*attname))));
-
- if (attnum == InvalidAttrNumber)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
- NameStr(*attname), get_rel_name(reloid))));
+ attname)));
stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG);
inherited = PG_GETARG_BOOL(INHERITED_ARG);
@@ -245,7 +293,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo, int elevel)
&elemtypid, &elem_eq_opr))
{
ereport(elevel,
- (errmsg("unable to determine element type of attribute \"%s\"", NameStr(*attname)),
+ (errmsg("unable to determine element type of attribute \"%s\"", attname),
errdetail("Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.")));
elemtypid = InvalidOid;
elem_eq_opr = InvalidOid;
@@ -261,7 +309,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo, int elevel)
{
ereport(elevel,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not determine less-than operator for attribute \"%s\"", NameStr(*attname)),
+ errmsg("could not determine less-than operator for attribute \"%s\"", attname),
errdetail("Cannot set STATISTIC_KIND_HISTOGRAM or STATISTIC_KIND_CORRELATION.")));
do_histogram = false;
@@ -275,7 +323,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo, int elevel)
{
ereport(elevel,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("attribute \"%s\" is not a range type", NameStr(*attname)),
+ errmsg("attribute \"%s\" is not a range type", attname),
errdetail("Cannot set STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM or STATISTIC_KIND_BOUNDS_HISTOGRAM.")));
do_bounds_histogram = false;
@@ -855,8 +903,8 @@ init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
* Import statistics for a given relation attribute.
*
* Inserts or replaces a row in pg_statistic for the given relation and
- * attribute name. It takes input parameters that correspond to columns in the
- * view pg_stats.
+ * attribute name or number. It takes input parameters that correspond to
+ * columns in the view pg_stats.
*
* Parameters null_frac, avg_width, and n_distinct all correspond to NOT NULL
* columns in pg_statistic. The remaining parameters all belong to a specific
@@ -889,8 +937,8 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
AttrNumber attnum;
bool inherited;
- stats_check_required_arg(fcinfo, attarginfo, ATTRELATION_ARG);
- reloid = PG_GETARG_OID(ATTRELATION_ARG);
+ stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELATION_ARG);
+ reloid = PG_GETARG_OID(C_ATTRELATION_ARG);
if (RecoveryInProgress())
ereport(ERROR,
@@ -900,8 +948,8 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
stats_lock_check_privileges(reloid);
- stats_check_required_arg(fcinfo, attarginfo, ATTNAME_ARG);
- attname = PG_GETARG_NAME(ATTNAME_ARG);
+ stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
+ attname = PG_GETARG_NAME(C_ATTNAME_ARG);
attnum = get_attnum(reloid, NameStr(*attname));
if (attnum < 0)
@@ -916,8 +964,8 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
errmsg("column \"%s\" of relation \"%s\" does not exist",
NameStr(*attname), get_rel_name(reloid))));
- stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG);
- inherited = PG_GETARG_BOOL(INHERITED_ARG);
+ stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
+ inherited = PG_GETARG_BOOL(C_INHERITED_ARG);
delete_pg_statistic(reloid, attnum, inherited);
PG_RETURN_VOID();
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index af9546de23..ce714b1fd1 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12433,8 +12433,8 @@
descr => 'set statistics on attribute',
proname => 'pg_set_attribute_stats', provolatile => 'v', proisstrict => 'f',
proparallel => 'u', prorettype => 'void',
- proargtypes => 'regclass name bool float4 int4 float4 text _float4 text float4 text _float4 _float4 text float4 text',
- proargnames => '{relation,attname,inherited,null_frac,avg_width,n_distinct,most_common_vals,most_common_freqs,histogram_bounds,correlation,most_common_elems,most_common_elem_freqs,elem_count_histogram,range_length_histogram,range_empty_frac,range_bounds_histogram}',
+ proargtypes => 'regclass name int4 bool float4 int4 float4 text _float4 text float4 text _float4 _float4 text float4 text',
+ proargnames => '{relation,attname,attnum,inherited,null_frac,avg_width,n_distinct,most_common_vals,most_common_freqs,histogram_bounds,correlation,most_common_elems,most_common_elem_freqs,elem_count_histogram,range_length_histogram,range_empty_frac,range_bounds_histogram}',
prosrc => 'pg_set_attribute_stats' },
{ oid => '9163',
descr => 'clear statistics on attribute',
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 0e8491131e..a020ff015d 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -364,7 +364,7 @@ SELECT pg_catalog.pg_set_attribute_stats(
null_frac => 0.1::real,
avg_width => 2::integer,
n_distinct => 0.3::real);
-ERROR: "attname" cannot be NULL
+ERROR: must specify one of attname and attnum
-- error: inherited null
SELECT pg_catalog.pg_set_attribute_stats(
relation => 'stats_import.test'::regclass,
@@ -968,7 +968,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'null_frac', 0.1::real,
'avg_width', 2::integer,
'n_distinct', 0.3::real);
-ERROR: "attname" cannot be NULL
+ERROR: must specify one of attname and attnum
-- error: attname doesn't exist
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-24 17:57 Corey Huinker <[email protected]>
parent: Andres Freund <[email protected]>
3 siblings, 1 reply; 199+ messages in thread
From: Corey Huinker @ 2025-02-24 17:57 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Jeff Davis <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Mon, Feb 24, 2025 at 9:54 AM Andres Freund <[email protected]> wrote:
> Hi,
>
> On 2025-02-24 05:11:48 -0500, Corey Huinker wrote:
> > Incorporating most of the feedback (I kept a few of
> > the appendNamedArgument() calls) presented over the weekend.
> >
> > * removeVersionNumStr is gone
> > * relpages/reltuples/relallvisible are now char[32] buffers in
> RelStatsInfo
> > and nowhere else (existing relpages conversion remains, however)
>
> I don't see the point. This will use more memory and if we can't get
> conversions between integers and strings right we have much bigger
> problems. The same code was used in the backend too!
>
As I see it, the point is that we're getting an input that is a string
representation from the query, and the end-goal is to convey that value
with fidelity to the destination database, so there's nothing we can do to
get us closer to the string that we already have.
I don't have benchmark numbers beyond the instinct that doing something
takes more time than doing nothing. Granted, "nothing" here means 96 bytes
of memory and 3 strncpy()s, and "something" is 24 bytes of memory, 2
atoi()s, 1 strtof() plus whatever memory and processing we do back in
converting back to strings.
> And it leads to storing relpages in two places, with different
> transformations, which doesn't seem great.
>
I didn't like that either, but balanced the ugliness of that vs the cost of
grinding the values back to where we started.
>
>
> > @@ -6921,6 +6927,7 @@ getTables(Archive *fout, int *numTables)
> > appendPQExpBufferStr(query,
> > "SELECT c.tableoid,
> c.oid, c.relname, "
> > "c.relnamespace,
> c.relkind, c.reltype, "
> > + "c.relpages, c.reltuples,
> c.relallvisible, "
> > "c.relowner, "
> > "c.relchecks, "
> > "c.relhasindex,
> c.relhasrules, c.relpages, "
>
> That query is already querying relpages a bit later in the query, so we'd
> query the column twice.
>
+1, must eliminate that duplicate.
>
>
>
> > + printfPQExpBuffer(query, "EXECUTE getAttributeStats(");
> > + appendStringLiteralAH(query, dobj->namespace->dobj.name, fout);
> > + appendPQExpBufferStr(query, ", ");
> > + appendStringLiteralAH(query, dobj->name, fout);
> > + appendPQExpBufferStr(query, "); ");
> > res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
>
> It seems somewhat ugly that we're building an SQL string with non-trivial
> constants. It'd be better to use PQexecParams() - but I guess we don't have
> any uses of it yet in pg_dump.
>
+1, I would like to see that change.
>
> ISTM that we ought to expose the relation oid in pg_stats. This query
> would be
> simpler and faster if we could just use the oid as the predicate. Will
> take a
> while till we can rely on that, but still.
>
+1, but we will need to support this until v18 is as old as v9.2 is
now...approx 2038.
> Have you compared performance of with/without stats after these
> optimizations?
I have not.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-24 18:42 Jeff Davis <[email protected]>
parent: Tom Lane <[email protected]>
2 siblings, 0 replies; 199+ messages in thread
From: Jeff Davis @ 2025-02-24 18:42 UTC (permalink / raw)
To: Tom Lane <[email protected]>; Andres Freund <[email protected]>; +Cc: Corey Huinker <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Mon, 2025-02-24 at 12:50 -0500, Tom Lane wrote:
> Also, while working on the attached, I couldn't help forming the
> opinion that we'd be better off to nuke pg_set_attribute_stats()
> from orbit and require people to use pg_restore_attribute_stats().
I had intended the pg_set variants to be useful for ad-hoc stats
hacking (e.g. for reproducing a plan or for testing the optimizer). For
those use cases, the following differences seem nice:
1. named arguments are easier to write ad-hoc than lining up the
parameters in pairs
2. elevel=ERROR makes more sense than WARNING for that kind of use
case.
3. for relation stats, we don't want in-place updates, because you
want ROLLBACK to work
Those seemed different enough from the restore case that another entry
point made sense to me.
> pg_set_attribute_stats() would be fine if we had a way to force
> people to call it with only named-argument notation, but we don't.
> So I'm afraid that its existence will encourage people to rely
> on a specific parameter order, and then they'll whine if we
> add/remove/reorder parameters, as indeed I had to do below.
That's a good point that I hadn't considered, so perhaps we can't solve
problem #1. The other two problems might be solvable though:
* To avoid in-place updates I think we do need a separate function,
at least for relation stats (attribute stats never do in-place
updates). We could potentially have another name/value pair to choose,
but it's impossible to choose a reasonable default: if "inplace" is the
default, that means the user would need to opt-out of it for ROLLBACK
to work; if "mvcc" is the default, that means pg_dump would need to
choose "inplace", and I don't think pg_dump should be making those
kinds of decisions.
* The elevel=ERROR is not terribly important, so perhaps we can just
always do elevel=WARNING. If we did try to present it as an option,
then that presents the same problems as an "inplace" option.
So perhaps we can just have the pg_set variants set elevel=ERROR and
inplace=false, and otherwise be identical to the pg_restore variants?
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-24 18:47 Corey Huinker <[email protected]>
parent: Tom Lane <[email protected]>
2 siblings, 2 replies; 199+ messages in thread
From: Corey Huinker @ 2025-02-24 18:47 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Andres Freund <[email protected]>; Jeff Davis <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Mon, Feb 24, 2025 at 12:51 PM Tom Lane <[email protected]> wrote:
> Andres Freund <[email protected]> writes:
> > On 2025-02-24 05:11:48 -0500, Corey Huinker wrote:
> >> * relpages/reltuples/relallvisible are now char[32] buffers in
> RelStatsInfo
> >> and nowhere else (existing relpages conversion remains, however)
>
> > I don't see the point. This will use more memory and if we can't get
> > conversions between integers and strings right we have much bigger
> > problems. The same code was used in the backend too!
>
> I don't like that either. But there's a bigger problem with 0002:
> it's still got mostly table-driven output. I've been working on
> fixing the problem discussed over in the -committers thread about how
> we need to identify index-expression columns by number not name [1].
>
There doesn't seem to be any way around it, but it will slightly complicate
the dump-ing side of things, in that we need to either:
a) switch to attnums for index expressions and keep attname calls for
everything else.
b) track what the attnum will be on the destination side, which will be
different when we're not doing a binary upgrade and there are any preceding
dropped columns.
The patch Tom provided opens the door for option "a", and I'm inclined to
take it.
> It's not too awful in the backend (WIP patch attached), but
> getting appendAttStatsImport to do it seems like a complete disaster,
> and this patch fails to make that any easier. It'd be much better
> if you gave up on that table-driven business and just open-coded the
> handling of the successive output values as was discussed upthread.
>
Can do.
> I don't think the table-driven approach has anything to recommend it
> anyway. It requires keeping att_stats_arginfo[] in sync with the
> query in getAttStatsExportQuery, an extremely nonobvious (and
> undocumented) connection. Personally I would nuke the separate
> getAttStatsExportQuery and appendAttStatsImport functions altogether,
> and have one function that executes a query and immediately interprets
> the PGresult.
>
+1, though that comes at the cost of shutting off the possibility of a mass
fetch from pg_stats without also rendering the pg_restore_attribute_stats
calls at the same time.
>
> Also, while working on the attached, I couldn't help forming the
> opinion that we'd be better off to nuke pg_set_attribute_stats()
> from orbit and require people to use pg_restore_attribute_stats().
> pg_set_attribute_stats() would be fine if we had a way to force
> people to call it with only named-argument notation, but we don't.
> So I'm afraid that its existence will encourage people to rely
> on a specific parameter order, and then they'll whine if we
> add/remove/reorder parameters, as indeed I had to do below.
>
They've always had split goals. To recap for people just joining the show,
the "set" family had the following properties:
1. transactional, even for pg_class
2. assumes all stats given are relevant and correct for current db version
3. guaranteed to ERROR if any parameter doesn't check out
4. unstable call signature, can and will change to match the realities of
the current version
5. intended for planner experiments and fuzzing
and the "restore" family has the following properties:
1. will inplace update pg_class to avoid table bloat
2. states the version from whence the stats came, so that adjustments can
be made to suit the current db version, up to and including rejecting that
particular statistic
3. attempts to sidestep errors with WARNINGs so as not to kill a restore
4. stable but highly fluid kwargs-ish call signature
5. intended to be machine generated and used only in restore/upgrade
The attnum change certainly throws a wrench into that, and if we get rid of
the setter functions then we will need to (re)introduce parameters to
indicate our choice for properties 1 and 3. I suppose we could use the
existence or non-existence of the "version" parameter as an indicator of
which mode we want (if it exists, we want WARNINGS and inplace updates, if
not we want pure transactional and ERROR at the first problem), but I'm not
certain that proxy will hold true in the future.
> BTW, I pushed the 0003 patch with minor adjustments.
>
Thanks!
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-24 18:54 Jeff Davis <[email protected]>
parent: Corey Huinker <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Jeff Davis @ 2025-02-24 18:54 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; Tom Lane <[email protected]>; +Cc: Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Mon, 2025-02-24 at 13:47 -0500, Corey Huinker wrote:
> There doesn't seem to be any way around it, but it will
> slightly complicate the dump-ing side of things, in that we need to
> either:
>
> a) switch to attnums for index expressions and keep attname calls for
> everything else.
The only stats for indexes are on expression columns, so AFAICT there's
no difference between the above description and "use attnums for
indexes and attnames for tables". Either way, I agree that's the way to
go.
We certainly want attnames for tables to keep it working reasonably
well for cases where the user might be doing something more interesting
than a binary upgrade, as you point out. But attribute numbers for
indexes seem much more reliable: an index with a different attribute
order is a fundamentally different index.
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-24 19:34 Tom Lane <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Tom Lane @ 2025-02-24 19:34 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Jeff Davis <[email protected]> writes:
> We certainly want attnames for tables to keep it working reasonably
> well for cases where the user might be doing something more interesting
> than a binary upgrade, as you point out. But attribute numbers for
> indexes seem much more reliable: an index with a different attribute
> order is a fundamentally different index.
Right. We went through pretty much this reasoning, as I recall,
when we invented ALTER INDEX ... SET STATISTICS. The original
version used a column name like ALTER TABLE did, and we ran into
exactly the present problem that the names aren't too stable across
dump/restore, and we decided that index column numbers would do
instead. You can't add or drop a column of an index, nor redefine it
meaningfully, except by dropping the whole index which will make any
associated stats go away.
The draft patch I posted allows callers to use attname or attnum
at their option, because I didn't see a reason to restrict that.
But I envisioned that pg_dump would always use attname for table
columns and attnum for index columns.
regards, tom lane
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-24 19:45 Jeff Davis <[email protected]>
parent: Corey Huinker <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Jeff Davis @ 2025-02-24 19:45 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; Andres Freund <[email protected]>; +Cc: Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Mon, 2025-02-24 at 12:57 -0500, Corey Huinker wrote:
> As I see it, the point is that we're getting an input that is a
> string representation from the query, and the end-goal is to convey
> that value with fidelity to the destination database, so there's
> nothing we can do to get us closer to the string that we already
> have.
As Andres mentioned, for the float-to-string conversion, we're using
what the backend does, so it doesn't seem like a problem.
But you have a point in that float4in() does slightly more work than
strtof() to handle platform differences about NaN/Inf. I'm not sure how
much to weigh that concern, but I agree that there is non-zero
cognitive overhead here.
Should we solve that problem by moving some of that code to src/common
src/port somewhere?
> I don't have benchmark numbers beyond the instinct that
> doing something takes more time than doing nothing. Granted,
> "nothing" here means 96 bytes of memory and 3 strncpy()s, and
> "something" is 24 bytes of memory, 2 atoi()s, 1 strtof() plus
> whatever memory and processing we do back in converting back to
> strings.
To me, this argument is, at best, premature optimization. Even if there
were a few cycles saved here somewhere, you'd need to compare that
against the wasted memory. Using 2 int32s and a float4 is only 12 bytes
(not 24) versus 96 for the strings.
Anyone looking at the structure would be wondering (a) why we're using
32 bytes to store something where the natural representation is 4
bytes; and (b) whether that memory adds up to anything worth worrying
about. I'm sure we could analyze that and write an explanatory comment,
but that has non-zero cognitive overhead, as well.
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-24 20:03 Tom Lane <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Tom Lane @ 2025-02-24 20:03 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Jeff Davis <[email protected]> writes:
> But you have a point in that float4in() does slightly more work than
> strtof() to handle platform differences about NaN/Inf. I'm not sure how
> much to weigh that concern, but I agree that there is non-zero
> cognitive overhead here.
If we're speaking strictly about the reltuples value, I'm not hugely
concerned about that. reltuples should never be NaN or Inf. There
is a nonzero chance that it will round off to a fractionally
different value if we pass it through strtof/sprintf on the pg_dump
side, but nobody is really going to care about that. (Maybe our
own pg_dump test script would, thanks to its not-too-bright dump
comparison logic. But that script is never going to see reltuples
values that are big enough to be inexact in a float4.)
I do buy the better-preserve-it-exactly argument for other sorts
of statistics, where we don't have such a good sense of what might
matter.
regards, tom lane
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-24 20:20 Jeff Davis <[email protected]>
parent: Tom Lane <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Jeff Davis @ 2025-02-24 20:20 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Mon, 2025-02-24 at 15:03 -0500, Tom Lane wrote:
> Jeff Davis <[email protected]> writes:
> > But you have a point in that float4in() does slightly more work
> > than
> > strtof() to handle platform differences about NaN/Inf. I'm not sure
> > how
> > much to weigh that concern, but I agree that there is non-zero
> > cognitive overhead here.
>
> If we're speaking strictly about the reltuples value, I'm not hugely
> concerned about that. reltuples should never be NaN or Inf.
There actually is a concern here, in that the backend always has
LC_NUMERIC=C when doing float4in/out, but pg_dump does not. Patch
attached.
Regards,
Jeff Davis
Attachments:
[text/x-patch] pg-dump-setlocale.diff (480B, ../../[email protected]/2-pg-dump-setlocale.diff)
download | inline diff:
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 955550b91d2..ed85d843d5f 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -521,6 +521,9 @@ main(int argc, char **argv)
{NULL, 0, NULL, 0}
};
+ /* ensure that locale does not affect floating point interpretation */
+ setlocale(LC_NUMERIC, "C");
+
pg_logging_init(argv[0]);
pg_logging_set_level(PG_LOG_WARNING);
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_dump"));
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-24 20:36 Andres Freund <[email protected]>
parent: Corey Huinker <[email protected]>
1 sibling, 3 replies; 199+ messages in thread
From: Andres Freund @ 2025-02-24 20:36 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Tom Lane <[email protected]>; Jeff Davis <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Hi,
On 2025-02-24 13:47:19 -0500, Corey Huinker wrote:
> and the "restore" family has the following properties:
>
> 1. will inplace update pg_class to avoid table bloat
I suspect that this is a *really* bad idea. It's very very hard to get inplace
updates right. We have several unfixed correctness bugs that are related to
the use of inplace updates. I really don't think it's wise to add additional
interfaces that can reach inplace updates unless there's really no other
alternative (like not being able to assign an xid in VACUUM to be able to deal
with anti-xid-wraparound-shutdown systems).
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-24 20:40 Tom Lane <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Tom Lane @ 2025-02-24 20:40 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Jeff Davis <[email protected]> writes:
> There actually is a concern here, in that the backend always has
> LC_NUMERIC=C when doing float4in/out, but pg_dump does not.
Hmm ... interesting point, but does it matter? I think we always use
our own sprintf even in frontend, and it doesn't react to LC_NUMERIC.
I guess atof might be more of a concern though.
> Patch attached.
I'm a little suspicious whether that has any effect if you insert it
before set_pglocale_pgservice().
regards, tom lane
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-24 20:41 Jeff Davis <[email protected]>
parent: Andres Freund <[email protected]>
2 siblings, 0 replies; 199+ messages in thread
From: Jeff Davis @ 2025-02-24 20:41 UTC (permalink / raw)
To: Andres Freund <[email protected]>; Corey Huinker <[email protected]>; +Cc: Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Mon, 2025-02-24 at 15:36 -0500, Andres Freund wrote:
> > 1. will inplace update pg_class to avoid table bloat
>
> I suspect that this is a *really* bad idea.
The reason we added it is that it's what ANALYZE does, and a big
restore bloats pg_class without it.
I don't think those are major concerns for v1, so in principle I'm fine
removing it. But the problem is that it affects the documented
semantics, so it would be hard to change later, and we'd be stuck with
the bloating behavior forever.
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-24 20:45 Corey Huinker <[email protected]>
parent: Andres Freund <[email protected]>
2 siblings, 1 reply; 199+ messages in thread
From: Corey Huinker @ 2025-02-24 20:45 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Tom Lane <[email protected]>; Jeff Davis <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
>
>
>
> I suspect that this is a *really* bad idea. It's very very hard to get
> inplace
> updates right. We have several unfixed correctness bugs that are related to
> the use of inplace updates. I really don't think it's wise to add
> additional
> interfaces that can reach inplace updates unless there's really no other
> alternative (like not being able to assign an xid in VACUUM to be able to
> deal
> with anti-xid-wraparound-shutdown systems).
In this case, the alternative is an immediate doubling of the size of
pg_class right after a restore/upgrade.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-24 20:53 Andres Freund <[email protected]>
parent: Corey Huinker <[email protected]>
0 siblings, 2 replies; 199+ messages in thread
From: Andres Freund @ 2025-02-24 20:53 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Tom Lane <[email protected]>; Jeff Davis <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Hi,
On 2025-02-24 15:45:10 -0500, Corey Huinker wrote:
> >
> >
> >
> > I suspect that this is a *really* bad idea. It's very very hard to get
> > inplace
> > updates right. We have several unfixed correctness bugs that are related to
> > the use of inplace updates. I really don't think it's wise to add
> > additional
> > interfaces that can reach inplace updates unless there's really no other
> > alternative (like not being able to assign an xid in VACUUM to be able to
> > deal
> > with anti-xid-wraparound-shutdown systems).
>
>
> In this case, the alternative is an immediate doubling of the size of
> pg_class right after a restore/upgrade.
I don't think that's necessarily true, hot pruning might help some, as afaict
the restore happens in multiple transactions.
But even if that's the case, I don't think it's worth using in place updates
to avoid it. We should work to get rid of them, not introduce them in more
places.
And typically pg_class size isn't the relevant factor, it's pg_attribute etc.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-24 21:01 Jeff Davis <[email protected]>
parent: Andres Freund <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Jeff Davis @ 2025-02-24 21:01 UTC (permalink / raw)
To: Andres Freund <[email protected]>; Corey Huinker <[email protected]>; +Cc: Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Mon, 2025-02-24 at 15:53 -0500, Andres Freund wrote:
> I don't think that's necessarily true, hot pruning might help some,
> as afaict
> the restore happens in multiple transactions.
Yeah, I just dumped and reloaded the regression database with and
without stats, and saw no difference in the resulting size. So it's
probably more correct to say "churn" rather than "bloat".
Even running "psql -1", I see modest bloat substantially less than 2x.
So if we agree that we don't mind a bit of churn and we will never need
this (despite what ANALYZE does), then I'm OK removing it. Which makes
me wonder why ANALYZE does it with inplace updates?
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-24 21:01 Corey Huinker <[email protected]>
parent: Andres Freund <[email protected]>
1 sibling, 0 replies; 199+ messages in thread
From: Corey Huinker @ 2025-02-24 21:01 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Tom Lane <[email protected]>; Jeff Davis <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
>
> I don't think that's necessarily true, hot pruning might help some, as
> afaict
> the restore happens in multiple transactions.
>
If we're willing to take the potential bloat to avoid a nasty complexity,
then I'm all for discarding it. Jeff just indicated off-list that he isn't
seeing noticeable difference in table size, maybe we're safe with how we
use the function now.
>
> But even if that's the case, I don't think it's worth using in place
> updates
> to avoid it. We should work to get rid of them, not introduce them in more
> places.
>
As the number of statlike columns in pg_class grows, might it make sense to
break them off into their own relation, leaving pg_class to be far more
stable?
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-24 21:07 Jeff Davis <[email protected]>
parent: Tom Lane <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Jeff Davis @ 2025-02-24 21:07 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Mon, 2025-02-24 at 15:40 -0500, Tom Lane wrote:
> I'm a little suspicious whether that has any effect if you insert it
> before set_pglocale_pgservice().
Ah, right. Corey, can you please include that (in the right place, of
course) to the next iteration of your 0001 patch, if it's doing the
conversions to/from float4?
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-24 21:13 Corey Huinker <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Corey Huinker @ 2025-02-24 21:13 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Tom Lane <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Mon, Feb 24, 2025 at 4:07 PM Jeff Davis <[email protected]> wrote:
> On Mon, 2025-02-24 at 15:40 -0500, Tom Lane wrote:
> > I'm a little suspicious whether that has any effect if you insert it
> > before set_pglocale_pgservice().
>
> Ah, right. Corey, can you please include that (in the right place, of
> course) to the next iteration of your 0001 patch, if it's doing the
> conversions to/from float4?
>
Sure, I'm debating whether I want to solve the index-expression-attname
issue before embarking on the next iteration, or temporarily shelve that so
that we can review the other changes so far.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-25 03:30 jian he <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 2 replies; 199+ messages in thread
From: jian he @ 2025-02-25 03:30 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Andres Freund <[email protected]>; Corey Huinker <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Tue, Feb 25, 2025 at 5:01 AM Jeff Davis <[email protected]> wrote:
>
> On Mon, 2025-02-24 at 15:53 -0500, Andres Freund wrote:
> > I don't think that's necessarily true, hot pruning might help some,
> > as afaict
> > the restore happens in multiple transactions.
>
> Yeah, I just dumped and reloaded the regression database with and
> without stats, and saw no difference in the resulting size. So it's
> probably more correct to say "churn" rather than "bloat".
>
> Even running "psql -1", I see modest bloat substantially less than 2x.
>
> So if we agree that we don't mind a bit of churn and we will never need
> this (despite what ANALYZE does), then I'm OK removing it. Which makes
> me wonder why ANALYZE does it with inplace updates?
>
hi.
looking at commit:
https://git.postgresql.org/cgit/postgresql.git/commit/?id=f3dae2ae5856dec9935a51e53216400566ef8d4f
I am confused by this:
```
ctup = SearchSysCache1(RELOID, ObjectIdGetDatum(reloid));
if (!HeapTupleIsValid(ctup))
{
ereport(elevel,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("pg_class entry for relid %u not found", reloid)));
table_close(crel, RowExclusiveLock);
return false;
}
```
First I thought ERRCODE_OBJECT_IN_USE was weird. maybe
ERRCODE_NO_DATA_FOUND would be more appropriate.
then but ``stats_lock_check_privileges(reloid);`` already proves there is
a pg_class entry for reloid.
maybe we can just use
elog(ERROR, "pg_class entry for relid %u not found", reloid)));
also in stats_lock_check_privileges.
check_inplace_rel_lock related comments should be removed?
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-25 05:41 Ashutosh Bapat <[email protected]>
parent: jian he <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Ashutosh Bapat @ 2025-02-25 05:41 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; Corey Huinker <[email protected]>; +Cc: Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>
Hi Jeff and Corey,
I think I have found a bug (arguably) with the dump/restore test I am
developing at [1].
#create table t1 (a int primary key, b int);
CREATE TABLE
#insert into t1 values (1, 2);
INSERT 0 1
$ createdb rdb
$ pg_dump -d postgres | psql -d rdb
$ pg_dump -d postgres > /tmp/pgdb.out
ashutosh@localhost:~/work/units/pg_dump_test$ pg_dump -d rdb > /tmp/rdb.out
ashutosh@localhost:~/work/units/pg_dump_test$ diff /tmp/pgdb.out /tmp/rdb.out
52,53c52,53
< 'relpages', '0'::integer,
< 'reltuples', '-1'::real,
---
> 'relpages', '1'::integer,
> 'reltuples', '1'::real,
So the dumped statistics are not restored exactly. The reason for this
is the table statistics is dumped before dumping ALTER TABLE ... ADD
CONSTRAINT command which changes the statistics. I think all the
pg_restore_relation_stats() calls should be dumped after all the
schema and data modifications have been done. OR what's the point in
dumping statistics only to get rewritten even before restore finishes.
[1] https://www.postgresql.org/message-id/CAExHW5sBbMki6Xs4XxFQQF3C4Wx3wxkLAcySrtuW3vrnOxXDNQ%40mail.gma...
--
Best Wishes,
Ashutosh Bapat
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-25 08:10 Jeff Davis <[email protected]>
parent: jian he <[email protected]>
1 sibling, 0 replies; 199+ messages in thread
From: Jeff Davis @ 2025-02-25 08:10 UTC (permalink / raw)
To: jian he <[email protected]>; +Cc: Andres Freund <[email protected]>; Corey Huinker <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Tue, 2025-02-25 at 11:30 +0800, jian he wrote:
> maybe we can just use
> elog(ERROR, "pg_class entry for relid %u not found", reloid)));
Thank you.
> also in stats_lock_check_privileges.
> check_inplace_rel_lock related comments should be removed?
In-place update locking rules still apply when updating pg_class or
pg_database even if the current caller is not performing an in-place
update. It might be better to point instead to
check_lock_if_inplace_updateable_rel()?
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-25 18:22 Jeff Davis <[email protected]>
parent: Tom Lane <[email protected]>
2 siblings, 1 reply; 199+ messages in thread
From: Jeff Davis @ 2025-02-25 18:22 UTC (permalink / raw)
To: Tom Lane <[email protected]>; Andres Freund <[email protected]>; +Cc: Corey Huinker <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Mon, 2025-02-24 at 12:50 -0500, Tom Lane wrote:
> Also, while working on the attached, I couldn't help forming the
> opinion that we'd be better off to nuke pg_set_attribute_stats()
> from orbit and require people to use pg_restore_attribute_stats().
Attached a patch to do so. The docs and tests required substantial
rework, but I think it's for the better now that we aren't trying to do
in-place updates.
Regards,
Jeff Davis
Attachments:
[text/x-patch] v1-0001-Remove-redundant-pg_set_-_stats-variants.patch (101.6K, ../../[email protected]/2-v1-0001-Remove-redundant-pg_set_-_stats-variants.patch)
download | inline diff:
From ea413ee48b10299530bafc3102395285b5ea8ce3 Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Mon, 24 Feb 2025 17:24:05 -0800
Subject: [PATCH v1] Remove redundant pg_set_*_stats() variants.
After commit f3dae2ae58, the primary purpose of separating the
pg_set_*_stats() from the pg_restore_*_stats() variants was
eliminated.
Leave pg_restore_relation_stats() and pg_restore_attribute_stats(),
which satisfy both purposes, and remove pg_set_relation_stats() and
pg_set_attribute_stats().
Discussion: https://postgr.es/m/[email protected]
---
doc/src/sgml/func.sgml | 254 +++----
src/backend/catalog/system_functions.sql | 32 -
src/backend/statistics/attribute_stats.c | 98 +--
src/backend/statistics/relation_stats.c | 24 +-
src/backend/statistics/stat_utils.c | 30 +-
src/include/catalog/pg_proc.dat | 14 -
src/include/statistics/stat_utils.h | 8 +-
src/test/regress/expected/stats_import.out | 832 +--------------------
src/test/regress/sql/stats_import.sql | 648 +---------------
9 files changed, 209 insertions(+), 1731 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index f0ccb751106..12206e0cfc6 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -30180,66 +30180,6 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
</thead>
<tbody>
- <row>
- <entry role="func_table_entry">
- <para role="func_signature">
- <indexterm>
- <primary>pg_set_relation_stats</primary>
- </indexterm>
- <function>pg_set_relation_stats</function> (
- <parameter>relation</parameter> <type>regclass</type>
- <optional>, <parameter>relpages</parameter> <type>integer</type></optional>
- <optional>, <parameter>reltuples</parameter> <type>real</type></optional>
- <optional>, <parameter>relallvisible</parameter> <type>integer</type></optional> )
- <returnvalue>void</returnvalue>
- </para>
- <para>
- Updates relation-level statistics for the given relation to the
- specified values. The parameters correspond to columns in <link
- linkend="catalog-pg-class"><structname>pg_class</structname></link>. Unspecified
- or <literal>NULL</literal> values leave the setting unchanged.
- </para>
- <para>
- Ordinarily, these statistics are collected automatically or updated
- as a part of <xref linkend="sql-vacuum"/> or <xref
- linkend="sql-analyze"/>, so it's not necessary to call this
- function. However, it may be useful when testing the effects of
- statistics on the planner to understand or anticipate plan changes.
- </para>
- <para>
- The caller must have the <literal>MAINTAIN</literal> privilege on
- the table or be the owner of the database.
- </para>
- <para>
- The value of <structfield>relpages</structfield> must be greater than
- or equal to <literal>-1</literal>,
- <structfield>reltuples</structfield> must be greater than or equal to
- <literal>-1.0</literal>, and <structfield>relallvisible</structfield>
- must be greater than or equal to <literal>0</literal>.
- </para>
- </entry>
- </row>
-
- <row>
- <entry role="func_table_entry">
- <para role="func_signature">
- <indexterm>
- <primary>pg_clear_relation_stats</primary>
- </indexterm>
- <function>pg_clear_relation_stats</function> ( <parameter>relation</parameter> <type>regclass</type> )
- <returnvalue>void</returnvalue>
- </para>
- <para>
- Clears table-level statistics for the given relation, as though the
- table was newly created.
- </para>
- <para>
- The caller must have the <literal>MAINTAIN</literal> privilege on
- the table or be the owner of the database.
- </para>
- </entry>
- </row>
-
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
@@ -30248,26 +30188,25 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<function>pg_restore_relation_stats</function> (
<literal>VARIADIC</literal> <parameter>kwargs</parameter> <type>"any"</type> )
<returnvalue>boolean</returnvalue>
- </para>
- <para>
- Similar to <function>pg_set_relation_stats()</function>, but intended
- for bulk restore of relation statistics. The tracked statistics may
- change from version to version, so the primary purpose of this
- function is to maintain a consistent function signature to avoid
- errors when restoring statistics from previous versions.
- </para>
+ </para>
<para>
- Arguments are passed as pairs of <replaceable>argname</replaceable>
- and <replaceable>argvalue</replaceable>, where
- <replaceable>argname</replaceable> corresponds to a named argument in
- <function>pg_set_relation_stats()</function> and
- <replaceable>argvalue</replaceable> is of the corresponding type.
+ Updates table-level statistics. Ordinarily, these statistics are
+ collected automatically or updated as a part of <xref
+ linkend="sql-vacuum"/> or <xref linkend="sql-analyze"/>, so it's not
+ necessary to call this function. However, it is useful after a
+ restore to enable the optimizer to choose better plans if
+ <command>ANALYZE</command> has not been run yet.
</para>
<para>
- Additionally, this function supports argument name
- <literal>version</literal> of type <type>integer</type>, which
- specifies the version from which the statistics originated, improving
- interpretation of older statistics.
+ The tracked statistics may change from version to version, so
+ arguments are passed as pairs of <replaceable>argname</replaceable>
+ and <replaceable>argvalue</replaceable> in the form:
+<programlisting>
+ SELECT pg_restore_relation_stats(
+ '<replaceable>arg1name</replaceable>', '<replaceable>arg1value</replaceable>'::<replaceable>arg1type</replaceable>,
+ '<replaceable>arg2name</replaceable>', '<replaceable>arg2value</replaceable>'::<replaceable>arg2type</replaceable>,
+ '<replaceable>arg3name</replaceable>', '<replaceable>arg3value</replaceable>'::<replaceable>arg3type</replaceable>);
+</programlisting>
</para>
<para>
For example, to set the <structname>relpages</structname> and
@@ -30277,62 +30216,37 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
SELECT pg_restore_relation_stats(
'relation', 'mytable'::regclass,
'relpages', 173::integer,
- 'reltuples', 10000::float4);
+ 'reltuples', 10000::real);
</programlisting>
</para>
<para>
- Minor errors are reported as a <literal>WARNING</literal> and
- ignored, and remaining statistics will still be restored. If all
- specified statistics are successfully restored, return
- <literal>true</literal>, otherwise <literal>false</literal>.
- </para>
- </entry>
- </row>
-
- <row>
- <entry role="func_table_entry">
- <para role="func_signature">
- <indexterm>
- <primary>pg_set_attribute_stats</primary>
- </indexterm>
- <function>pg_set_attribute_stats</function> (
- <parameter>relation</parameter> <type>regclass</type>,
- <parameter>attname</parameter> <type>name</type>,
- <parameter>inherited</parameter> <type>boolean</type>
- <optional>, <parameter>null_frac</parameter> <type>real</type></optional>
- <optional>, <parameter>avg_width</parameter> <type>integer</type></optional>
- <optional>, <parameter>n_distinct</parameter> <type>real</type></optional>
- <optional>, <parameter>most_common_vals</parameter> <type>text</type>, <parameter>most_common_freqs</parameter> <type>real[]</type> </optional>
- <optional>, <parameter>histogram_bounds</parameter> <type>text</type> </optional>
- <optional>, <parameter>correlation</parameter> <type>real</type> </optional>
- <optional>, <parameter>most_common_elems</parameter> <type>text</type>, <parameter>most_common_elem_freqs</parameter> <type>real[]</type> </optional>
- <optional>, <parameter>elem_count_histogram</parameter> <type>real[]</type> </optional>
- <optional>, <parameter>range_length_histogram</parameter> <type>text</type> </optional>
- <optional>, <parameter>range_empty_frac</parameter> <type>real</type> </optional>
- <optional>, <parameter>range_bounds_histogram</parameter> <type>text</type> </optional> )
- <returnvalue>void</returnvalue>
- </para>
- <para>
- Creates or updates attribute-level statistics for the given relation
- and attribute name to the specified values. The parameters correspond
- to attributes of the same name found in the <link
- linkend="view-pg-stats"><structname>pg_stats</structname></link>
- view.
+ The argument <literal>relation</literal> with a value of type
+ <type>regclass</type> is required, and specifies the table. Other
+ arguments are the names of statistics corresponding to certain
+ columns in <link
+ linkend="catalog-pg-class"><structname>pg_class</structname></link>.
+ The currently-supported relation statistics are
+ <literal>relpages</literal> with a value of type
+ <type>integer</type>, <literal>reltuples</literal> with a value of
+ type <type>real</type>, and <literal>relallvisible</literal> with a
+ value of type <type>integer</type>.
</para>
<para>
- Optional parameters default to <literal>NULL</literal>, which leave
- the corresponding statistic unchanged.
+ Additionally, this function supports argument name
+ <literal>version</literal> of type <type>integer</type>, which
+ specifies the version from which the statistics originated, improving
+ interpretation of statistics from older versions of
+ <productname>PostgreSQL</productname>.
</para>
<para>
- Ordinarily, these statistics are collected automatically or updated
- as a part of <xref linkend="sql-vacuum"/> or <xref
- linkend="sql-analyze"/>, so it's not necessary to call this
- function. However, it may be useful when testing the effects of
- statistics on the planner to understand or anticipate plan changes.
+ Minor errors are reported as a <literal>WARNING</literal> and
+ ignored, and remaining statistics will still be restored. If all
+ specified statistics are successfully restored, return
+ <literal>true</literal>, otherwise <literal>false</literal>.
</para>
<para>
- The caller must have the <literal>MAINTAIN</literal> privilege on
- the table or be the owner of the database.
+ The caller must have the <literal>MAINTAIN</literal> privilege on the
+ table or be the owner of the database.
</para>
</entry>
</row>
@@ -30341,21 +30255,18 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<entry role="func_table_entry">
<para role="func_signature">
<indexterm>
- <primary>pg_clear_attribute_stats</primary>
+ <primary>pg_clear_relation_stats</primary>
</indexterm>
- <function>pg_clear_attribute_stats</function> (
- <parameter>relation</parameter> <type>regclass</type>,
- <parameter>attname</parameter> <type>name</type>,
- <parameter>inherited</parameter> <type>boolean</type> )
+ <function>pg_clear_relation_stats</function> ( <parameter>relation</parameter> <type>regclass</type> )
<returnvalue>void</returnvalue>
</para>
<para>
- Clears table-level statistics for the given relation attribute, as
- though the table was newly created.
+ Clears table-level statistics for the given relation, as though the
+ table was newly created.
</para>
<para>
- The caller must have the <literal>MAINTAIN</literal> privilege on
- the table or be the owner of the database.
+ The caller must have the <literal>MAINTAIN</literal> privilege on the
+ table or be the owner of the database.
</para>
</entry>
</row>
@@ -30368,26 +30279,25 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<function>pg_restore_attribute_stats</function> (
<literal>VARIADIC</literal> <parameter>kwargs</parameter> <type>"any"</type> )
<returnvalue>boolean</returnvalue>
- </para>
- <para>
- Similar to <function>pg_set_attribute_stats()</function>, but
- intended for bulk restore of attribute statistics. The tracked
- statistics may change from version to version, so the primary purpose
- of this function is to maintain a consistent function signature to
- avoid errors when restoring statistics from previous versions.
- </para>
+ </para>
<para>
- Arguments are passed as pairs of <replaceable>argname</replaceable>
- and <replaceable>argvalue</replaceable>, where
- <replaceable>argname</replaceable> corresponds to a named argument in
- <function>pg_set_attribute_stats()</function> and
- <replaceable>argvalue</replaceable> is of the corresponding type.
+ Create or update column-level statistics. Ordinarily, these
+ statistics are collected automatically or updated as a part of <xref
+ linkend="sql-vacuum"/> or <xref linkend="sql-analyze"/>, so it's not
+ necessary to call this function. However, it is useful after a
+ restore to enable the optimizer to choose better plans if
+ <command>ANALYZE</command> has not been run yet.
</para>
<para>
- Additionally, this function supports argument name
- <literal>version</literal> of type <type>integer</type>, which
- specifies the version from which the statistics originated, improving
- interpretation of older statistics.
+ The tracked statistics may change from version to version, so
+ arguments are passed as pairs of <replaceable>argname</replaceable>
+ and <replaceable>argvalue</replaceable> in the form:
+<programlisting>
+ SELECT pg_restore_attribute_stats(
+ '<replaceable>arg1name</replaceable>', '<replaceable>arg1value</replaceable>'::<replaceable>arg1type</replaceable>,
+ '<replaceable>arg2name</replaceable>', '<replaceable>arg2value</replaceable>'::<replaceable>arg2type</replaceable>,
+ '<replaceable>arg3name</replaceable>', '<replaceable>arg3value</replaceable>'::<replaceable>arg3type</replaceable>);
+</programlisting>
</para>
<para>
For example, to set the <structname>avg_width</structname> and
@@ -30403,12 +30313,56 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
'null_frac', 0.5::real);
</programlisting>
</para>
+ <para>
+ The required arguments are <literal>relation</literal> with a value
+ of type <type>regclass</type>, which specifies the table;
+ <literal>attname</literal> with a value of type <type>name</type>,
+ which specifies the column; and <literal>inherited</literal>, which
+ specifies whether the statistics includes values from child tables.
+ Other arguments are the names of statistics corresponding to columns
+ in <link
+ linkend="view-pg-stats"><structname>pg_stats</structname></link>.
+ </para>
+ <para>
+ Additionally, this function supports argument name
+ <literal>version</literal> of type <type>integer</type>, which
+ specifies the version from which the statistics originated, improving
+ interpretation of statistics from older versions of
+ <productname>PostgreSQL</productname>.
+ </para>
<para>
Minor errors are reported as a <literal>WARNING</literal> and
ignored, and remaining statistics will still be restored. If all
specified statistics are successfully restored, return
<literal>true</literal>, otherwise <literal>false</literal>.
</para>
+ <para>
+ The caller must have the <literal>MAINTAIN</literal> privilege on the
+ table or be the owner of the database.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry">
+ <para role="func_signature">
+ <indexterm>
+ <primary>pg_clear_attribute_stats</primary>
+ </indexterm>
+ <function>pg_clear_attribute_stats</function> (
+ <parameter>relation</parameter> <type>regclass</type>,
+ <parameter>attname</parameter> <type>name</type>,
+ <parameter>inherited</parameter> <type>boolean</type> )
+ <returnvalue>void</returnvalue>
+ </para>
+ <para>
+ Clears column-level statistics for the given relation and
+ attribute, as though the table was newly created.
+ </para>
+ <para>
+ The caller must have the <literal>MAINTAIN</literal> privilege on
+ the table or be the owner of the database.
+ </para>
</entry>
</row>
</tbody>
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 591157b1d1b..86888cd3201 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -636,38 +636,6 @@ LANGUAGE INTERNAL
CALLED ON NULL INPUT VOLATILE PARALLEL SAFE
AS 'pg_stat_reset_slru';
-CREATE OR REPLACE FUNCTION
- pg_set_relation_stats(relation regclass,
- relpages integer DEFAULT NULL,
- reltuples real DEFAULT NULL,
- relallvisible integer DEFAULT NULL)
-RETURNS void
-LANGUAGE INTERNAL
-CALLED ON NULL INPUT VOLATILE
-AS 'pg_set_relation_stats';
-
-CREATE OR REPLACE FUNCTION
- pg_set_attribute_stats(relation regclass,
- attname name,
- inherited bool,
- null_frac real DEFAULT NULL,
- avg_width integer DEFAULT NULL,
- n_distinct real DEFAULT NULL,
- most_common_vals text DEFAULT NULL,
- most_common_freqs real[] DEFAULT NULL,
- histogram_bounds text DEFAULT NULL,
- correlation real DEFAULT NULL,
- most_common_elems text DEFAULT NULL,
- most_common_elem_freqs real[] DEFAULT NULL,
- elem_count_histogram real[] DEFAULT NULL,
- range_length_histogram text DEFAULT NULL,
- range_empty_frac real DEFAULT NULL,
- range_bounds_histogram text DEFAULT NULL)
-RETURNS void
-LANGUAGE INTERNAL
-CALLED ON NULL INPUT VOLATILE
-AS 'pg_set_attribute_stats';
-
--
-- The default permissions for functions mean that anyone can execute them.
-- A number of functions shouldn't be executable by just anyone, but rather
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index c0c398a4bb2..66a5676c810 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -76,16 +76,16 @@ static struct StatsArgInfo attarginfo[] =
[NUM_ATTRIBUTE_STATS_ARGS] = {0}
};
-static bool attribute_statistics_update(FunctionCallInfo fcinfo, int elevel);
+static bool attribute_statistics_update(FunctionCallInfo fcinfo);
static Node *get_attr_expr(Relation rel, int attnum);
-static void get_attr_stat_type(Oid reloid, AttrNumber attnum, int elevel,
+static void get_attr_stat_type(Oid reloid, AttrNumber attnum,
Oid *atttypid, int32 *atttypmod,
char *atttyptype, Oid *atttypcoll,
Oid *eq_opr, Oid *lt_opr);
-static bool get_elem_stat_type(Oid atttypid, char atttyptype, int elevel,
+static bool get_elem_stat_type(Oid atttypid, char atttyptype,
Oid *elemtypid, Oid *elem_eq_opr);
static Datum text_to_stavalues(const char *staname, FmgrInfo *array_in, Datum d,
- Oid typid, int32 typmod, int elevel, bool *ok);
+ Oid typid, int32 typmod, bool *ok);
static void set_stats_slot(Datum *values, bool *nulls, bool *replaces,
int16 stakind, Oid staop, Oid stacoll,
Datum stanumbers, bool stanumbers_isnull,
@@ -109,11 +109,11 @@ static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
*
* Major errors, such as the table not existing, the attribute not existing,
* or a permissions failure are always reported at ERROR. Other errors, such
- * as a conversion failure on one statistic kind, are reported at 'elevel',
+ * as a conversion failure on one statistic kind, are reported as a WARNING
* and other statistic kinds may still be updated.
*/
static bool
-attribute_statistics_update(FunctionCallInfo fcinfo, int elevel)
+attribute_statistics_update(FunctionCallInfo fcinfo)
{
Oid reloid;
Name attname;
@@ -184,33 +184,29 @@ attribute_statistics_update(FunctionCallInfo fcinfo, int elevel)
inherited = PG_GETARG_BOOL(INHERITED_ARG);
/*
- * Check argument sanity. If some arguments are unusable, emit at elevel
+ * Check argument sanity. If some arguments are unusable, emit a WARNING
* and set the corresponding argument to NULL in fcinfo.
*/
- if (!stats_check_arg_array(fcinfo, attarginfo, MOST_COMMON_FREQS_ARG,
- elevel))
+ if (!stats_check_arg_array(fcinfo, attarginfo, MOST_COMMON_FREQS_ARG))
{
do_mcv = false;
result = false;
}
- if (!stats_check_arg_array(fcinfo, attarginfo, MOST_COMMON_ELEM_FREQS_ARG,
- elevel))
+ if (!stats_check_arg_array(fcinfo, attarginfo, MOST_COMMON_ELEM_FREQS_ARG))
{
do_mcelem = false;
result = false;
}
- if (!stats_check_arg_array(fcinfo, attarginfo, ELEM_COUNT_HISTOGRAM_ARG,
- elevel))
+ if (!stats_check_arg_array(fcinfo, attarginfo, ELEM_COUNT_HISTOGRAM_ARG))
{
do_dechist = false;
result = false;
}
if (!stats_check_arg_pair(fcinfo, attarginfo,
- MOST_COMMON_VALS_ARG, MOST_COMMON_FREQS_ARG,
- elevel))
+ MOST_COMMON_VALS_ARG, MOST_COMMON_FREQS_ARG))
{
do_mcv = false;
result = false;
@@ -218,7 +214,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo, int elevel)
if (!stats_check_arg_pair(fcinfo, attarginfo,
MOST_COMMON_ELEMS_ARG,
- MOST_COMMON_ELEM_FREQS_ARG, elevel))
+ MOST_COMMON_ELEM_FREQS_ARG))
{
do_mcelem = false;
result = false;
@@ -226,14 +222,14 @@ attribute_statistics_update(FunctionCallInfo fcinfo, int elevel)
if (!stats_check_arg_pair(fcinfo, attarginfo,
RANGE_LENGTH_HISTOGRAM_ARG,
- RANGE_EMPTY_FRAC_ARG, elevel))
+ RANGE_EMPTY_FRAC_ARG))
{
do_range_length_histogram = false;
result = false;
}
/* derive information from attribute */
- get_attr_stat_type(reloid, attnum, elevel,
+ get_attr_stat_type(reloid, attnum,
&atttypid, &atttypmod,
&atttyptype, &atttypcoll,
&eq_opr, <_opr);
@@ -241,10 +237,10 @@ attribute_statistics_update(FunctionCallInfo fcinfo, int elevel)
/* if needed, derive element type */
if (do_mcelem || do_dechist)
{
- if (!get_elem_stat_type(atttypid, atttyptype, elevel,
+ if (!get_elem_stat_type(atttypid, atttyptype,
&elemtypid, &elem_eq_opr))
{
- ereport(elevel,
+ ereport(WARNING,
(errmsg("unable to determine element type of attribute \"%s\"", NameStr(*attname)),
errdetail("Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.")));
elemtypid = InvalidOid;
@@ -259,7 +255,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo, int elevel)
/* histogram and correlation require less-than operator */
if ((do_histogram || do_correlation) && !OidIsValid(lt_opr))
{
- ereport(elevel,
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("could not determine less-than operator for attribute \"%s\"", NameStr(*attname)),
errdetail("Cannot set STATISTIC_KIND_HISTOGRAM or STATISTIC_KIND_CORRELATION.")));
@@ -273,7 +269,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo, int elevel)
if ((do_range_length_histogram || do_bounds_histogram) &&
!(atttyptype == TYPTYPE_RANGE || atttyptype == TYPTYPE_MULTIRANGE))
{
- ereport(elevel,
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("attribute \"%s\" is not a range type", NameStr(*attname)),
errdetail("Cannot set STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM or STATISTIC_KIND_BOUNDS_HISTOGRAM.")));
@@ -322,7 +318,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo, int elevel)
&array_in_fn,
PG_GETARG_DATUM(MOST_COMMON_VALS_ARG),
atttypid, atttypmod,
- elevel, &converted);
+ &converted);
if (converted)
{
@@ -344,7 +340,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo, int elevel)
stavalues = text_to_stavalues("histogram_bounds",
&array_in_fn,
PG_GETARG_DATUM(HISTOGRAM_BOUNDS_ARG),
- atttypid, atttypmod, elevel,
+ atttypid, atttypmod,
&converted);
if (converted)
@@ -382,7 +378,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo, int elevel)
&array_in_fn,
PG_GETARG_DATUM(MOST_COMMON_ELEMS_ARG),
elemtypid, atttypmod,
- elevel, &converted);
+ &converted);
if (converted)
{
@@ -422,7 +418,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo, int elevel)
&array_in_fn,
PG_GETARG_DATUM(RANGE_BOUNDS_HISTOGRAM_ARG),
atttypid, atttypmod,
- elevel, &converted);
+ &converted);
if (converted)
{
@@ -449,7 +445,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo, int elevel)
stavalues = text_to_stavalues("range_length_histogram",
&array_in_fn,
PG_GETARG_DATUM(RANGE_LENGTH_HISTOGRAM_ARG),
- FLOAT8OID, 0, elevel, &converted);
+ FLOAT8OID, 0, &converted);
if (converted)
{
@@ -517,7 +513,7 @@ get_attr_expr(Relation rel, int attnum)
* Derive type information from the attribute.
*/
static void
-get_attr_stat_type(Oid reloid, AttrNumber attnum, int elevel,
+get_attr_stat_type(Oid reloid, AttrNumber attnum,
Oid *atttypid, int32 *atttypmod,
char *atttyptype, Oid *atttypcoll,
Oid *eq_opr, Oid *lt_opr)
@@ -599,7 +595,7 @@ get_attr_stat_type(Oid reloid, AttrNumber attnum, int elevel,
* Derive element type information from the attribute type.
*/
static bool
-get_elem_stat_type(Oid atttypid, char atttyptype, int elevel,
+get_elem_stat_type(Oid atttypid, char atttyptype,
Oid *elemtypid, Oid *elem_eq_opr)
{
TypeCacheEntry *elemtypcache;
@@ -634,13 +630,13 @@ get_elem_stat_type(Oid atttypid, char atttyptype, int elevel,
/*
* Cast a text datum into an array with element type elemtypid.
*
- * If an error is encountered, capture it and re-throw at elevel, and set ok
- * to false. If the resulting array contains NULLs, raise an error at elevel
- * and set ok to false. Otherwise, set ok to true.
+ * If an error is encountered, capture it and re-throw a WARNING, and set ok
+ * to false. If the resulting array contains NULLs, raise a WARNING and set ok
+ * to false. Otherwise, set ok to true.
*/
static Datum
text_to_stavalues(const char *staname, FmgrInfo *array_in, Datum d, Oid typid,
- int32 typmod, int elevel, bool *ok)
+ int32 typmod, bool *ok)
{
LOCAL_FCINFO(fcinfo, 8);
char *s;
@@ -667,8 +663,7 @@ text_to_stavalues(const char *staname, FmgrInfo *array_in, Datum d, Oid typid,
if (escontext.error_occurred)
{
- if (elevel != ERROR)
- escontext.error_data->elevel = elevel;
+ escontext.error_data->elevel = WARNING;
ThrowErrorData(escontext.error_data);
*ok = false;
return (Datum) 0;
@@ -676,7 +671,7 @@ text_to_stavalues(const char *staname, FmgrInfo *array_in, Datum d, Oid typid,
if (array_contains_nulls(DatumGetArrayTypeP(result)))
{
- ereport(elevel,
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("\"%s\" array cannot contain NULL values", staname)));
*ok = false;
@@ -851,33 +846,6 @@ init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
}
}
-/*
- * Import statistics for a given relation attribute.
- *
- * Inserts or replaces a row in pg_statistic for the given relation and
- * attribute name. It takes input parameters that correspond to columns in the
- * view pg_stats.
- *
- * Parameters null_frac, avg_width, and n_distinct all correspond to NOT NULL
- * columns in pg_statistic. The remaining parameters all belong to a specific
- * stakind. Some stakinds require multiple parameters, which must be specified
- * together (or neither specified).
- *
- * Parameters are only superficially validated. Omitting a parameter or
- * passing NULL leaves the statistic unchanged.
- *
- * Parameters corresponding to ANYARRAY columns are instead passed in as text
- * values, which is a valid input string for an array of the type or element
- * type of the attribute. Any error generated by the array_in() function will
- * in turn fail the function.
- */
-Datum
-pg_set_attribute_stats(PG_FUNCTION_ARGS)
-{
- attribute_statistics_update(fcinfo, ERROR);
- PG_RETURN_VOID();
-}
-
/*
* Delete statistics for the given attribute.
*/
@@ -933,10 +901,10 @@ pg_restore_attribute_stats(PG_FUNCTION_ARGS)
InvalidOid, NULL, NULL);
if (!stats_fill_fcinfo_from_arg_pairs(fcinfo, positional_fcinfo,
- attarginfo, WARNING))
+ attarginfo))
result = false;
- if (!attribute_statistics_update(positional_fcinfo, WARNING))
+ if (!attribute_statistics_update(positional_fcinfo))
result = false;
PG_RETURN_BOOL(result);
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index 66731290a3e..11b1ef2dbc2 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -48,13 +48,13 @@ static struct StatsArgInfo relarginfo[] =
[NUM_RELATION_STATS_ARGS] = {0}
};
-static bool relation_statistics_update(FunctionCallInfo fcinfo, int elevel);
+static bool relation_statistics_update(FunctionCallInfo fcinfo);
/*
* Internal function for modifying statistics for a relation.
*/
static bool
-relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
+relation_statistics_update(FunctionCallInfo fcinfo)
{
bool result = true;
Oid reloid;
@@ -83,7 +83,7 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
reltuples = PG_GETARG_FLOAT4(RELTUPLES_ARG);
if (reltuples < -1.0)
{
- ereport(elevel,
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("reltuples cannot be < -1.0")));
result = false;
@@ -118,7 +118,7 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
ctup = SearchSysCache1(RELOID, ObjectIdGetDatum(reloid));
if (!HeapTupleIsValid(ctup))
{
- ereport(elevel,
+ ereport(WARNING,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("pg_class entry for relid %u not found", reloid)));
table_close(crel, RowExclusiveLock);
@@ -169,16 +169,6 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
return result;
}
-/*
- * Set statistics for a given pg_class entry.
- */
-Datum
-pg_set_relation_stats(PG_FUNCTION_ARGS)
-{
- relation_statistics_update(fcinfo, ERROR);
- PG_RETURN_VOID();
-}
-
/*
* Clear statistics for a given pg_class entry; that is, set back to initial
* stats for a newly-created table.
@@ -199,7 +189,7 @@ pg_clear_relation_stats(PG_FUNCTION_ARGS)
newfcinfo->args[3].value = UInt32GetDatum(0);
newfcinfo->args[3].isnull = false;
- relation_statistics_update(newfcinfo, ERROR);
+ relation_statistics_update(newfcinfo);
PG_RETURN_VOID();
}
@@ -214,10 +204,10 @@ pg_restore_relation_stats(PG_FUNCTION_ARGS)
InvalidOid, NULL, NULL);
if (!stats_fill_fcinfo_from_arg_pairs(fcinfo, positional_fcinfo,
- relarginfo, WARNING))
+ relarginfo))
result = false;
- if (!relation_statistics_update(positional_fcinfo, WARNING))
+ if (!relation_statistics_update(positional_fcinfo))
result = false;
PG_RETURN_BOOL(result);
diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c
index e70ea1ce738..54ead90b5bb 100644
--- a/src/backend/statistics/stat_utils.c
+++ b/src/backend/statistics/stat_utils.c
@@ -48,13 +48,13 @@ stats_check_required_arg(FunctionCallInfo fcinfo,
* Check that argument is either NULL or a one dimensional array with no
* NULLs.
*
- * If a problem is found, emit at elevel, and return false. Otherwise return
+ * If a problem is found, emit a WARNING, and return false. Otherwise return
* true.
*/
bool
stats_check_arg_array(FunctionCallInfo fcinfo,
struct StatsArgInfo *arginfo,
- int argnum, int elevel)
+ int argnum)
{
ArrayType *arr;
@@ -65,7 +65,7 @@ stats_check_arg_array(FunctionCallInfo fcinfo,
if (ARR_NDIM(arr) != 1)
{
- ereport(elevel,
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("\"%s\" cannot be a multidimensional array",
arginfo[argnum].argname)));
@@ -74,7 +74,7 @@ stats_check_arg_array(FunctionCallInfo fcinfo,
if (array_contains_nulls(arr))
{
- ereport(elevel,
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("\"%s\" array cannot contain NULL values",
arginfo[argnum].argname)));
@@ -89,13 +89,13 @@ stats_check_arg_array(FunctionCallInfo fcinfo,
* a particular stakind, such as most_common_vals and most_common_freqs for
* STATISTIC_KIND_MCV.
*
- * If a problem is found, emit at elevel, and return false. Otherwise return
+ * If a problem is found, emit a WARNING, and return false. Otherwise return
* true.
*/
bool
stats_check_arg_pair(FunctionCallInfo fcinfo,
struct StatsArgInfo *arginfo,
- int argnum1, int argnum2, int elevel)
+ int argnum1, int argnum2)
{
if (PG_ARGISNULL(argnum1) && PG_ARGISNULL(argnum2))
return true;
@@ -105,7 +105,7 @@ stats_check_arg_pair(FunctionCallInfo fcinfo,
int nullarg = PG_ARGISNULL(argnum1) ? argnum1 : argnum2;
int otherarg = PG_ARGISNULL(argnum1) ? argnum2 : argnum1;
- ereport(elevel,
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("\"%s\" must be specified when \"%s\" is specified",
arginfo[nullarg].argname,
@@ -216,7 +216,7 @@ stats_lock_check_privileges(Oid reloid)
* found.
*/
static int
-get_arg_by_name(const char *argname, struct StatsArgInfo *arginfo, int elevel)
+get_arg_by_name(const char *argname, struct StatsArgInfo *arginfo)
{
int argnum;
@@ -224,7 +224,7 @@ get_arg_by_name(const char *argname, struct StatsArgInfo *arginfo, int elevel)
if (pg_strcasecmp(argname, arginfo[argnum].argname) == 0)
return argnum;
- ereport(elevel,
+ ereport(WARNING,
(errmsg("unrecognized argument name: \"%s\"", argname)));
return -1;
@@ -234,11 +234,11 @@ get_arg_by_name(const char *argname, struct StatsArgInfo *arginfo, int elevel)
* Ensure that a given argument matched the expected type.
*/
static bool
-stats_check_arg_type(const char *argname, Oid argtype, Oid expectedtype, int elevel)
+stats_check_arg_type(const char *argname, Oid argtype, Oid expectedtype)
{
if (argtype != expectedtype)
{
- ereport(elevel,
+ ereport(WARNING,
(errmsg("argument \"%s\" has type \"%s\", expected type \"%s\"",
argname, format_type_be(argtype),
format_type_be(expectedtype))));
@@ -260,8 +260,7 @@ stats_check_arg_type(const char *argname, Oid argtype, Oid expectedtype, int ele
bool
stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
FunctionCallInfo positional_fcinfo,
- struct StatsArgInfo *arginfo,
- int elevel)
+ struct StatsArgInfo *arginfo)
{
Datum *args;
bool *argnulls;
@@ -319,11 +318,10 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
if (pg_strcasecmp(argname, "version") == 0)
continue;
- argnum = get_arg_by_name(argname, arginfo, elevel);
+ argnum = get_arg_by_name(argname, arginfo);
if (argnum < 0 || !stats_check_arg_type(argname, types[i + 1],
- arginfo[argnum].argtype,
- elevel))
+ arginfo[argnum].argtype))
{
result = false;
continue;
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index af9546de23d..9f0c676e22d 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12429,13 +12429,6 @@
proargnames => '{kwargs}',
proargmodes => '{v}',
prosrc => 'pg_restore_attribute_stats' },
-{ oid => '9162',
- descr => 'set statistics on attribute',
- proname => 'pg_set_attribute_stats', provolatile => 'v', proisstrict => 'f',
- proparallel => 'u', prorettype => 'void',
- proargtypes => 'regclass name bool float4 int4 float4 text _float4 text float4 text _float4 _float4 text float4 text',
- proargnames => '{relation,attname,inherited,null_frac,avg_width,n_distinct,most_common_vals,most_common_freqs,histogram_bounds,correlation,most_common_elems,most_common_elem_freqs,elem_count_histogram,range_length_histogram,range_empty_frac,range_bounds_histogram}',
- prosrc => 'pg_set_attribute_stats' },
{ oid => '9163',
descr => 'clear statistics on attribute',
proname => 'pg_clear_attribute_stats', provolatile => 'v', proisstrict => 'f',
@@ -12443,13 +12436,6 @@
proargtypes => 'regclass name bool',
proargnames => '{relation,attname,inherited}',
prosrc => 'pg_clear_attribute_stats' },
-{ oid => '9944',
- descr => 'set statistics on relation',
- proname => 'pg_set_relation_stats', provolatile => 'v', proisstrict => 'f',
- proparallel => 'u', prorettype => 'void',
- proargtypes => 'regclass int4 float4 int4',
- proargnames => '{relation,relpages,reltuples,relallvisible}',
- prosrc => 'pg_set_relation_stats' },
{ oid => '9945',
descr => 'clear statistics on relation',
proname => 'pg_clear_relation_stats', provolatile => 'v', proisstrict => 'f',
diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h
index 6edb5ea0321..0eb4decfcac 100644
--- a/src/include/statistics/stat_utils.h
+++ b/src/include/statistics/stat_utils.h
@@ -25,17 +25,15 @@ extern void stats_check_required_arg(FunctionCallInfo fcinfo,
struct StatsArgInfo *arginfo,
int argnum);
extern bool stats_check_arg_array(FunctionCallInfo fcinfo,
- struct StatsArgInfo *arginfo, int argnum,
- int elevel);
+ struct StatsArgInfo *arginfo, int argnum);
extern bool stats_check_arg_pair(FunctionCallInfo fcinfo,
struct StatsArgInfo *arginfo,
- int argnum1, int argnum2, int elevel);
+ int argnum1, int argnum2);
extern void stats_lock_check_privileges(Oid reloid);
extern bool stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
FunctionCallInfo positional_fcinfo,
- struct StatsArgInfo *arginfo,
- int elevel);
+ struct StatsArgInfo *arginfo);
#endif /* STATS_UTILS_H */
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index d6713eacc2c..7c7784efaf1 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -12,6 +12,7 @@ CREATE TABLE stats_import.test(
arange int4range,
tags text[]
) WITH (autovacuum_enabled = false);
+CREATE INDEX test_i ON stats_import.test(id);
-- starting stats
SELECT relpages, reltuples, relallvisible
FROM pg_class
@@ -21,80 +22,15 @@ WHERE oid = 'stats_import.test'::regclass;
0 | -1 | 0
(1 row)
--- error: regclass not found
-SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 0::Oid,
- relpages => 17::integer,
- reltuples => 400.0::real,
- relallvisible => 4::integer);
-ERROR: could not open relation with OID 0
--- relpages default
-SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.test'::regclass,
- relpages => NULL::integer,
- reltuples => 400.0::real,
- relallvisible => 4::integer);
- pg_set_relation_stats
------------------------
-
-(1 row)
-
--- reltuples default
-SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.test'::regclass,
- relpages => 17::integer,
- reltuples => NULL::real,
- relallvisible => 4::integer);
- pg_set_relation_stats
------------------------
-
-(1 row)
-
--- relallvisible default
-SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.test'::regclass,
- relpages => 17::integer,
- reltuples => 400.0::real,
- relallvisible => NULL::integer);
- pg_set_relation_stats
------------------------
-
-(1 row)
-
--- named arguments
-SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.test'::regclass,
- relpages => 17::integer,
- reltuples => 400.0::real,
- relallvisible => 4::integer);
- pg_set_relation_stats
------------------------
-
-(1 row)
-
-SELECT relpages, reltuples, relallvisible
-FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
- relpages | reltuples | relallvisible
-----------+-----------+---------------
- 17 | 400 | 4
-(1 row)
-
-CREATE INDEX test_i ON stats_import.test(id);
BEGIN;
-- regular indexes have special case locking rules
SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.test_i'::regclass,
- relpages => 18::integer);
- pg_set_relation_stats
------------------------
-
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.test_i'::regclass,
+ 'relpages', 18::integer);
+ pg_restore_relation_stats
+---------------------------
+ t
(1 row)
SELECT mode FROM pg_locks
@@ -123,34 +59,6 @@ SELECT
t
(1 row)
--- positional arguments
-SELECT
- pg_catalog.pg_set_relation_stats(
- 'stats_import.test'::regclass,
- 18::integer,
- 401.0::real,
- 5::integer);
- pg_set_relation_stats
------------------------
-
-(1 row)
-
-SELECT relpages, reltuples, relallvisible
-FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
- relpages | reltuples | relallvisible
-----------+-----------+---------------
- 18 | 401 | 5
-(1 row)
-
-SELECT relpages, reltuples, relallvisible
-FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
- relpages | reltuples | relallvisible
-----------+-----------+---------------
- 18 | 401 | 5
-(1 row)
-
-- clear
SELECT
pg_catalog.pg_clear_relation_stats(
@@ -200,21 +108,21 @@ WHERE oid = 'stats_import.part_parent'::regclass;
-- although partitioned tables have no storage, setting relpages to a
-- positive value is still allowed
SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.part_parent_i'::regclass,
- relpages => 2::integer);
- pg_set_relation_stats
------------------------
-
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.part_parent_i'::regclass,
+ 'relpages', 2::integer);
+ pg_restore_relation_stats
+---------------------------
+ t
(1 row)
SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.part_parent'::regclass,
- relpages => 2::integer);
- pg_set_relation_stats
------------------------
-
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.part_parent'::regclass,
+ 'relpages', 2::integer);
+ pg_restore_relation_stats
+---------------------------
+ t
(1 row)
--
@@ -225,12 +133,12 @@ SELECT
--
BEGIN;
SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.part_parent_i'::regclass,
- relpages => 2::integer);
- pg_set_relation_stats
------------------------
-
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.part_parent_i'::regclass,
+ 'relpages', 2::integer);
+ pg_restore_relation_stats
+---------------------------
+ t
(1 row)
SELECT mode FROM pg_locks
@@ -261,56 +169,14 @@ SELECT
-- nothing stops us from setting it to -1
SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.part_parent'::regclass,
- relpages => -1::integer);
- pg_set_relation_stats
------------------------
-
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.part_parent'::regclass,
+ 'relpages', -1::integer);
+ pg_restore_relation_stats
+---------------------------
+ t
(1 row)
--- error: object doesn't exist
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => '0'::oid,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.1::real,
- avg_width => 2::integer,
- n_distinct => 0.3::real);
-ERROR: could not open relation with OID 0
--- error: object doesn't exist
-SELECT pg_catalog.pg_clear_attribute_stats(
- relation => '0'::oid,
- attname => 'id'::name,
- inherited => false::boolean);
-ERROR: could not open relation with OID 0
--- error: relation null
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => NULL::oid,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.1::real,
- avg_width => 2::integer,
- n_distinct => 0.3::real);
-ERROR: "relation" cannot be NULL
--- error: attribute is system column
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'xmin'::name,
- inherited => false::boolean,
- null_frac => 0.1::real,
- avg_width => 2::integer,
- n_distinct => 0.3::real);
-ERROR: cannot modify statistics on system column "xmin"
--- error: attname doesn't exist
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'nope'::name,
- inherited => false::boolean,
- null_frac => 0.1::real,
- avg_width => 2::integer,
- n_distinct => 0.3::real);
-ERROR: column "nope" of relation "test" does not exist
-- error: attribute is system column
SELECT pg_catalog.pg_clear_attribute_stats(
relation => 'stats_import.test'::regclass,
@@ -323,432 +189,6 @@ SELECT pg_catalog.pg_clear_attribute_stats(
attname => 'nope'::name,
inherited => false::boolean);
ERROR: column "nope" of relation "test" does not exist
--- error: attname null
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => NULL::name,
- inherited => false::boolean,
- null_frac => 0.1::real,
- avg_width => 2::integer,
- n_distinct => 0.3::real);
-ERROR: "attname" cannot be NULL
--- error: inherited null
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => NULL::boolean,
- null_frac => 0.1::real,
- avg_width => 2::integer,
- n_distinct => 0.3::real);
-ERROR: "inherited" cannot be NULL
--- ok: no stakinds
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.1::real,
- avg_width => 2::integer,
- n_distinct => 0.3::real);
- pg_set_attribute_stats
-------------------------
-
-(1 row)
-
-SELECT stanullfrac, stawidth, stadistinct
-FROM pg_statistic
-WHERE starelid = 'stats_import.test'::regclass;
- stanullfrac | stawidth | stadistinct
--------------+----------+-------------
- 0.1 | 2 | 0.3
-(1 row)
-
--- error: mcv / mcf null mismatch
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_freqs => '{0.1,0.2,0.3}'::real[]
- );
-ERROR: "most_common_vals" must be specified when "most_common_freqs" is specified
--- error: mcv / mcf null mismatch part 2
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_vals => '{1,2,3}'::text
- );
-ERROR: "most_common_freqs" must be specified when "most_common_vals" is specified
--- error: mcv / mcf type mismatch
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_vals => '{2023-09-30,2024-10-31,3}'::text,
- most_common_freqs => '{0.2,0.1}'::real[]
- );
-ERROR: invalid input syntax for type integer: "2023-09-30"
--- error: mcv cast failure
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_vals => '{2,four,3}'::text,
- most_common_freqs => '{0.3,0.25,0.05}'::real[]
- );
-ERROR: invalid input syntax for type integer: "four"
--- ok: mcv+mcf
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_vals => '{2,1,3}'::text,
- most_common_freqs => '{0.3,0.25,0.05}'::real[]
- );
- pg_set_attribute_stats
-------------------------
-
-(1 row)
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'id';
- schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
---------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.5 | 2 | -0.1 | {2,1,3} | {0.3,0.25,0.05} | | | | | | | |
-(1 row)
-
--- error: histogram elements null value
--- this generates no warnings, but perhaps it should
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- histogram_bounds => '{1,NULL,3,4}'::text
- );
-ERROR: "histogram_bounds" array cannot contain NULL values
--- ok: histogram_bounds
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- histogram_bounds => '{1,2,3,4}'::text
- );
- pg_set_attribute_stats
-------------------------
-
-(1 row)
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'id';
- schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
---------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.5 | 2 | -0.1 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
-(1 row)
-
--- ok: correlation
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- correlation => 0.5::real);
- pg_set_attribute_stats
-------------------------
-
-(1 row)
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'id';
- schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
---------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.5 | 2 | -0.1 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | 0.5 | | | | | |
-(1 row)
-
--- error: scalars can't have mcelem
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_elems => '{1,3}'::text,
- most_common_elem_freqs => '{0.3,0.2,0.2,0.3,0.0}'::real[]
- );
-ERROR: unable to determine element type of attribute "id"
-DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.
--- error: mcelem / mcelem mismatch
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'tags'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_elems => '{one,two}'::text
- );
-ERROR: "most_common_elem_freqs" must be specified when "most_common_elems" is specified
--- error: mcelem / mcelem null mismatch part 2
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'tags'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_elem_freqs => '{0.3,0.2,0.2,0.3}'::real[]
- );
-ERROR: "most_common_elems" must be specified when "most_common_elem_freqs" is specified
--- ok: mcelem
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'tags'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_elems => '{one,three}'::text,
- most_common_elem_freqs => '{0.3,0.2,0.2,0.3,0.0}'::real[]
- );
- pg_set_attribute_stats
-------------------------
-
-(1 row)
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'tags';
- schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
---------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | tags | f | 0.5 | 2 | -0.1 | | | | | {one,three} | {0.3,0.2,0.2,0.3,0} | | | |
-(1 row)
-
--- error: scalars can't have elem_count_histogram
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- elem_count_histogram => '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
- );
-ERROR: unable to determine element type of attribute "id"
-DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.
--- error: elem_count_histogram null element
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'tags'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- elem_count_histogram => '{1,1,NULL,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
- );
-ERROR: "elem_count_histogram" array cannot contain NULL values
--- ok: elem_count_histogram
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'tags'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- elem_count_histogram => '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
- );
- pg_set_attribute_stats
-------------------------
-
-(1 row)
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'tags';
- schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
---------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------+------------------+------------------------
- stats_import | test | tags | f | 0.5 | 2 | -0.1 | | | | | {one,three} | {0.3,0.2,0.2,0.3,0} | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
-(1 row)
-
--- error: scalars can't have range stats
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- range_empty_frac => 0.5::real,
- range_length_histogram => '{399,499,Infinity}'::text
- );
-ERROR: attribute "id" is not a range type
-DETAIL: Cannot set STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM or STATISTIC_KIND_BOUNDS_HISTOGRAM.
--- error: range_empty_frac range_length_hist null mismatch
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- range_length_histogram => '{399,499,Infinity}'::text
- );
-ERROR: "range_empty_frac" must be specified when "range_length_histogram" is specified
--- error: range_empty_frac range_length_hist null mismatch part 2
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- range_empty_frac => 0.5::real
- );
-ERROR: "range_length_histogram" must be specified when "range_empty_frac" is specified
--- ok: range_empty_frac + range_length_hist
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- range_empty_frac => 0.5::real,
- range_length_histogram => '{399,499,Infinity}'::text
- );
- pg_set_attribute_stats
-------------------------
-
-(1 row)
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'arange';
- schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
---------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | arange | f | 0.5 | 2 | -0.1 | | | | | | | | {399,499,Infinity} | 0.5 |
-(1 row)
-
--- error: scalars can't have range stats
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- range_bounds_histogram => '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
- );
-ERROR: attribute "id" is not a range type
-DETAIL: Cannot set STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM or STATISTIC_KIND_BOUNDS_HISTOGRAM.
--- ok: range_bounds_histogram
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- range_bounds_histogram => '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
- );
- pg_set_attribute_stats
-------------------------
-
-(1 row)
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'arange';
- schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
---------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+--------------------------------------
- stats_import | test | arange | f | 0.5 | 2 | -0.1 | | | | | | | | {399,499,Infinity} | 0.5 | {"[-1,1)","[0,4)","[1,4)","[1,100)"}
-(1 row)
-
--- error: cannot set most_common_elems for range type
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_vals => '{"[2,3)","[1,2)","[3,4)"}'::text,
- most_common_freqs => '{0.3,0.25,0.05}'::real[],
- histogram_bounds => '{"[1,2)","[2,3)","[3,4)","[4,5)"}'::text,
- correlation => 1.1::real,
- most_common_elems => '{3,1}'::text,
- most_common_elem_freqs => '{0.3,0.2,0.2,0.3,0.0}'::real[],
- range_empty_frac => -0.5::real,
- range_length_histogram => '{399,499,Infinity}'::text,
- range_bounds_histogram => '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
- );
-ERROR: unable to determine element type of attribute "arange"
-DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.
---
--- Clear attribute stats to try again with restore functions
--- (relation stats were already cleared).
---
-SELECT
- pg_catalog.pg_clear_attribute_stats(
- 'stats_import.test'::regclass,
- s.attname,
- s.inherited)
-FROM pg_catalog.pg_stats AS s
-WHERE s.schemaname = 'stats_import'
-AND s.tablename = 'test'
-ORDER BY s.attname, s.inherited;
- pg_clear_attribute_stats
---------------------------
-
-
-
-(3 rows)
-
-- reject: argument name is NULL
SELECT pg_restore_relation_stats(
'relation', '0'::oid::regclass,
@@ -1472,216 +912,6 @@ CREATE INDEX is_odd_clone ON stats_import.test_clone(((comp).a % 2 = 1));
--
-- Copy stats from test to test_clone, and is_odd to is_odd_clone
--
-SELECT s.schemaname, s.tablename, s.attname, s.inherited
-FROM pg_catalog.pg_stats AS s
-CROSS JOIN LATERAL
- pg_catalog.pg_set_attribute_stats(
- relation => ('stats_import.' || s.tablename || '_clone')::regclass::oid,
- attname => s.attname,
- inherited => s.inherited,
- null_frac => s.null_frac,
- avg_width => s.avg_width,
- n_distinct => s.n_distinct,
- most_common_vals => s.most_common_vals::text,
- most_common_freqs => s.most_common_freqs,
- histogram_bounds => s.histogram_bounds::text,
- correlation => s.correlation,
- most_common_elems => s.most_common_elems::text,
- most_common_elem_freqs => s.most_common_elem_freqs,
- elem_count_histogram => s.elem_count_histogram,
- range_bounds_histogram => s.range_bounds_histogram::text,
- range_empty_frac => s.range_empty_frac,
- range_length_histogram => s.range_length_histogram::text) AS r
-WHERE s.schemaname = 'stats_import'
-AND s.tablename IN ('test', 'is_odd')
-ORDER BY s.tablename, s.attname, s.inherited;
- schemaname | tablename | attname | inherited
---------------+-----------+---------+-----------
- stats_import | is_odd | expr | f
- stats_import | test | arange | f
- stats_import | test | comp | f
- stats_import | test | id | f
- stats_import | test | name | f
- stats_import | test | tags | f
-(6 rows)
-
-SELECT c.relname, COUNT(*) AS num_stats
-FROM pg_class AS c
-JOIN pg_statistic s ON s.starelid = c.oid
-WHERE c.relnamespace = 'stats_import'::regnamespace
-AND c.relname IN ('test', 'test_clone', 'is_odd', 'is_odd_clone')
-GROUP BY c.relname
-ORDER BY c.relname;
- relname | num_stats
---------------+-----------
- is_odd | 1
- is_odd_clone | 1
- test | 5
- test_clone | 5
-(4 rows)
-
--- check test minus test_clone
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'test' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.test'::regclass
-EXCEPT
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'test' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.test_clone'::regclass;
- attname | stainherit | stanullfrac | stawidth | stadistinct | stakind1 | stakind2 | stakind3 | stakind4 | stakind5 | staop1 | staop2 | staop3 | staop4 | staop5 | stacoll1 | stacoll2 | stacoll3 | stacoll4 | stacoll5 | stanumbers1 | stanumbers2 | stanumbers3 | stanumbers4 | stanumbers5 | sv1 | sv2 | sv3 | sv4 | sv5 | direction
----------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+-----------
-(0 rows)
-
--- check test_clone minus test
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'test_clone' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.test_clone'::regclass
-EXCEPT
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'test_clone' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.test'::regclass;
- attname | stainherit | stanullfrac | stawidth | stadistinct | stakind1 | stakind2 | stakind3 | stakind4 | stakind5 | staop1 | staop2 | staop3 | staop4 | staop5 | stacoll1 | stacoll2 | stacoll3 | stacoll4 | stacoll5 | stanumbers1 | stanumbers2 | stanumbers3 | stanumbers4 | stanumbers5 | sv1 | sv2 | sv3 | sv4 | sv5 | direction
----------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+-----------
-(0 rows)
-
--- check is_odd minus is_odd_clone
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'is_odd' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.is_odd'::regclass
-EXCEPT
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'is_odd' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.is_odd_clone'::regclass;
- attname | stainherit | stanullfrac | stawidth | stadistinct | stakind1 | stakind2 | stakind3 | stakind4 | stakind5 | staop1 | staop2 | staop3 | staop4 | staop5 | stacoll1 | stacoll2 | stacoll3 | stacoll4 | stacoll5 | stanumbers1 | stanumbers2 | stanumbers3 | stanumbers4 | stanumbers5 | sv1 | sv2 | sv3 | sv4 | sv5 | direction
----------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+-----------
-(0 rows)
-
--- check is_odd_clone minus is_odd
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'is_odd_clone' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.is_odd_clone'::regclass
-EXCEPT
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'is_odd_clone' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.is_odd'::regclass;
- attname | stainherit | stanullfrac | stawidth | stadistinct | stakind1 | stakind2 | stakind3 | stakind4 | stakind5 | staop1 | staop2 | staop3 | staop4 | staop5 | stacoll1 | stacoll2 | stacoll3 | stacoll4 | stacoll5 | stanumbers1 | stanumbers2 | stanumbers3 | stanumbers4 | stanumbers5 | sv1 | sv2 | sv3 | sv4 | sv5 | direction
----------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+-----------
-(0 rows)
-
---
-SELECT relpages, reltuples, relallvisible
-FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
- relpages | reltuples | relallvisible
-----------+-----------+---------------
- 1 | 4 | 0
-(1 row)
-
---
--- Clear clone stats to try again with pg_restore_attribute_stats
---
-SELECT
- pg_catalog.pg_clear_attribute_stats(
- ('stats_import.' || s.tablename)::regclass,
- s.attname,
- s.inherited)
-FROM pg_catalog.pg_stats AS s
-WHERE s.schemaname = 'stats_import'
-AND s.tablename IN ('test_clone', 'is_odd_clone')
-ORDER BY s.tablename, s.attname, s.inherited;
- pg_clear_attribute_stats
---------------------------
-
-
-
-
-
-
-(6 rows)
-
-SELECT
-SELECT COUNT(*)
-FROM pg_catalog.pg_stats AS s
-WHERE s.schemaname = 'stats_import'
-AND s.tablename IN ('test_clone', 'is_odd_clone');
-ERROR: syntax error at or near "SELECT"
-LINE 2: SELECT COUNT(*)
- ^
---
--- Copy stats from test to test_clone, and is_odd to is_odd_clone
---
SELECT s.schemaname, s.tablename, s.attname, s.inherited, r.*
FROM pg_catalog.pg_stats AS s
CROSS JOIN LATERAL
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index 9740ab3ff02..f26f7857748 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -15,63 +15,19 @@ CREATE TABLE stats_import.test(
tags text[]
) WITH (autovacuum_enabled = false);
--- starting stats
-SELECT relpages, reltuples, relallvisible
-FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
-
--- error: regclass not found
-SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 0::Oid,
- relpages => 17::integer,
- reltuples => 400.0::real,
- relallvisible => 4::integer);
-
--- relpages default
-SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.test'::regclass,
- relpages => NULL::integer,
- reltuples => 400.0::real,
- relallvisible => 4::integer);
-
--- reltuples default
-SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.test'::regclass,
- relpages => 17::integer,
- reltuples => NULL::real,
- relallvisible => 4::integer);
-
--- relallvisible default
-SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.test'::regclass,
- relpages => 17::integer,
- reltuples => 400.0::real,
- relallvisible => NULL::integer);
-
--- named arguments
-SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.test'::regclass,
- relpages => 17::integer,
- reltuples => 400.0::real,
- relallvisible => 4::integer);
+CREATE INDEX test_i ON stats_import.test(id);
+-- starting stats
SELECT relpages, reltuples, relallvisible
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
-CREATE INDEX test_i ON stats_import.test(id);
-
BEGIN;
-- regular indexes have special case locking rules
SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.test_i'::regclass,
- relpages => 18::integer);
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.test_i'::regclass,
+ 'relpages', 18::integer);
SELECT mode FROM pg_locks
WHERE relation = 'stats_import.test'::regclass AND
@@ -88,22 +44,6 @@ SELECT
'relation', 'stats_import.test_i'::regclass,
'relpages', 19::integer );
--- positional arguments
-SELECT
- pg_catalog.pg_set_relation_stats(
- 'stats_import.test'::regclass,
- 18::integer,
- 401.0::real,
- 5::integer);
-
-SELECT relpages, reltuples, relallvisible
-FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
-
-SELECT relpages, reltuples, relallvisible
-FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
-
-- clear
SELECT
pg_catalog.pg_clear_relation_stats(
@@ -141,14 +81,14 @@ WHERE oid = 'stats_import.part_parent'::regclass;
-- although partitioned tables have no storage, setting relpages to a
-- positive value is still allowed
SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.part_parent_i'::regclass,
- relpages => 2::integer);
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.part_parent_i'::regclass,
+ 'relpages', 2::integer);
SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.part_parent'::regclass,
- relpages => 2::integer);
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.part_parent'::regclass,
+ 'relpages', 2::integer);
--
-- Partitioned indexes aren't analyzed but it is possible to set
@@ -159,9 +99,9 @@ SELECT
BEGIN;
SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.part_parent_i'::regclass,
- relpages => 2::integer);
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.part_parent_i'::regclass,
+ 'relpages', 2::integer);
SELECT mode FROM pg_locks
WHERE relation = 'stats_import.part_parent'::regclass AND
@@ -180,51 +120,9 @@ SELECT
-- nothing stops us from setting it to -1
SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.part_parent'::regclass,
- relpages => -1::integer);
-
--- error: object doesn't exist
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => '0'::oid,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.1::real,
- avg_width => 2::integer,
- n_distinct => 0.3::real);
-
--- error: object doesn't exist
-SELECT pg_catalog.pg_clear_attribute_stats(
- relation => '0'::oid,
- attname => 'id'::name,
- inherited => false::boolean);
-
--- error: relation null
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => NULL::oid,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.1::real,
- avg_width => 2::integer,
- n_distinct => 0.3::real);
-
--- error: attribute is system column
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'xmin'::name,
- inherited => false::boolean,
- null_frac => 0.1::real,
- avg_width => 2::integer,
- n_distinct => 0.3::real);
-
--- error: attname doesn't exist
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'nope'::name,
- inherited => false::boolean,
- null_frac => 0.1::real,
- avg_width => 2::integer,
- n_distinct => 0.3::real);
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.part_parent'::regclass,
+ 'relpages', -1::integer);
-- error: attribute is system column
SELECT pg_catalog.pg_clear_attribute_stats(
@@ -238,351 +136,6 @@ SELECT pg_catalog.pg_clear_attribute_stats(
attname => 'nope'::name,
inherited => false::boolean);
--- error: attname null
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => NULL::name,
- inherited => false::boolean,
- null_frac => 0.1::real,
- avg_width => 2::integer,
- n_distinct => 0.3::real);
-
--- error: inherited null
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => NULL::boolean,
- null_frac => 0.1::real,
- avg_width => 2::integer,
- n_distinct => 0.3::real);
-
--- ok: no stakinds
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.1::real,
- avg_width => 2::integer,
- n_distinct => 0.3::real);
-
-SELECT stanullfrac, stawidth, stadistinct
-FROM pg_statistic
-WHERE starelid = 'stats_import.test'::regclass;
-
--- error: mcv / mcf null mismatch
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_freqs => '{0.1,0.2,0.3}'::real[]
- );
-
--- error: mcv / mcf null mismatch part 2
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_vals => '{1,2,3}'::text
- );
-
--- error: mcv / mcf type mismatch
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_vals => '{2023-09-30,2024-10-31,3}'::text,
- most_common_freqs => '{0.2,0.1}'::real[]
- );
-
--- error: mcv cast failure
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_vals => '{2,four,3}'::text,
- most_common_freqs => '{0.3,0.25,0.05}'::real[]
- );
-
--- ok: mcv+mcf
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_vals => '{2,1,3}'::text,
- most_common_freqs => '{0.3,0.25,0.05}'::real[]
- );
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'id';
-
--- error: histogram elements null value
--- this generates no warnings, but perhaps it should
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- histogram_bounds => '{1,NULL,3,4}'::text
- );
-
--- ok: histogram_bounds
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- histogram_bounds => '{1,2,3,4}'::text
- );
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'id';
-
--- ok: correlation
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- correlation => 0.5::real);
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'id';
-
--- error: scalars can't have mcelem
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_elems => '{1,3}'::text,
- most_common_elem_freqs => '{0.3,0.2,0.2,0.3,0.0}'::real[]
- );
-
--- error: mcelem / mcelem mismatch
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'tags'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_elems => '{one,two}'::text
- );
-
--- error: mcelem / mcelem null mismatch part 2
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'tags'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_elem_freqs => '{0.3,0.2,0.2,0.3}'::real[]
- );
-
--- ok: mcelem
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'tags'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_elems => '{one,three}'::text,
- most_common_elem_freqs => '{0.3,0.2,0.2,0.3,0.0}'::real[]
- );
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'tags';
-
--- error: scalars can't have elem_count_histogram
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- elem_count_histogram => '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
- );
--- error: elem_count_histogram null element
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'tags'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- elem_count_histogram => '{1,1,NULL,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
- );
--- ok: elem_count_histogram
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'tags'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- elem_count_histogram => '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
- );
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'tags';
-
--- error: scalars can't have range stats
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- range_empty_frac => 0.5::real,
- range_length_histogram => '{399,499,Infinity}'::text
- );
--- error: range_empty_frac range_length_hist null mismatch
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- range_length_histogram => '{399,499,Infinity}'::text
- );
--- error: range_empty_frac range_length_hist null mismatch part 2
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- range_empty_frac => 0.5::real
- );
--- ok: range_empty_frac + range_length_hist
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- range_empty_frac => 0.5::real,
- range_length_histogram => '{399,499,Infinity}'::text
- );
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'arange';
-
--- error: scalars can't have range stats
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- range_bounds_histogram => '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
- );
--- ok: range_bounds_histogram
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- range_bounds_histogram => '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
- );
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'arange';
-
--- error: cannot set most_common_elems for range type
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_vals => '{"[2,3)","[1,2)","[3,4)"}'::text,
- most_common_freqs => '{0.3,0.25,0.05}'::real[],
- histogram_bounds => '{"[1,2)","[2,3)","[3,4)","[4,5)"}'::text,
- correlation => 1.1::real,
- most_common_elems => '{3,1}'::text,
- most_common_elem_freqs => '{0.3,0.2,0.2,0.3,0.0}'::real[],
- range_empty_frac => -0.5::real,
- range_length_histogram => '{399,499,Infinity}'::text,
- range_bounds_histogram => '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
- );
-
---
--- Clear attribute stats to try again with restore functions
--- (relation stats were already cleared).
---
-SELECT
- pg_catalog.pg_clear_attribute_stats(
- 'stats_import.test'::regclass,
- s.attname,
- s.inherited)
-FROM pg_catalog.pg_stats AS s
-WHERE s.schemaname = 'stats_import'
-AND s.tablename = 'test'
-ORDER BY s.attname, s.inherited;
-
-- reject: argument name is NULL
SELECT pg_restore_relation_stats(
'relation', '0'::oid::regclass,
@@ -1105,173 +658,6 @@ CREATE TABLE stats_import.test_clone ( LIKE stats_import.test )
CREATE INDEX is_odd_clone ON stats_import.test_clone(((comp).a % 2 = 1));
---
--- Copy stats from test to test_clone, and is_odd to is_odd_clone
---
-SELECT s.schemaname, s.tablename, s.attname, s.inherited
-FROM pg_catalog.pg_stats AS s
-CROSS JOIN LATERAL
- pg_catalog.pg_set_attribute_stats(
- relation => ('stats_import.' || s.tablename || '_clone')::regclass::oid,
- attname => s.attname,
- inherited => s.inherited,
- null_frac => s.null_frac,
- avg_width => s.avg_width,
- n_distinct => s.n_distinct,
- most_common_vals => s.most_common_vals::text,
- most_common_freqs => s.most_common_freqs,
- histogram_bounds => s.histogram_bounds::text,
- correlation => s.correlation,
- most_common_elems => s.most_common_elems::text,
- most_common_elem_freqs => s.most_common_elem_freqs,
- elem_count_histogram => s.elem_count_histogram,
- range_bounds_histogram => s.range_bounds_histogram::text,
- range_empty_frac => s.range_empty_frac,
- range_length_histogram => s.range_length_histogram::text) AS r
-WHERE s.schemaname = 'stats_import'
-AND s.tablename IN ('test', 'is_odd')
-ORDER BY s.tablename, s.attname, s.inherited;
-
-SELECT c.relname, COUNT(*) AS num_stats
-FROM pg_class AS c
-JOIN pg_statistic s ON s.starelid = c.oid
-WHERE c.relnamespace = 'stats_import'::regnamespace
-AND c.relname IN ('test', 'test_clone', 'is_odd', 'is_odd_clone')
-GROUP BY c.relname
-ORDER BY c.relname;
-
--- check test minus test_clone
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'test' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.test'::regclass
-EXCEPT
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'test' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.test_clone'::regclass;
-
--- check test_clone minus test
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'test_clone' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.test_clone'::regclass
-EXCEPT
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'test_clone' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.test'::regclass;
-
--- check is_odd minus is_odd_clone
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'is_odd' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.is_odd'::regclass
-EXCEPT
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'is_odd' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.is_odd_clone'::regclass;
-
--- check is_odd_clone minus is_odd
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'is_odd_clone' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.is_odd_clone'::regclass
-EXCEPT
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'is_odd_clone' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.is_odd'::regclass;
-
---
-SELECT relpages, reltuples, relallvisible
-FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
-
---
--- Clear clone stats to try again with pg_restore_attribute_stats
---
-SELECT
- pg_catalog.pg_clear_attribute_stats(
- ('stats_import.' || s.tablename)::regclass,
- s.attname,
- s.inherited)
-FROM pg_catalog.pg_stats AS s
-WHERE s.schemaname = 'stats_import'
-AND s.tablename IN ('test_clone', 'is_odd_clone')
-ORDER BY s.tablename, s.attname, s.inherited;
-SELECT
-
-SELECT COUNT(*)
-FROM pg_catalog.pg_stats AS s
-WHERE s.schemaname = 'stats_import'
-AND s.tablename IN ('test_clone', 'is_odd_clone');
-
--
-- Copy stats from test to test_clone, and is_odd to is_odd_clone
--
--
2.34.1
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-25 20:31 Corey Huinker <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 2 replies; 199+ messages in thread
From: Corey Huinker @ 2025-02-25 20:31 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Tom Lane <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Tue, Feb 25, 2025 at 1:22 PM Jeff Davis <[email protected]> wrote:
> On Mon, 2025-02-24 at 12:50 -0500, Tom Lane wrote:
> > Also, while working on the attached, I couldn't help forming the
> > opinion that we'd be better off to nuke pg_set_attribute_stats()
> > from orbit and require people to use pg_restore_attribute_stats().
>
> Attached a patch to do so. The docs and tests required substantial
> rework, but I think it's for the better now that we aren't trying to do
> in-place updates.
>
> Regards,
> Jeff Davis
>
>
All the C code changes make sense to me. Though as an aside, we're going to
run into the parameter-ordering problem when it comes to
pg_clear_attribute_stats, but that's a (read: my) problem for a later patch.
Documentation:
+ The currently-supported relation statistics are
+ <literal>relpages</literal> with a value of type
+ <type>integer</type>, <literal>reltuples</literal> with a value of
+ type <type>real</type>, and <literal>relallvisible</literal> with
a
+ value of type <type>integer</type>.
Could we make this a bullet-list? Same for the required attribute stats and
optional attribute stats. I think it would be more eye-catching and useful
to people skimming to recall the name of a parameter, which is probably
what most people will do after they've read it once to get the core
concepts.
Question:
Do we want to re-compact the oids we consumed in pg_proc.dat?
Test cases:
We're ripping out a lot of regression tests here. Some of them obviously
have no possible pg_restore_* analogs, such as explicitly set NULL values
vs omitting the param entirely, but some others may not, especially the
ones that test required arg-pairs.
Specifically missing are:
* regclass not found
* attribute is system column
* scalars can't have mcelem
* mcelem / mcelem freqs mismatch (parts 1 and 2)
* scalars can't have elem_count_histogram
* cannot set most_common_elems for range type
I'm less worried about all the tests of successful import calls, as the
pg_upgrade TAP tests kick those tires pretty well.
I'm also ok with losing the copies from test to test_clone, those are also
covered well by the TAP tests.
I'd feel better if we adapted the above tests from set-tests to
restore-tests, as the TAP suite doesn't really cover intentionally bad
stats.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-25 23:40 Corey Huinker <[email protected]>
parent: Corey Huinker <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Corey Huinker @ 2025-02-25 23:40 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Tom Lane <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
>
> Specifically missing are:
>
> * regclass not found
> * attribute is system column
> * scalars can't have mcelem
> * mcelem / mcelem freqs mismatch (parts 1 and 2)
> * scalars can't have elem_count_histogram
> * cannot set most_common_elems for range type
>
>
This patchset is as follows:
0001 - Jeff's patch from earlier today
0002 - Changing the parameter lists to <itemizedlist> to aid skim-ability
0003 - converting some of the deleted pg_set* tests into pg_restore* tests
to keep the error coverage that they had.
Next I'm going to incorporate Tom's attnum change, the locale set, and the
set-index-by-attnum changes.
Attachments:
[text/x-patch] vAdios-Set-0001-Remove-redundant-pg_set_-_stats-variants.patch (101.4K, ../../CADkLM=ctPiAAr7WfWcBbhJf_fTiGrrnBNhVvWp9_wCFhBs2D6w@mail.gmail.com/3-vAdios-Set-0001-Remove-redundant-pg_set_-_stats-variants.patch)
download | inline diff:
From 30e9010a54d43376af2b33281974c1ebb0ea6382 Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Mon, 24 Feb 2025 17:24:05 -0800
Subject: [PATCH vAdios-Set 1/3] Remove redundant pg_set_*_stats() variants.
After commit f3dae2ae58, the primary purpose of separating the
pg_set_*_stats() from the pg_restore_*_stats() variants was
eliminated.
Leave pg_restore_relation_stats() and pg_restore_attribute_stats(),
which satisfy both purposes, and remove pg_set_relation_stats() and
pg_set_attribute_stats().
Discussion: https://postgr.es/m/[email protected]
---
src/include/catalog/pg_proc.dat | 14 -
src/include/statistics/stat_utils.h | 8 +-
src/backend/catalog/system_functions.sql | 32 -
src/backend/statistics/attribute_stats.c | 98 +--
src/backend/statistics/relation_stats.c | 24 +-
src/backend/statistics/stat_utils.c | 30 +-
src/test/regress/expected/stats_import.out | 832 +--------------------
src/test/regress/sql/stats_import.sql | 648 +---------------
doc/src/sgml/func.sgml | 260 +++----
9 files changed, 212 insertions(+), 1734 deletions(-)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index af9546de23d..9f0c676e22d 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12429,13 +12429,6 @@
proargnames => '{kwargs}',
proargmodes => '{v}',
prosrc => 'pg_restore_attribute_stats' },
-{ oid => '9162',
- descr => 'set statistics on attribute',
- proname => 'pg_set_attribute_stats', provolatile => 'v', proisstrict => 'f',
- proparallel => 'u', prorettype => 'void',
- proargtypes => 'regclass name bool float4 int4 float4 text _float4 text float4 text _float4 _float4 text float4 text',
- proargnames => '{relation,attname,inherited,null_frac,avg_width,n_distinct,most_common_vals,most_common_freqs,histogram_bounds,correlation,most_common_elems,most_common_elem_freqs,elem_count_histogram,range_length_histogram,range_empty_frac,range_bounds_histogram}',
- prosrc => 'pg_set_attribute_stats' },
{ oid => '9163',
descr => 'clear statistics on attribute',
proname => 'pg_clear_attribute_stats', provolatile => 'v', proisstrict => 'f',
@@ -12443,13 +12436,6 @@
proargtypes => 'regclass name bool',
proargnames => '{relation,attname,inherited}',
prosrc => 'pg_clear_attribute_stats' },
-{ oid => '9944',
- descr => 'set statistics on relation',
- proname => 'pg_set_relation_stats', provolatile => 'v', proisstrict => 'f',
- proparallel => 'u', prorettype => 'void',
- proargtypes => 'regclass int4 float4 int4',
- proargnames => '{relation,relpages,reltuples,relallvisible}',
- prosrc => 'pg_set_relation_stats' },
{ oid => '9945',
descr => 'clear statistics on relation',
proname => 'pg_clear_relation_stats', provolatile => 'v', proisstrict => 'f',
diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h
index 6edb5ea0321..0eb4decfcac 100644
--- a/src/include/statistics/stat_utils.h
+++ b/src/include/statistics/stat_utils.h
@@ -25,17 +25,15 @@ extern void stats_check_required_arg(FunctionCallInfo fcinfo,
struct StatsArgInfo *arginfo,
int argnum);
extern bool stats_check_arg_array(FunctionCallInfo fcinfo,
- struct StatsArgInfo *arginfo, int argnum,
- int elevel);
+ struct StatsArgInfo *arginfo, int argnum);
extern bool stats_check_arg_pair(FunctionCallInfo fcinfo,
struct StatsArgInfo *arginfo,
- int argnum1, int argnum2, int elevel);
+ int argnum1, int argnum2);
extern void stats_lock_check_privileges(Oid reloid);
extern bool stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
FunctionCallInfo positional_fcinfo,
- struct StatsArgInfo *arginfo,
- int elevel);
+ struct StatsArgInfo *arginfo);
#endif /* STATS_UTILS_H */
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 591157b1d1b..86888cd3201 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -636,38 +636,6 @@ LANGUAGE INTERNAL
CALLED ON NULL INPUT VOLATILE PARALLEL SAFE
AS 'pg_stat_reset_slru';
-CREATE OR REPLACE FUNCTION
- pg_set_relation_stats(relation regclass,
- relpages integer DEFAULT NULL,
- reltuples real DEFAULT NULL,
- relallvisible integer DEFAULT NULL)
-RETURNS void
-LANGUAGE INTERNAL
-CALLED ON NULL INPUT VOLATILE
-AS 'pg_set_relation_stats';
-
-CREATE OR REPLACE FUNCTION
- pg_set_attribute_stats(relation regclass,
- attname name,
- inherited bool,
- null_frac real DEFAULT NULL,
- avg_width integer DEFAULT NULL,
- n_distinct real DEFAULT NULL,
- most_common_vals text DEFAULT NULL,
- most_common_freqs real[] DEFAULT NULL,
- histogram_bounds text DEFAULT NULL,
- correlation real DEFAULT NULL,
- most_common_elems text DEFAULT NULL,
- most_common_elem_freqs real[] DEFAULT NULL,
- elem_count_histogram real[] DEFAULT NULL,
- range_length_histogram text DEFAULT NULL,
- range_empty_frac real DEFAULT NULL,
- range_bounds_histogram text DEFAULT NULL)
-RETURNS void
-LANGUAGE INTERNAL
-CALLED ON NULL INPUT VOLATILE
-AS 'pg_set_attribute_stats';
-
--
-- The default permissions for functions mean that anyone can execute them.
-- A number of functions shouldn't be executable by just anyone, but rather
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index c0c398a4bb2..66a5676c810 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -76,16 +76,16 @@ static struct StatsArgInfo attarginfo[] =
[NUM_ATTRIBUTE_STATS_ARGS] = {0}
};
-static bool attribute_statistics_update(FunctionCallInfo fcinfo, int elevel);
+static bool attribute_statistics_update(FunctionCallInfo fcinfo);
static Node *get_attr_expr(Relation rel, int attnum);
-static void get_attr_stat_type(Oid reloid, AttrNumber attnum, int elevel,
+static void get_attr_stat_type(Oid reloid, AttrNumber attnum,
Oid *atttypid, int32 *atttypmod,
char *atttyptype, Oid *atttypcoll,
Oid *eq_opr, Oid *lt_opr);
-static bool get_elem_stat_type(Oid atttypid, char atttyptype, int elevel,
+static bool get_elem_stat_type(Oid atttypid, char atttyptype,
Oid *elemtypid, Oid *elem_eq_opr);
static Datum text_to_stavalues(const char *staname, FmgrInfo *array_in, Datum d,
- Oid typid, int32 typmod, int elevel, bool *ok);
+ Oid typid, int32 typmod, bool *ok);
static void set_stats_slot(Datum *values, bool *nulls, bool *replaces,
int16 stakind, Oid staop, Oid stacoll,
Datum stanumbers, bool stanumbers_isnull,
@@ -109,11 +109,11 @@ static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
*
* Major errors, such as the table not existing, the attribute not existing,
* or a permissions failure are always reported at ERROR. Other errors, such
- * as a conversion failure on one statistic kind, are reported at 'elevel',
+ * as a conversion failure on one statistic kind, are reported as a WARNING
* and other statistic kinds may still be updated.
*/
static bool
-attribute_statistics_update(FunctionCallInfo fcinfo, int elevel)
+attribute_statistics_update(FunctionCallInfo fcinfo)
{
Oid reloid;
Name attname;
@@ -184,33 +184,29 @@ attribute_statistics_update(FunctionCallInfo fcinfo, int elevel)
inherited = PG_GETARG_BOOL(INHERITED_ARG);
/*
- * Check argument sanity. If some arguments are unusable, emit at elevel
+ * Check argument sanity. If some arguments are unusable, emit a WARNING
* and set the corresponding argument to NULL in fcinfo.
*/
- if (!stats_check_arg_array(fcinfo, attarginfo, MOST_COMMON_FREQS_ARG,
- elevel))
+ if (!stats_check_arg_array(fcinfo, attarginfo, MOST_COMMON_FREQS_ARG))
{
do_mcv = false;
result = false;
}
- if (!stats_check_arg_array(fcinfo, attarginfo, MOST_COMMON_ELEM_FREQS_ARG,
- elevel))
+ if (!stats_check_arg_array(fcinfo, attarginfo, MOST_COMMON_ELEM_FREQS_ARG))
{
do_mcelem = false;
result = false;
}
- if (!stats_check_arg_array(fcinfo, attarginfo, ELEM_COUNT_HISTOGRAM_ARG,
- elevel))
+ if (!stats_check_arg_array(fcinfo, attarginfo, ELEM_COUNT_HISTOGRAM_ARG))
{
do_dechist = false;
result = false;
}
if (!stats_check_arg_pair(fcinfo, attarginfo,
- MOST_COMMON_VALS_ARG, MOST_COMMON_FREQS_ARG,
- elevel))
+ MOST_COMMON_VALS_ARG, MOST_COMMON_FREQS_ARG))
{
do_mcv = false;
result = false;
@@ -218,7 +214,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo, int elevel)
if (!stats_check_arg_pair(fcinfo, attarginfo,
MOST_COMMON_ELEMS_ARG,
- MOST_COMMON_ELEM_FREQS_ARG, elevel))
+ MOST_COMMON_ELEM_FREQS_ARG))
{
do_mcelem = false;
result = false;
@@ -226,14 +222,14 @@ attribute_statistics_update(FunctionCallInfo fcinfo, int elevel)
if (!stats_check_arg_pair(fcinfo, attarginfo,
RANGE_LENGTH_HISTOGRAM_ARG,
- RANGE_EMPTY_FRAC_ARG, elevel))
+ RANGE_EMPTY_FRAC_ARG))
{
do_range_length_histogram = false;
result = false;
}
/* derive information from attribute */
- get_attr_stat_type(reloid, attnum, elevel,
+ get_attr_stat_type(reloid, attnum,
&atttypid, &atttypmod,
&atttyptype, &atttypcoll,
&eq_opr, <_opr);
@@ -241,10 +237,10 @@ attribute_statistics_update(FunctionCallInfo fcinfo, int elevel)
/* if needed, derive element type */
if (do_mcelem || do_dechist)
{
- if (!get_elem_stat_type(atttypid, atttyptype, elevel,
+ if (!get_elem_stat_type(atttypid, atttyptype,
&elemtypid, &elem_eq_opr))
{
- ereport(elevel,
+ ereport(WARNING,
(errmsg("unable to determine element type of attribute \"%s\"", NameStr(*attname)),
errdetail("Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.")));
elemtypid = InvalidOid;
@@ -259,7 +255,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo, int elevel)
/* histogram and correlation require less-than operator */
if ((do_histogram || do_correlation) && !OidIsValid(lt_opr))
{
- ereport(elevel,
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("could not determine less-than operator for attribute \"%s\"", NameStr(*attname)),
errdetail("Cannot set STATISTIC_KIND_HISTOGRAM or STATISTIC_KIND_CORRELATION.")));
@@ -273,7 +269,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo, int elevel)
if ((do_range_length_histogram || do_bounds_histogram) &&
!(atttyptype == TYPTYPE_RANGE || atttyptype == TYPTYPE_MULTIRANGE))
{
- ereport(elevel,
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("attribute \"%s\" is not a range type", NameStr(*attname)),
errdetail("Cannot set STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM or STATISTIC_KIND_BOUNDS_HISTOGRAM.")));
@@ -322,7 +318,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo, int elevel)
&array_in_fn,
PG_GETARG_DATUM(MOST_COMMON_VALS_ARG),
atttypid, atttypmod,
- elevel, &converted);
+ &converted);
if (converted)
{
@@ -344,7 +340,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo, int elevel)
stavalues = text_to_stavalues("histogram_bounds",
&array_in_fn,
PG_GETARG_DATUM(HISTOGRAM_BOUNDS_ARG),
- atttypid, atttypmod, elevel,
+ atttypid, atttypmod,
&converted);
if (converted)
@@ -382,7 +378,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo, int elevel)
&array_in_fn,
PG_GETARG_DATUM(MOST_COMMON_ELEMS_ARG),
elemtypid, atttypmod,
- elevel, &converted);
+ &converted);
if (converted)
{
@@ -422,7 +418,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo, int elevel)
&array_in_fn,
PG_GETARG_DATUM(RANGE_BOUNDS_HISTOGRAM_ARG),
atttypid, atttypmod,
- elevel, &converted);
+ &converted);
if (converted)
{
@@ -449,7 +445,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo, int elevel)
stavalues = text_to_stavalues("range_length_histogram",
&array_in_fn,
PG_GETARG_DATUM(RANGE_LENGTH_HISTOGRAM_ARG),
- FLOAT8OID, 0, elevel, &converted);
+ FLOAT8OID, 0, &converted);
if (converted)
{
@@ -517,7 +513,7 @@ get_attr_expr(Relation rel, int attnum)
* Derive type information from the attribute.
*/
static void
-get_attr_stat_type(Oid reloid, AttrNumber attnum, int elevel,
+get_attr_stat_type(Oid reloid, AttrNumber attnum,
Oid *atttypid, int32 *atttypmod,
char *atttyptype, Oid *atttypcoll,
Oid *eq_opr, Oid *lt_opr)
@@ -599,7 +595,7 @@ get_attr_stat_type(Oid reloid, AttrNumber attnum, int elevel,
* Derive element type information from the attribute type.
*/
static bool
-get_elem_stat_type(Oid atttypid, char atttyptype, int elevel,
+get_elem_stat_type(Oid atttypid, char atttyptype,
Oid *elemtypid, Oid *elem_eq_opr)
{
TypeCacheEntry *elemtypcache;
@@ -634,13 +630,13 @@ get_elem_stat_type(Oid atttypid, char atttyptype, int elevel,
/*
* Cast a text datum into an array with element type elemtypid.
*
- * If an error is encountered, capture it and re-throw at elevel, and set ok
- * to false. If the resulting array contains NULLs, raise an error at elevel
- * and set ok to false. Otherwise, set ok to true.
+ * If an error is encountered, capture it and re-throw a WARNING, and set ok
+ * to false. If the resulting array contains NULLs, raise a WARNING and set ok
+ * to false. Otherwise, set ok to true.
*/
static Datum
text_to_stavalues(const char *staname, FmgrInfo *array_in, Datum d, Oid typid,
- int32 typmod, int elevel, bool *ok)
+ int32 typmod, bool *ok)
{
LOCAL_FCINFO(fcinfo, 8);
char *s;
@@ -667,8 +663,7 @@ text_to_stavalues(const char *staname, FmgrInfo *array_in, Datum d, Oid typid,
if (escontext.error_occurred)
{
- if (elevel != ERROR)
- escontext.error_data->elevel = elevel;
+ escontext.error_data->elevel = WARNING;
ThrowErrorData(escontext.error_data);
*ok = false;
return (Datum) 0;
@@ -676,7 +671,7 @@ text_to_stavalues(const char *staname, FmgrInfo *array_in, Datum d, Oid typid,
if (array_contains_nulls(DatumGetArrayTypeP(result)))
{
- ereport(elevel,
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("\"%s\" array cannot contain NULL values", staname)));
*ok = false;
@@ -851,33 +846,6 @@ init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
}
}
-/*
- * Import statistics for a given relation attribute.
- *
- * Inserts or replaces a row in pg_statistic for the given relation and
- * attribute name. It takes input parameters that correspond to columns in the
- * view pg_stats.
- *
- * Parameters null_frac, avg_width, and n_distinct all correspond to NOT NULL
- * columns in pg_statistic. The remaining parameters all belong to a specific
- * stakind. Some stakinds require multiple parameters, which must be specified
- * together (or neither specified).
- *
- * Parameters are only superficially validated. Omitting a parameter or
- * passing NULL leaves the statistic unchanged.
- *
- * Parameters corresponding to ANYARRAY columns are instead passed in as text
- * values, which is a valid input string for an array of the type or element
- * type of the attribute. Any error generated by the array_in() function will
- * in turn fail the function.
- */
-Datum
-pg_set_attribute_stats(PG_FUNCTION_ARGS)
-{
- attribute_statistics_update(fcinfo, ERROR);
- PG_RETURN_VOID();
-}
-
/*
* Delete statistics for the given attribute.
*/
@@ -933,10 +901,10 @@ pg_restore_attribute_stats(PG_FUNCTION_ARGS)
InvalidOid, NULL, NULL);
if (!stats_fill_fcinfo_from_arg_pairs(fcinfo, positional_fcinfo,
- attarginfo, WARNING))
+ attarginfo))
result = false;
- if (!attribute_statistics_update(positional_fcinfo, WARNING))
+ if (!attribute_statistics_update(positional_fcinfo))
result = false;
PG_RETURN_BOOL(result);
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index 66731290a3e..11b1ef2dbc2 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -48,13 +48,13 @@ static struct StatsArgInfo relarginfo[] =
[NUM_RELATION_STATS_ARGS] = {0}
};
-static bool relation_statistics_update(FunctionCallInfo fcinfo, int elevel);
+static bool relation_statistics_update(FunctionCallInfo fcinfo);
/*
* Internal function for modifying statistics for a relation.
*/
static bool
-relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
+relation_statistics_update(FunctionCallInfo fcinfo)
{
bool result = true;
Oid reloid;
@@ -83,7 +83,7 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
reltuples = PG_GETARG_FLOAT4(RELTUPLES_ARG);
if (reltuples < -1.0)
{
- ereport(elevel,
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("reltuples cannot be < -1.0")));
result = false;
@@ -118,7 +118,7 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
ctup = SearchSysCache1(RELOID, ObjectIdGetDatum(reloid));
if (!HeapTupleIsValid(ctup))
{
- ereport(elevel,
+ ereport(WARNING,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("pg_class entry for relid %u not found", reloid)));
table_close(crel, RowExclusiveLock);
@@ -169,16 +169,6 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
return result;
}
-/*
- * Set statistics for a given pg_class entry.
- */
-Datum
-pg_set_relation_stats(PG_FUNCTION_ARGS)
-{
- relation_statistics_update(fcinfo, ERROR);
- PG_RETURN_VOID();
-}
-
/*
* Clear statistics for a given pg_class entry; that is, set back to initial
* stats for a newly-created table.
@@ -199,7 +189,7 @@ pg_clear_relation_stats(PG_FUNCTION_ARGS)
newfcinfo->args[3].value = UInt32GetDatum(0);
newfcinfo->args[3].isnull = false;
- relation_statistics_update(newfcinfo, ERROR);
+ relation_statistics_update(newfcinfo);
PG_RETURN_VOID();
}
@@ -214,10 +204,10 @@ pg_restore_relation_stats(PG_FUNCTION_ARGS)
InvalidOid, NULL, NULL);
if (!stats_fill_fcinfo_from_arg_pairs(fcinfo, positional_fcinfo,
- relarginfo, WARNING))
+ relarginfo))
result = false;
- if (!relation_statistics_update(positional_fcinfo, WARNING))
+ if (!relation_statistics_update(positional_fcinfo))
result = false;
PG_RETURN_BOOL(result);
diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c
index e70ea1ce738..54ead90b5bb 100644
--- a/src/backend/statistics/stat_utils.c
+++ b/src/backend/statistics/stat_utils.c
@@ -48,13 +48,13 @@ stats_check_required_arg(FunctionCallInfo fcinfo,
* Check that argument is either NULL or a one dimensional array with no
* NULLs.
*
- * If a problem is found, emit at elevel, and return false. Otherwise return
+ * If a problem is found, emit a WARNING, and return false. Otherwise return
* true.
*/
bool
stats_check_arg_array(FunctionCallInfo fcinfo,
struct StatsArgInfo *arginfo,
- int argnum, int elevel)
+ int argnum)
{
ArrayType *arr;
@@ -65,7 +65,7 @@ stats_check_arg_array(FunctionCallInfo fcinfo,
if (ARR_NDIM(arr) != 1)
{
- ereport(elevel,
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("\"%s\" cannot be a multidimensional array",
arginfo[argnum].argname)));
@@ -74,7 +74,7 @@ stats_check_arg_array(FunctionCallInfo fcinfo,
if (array_contains_nulls(arr))
{
- ereport(elevel,
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("\"%s\" array cannot contain NULL values",
arginfo[argnum].argname)));
@@ -89,13 +89,13 @@ stats_check_arg_array(FunctionCallInfo fcinfo,
* a particular stakind, such as most_common_vals and most_common_freqs for
* STATISTIC_KIND_MCV.
*
- * If a problem is found, emit at elevel, and return false. Otherwise return
+ * If a problem is found, emit a WARNING, and return false. Otherwise return
* true.
*/
bool
stats_check_arg_pair(FunctionCallInfo fcinfo,
struct StatsArgInfo *arginfo,
- int argnum1, int argnum2, int elevel)
+ int argnum1, int argnum2)
{
if (PG_ARGISNULL(argnum1) && PG_ARGISNULL(argnum2))
return true;
@@ -105,7 +105,7 @@ stats_check_arg_pair(FunctionCallInfo fcinfo,
int nullarg = PG_ARGISNULL(argnum1) ? argnum1 : argnum2;
int otherarg = PG_ARGISNULL(argnum1) ? argnum2 : argnum1;
- ereport(elevel,
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("\"%s\" must be specified when \"%s\" is specified",
arginfo[nullarg].argname,
@@ -216,7 +216,7 @@ stats_lock_check_privileges(Oid reloid)
* found.
*/
static int
-get_arg_by_name(const char *argname, struct StatsArgInfo *arginfo, int elevel)
+get_arg_by_name(const char *argname, struct StatsArgInfo *arginfo)
{
int argnum;
@@ -224,7 +224,7 @@ get_arg_by_name(const char *argname, struct StatsArgInfo *arginfo, int elevel)
if (pg_strcasecmp(argname, arginfo[argnum].argname) == 0)
return argnum;
- ereport(elevel,
+ ereport(WARNING,
(errmsg("unrecognized argument name: \"%s\"", argname)));
return -1;
@@ -234,11 +234,11 @@ get_arg_by_name(const char *argname, struct StatsArgInfo *arginfo, int elevel)
* Ensure that a given argument matched the expected type.
*/
static bool
-stats_check_arg_type(const char *argname, Oid argtype, Oid expectedtype, int elevel)
+stats_check_arg_type(const char *argname, Oid argtype, Oid expectedtype)
{
if (argtype != expectedtype)
{
- ereport(elevel,
+ ereport(WARNING,
(errmsg("argument \"%s\" has type \"%s\", expected type \"%s\"",
argname, format_type_be(argtype),
format_type_be(expectedtype))));
@@ -260,8 +260,7 @@ stats_check_arg_type(const char *argname, Oid argtype, Oid expectedtype, int ele
bool
stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
FunctionCallInfo positional_fcinfo,
- struct StatsArgInfo *arginfo,
- int elevel)
+ struct StatsArgInfo *arginfo)
{
Datum *args;
bool *argnulls;
@@ -319,11 +318,10 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
if (pg_strcasecmp(argname, "version") == 0)
continue;
- argnum = get_arg_by_name(argname, arginfo, elevel);
+ argnum = get_arg_by_name(argname, arginfo);
if (argnum < 0 || !stats_check_arg_type(argname, types[i + 1],
- arginfo[argnum].argtype,
- elevel))
+ arginfo[argnum].argtype))
{
result = false;
continue;
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index d6713eacc2c..7c7784efaf1 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -12,6 +12,7 @@ CREATE TABLE stats_import.test(
arange int4range,
tags text[]
) WITH (autovacuum_enabled = false);
+CREATE INDEX test_i ON stats_import.test(id);
-- starting stats
SELECT relpages, reltuples, relallvisible
FROM pg_class
@@ -21,80 +22,15 @@ WHERE oid = 'stats_import.test'::regclass;
0 | -1 | 0
(1 row)
--- error: regclass not found
-SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 0::Oid,
- relpages => 17::integer,
- reltuples => 400.0::real,
- relallvisible => 4::integer);
-ERROR: could not open relation with OID 0
--- relpages default
-SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.test'::regclass,
- relpages => NULL::integer,
- reltuples => 400.0::real,
- relallvisible => 4::integer);
- pg_set_relation_stats
------------------------
-
-(1 row)
-
--- reltuples default
-SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.test'::regclass,
- relpages => 17::integer,
- reltuples => NULL::real,
- relallvisible => 4::integer);
- pg_set_relation_stats
------------------------
-
-(1 row)
-
--- relallvisible default
-SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.test'::regclass,
- relpages => 17::integer,
- reltuples => 400.0::real,
- relallvisible => NULL::integer);
- pg_set_relation_stats
------------------------
-
-(1 row)
-
--- named arguments
-SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.test'::regclass,
- relpages => 17::integer,
- reltuples => 400.0::real,
- relallvisible => 4::integer);
- pg_set_relation_stats
------------------------
-
-(1 row)
-
-SELECT relpages, reltuples, relallvisible
-FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
- relpages | reltuples | relallvisible
-----------+-----------+---------------
- 17 | 400 | 4
-(1 row)
-
-CREATE INDEX test_i ON stats_import.test(id);
BEGIN;
-- regular indexes have special case locking rules
SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.test_i'::regclass,
- relpages => 18::integer);
- pg_set_relation_stats
------------------------
-
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.test_i'::regclass,
+ 'relpages', 18::integer);
+ pg_restore_relation_stats
+---------------------------
+ t
(1 row)
SELECT mode FROM pg_locks
@@ -123,34 +59,6 @@ SELECT
t
(1 row)
--- positional arguments
-SELECT
- pg_catalog.pg_set_relation_stats(
- 'stats_import.test'::regclass,
- 18::integer,
- 401.0::real,
- 5::integer);
- pg_set_relation_stats
------------------------
-
-(1 row)
-
-SELECT relpages, reltuples, relallvisible
-FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
- relpages | reltuples | relallvisible
-----------+-----------+---------------
- 18 | 401 | 5
-(1 row)
-
-SELECT relpages, reltuples, relallvisible
-FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
- relpages | reltuples | relallvisible
-----------+-----------+---------------
- 18 | 401 | 5
-(1 row)
-
-- clear
SELECT
pg_catalog.pg_clear_relation_stats(
@@ -200,21 +108,21 @@ WHERE oid = 'stats_import.part_parent'::regclass;
-- although partitioned tables have no storage, setting relpages to a
-- positive value is still allowed
SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.part_parent_i'::regclass,
- relpages => 2::integer);
- pg_set_relation_stats
------------------------
-
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.part_parent_i'::regclass,
+ 'relpages', 2::integer);
+ pg_restore_relation_stats
+---------------------------
+ t
(1 row)
SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.part_parent'::regclass,
- relpages => 2::integer);
- pg_set_relation_stats
------------------------
-
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.part_parent'::regclass,
+ 'relpages', 2::integer);
+ pg_restore_relation_stats
+---------------------------
+ t
(1 row)
--
@@ -225,12 +133,12 @@ SELECT
--
BEGIN;
SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.part_parent_i'::regclass,
- relpages => 2::integer);
- pg_set_relation_stats
------------------------
-
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.part_parent_i'::regclass,
+ 'relpages', 2::integer);
+ pg_restore_relation_stats
+---------------------------
+ t
(1 row)
SELECT mode FROM pg_locks
@@ -261,56 +169,14 @@ SELECT
-- nothing stops us from setting it to -1
SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.part_parent'::regclass,
- relpages => -1::integer);
- pg_set_relation_stats
------------------------
-
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.part_parent'::regclass,
+ 'relpages', -1::integer);
+ pg_restore_relation_stats
+---------------------------
+ t
(1 row)
--- error: object doesn't exist
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => '0'::oid,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.1::real,
- avg_width => 2::integer,
- n_distinct => 0.3::real);
-ERROR: could not open relation with OID 0
--- error: object doesn't exist
-SELECT pg_catalog.pg_clear_attribute_stats(
- relation => '0'::oid,
- attname => 'id'::name,
- inherited => false::boolean);
-ERROR: could not open relation with OID 0
--- error: relation null
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => NULL::oid,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.1::real,
- avg_width => 2::integer,
- n_distinct => 0.3::real);
-ERROR: "relation" cannot be NULL
--- error: attribute is system column
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'xmin'::name,
- inherited => false::boolean,
- null_frac => 0.1::real,
- avg_width => 2::integer,
- n_distinct => 0.3::real);
-ERROR: cannot modify statistics on system column "xmin"
--- error: attname doesn't exist
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'nope'::name,
- inherited => false::boolean,
- null_frac => 0.1::real,
- avg_width => 2::integer,
- n_distinct => 0.3::real);
-ERROR: column "nope" of relation "test" does not exist
-- error: attribute is system column
SELECT pg_catalog.pg_clear_attribute_stats(
relation => 'stats_import.test'::regclass,
@@ -323,432 +189,6 @@ SELECT pg_catalog.pg_clear_attribute_stats(
attname => 'nope'::name,
inherited => false::boolean);
ERROR: column "nope" of relation "test" does not exist
--- error: attname null
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => NULL::name,
- inherited => false::boolean,
- null_frac => 0.1::real,
- avg_width => 2::integer,
- n_distinct => 0.3::real);
-ERROR: "attname" cannot be NULL
--- error: inherited null
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => NULL::boolean,
- null_frac => 0.1::real,
- avg_width => 2::integer,
- n_distinct => 0.3::real);
-ERROR: "inherited" cannot be NULL
--- ok: no stakinds
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.1::real,
- avg_width => 2::integer,
- n_distinct => 0.3::real);
- pg_set_attribute_stats
-------------------------
-
-(1 row)
-
-SELECT stanullfrac, stawidth, stadistinct
-FROM pg_statistic
-WHERE starelid = 'stats_import.test'::regclass;
- stanullfrac | stawidth | stadistinct
--------------+----------+-------------
- 0.1 | 2 | 0.3
-(1 row)
-
--- error: mcv / mcf null mismatch
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_freqs => '{0.1,0.2,0.3}'::real[]
- );
-ERROR: "most_common_vals" must be specified when "most_common_freqs" is specified
--- error: mcv / mcf null mismatch part 2
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_vals => '{1,2,3}'::text
- );
-ERROR: "most_common_freqs" must be specified when "most_common_vals" is specified
--- error: mcv / mcf type mismatch
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_vals => '{2023-09-30,2024-10-31,3}'::text,
- most_common_freqs => '{0.2,0.1}'::real[]
- );
-ERROR: invalid input syntax for type integer: "2023-09-30"
--- error: mcv cast failure
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_vals => '{2,four,3}'::text,
- most_common_freqs => '{0.3,0.25,0.05}'::real[]
- );
-ERROR: invalid input syntax for type integer: "four"
--- ok: mcv+mcf
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_vals => '{2,1,3}'::text,
- most_common_freqs => '{0.3,0.25,0.05}'::real[]
- );
- pg_set_attribute_stats
-------------------------
-
-(1 row)
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'id';
- schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
---------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.5 | 2 | -0.1 | {2,1,3} | {0.3,0.25,0.05} | | | | | | | |
-(1 row)
-
--- error: histogram elements null value
--- this generates no warnings, but perhaps it should
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- histogram_bounds => '{1,NULL,3,4}'::text
- );
-ERROR: "histogram_bounds" array cannot contain NULL values
--- ok: histogram_bounds
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- histogram_bounds => '{1,2,3,4}'::text
- );
- pg_set_attribute_stats
-------------------------
-
-(1 row)
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'id';
- schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
---------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.5 | 2 | -0.1 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
-(1 row)
-
--- ok: correlation
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- correlation => 0.5::real);
- pg_set_attribute_stats
-------------------------
-
-(1 row)
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'id';
- schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
---------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.5 | 2 | -0.1 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | 0.5 | | | | | |
-(1 row)
-
--- error: scalars can't have mcelem
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_elems => '{1,3}'::text,
- most_common_elem_freqs => '{0.3,0.2,0.2,0.3,0.0}'::real[]
- );
-ERROR: unable to determine element type of attribute "id"
-DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.
--- error: mcelem / mcelem mismatch
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'tags'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_elems => '{one,two}'::text
- );
-ERROR: "most_common_elem_freqs" must be specified when "most_common_elems" is specified
--- error: mcelem / mcelem null mismatch part 2
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'tags'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_elem_freqs => '{0.3,0.2,0.2,0.3}'::real[]
- );
-ERROR: "most_common_elems" must be specified when "most_common_elem_freqs" is specified
--- ok: mcelem
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'tags'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_elems => '{one,three}'::text,
- most_common_elem_freqs => '{0.3,0.2,0.2,0.3,0.0}'::real[]
- );
- pg_set_attribute_stats
-------------------------
-
-(1 row)
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'tags';
- schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
---------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | tags | f | 0.5 | 2 | -0.1 | | | | | {one,three} | {0.3,0.2,0.2,0.3,0} | | | |
-(1 row)
-
--- error: scalars can't have elem_count_histogram
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- elem_count_histogram => '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
- );
-ERROR: unable to determine element type of attribute "id"
-DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.
--- error: elem_count_histogram null element
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'tags'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- elem_count_histogram => '{1,1,NULL,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
- );
-ERROR: "elem_count_histogram" array cannot contain NULL values
--- ok: elem_count_histogram
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'tags'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- elem_count_histogram => '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
- );
- pg_set_attribute_stats
-------------------------
-
-(1 row)
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'tags';
- schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
---------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------+------------------+------------------------
- stats_import | test | tags | f | 0.5 | 2 | -0.1 | | | | | {one,three} | {0.3,0.2,0.2,0.3,0} | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
-(1 row)
-
--- error: scalars can't have range stats
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- range_empty_frac => 0.5::real,
- range_length_histogram => '{399,499,Infinity}'::text
- );
-ERROR: attribute "id" is not a range type
-DETAIL: Cannot set STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM or STATISTIC_KIND_BOUNDS_HISTOGRAM.
--- error: range_empty_frac range_length_hist null mismatch
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- range_length_histogram => '{399,499,Infinity}'::text
- );
-ERROR: "range_empty_frac" must be specified when "range_length_histogram" is specified
--- error: range_empty_frac range_length_hist null mismatch part 2
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- range_empty_frac => 0.5::real
- );
-ERROR: "range_length_histogram" must be specified when "range_empty_frac" is specified
--- ok: range_empty_frac + range_length_hist
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- range_empty_frac => 0.5::real,
- range_length_histogram => '{399,499,Infinity}'::text
- );
- pg_set_attribute_stats
-------------------------
-
-(1 row)
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'arange';
- schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
---------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | arange | f | 0.5 | 2 | -0.1 | | | | | | | | {399,499,Infinity} | 0.5 |
-(1 row)
-
--- error: scalars can't have range stats
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- range_bounds_histogram => '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
- );
-ERROR: attribute "id" is not a range type
-DETAIL: Cannot set STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM or STATISTIC_KIND_BOUNDS_HISTOGRAM.
--- ok: range_bounds_histogram
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- range_bounds_histogram => '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
- );
- pg_set_attribute_stats
-------------------------
-
-(1 row)
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'arange';
- schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
---------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+--------------------------------------
- stats_import | test | arange | f | 0.5 | 2 | -0.1 | | | | | | | | {399,499,Infinity} | 0.5 | {"[-1,1)","[0,4)","[1,4)","[1,100)"}
-(1 row)
-
--- error: cannot set most_common_elems for range type
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_vals => '{"[2,3)","[1,2)","[3,4)"}'::text,
- most_common_freqs => '{0.3,0.25,0.05}'::real[],
- histogram_bounds => '{"[1,2)","[2,3)","[3,4)","[4,5)"}'::text,
- correlation => 1.1::real,
- most_common_elems => '{3,1}'::text,
- most_common_elem_freqs => '{0.3,0.2,0.2,0.3,0.0}'::real[],
- range_empty_frac => -0.5::real,
- range_length_histogram => '{399,499,Infinity}'::text,
- range_bounds_histogram => '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
- );
-ERROR: unable to determine element type of attribute "arange"
-DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.
---
--- Clear attribute stats to try again with restore functions
--- (relation stats were already cleared).
---
-SELECT
- pg_catalog.pg_clear_attribute_stats(
- 'stats_import.test'::regclass,
- s.attname,
- s.inherited)
-FROM pg_catalog.pg_stats AS s
-WHERE s.schemaname = 'stats_import'
-AND s.tablename = 'test'
-ORDER BY s.attname, s.inherited;
- pg_clear_attribute_stats
---------------------------
-
-
-
-(3 rows)
-
-- reject: argument name is NULL
SELECT pg_restore_relation_stats(
'relation', '0'::oid::regclass,
@@ -1472,216 +912,6 @@ CREATE INDEX is_odd_clone ON stats_import.test_clone(((comp).a % 2 = 1));
--
-- Copy stats from test to test_clone, and is_odd to is_odd_clone
--
-SELECT s.schemaname, s.tablename, s.attname, s.inherited
-FROM pg_catalog.pg_stats AS s
-CROSS JOIN LATERAL
- pg_catalog.pg_set_attribute_stats(
- relation => ('stats_import.' || s.tablename || '_clone')::regclass::oid,
- attname => s.attname,
- inherited => s.inherited,
- null_frac => s.null_frac,
- avg_width => s.avg_width,
- n_distinct => s.n_distinct,
- most_common_vals => s.most_common_vals::text,
- most_common_freqs => s.most_common_freqs,
- histogram_bounds => s.histogram_bounds::text,
- correlation => s.correlation,
- most_common_elems => s.most_common_elems::text,
- most_common_elem_freqs => s.most_common_elem_freqs,
- elem_count_histogram => s.elem_count_histogram,
- range_bounds_histogram => s.range_bounds_histogram::text,
- range_empty_frac => s.range_empty_frac,
- range_length_histogram => s.range_length_histogram::text) AS r
-WHERE s.schemaname = 'stats_import'
-AND s.tablename IN ('test', 'is_odd')
-ORDER BY s.tablename, s.attname, s.inherited;
- schemaname | tablename | attname | inherited
---------------+-----------+---------+-----------
- stats_import | is_odd | expr | f
- stats_import | test | arange | f
- stats_import | test | comp | f
- stats_import | test | id | f
- stats_import | test | name | f
- stats_import | test | tags | f
-(6 rows)
-
-SELECT c.relname, COUNT(*) AS num_stats
-FROM pg_class AS c
-JOIN pg_statistic s ON s.starelid = c.oid
-WHERE c.relnamespace = 'stats_import'::regnamespace
-AND c.relname IN ('test', 'test_clone', 'is_odd', 'is_odd_clone')
-GROUP BY c.relname
-ORDER BY c.relname;
- relname | num_stats
---------------+-----------
- is_odd | 1
- is_odd_clone | 1
- test | 5
- test_clone | 5
-(4 rows)
-
--- check test minus test_clone
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'test' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.test'::regclass
-EXCEPT
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'test' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.test_clone'::regclass;
- attname | stainherit | stanullfrac | stawidth | stadistinct | stakind1 | stakind2 | stakind3 | stakind4 | stakind5 | staop1 | staop2 | staop3 | staop4 | staop5 | stacoll1 | stacoll2 | stacoll3 | stacoll4 | stacoll5 | stanumbers1 | stanumbers2 | stanumbers3 | stanumbers4 | stanumbers5 | sv1 | sv2 | sv3 | sv4 | sv5 | direction
----------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+-----------
-(0 rows)
-
--- check test_clone minus test
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'test_clone' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.test_clone'::regclass
-EXCEPT
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'test_clone' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.test'::regclass;
- attname | stainherit | stanullfrac | stawidth | stadistinct | stakind1 | stakind2 | stakind3 | stakind4 | stakind5 | staop1 | staop2 | staop3 | staop4 | staop5 | stacoll1 | stacoll2 | stacoll3 | stacoll4 | stacoll5 | stanumbers1 | stanumbers2 | stanumbers3 | stanumbers4 | stanumbers5 | sv1 | sv2 | sv3 | sv4 | sv5 | direction
----------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+-----------
-(0 rows)
-
--- check is_odd minus is_odd_clone
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'is_odd' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.is_odd'::regclass
-EXCEPT
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'is_odd' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.is_odd_clone'::regclass;
- attname | stainherit | stanullfrac | stawidth | stadistinct | stakind1 | stakind2 | stakind3 | stakind4 | stakind5 | staop1 | staop2 | staop3 | staop4 | staop5 | stacoll1 | stacoll2 | stacoll3 | stacoll4 | stacoll5 | stanumbers1 | stanumbers2 | stanumbers3 | stanumbers4 | stanumbers5 | sv1 | sv2 | sv3 | sv4 | sv5 | direction
----------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+-----------
-(0 rows)
-
--- check is_odd_clone minus is_odd
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'is_odd_clone' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.is_odd_clone'::regclass
-EXCEPT
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'is_odd_clone' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.is_odd'::regclass;
- attname | stainherit | stanullfrac | stawidth | stadistinct | stakind1 | stakind2 | stakind3 | stakind4 | stakind5 | staop1 | staop2 | staop3 | staop4 | staop5 | stacoll1 | stacoll2 | stacoll3 | stacoll4 | stacoll5 | stanumbers1 | stanumbers2 | stanumbers3 | stanumbers4 | stanumbers5 | sv1 | sv2 | sv3 | sv4 | sv5 | direction
----------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+-----------
-(0 rows)
-
---
-SELECT relpages, reltuples, relallvisible
-FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
- relpages | reltuples | relallvisible
-----------+-----------+---------------
- 1 | 4 | 0
-(1 row)
-
---
--- Clear clone stats to try again with pg_restore_attribute_stats
---
-SELECT
- pg_catalog.pg_clear_attribute_stats(
- ('stats_import.' || s.tablename)::regclass,
- s.attname,
- s.inherited)
-FROM pg_catalog.pg_stats AS s
-WHERE s.schemaname = 'stats_import'
-AND s.tablename IN ('test_clone', 'is_odd_clone')
-ORDER BY s.tablename, s.attname, s.inherited;
- pg_clear_attribute_stats
---------------------------
-
-
-
-
-
-
-(6 rows)
-
-SELECT
-SELECT COUNT(*)
-FROM pg_catalog.pg_stats AS s
-WHERE s.schemaname = 'stats_import'
-AND s.tablename IN ('test_clone', 'is_odd_clone');
-ERROR: syntax error at or near "SELECT"
-LINE 2: SELECT COUNT(*)
- ^
---
--- Copy stats from test to test_clone, and is_odd to is_odd_clone
---
SELECT s.schemaname, s.tablename, s.attname, s.inherited, r.*
FROM pg_catalog.pg_stats AS s
CROSS JOIN LATERAL
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index 9740ab3ff02..f26f7857748 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -15,63 +15,19 @@ CREATE TABLE stats_import.test(
tags text[]
) WITH (autovacuum_enabled = false);
+CREATE INDEX test_i ON stats_import.test(id);
+
-- starting stats
SELECT relpages, reltuples, relallvisible
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
--- error: regclass not found
-SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 0::Oid,
- relpages => 17::integer,
- reltuples => 400.0::real,
- relallvisible => 4::integer);
-
--- relpages default
-SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.test'::regclass,
- relpages => NULL::integer,
- reltuples => 400.0::real,
- relallvisible => 4::integer);
-
--- reltuples default
-SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.test'::regclass,
- relpages => 17::integer,
- reltuples => NULL::real,
- relallvisible => 4::integer);
-
--- relallvisible default
-SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.test'::regclass,
- relpages => 17::integer,
- reltuples => 400.0::real,
- relallvisible => NULL::integer);
-
--- named arguments
-SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.test'::regclass,
- relpages => 17::integer,
- reltuples => 400.0::real,
- relallvisible => 4::integer);
-
-SELECT relpages, reltuples, relallvisible
-FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
-
-CREATE INDEX test_i ON stats_import.test(id);
-
BEGIN;
-- regular indexes have special case locking rules
SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.test_i'::regclass,
- relpages => 18::integer);
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.test_i'::regclass,
+ 'relpages', 18::integer);
SELECT mode FROM pg_locks
WHERE relation = 'stats_import.test'::regclass AND
@@ -88,22 +44,6 @@ SELECT
'relation', 'stats_import.test_i'::regclass,
'relpages', 19::integer );
--- positional arguments
-SELECT
- pg_catalog.pg_set_relation_stats(
- 'stats_import.test'::regclass,
- 18::integer,
- 401.0::real,
- 5::integer);
-
-SELECT relpages, reltuples, relallvisible
-FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
-
-SELECT relpages, reltuples, relallvisible
-FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
-
-- clear
SELECT
pg_catalog.pg_clear_relation_stats(
@@ -141,14 +81,14 @@ WHERE oid = 'stats_import.part_parent'::regclass;
-- although partitioned tables have no storage, setting relpages to a
-- positive value is still allowed
SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.part_parent_i'::regclass,
- relpages => 2::integer);
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.part_parent_i'::regclass,
+ 'relpages', 2::integer);
SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.part_parent'::regclass,
- relpages => 2::integer);
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.part_parent'::regclass,
+ 'relpages', 2::integer);
--
-- Partitioned indexes aren't analyzed but it is possible to set
@@ -159,9 +99,9 @@ SELECT
BEGIN;
SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.part_parent_i'::regclass,
- relpages => 2::integer);
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.part_parent_i'::regclass,
+ 'relpages', 2::integer);
SELECT mode FROM pg_locks
WHERE relation = 'stats_import.part_parent'::regclass AND
@@ -180,51 +120,9 @@ SELECT
-- nothing stops us from setting it to -1
SELECT
- pg_catalog.pg_set_relation_stats(
- relation => 'stats_import.part_parent'::regclass,
- relpages => -1::integer);
-
--- error: object doesn't exist
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => '0'::oid,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.1::real,
- avg_width => 2::integer,
- n_distinct => 0.3::real);
-
--- error: object doesn't exist
-SELECT pg_catalog.pg_clear_attribute_stats(
- relation => '0'::oid,
- attname => 'id'::name,
- inherited => false::boolean);
-
--- error: relation null
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => NULL::oid,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.1::real,
- avg_width => 2::integer,
- n_distinct => 0.3::real);
-
--- error: attribute is system column
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'xmin'::name,
- inherited => false::boolean,
- null_frac => 0.1::real,
- avg_width => 2::integer,
- n_distinct => 0.3::real);
-
--- error: attname doesn't exist
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'nope'::name,
- inherited => false::boolean,
- null_frac => 0.1::real,
- avg_width => 2::integer,
- n_distinct => 0.3::real);
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.part_parent'::regclass,
+ 'relpages', -1::integer);
-- error: attribute is system column
SELECT pg_catalog.pg_clear_attribute_stats(
@@ -238,351 +136,6 @@ SELECT pg_catalog.pg_clear_attribute_stats(
attname => 'nope'::name,
inherited => false::boolean);
--- error: attname null
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => NULL::name,
- inherited => false::boolean,
- null_frac => 0.1::real,
- avg_width => 2::integer,
- n_distinct => 0.3::real);
-
--- error: inherited null
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => NULL::boolean,
- null_frac => 0.1::real,
- avg_width => 2::integer,
- n_distinct => 0.3::real);
-
--- ok: no stakinds
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.1::real,
- avg_width => 2::integer,
- n_distinct => 0.3::real);
-
-SELECT stanullfrac, stawidth, stadistinct
-FROM pg_statistic
-WHERE starelid = 'stats_import.test'::regclass;
-
--- error: mcv / mcf null mismatch
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_freqs => '{0.1,0.2,0.3}'::real[]
- );
-
--- error: mcv / mcf null mismatch part 2
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_vals => '{1,2,3}'::text
- );
-
--- error: mcv / mcf type mismatch
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_vals => '{2023-09-30,2024-10-31,3}'::text,
- most_common_freqs => '{0.2,0.1}'::real[]
- );
-
--- error: mcv cast failure
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_vals => '{2,four,3}'::text,
- most_common_freqs => '{0.3,0.25,0.05}'::real[]
- );
-
--- ok: mcv+mcf
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_vals => '{2,1,3}'::text,
- most_common_freqs => '{0.3,0.25,0.05}'::real[]
- );
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'id';
-
--- error: histogram elements null value
--- this generates no warnings, but perhaps it should
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- histogram_bounds => '{1,NULL,3,4}'::text
- );
-
--- ok: histogram_bounds
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- histogram_bounds => '{1,2,3,4}'::text
- );
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'id';
-
--- ok: correlation
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- correlation => 0.5::real);
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'id';
-
--- error: scalars can't have mcelem
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_elems => '{1,3}'::text,
- most_common_elem_freqs => '{0.3,0.2,0.2,0.3,0.0}'::real[]
- );
-
--- error: mcelem / mcelem mismatch
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'tags'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_elems => '{one,two}'::text
- );
-
--- error: mcelem / mcelem null mismatch part 2
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'tags'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_elem_freqs => '{0.3,0.2,0.2,0.3}'::real[]
- );
-
--- ok: mcelem
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'tags'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_elems => '{one,three}'::text,
- most_common_elem_freqs => '{0.3,0.2,0.2,0.3,0.0}'::real[]
- );
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'tags';
-
--- error: scalars can't have elem_count_histogram
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- elem_count_histogram => '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
- );
--- error: elem_count_histogram null element
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'tags'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- elem_count_histogram => '{1,1,NULL,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
- );
--- ok: elem_count_histogram
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'tags'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- elem_count_histogram => '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
- );
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'tags';
-
--- error: scalars can't have range stats
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- range_empty_frac => 0.5::real,
- range_length_histogram => '{399,499,Infinity}'::text
- );
--- error: range_empty_frac range_length_hist null mismatch
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- range_length_histogram => '{399,499,Infinity}'::text
- );
--- error: range_empty_frac range_length_hist null mismatch part 2
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- range_empty_frac => 0.5::real
- );
--- ok: range_empty_frac + range_length_hist
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- range_empty_frac => 0.5::real,
- range_length_histogram => '{399,499,Infinity}'::text
- );
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'arange';
-
--- error: scalars can't have range stats
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'id'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- range_bounds_histogram => '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
- );
--- ok: range_bounds_histogram
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- range_bounds_histogram => '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
- );
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'arange';
-
--- error: cannot set most_common_elems for range type
-SELECT pg_catalog.pg_set_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean,
- null_frac => 0.5::real,
- avg_width => 2::integer,
- n_distinct => -0.1::real,
- most_common_vals => '{"[2,3)","[1,2)","[3,4)"}'::text,
- most_common_freqs => '{0.3,0.25,0.05}'::real[],
- histogram_bounds => '{"[1,2)","[2,3)","[3,4)","[4,5)"}'::text,
- correlation => 1.1::real,
- most_common_elems => '{3,1}'::text,
- most_common_elem_freqs => '{0.3,0.2,0.2,0.3,0.0}'::real[],
- range_empty_frac => -0.5::real,
- range_length_histogram => '{399,499,Infinity}'::text,
- range_bounds_histogram => '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
- );
-
---
--- Clear attribute stats to try again with restore functions
--- (relation stats were already cleared).
---
-SELECT
- pg_catalog.pg_clear_attribute_stats(
- 'stats_import.test'::regclass,
- s.attname,
- s.inherited)
-FROM pg_catalog.pg_stats AS s
-WHERE s.schemaname = 'stats_import'
-AND s.tablename = 'test'
-ORDER BY s.attname, s.inherited;
-
-- reject: argument name is NULL
SELECT pg_restore_relation_stats(
'relation', '0'::oid::regclass,
@@ -1105,173 +658,6 @@ CREATE TABLE stats_import.test_clone ( LIKE stats_import.test )
CREATE INDEX is_odd_clone ON stats_import.test_clone(((comp).a % 2 = 1));
---
--- Copy stats from test to test_clone, and is_odd to is_odd_clone
---
-SELECT s.schemaname, s.tablename, s.attname, s.inherited
-FROM pg_catalog.pg_stats AS s
-CROSS JOIN LATERAL
- pg_catalog.pg_set_attribute_stats(
- relation => ('stats_import.' || s.tablename || '_clone')::regclass::oid,
- attname => s.attname,
- inherited => s.inherited,
- null_frac => s.null_frac,
- avg_width => s.avg_width,
- n_distinct => s.n_distinct,
- most_common_vals => s.most_common_vals::text,
- most_common_freqs => s.most_common_freqs,
- histogram_bounds => s.histogram_bounds::text,
- correlation => s.correlation,
- most_common_elems => s.most_common_elems::text,
- most_common_elem_freqs => s.most_common_elem_freqs,
- elem_count_histogram => s.elem_count_histogram,
- range_bounds_histogram => s.range_bounds_histogram::text,
- range_empty_frac => s.range_empty_frac,
- range_length_histogram => s.range_length_histogram::text) AS r
-WHERE s.schemaname = 'stats_import'
-AND s.tablename IN ('test', 'is_odd')
-ORDER BY s.tablename, s.attname, s.inherited;
-
-SELECT c.relname, COUNT(*) AS num_stats
-FROM pg_class AS c
-JOIN pg_statistic s ON s.starelid = c.oid
-WHERE c.relnamespace = 'stats_import'::regnamespace
-AND c.relname IN ('test', 'test_clone', 'is_odd', 'is_odd_clone')
-GROUP BY c.relname
-ORDER BY c.relname;
-
--- check test minus test_clone
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'test' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.test'::regclass
-EXCEPT
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'test' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.test_clone'::regclass;
-
--- check test_clone minus test
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'test_clone' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.test_clone'::regclass
-EXCEPT
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'test_clone' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.test'::regclass;
-
--- check is_odd minus is_odd_clone
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'is_odd' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.is_odd'::regclass
-EXCEPT
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'is_odd' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.is_odd_clone'::regclass;
-
--- check is_odd_clone minus is_odd
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'is_odd_clone' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.is_odd_clone'::regclass
-EXCEPT
-SELECT
- a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
- s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
- s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
- s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
- s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
- s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
- s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
- s.stavalues5::text AS sv5, 'is_odd_clone' AS direction
-FROM pg_statistic s
-JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
-WHERE s.starelid = 'stats_import.is_odd'::regclass;
-
---
-SELECT relpages, reltuples, relallvisible
-FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
-
---
--- Clear clone stats to try again with pg_restore_attribute_stats
---
-SELECT
- pg_catalog.pg_clear_attribute_stats(
- ('stats_import.' || s.tablename)::regclass,
- s.attname,
- s.inherited)
-FROM pg_catalog.pg_stats AS s
-WHERE s.schemaname = 'stats_import'
-AND s.tablename IN ('test_clone', 'is_odd_clone')
-ORDER BY s.tablename, s.attname, s.inherited;
-SELECT
-
-SELECT COUNT(*)
-FROM pg_catalog.pg_stats AS s
-WHERE s.schemaname = 'stats_import'
-AND s.tablename IN ('test_clone', 'is_odd_clone');
-
--
-- Copy stats from test to test_clone, and is_odd to is_odd_clone
--
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index f0ccb751106..12206e0cfc6 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -30181,41 +30181,72 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<tbody>
<row>
- <entry role="func_table_entry">
- <para role="func_signature">
- <indexterm>
- <primary>pg_set_relation_stats</primary>
- </indexterm>
- <function>pg_set_relation_stats</function> (
- <parameter>relation</parameter> <type>regclass</type>
- <optional>, <parameter>relpages</parameter> <type>integer</type></optional>
- <optional>, <parameter>reltuples</parameter> <type>real</type></optional>
- <optional>, <parameter>relallvisible</parameter> <type>integer</type></optional> )
- <returnvalue>void</returnvalue>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_restore_relation_stats</primary>
+ </indexterm>
+ <function>pg_restore_relation_stats</function> (
+ <literal>VARIADIC</literal> <parameter>kwargs</parameter> <type>"any"</type> )
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Updates table-level statistics. Ordinarily, these statistics are
+ collected automatically or updated as a part of <xref
+ linkend="sql-vacuum"/> or <xref linkend="sql-analyze"/>, so it's not
+ necessary to call this function. However, it is useful after a
+ restore to enable the optimizer to choose better plans if
+ <command>ANALYZE</command> has not been run yet.
</para>
<para>
- Updates relation-level statistics for the given relation to the
- specified values. The parameters correspond to columns in <link
- linkend="catalog-pg-class"><structname>pg_class</structname></link>. Unspecified
- or <literal>NULL</literal> values leave the setting unchanged.
+ The tracked statistics may change from version to version, so
+ arguments are passed as pairs of <replaceable>argname</replaceable>
+ and <replaceable>argvalue</replaceable> in the form:
+<programlisting>
+ SELECT pg_restore_relation_stats(
+ '<replaceable>arg1name</replaceable>', '<replaceable>arg1value</replaceable>'::<replaceable>arg1type</replaceable>,
+ '<replaceable>arg2name</replaceable>', '<replaceable>arg2value</replaceable>'::<replaceable>arg2type</replaceable>,
+ '<replaceable>arg3name</replaceable>', '<replaceable>arg3value</replaceable>'::<replaceable>arg3type</replaceable>);
+</programlisting>
</para>
<para>
- Ordinarily, these statistics are collected automatically or updated
- as a part of <xref linkend="sql-vacuum"/> or <xref
- linkend="sql-analyze"/>, so it's not necessary to call this
- function. However, it may be useful when testing the effects of
- statistics on the planner to understand or anticipate plan changes.
+ For example, to set the <structname>relpages</structname> and
+ <structname>reltuples</structname> of the table
+ <structname>mytable</structname>:
+<programlisting>
+ SELECT pg_restore_relation_stats(
+ 'relation', 'mytable'::regclass,
+ 'relpages', 173::integer,
+ 'reltuples', 10000::real);
+</programlisting>
</para>
<para>
- The caller must have the <literal>MAINTAIN</literal> privilege on
- the table or be the owner of the database.
+ The argument <literal>relation</literal> with a value of type
+ <type>regclass</type> is required, and specifies the table. Other
+ arguments are the names of statistics corresponding to certain
+ columns in <link
+ linkend="catalog-pg-class"><structname>pg_class</structname></link>.
+ The currently-supported relation statistics are
+ <literal>relpages</literal> with a value of type
+ <type>integer</type>, <literal>reltuples</literal> with a value of
+ type <type>real</type>, and <literal>relallvisible</literal> with a
+ value of type <type>integer</type>.
</para>
<para>
- The value of <structfield>relpages</structfield> must be greater than
- or equal to <literal>-1</literal>,
- <structfield>reltuples</structfield> must be greater than or equal to
- <literal>-1.0</literal>, and <structfield>relallvisible</structfield>
- must be greater than or equal to <literal>0</literal>.
+ Additionally, this function supports argument name
+ <literal>version</literal> of type <type>integer</type>, which
+ specifies the version from which the statistics originated, improving
+ interpretation of statistics from older versions of
+ <productname>PostgreSQL</productname>.
+ </para>
+ <para>
+ Minor errors are reported as a <literal>WARNING</literal> and
+ ignored, and remaining statistics will still be restored. If all
+ specified statistics are successfully restored, return
+ <literal>true</literal>, otherwise <literal>false</literal>.
+ </para>
+ <para>
+ The caller must have the <literal>MAINTAIN</literal> privilege on the
+ table or be the owner of the database.
</para>
</entry>
</row>
@@ -30234,8 +30265,8 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
table was newly created.
</para>
<para>
- The caller must have the <literal>MAINTAIN</literal> privilege on
- the table or be the owner of the database.
+ The caller must have the <literal>MAINTAIN</literal> privilege on the
+ table or be the owner of the database.
</para>
</entry>
</row>
@@ -30243,42 +30274,61 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
- <primary>pg_restore_relation_stats</primary>
+ <primary>pg_restore_attribute_stats</primary>
</indexterm>
- <function>pg_restore_relation_stats</function> (
+ <function>pg_restore_attribute_stats</function> (
<literal>VARIADIC</literal> <parameter>kwargs</parameter> <type>"any"</type> )
<returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Create or update column-level statistics. Ordinarily, these
+ statistics are collected automatically or updated as a part of <xref
+ linkend="sql-vacuum"/> or <xref linkend="sql-analyze"/>, so it's not
+ necessary to call this function. However, it is useful after a
+ restore to enable the optimizer to choose better plans if
+ <command>ANALYZE</command> has not been run yet.
</para>
<para>
- Similar to <function>pg_set_relation_stats()</function>, but intended
- for bulk restore of relation statistics. The tracked statistics may
- change from version to version, so the primary purpose of this
- function is to maintain a consistent function signature to avoid
- errors when restoring statistics from previous versions.
+ The tracked statistics may change from version to version, so
+ arguments are passed as pairs of <replaceable>argname</replaceable>
+ and <replaceable>argvalue</replaceable> in the form:
+<programlisting>
+ SELECT pg_restore_attribute_stats(
+ '<replaceable>arg1name</replaceable>', '<replaceable>arg1value</replaceable>'::<replaceable>arg1type</replaceable>,
+ '<replaceable>arg2name</replaceable>', '<replaceable>arg2value</replaceable>'::<replaceable>arg2type</replaceable>,
+ '<replaceable>arg3name</replaceable>', '<replaceable>arg3value</replaceable>'::<replaceable>arg3type</replaceable>);
+</programlisting>
</para>
<para>
- Arguments are passed as pairs of <replaceable>argname</replaceable>
- and <replaceable>argvalue</replaceable>, where
- <replaceable>argname</replaceable> corresponds to a named argument in
- <function>pg_set_relation_stats()</function> and
- <replaceable>argvalue</replaceable> is of the corresponding type.
+ For example, to set the <structname>avg_width</structname> and
+ <structname>null_frac</structname> for the attribute
+ <structname>col1</structname> of the table
+ <structname>mytable</structname>:
+<programlisting>
+ SELECT pg_restore_attribute_stats(
+ 'relation', 'mytable'::regclass,
+ 'attname', 'col1'::name,
+ 'inherited', false,
+ 'avg_width', 125::integer,
+ 'null_frac', 0.5::real);
+</programlisting>
+ </para>
+ <para>
+ The required arguments are <literal>relation</literal> with a value
+ of type <type>regclass</type>, which specifies the table;
+ <literal>attname</literal> with a value of type <type>name</type>,
+ which specifies the column; and <literal>inherited</literal>, which
+ specifies whether the statistics includes values from child tables.
+ Other arguments are the names of statistics corresponding to columns
+ in <link
+ linkend="view-pg-stats"><structname>pg_stats</structname></link>.
</para>
<para>
Additionally, this function supports argument name
<literal>version</literal> of type <type>integer</type>, which
specifies the version from which the statistics originated, improving
- interpretation of older statistics.
- </para>
- <para>
- For example, to set the <structname>relpages</structname> and
- <structname>reltuples</structname> of the table
- <structname>mytable</structname>:
-<programlisting>
- SELECT pg_restore_relation_stats(
- 'relation', 'mytable'::regclass,
- 'relpages', 173::integer,
- 'reltuples', 10000::float4);
-</programlisting>
+ interpretation of statistics from older versions of
+ <productname>PostgreSQL</productname>.
</para>
<para>
Minor errors are reported as a <literal>WARNING</literal> and
@@ -30286,53 +30336,9 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
specified statistics are successfully restored, return
<literal>true</literal>, otherwise <literal>false</literal>.
</para>
- </entry>
- </row>
-
- <row>
- <entry role="func_table_entry">
- <para role="func_signature">
- <indexterm>
- <primary>pg_set_attribute_stats</primary>
- </indexterm>
- <function>pg_set_attribute_stats</function> (
- <parameter>relation</parameter> <type>regclass</type>,
- <parameter>attname</parameter> <type>name</type>,
- <parameter>inherited</parameter> <type>boolean</type>
- <optional>, <parameter>null_frac</parameter> <type>real</type></optional>
- <optional>, <parameter>avg_width</parameter> <type>integer</type></optional>
- <optional>, <parameter>n_distinct</parameter> <type>real</type></optional>
- <optional>, <parameter>most_common_vals</parameter> <type>text</type>, <parameter>most_common_freqs</parameter> <type>real[]</type> </optional>
- <optional>, <parameter>histogram_bounds</parameter> <type>text</type> </optional>
- <optional>, <parameter>correlation</parameter> <type>real</type> </optional>
- <optional>, <parameter>most_common_elems</parameter> <type>text</type>, <parameter>most_common_elem_freqs</parameter> <type>real[]</type> </optional>
- <optional>, <parameter>elem_count_histogram</parameter> <type>real[]</type> </optional>
- <optional>, <parameter>range_length_histogram</parameter> <type>text</type> </optional>
- <optional>, <parameter>range_empty_frac</parameter> <type>real</type> </optional>
- <optional>, <parameter>range_bounds_histogram</parameter> <type>text</type> </optional> )
- <returnvalue>void</returnvalue>
- </para>
<para>
- Creates or updates attribute-level statistics for the given relation
- and attribute name to the specified values. The parameters correspond
- to attributes of the same name found in the <link
- linkend="view-pg-stats"><structname>pg_stats</structname></link>
- view.
- </para>
- <para>
- Optional parameters default to <literal>NULL</literal>, which leave
- the corresponding statistic unchanged.
- </para>
- <para>
- Ordinarily, these statistics are collected automatically or updated
- as a part of <xref linkend="sql-vacuum"/> or <xref
- linkend="sql-analyze"/>, so it's not necessary to call this
- function. However, it may be useful when testing the effects of
- statistics on the planner to understand or anticipate plan changes.
- </para>
- <para>
- The caller must have the <literal>MAINTAIN</literal> privilege on
- the table or be the owner of the database.
+ The caller must have the <literal>MAINTAIN</literal> privilege on the
+ table or be the owner of the database.
</para>
</entry>
</row>
@@ -30350,8 +30356,8 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<returnvalue>void</returnvalue>
</para>
<para>
- Clears table-level statistics for the given relation attribute, as
- though the table was newly created.
+ Clears column-level statistics for the given relation and
+ attribute, as though the table was newly created.
</para>
<para>
The caller must have the <literal>MAINTAIN</literal> privilege on
@@ -30359,58 +30365,6 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
</para>
</entry>
</row>
-
- <row>
- <entry role="func_table_entry"><para role="func_signature">
- <indexterm>
- <primary>pg_restore_attribute_stats</primary>
- </indexterm>
- <function>pg_restore_attribute_stats</function> (
- <literal>VARIADIC</literal> <parameter>kwargs</parameter> <type>"any"</type> )
- <returnvalue>boolean</returnvalue>
- </para>
- <para>
- Similar to <function>pg_set_attribute_stats()</function>, but
- intended for bulk restore of attribute statistics. The tracked
- statistics may change from version to version, so the primary purpose
- of this function is to maintain a consistent function signature to
- avoid errors when restoring statistics from previous versions.
- </para>
- <para>
- Arguments are passed as pairs of <replaceable>argname</replaceable>
- and <replaceable>argvalue</replaceable>, where
- <replaceable>argname</replaceable> corresponds to a named argument in
- <function>pg_set_attribute_stats()</function> and
- <replaceable>argvalue</replaceable> is of the corresponding type.
- </para>
- <para>
- Additionally, this function supports argument name
- <literal>version</literal> of type <type>integer</type>, which
- specifies the version from which the statistics originated, improving
- interpretation of older statistics.
- </para>
- <para>
- For example, to set the <structname>avg_width</structname> and
- <structname>null_frac</structname> for the attribute
- <structname>col1</structname> of the table
- <structname>mytable</structname>:
-<programlisting>
- SELECT pg_restore_attribute_stats(
- 'relation', 'mytable'::regclass,
- 'attname', 'col1'::name,
- 'inherited', false,
- 'avg_width', 125::integer,
- 'null_frac', 0.5::real);
-</programlisting>
- </para>
- <para>
- Minor errors are reported as a <literal>WARNING</literal> and
- ignored, and remaining statistics will still be restored. If all
- specified statistics are successfully restored, return
- <literal>true</literal>, otherwise <literal>false</literal>.
- </para>
- </entry>
- </row>
</tbody>
</tgroup>
</table>
base-commit: ecbff4378beecb0b1d12fc758538005a69821db1
--
2.48.1
[text/x-patch] vAdios-Set-0002-Change-variable-lists-to-itemizedlists.patch (3.1K, ../../CADkLM=ctPiAAr7WfWcBbhJf_fTiGrrnBNhVvWp9_wCFhBs2D6w@mail.gmail.com/4-vAdios-Set-0002-Change-variable-lists-to-itemizedlists.patch)
download | inline diff:
From 925176e7524291dea63d69e0893d236491894663 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Tue, 25 Feb 2025 18:32:13 -0500
Subject: [PATCH vAdios-Set 2/3] Change variable lists to itemizedlists
---
doc/src/sgml/func.sgml | 47 ++++++++++++++++++++++++++++++++++--------
1 file changed, 38 insertions(+), 9 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 12206e0cfc6..4b3a84c6766 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -30226,10 +30226,23 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
columns in <link
linkend="catalog-pg-class"><structname>pg_class</structname></link>.
The currently-supported relation statistics are
- <literal>relpages</literal> with a value of type
- <type>integer</type>, <literal>reltuples</literal> with a value of
- type <type>real</type>, and <literal>relallvisible</literal> with a
- value of type <type>integer</type>.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>relpages</literal> with a value of type <type>integer</type>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>reltuples</literal> with a value of type <type>real</type>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>relallvisible</literal> with a value of type <type>integer</type>
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
<para>
Additionally, this function supports argument name
@@ -30314,11 +30327,27 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
</programlisting>
</para>
<para>
- The required arguments are <literal>relation</literal> with a value
- of type <type>regclass</type>, which specifies the table;
- <literal>attname</literal> with a value of type <type>name</type>,
- which specifies the column; and <literal>inherited</literal>, which
- specifies whether the statistics includes values from child tables.
+ The required arguments are
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>relation</literal> with a value of type
+ <type>regclass</type>, which specifies the table
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>attname</literal> with a value of type
+ <type>name</type>, which specifies the column
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>inherited</literal>, which specifies whether the
+ statistics includes values from child tables.
+ </para>
+ </listitem>
+ </itemizedlist>
Other arguments are the names of statistics corresponding to columns
in <link
linkend="view-pg-stats"><structname>pg_stats</structname></link>.
--
2.48.1
[text/x-patch] vAdios-Set-0003-Adapt-some-pg_set_-_stats-calls-to-pg_res.patch (13.0K, ../../CADkLM=ctPiAAr7WfWcBbhJf_fTiGrrnBNhVvWp9_wCFhBs2D6w@mail.gmail.com/5-vAdios-Set-0003-Adapt-some-pg_set_-_stats-calls-to-pg_res.patch)
download | inline diff:
From b23e5bae0c0e650ec76cffa7343cb17b0295e4d5 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Tue, 25 Feb 2025 18:32:55 -0500
Subject: [PATCH vAdios-Set 3/3] Adapt some pg_set_*_stats() calls to
pg_restore_*_stats() to preserve coverage
---
src/test/regress/expected/stats_import.out | 119 +++++++++++++++++++--
src/test/regress/sql/stats_import.sql | 79 ++++++++++++--
2 files changed, 182 insertions(+), 16 deletions(-)
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 7c7784efaf1..e907d76b1c1 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -125,6 +125,13 @@ SELECT
t
(1 row)
+-- error: regclass not found
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 0::Oid::regclass,
+ 'relpages', '17'::integer,
+ 'reltuples', '400.0'::real,
+ 'relallvisible', '4'::integer);
+ERROR: could not open relation with OID 0
--
-- Partitioned indexes aren't analyzed but it is possible to set
-- stats. The locking rules are different from normal indexes due to
@@ -177,7 +184,12 @@ SELECT
t
(1 row)
--- error: attribute is system column
+-- error: relation null
+SELECT
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', NULL::regclass,
+ 'relpages', -1::integer);
+ERROR: "relation" cannot be NULL
SELECT pg_catalog.pg_clear_attribute_stats(
relation => 'stats_import.test'::regclass,
attname => 'ctid'::name,
@@ -191,7 +203,7 @@ SELECT pg_catalog.pg_clear_attribute_stats(
ERROR: column "nope" of relation "test" does not exist
-- reject: argument name is NULL
SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
+ 'relation', 'stats_import.test'::regclass,
'version', 150000::integer,
NULL, '17'::integer,
'reltuples', 400::real,
@@ -199,7 +211,7 @@ SELECT pg_restore_relation_stats(
ERROR: name at variadic position 5 is NULL
-- reject: argument name is an integer
SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
+ 'relation', 'stats_import.test'::regclass,
'version', 150000::integer,
17, '17'::integer,
'reltuples', 400::real,
@@ -207,7 +219,7 @@ SELECT pg_restore_relation_stats(
ERROR: name at variadic position 5 has type "integer", expected type "text"
-- reject: odd number of variadic arguments cannot be pairs
SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
+ 'relation', 'stats_import.test'::regclass,
'version', 150000::integer,
'relpages', '17'::integer,
'reltuples', 400::real,
@@ -298,32 +310,46 @@ WHERE oid = 'stats_import.test'::regclass;
-- warn and error: unrecognized argument name
SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
+ 'relation', 'stats_import.test'::regclass,
'version', 150000::integer,
'relpages', '17'::integer,
'reltuples', 400::real,
'nope', 4::integer);
WARNING: unrecognized argument name: "nope"
-ERROR: could not open relation with OID 0
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+-- but relpages, reltuples got set anyway
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+ relpages | reltuples | relallvisible
+----------+-----------+---------------
+ 17 | 400 | 5
+(1 row)
+
-- warn: bad relpages type
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
'version', 150000::integer,
'relpages', 'nope'::text,
- 'reltuples', 400.0::real,
- 'relallvisible', 4::integer);
+ 'reltuples', 401.0::real,
+ 'relallvisible', 7::integer);
WARNING: argument "relpages" has type "text", expected type "integer"
pg_restore_relation_stats
---------------------------
f
(1 row)
+-- but reltuples, relallvisible got set anyway
SELECT relpages, reltuples, relallvisible
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
relpages | reltuples | relallvisible
----------+-----------+---------------
- 16 | 400 | 4
+ 17 | 401 | 7
(1 row)
-- error: object does not exist
@@ -366,6 +392,12 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'avg_width', 2::integer,
'n_distinct', 0.3::real);
ERROR: column "nope" of relation "test" does not exist
+-- error: attribute is system column
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'ctid'::name,
+ 'inherited', 'false'::boolean);
+ERROR: cannot modify statistics on system column "ctid"
-- error: inherited null
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
@@ -483,6 +515,75 @@ AND attname = 'id';
stats_import | test | id | f | 0.7 | 8 | -0.8 | | | | | | | | | |
(1 row)
+-- scalars can't have mcelem
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'most_common_elems', '{1,3}'::text,
+ 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
+ );
+WARNING: unable to determine element type of attribute "id"
+DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- mcelem / mcelem freqs mismatch (each way)
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'tags'::name,
+ 'inherited', false::boolean,
+ 'most_common_elems', '{one,two}'::text
+ );
+WARNING: "most_common_elem_freqs" must be specified when "most_common_elems" is specified
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'tags'::name,
+ 'inherited', false::boolean,
+ 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
+ );
+WARNING: "most_common_elems" must be specified when "most_common_elem_freqs" is specified
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- scalars can't have elem_count_histogram
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
+ );
+WARNING: unable to determine element type of attribute "id"
+DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- range types can't have most_common_elems
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'arange'::name,
+ 'inherited', false::boolean,
+ 'most_common_elems', '{3,1}'::text,
+ 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
+ );
+WARNING: unable to determine element type of attribute "arange"
+DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
-- warn: mcv / mcf type mismatch
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index f26f7857748..6098fb49b85 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -90,6 +90,13 @@ SELECT
'relation', 'stats_import.part_parent'::regclass,
'relpages', 2::integer);
+-- error: regclass not found
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 0::Oid::regclass,
+ 'relpages', '17'::integer,
+ 'reltuples', '400.0'::real,
+ 'relallvisible', '4'::integer);
+
--
-- Partitioned indexes aren't analyzed but it is possible to set
-- stats. The locking rules are different from normal indexes due to
@@ -124,7 +131,12 @@ SELECT
'relation', 'stats_import.part_parent'::regclass,
'relpages', -1::integer);
--- error: attribute is system column
+-- error: relation null
+SELECT
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', NULL::regclass,
+ 'relpages', -1::integer);
+
SELECT pg_catalog.pg_clear_attribute_stats(
relation => 'stats_import.test'::regclass,
attname => 'ctid'::name,
@@ -138,7 +150,7 @@ SELECT pg_catalog.pg_clear_attribute_stats(
-- reject: argument name is NULL
SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
+ 'relation', 'stats_import.test'::regclass,
'version', 150000::integer,
NULL, '17'::integer,
'reltuples', 400::real,
@@ -146,7 +158,7 @@ SELECT pg_restore_relation_stats(
-- reject: argument name is an integer
SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
+ 'relation', 'stats_import.test'::regclass,
'version', 150000::integer,
17, '17'::integer,
'reltuples', 400::real,
@@ -154,7 +166,7 @@ SELECT pg_restore_relation_stats(
-- reject: odd number of variadic arguments cannot be pairs
SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
+ 'relation', 'stats_import.test'::regclass,
'version', 150000::integer,
'relpages', '17'::integer,
'reltuples', 400::real,
@@ -212,20 +224,26 @@ WHERE oid = 'stats_import.test'::regclass;
-- warn and error: unrecognized argument name
SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
+ 'relation', 'stats_import.test'::regclass,
'version', 150000::integer,
'relpages', '17'::integer,
'reltuples', 400::real,
'nope', 4::integer);
+-- but relpages, reltuples got set anyway
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+
-- warn: bad relpages type
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
'version', 150000::integer,
'relpages', 'nope'::text,
- 'reltuples', 400.0::real,
- 'relallvisible', 4::integer);
+ 'reltuples', 401.0::real,
+ 'relallvisible', 7::integer);
+-- but reltuples, relallvisible got set anyway
SELECT relpages, reltuples, relallvisible
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
@@ -270,6 +288,12 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'avg_width', 2::integer,
'n_distinct', 0.3::real);
+-- error: attribute is system column
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'ctid'::name,
+ 'inherited', 'false'::boolean);
+
-- error: inherited null
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
@@ -352,6 +376,47 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
+-- scalars can't have mcelem
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'most_common_elems', '{1,3}'::text,
+ 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
+ );
+
+-- mcelem / mcelem freqs mismatch (each way)
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'tags'::name,
+ 'inherited', false::boolean,
+ 'most_common_elems', '{one,two}'::text
+ );
+
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'tags'::name,
+ 'inherited', false::boolean,
+ 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
+ );
+
+-- scalars can't have elem_count_histogram
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
+ );
+
+-- range types can't have most_common_elems
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'arange'::name,
+ 'inherited', false::boolean,
+ 'most_common_elems', '{3,1}'::text,
+ 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
+ );
+
-- warn: mcv / mcf type mismatch
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
--
2.48.1
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-26 00:17 Jeff Davis <[email protected]>
parent: Corey Huinker <[email protected]>
1 sibling, 0 replies; 199+ messages in thread
From: Jeff Davis @ 2025-02-26 00:17 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Tom Lane <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Tue, 2025-02-25 at 15:31 -0500, Corey Huinker wrote:
> Documentation:
>
> + The currently-supported relation statistics are
> + <literal>relpages</literal> with a value of type
> + <type>integer</type>, <literal>reltuples</literal> with a
> value of
> + type <type>real</type>, and
> <literal>relallvisible</literal> with a
> + value of type <type>integer</type>.
>
> Could we make this a bullet-list? Same for the required attribute
> stats and optional attribute stats. I think it would be more eye-
> catching and useful to people skimming to recall the name of a
> parameter, which is probably what most people will do after they've
> read it once to get the core concepts.
I couldn't make that look quite right. These functions are mostly for
use by pg_dump, and while documentation is necessary, I don't think we
should go so far as to make it "eye-catching". At least not until
things settle a bit.
> Question:
>
> Do we want to re-compact the oids we consumed in pg_proc.dat?
Done.
> Specifically missing are:
>
> * regclass not found
> * attribute is system column
> * scalars can't have mcelem
> * mcelem / mcelem freqs mismatch (parts 1 and 2)
> * scalars can't have elem_count_histogram
> * cannot set most_common_elems for range type
Done.
And committed.
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-26 02:00 Jeff Davis <[email protected]>
parent: Andres Freund <[email protected]>
3 siblings, 2 replies; 199+ messages in thread
From: Jeff Davis @ 2025-02-26 02:00 UTC (permalink / raw)
To: Andres Freund <[email protected]>; Corey Huinker <[email protected]>; +Cc: Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Mon, 2025-02-24 at 09:54 -0500, Andres Freund wrote:
> Have you compared performance of with/without stats after these
> optimizations?
On unoptimized build with asserts enabled, dumping the regression
database:
--no-statistics: 1.0s
master: 3.6s
v3j-0001: 3.0s
v3j-0002: 1.7s
I plan to commit the patches soon.
Regards,
Jeff Davis
Attachments:
[text/x-patch] v3j-0001-Avoid-unnecessary-relation-stats-query-in-pg_dum.patch (11.6K, ../../[email protected]/2-v3j-0001-Avoid-unnecessary-relation-stats-query-in-pg_dum.patch)
download | inline diff:
From d617fb142158e0ca964e5bc8bb3351d993de6062 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Fri, 21 Feb 2025 23:31:04 -0500
Subject: [PATCH v3j 1/2] Avoid unnecessary relation stats query in pg_dump.
The few fields we need can be easily collected in getTables() and
getIndexes() and stored in RelStatsInfo.
Co-authored-by: Corey Huinker <[email protected]>
Co-authored-by: Jeff Davis <[email protected]>
Discussion: https://postgr.es/m/CADkLM=f0a43aTd88xW4xCFayEF25g-7hTrHX_WhV40HyocsUGg@mail.gmail.com
---
src/bin/pg_dump/pg_dump.c | 145 ++++++++++++++++----------------------
src/bin/pg_dump/pg_dump.h | 5 +-
2 files changed, 64 insertions(+), 86 deletions(-)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index afd79287177..a1823914656 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -56,6 +56,7 @@
#include "common/connect.h"
#include "common/int.h"
#include "common/relpath.h"
+#include "common/shortest_dec.h"
#include "compress_io.h"
#include "dumputils.h"
#include "fe_utils/option_utils.h"
@@ -524,6 +525,9 @@ main(int argc, char **argv)
pg_logging_set_level(PG_LOG_WARNING);
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_dump"));
+ /* ensure that locale does not affect floating point interpretation */
+ setlocale(LC_NUMERIC, "C");
+
/*
* Initialize what we need for parallel execution, especially for thread
* support on Windows.
@@ -6814,7 +6818,8 @@ getFuncs(Archive *fout)
*
*/
static RelStatsInfo *
-getRelationStatistics(Archive *fout, DumpableObject *rel, char relkind)
+getRelationStatistics(Archive *fout, DumpableObject *rel, int32 relpages,
+ float reltuples, int32 relallvisible, char relkind)
{
if (!fout->dopt->dumpStatistics)
return NULL;
@@ -6839,6 +6844,9 @@ getRelationStatistics(Archive *fout, DumpableObject *rel, char relkind)
dobj->components |= DUMP_COMPONENT_STATISTICS;
dobj->name = pg_strdup(rel->name);
dobj->namespace = rel->namespace;
+ info->relpages = relpages;
+ info->reltuples = reltuples;
+ info->relallvisible = relallvisible;
info->relkind = relkind;
info->postponed_def = false;
@@ -6874,6 +6882,8 @@ getTables(Archive *fout, int *numTables)
int i_relhasindex;
int i_relhasrules;
int i_relpages;
+ int i_reltuples;
+ int i_relallvisible;
int i_toastpages;
int i_owning_tab;
int i_owning_col;
@@ -6924,7 +6934,7 @@ getTables(Archive *fout, int *numTables)
"c.relowner, "
"c.relchecks, "
"c.relhasindex, c.relhasrules, c.relpages, "
- "c.relhastriggers, "
+ "c.reltuples, c.relallvisible, c.relhastriggers, "
"c.relpersistence, "
"c.reloftype, "
"c.relacl, "
@@ -7088,6 +7098,8 @@ getTables(Archive *fout, int *numTables)
i_relhasindex = PQfnumber(res, "relhasindex");
i_relhasrules = PQfnumber(res, "relhasrules");
i_relpages = PQfnumber(res, "relpages");
+ i_reltuples = PQfnumber(res, "reltuples");
+ i_relallvisible = PQfnumber(res, "relallvisible");
i_toastpages = PQfnumber(res, "toastpages");
i_owning_tab = PQfnumber(res, "owning_tab");
i_owning_col = PQfnumber(res, "owning_col");
@@ -7134,6 +7146,9 @@ getTables(Archive *fout, int *numTables)
for (i = 0; i < ntups; i++)
{
+ float reltuples = strtof(PQgetvalue(res, i, i_reltuples), NULL);
+ int32 relallvisible = atoi(PQgetvalue(res, i, i_relallvisible));
+
tblinfo[i].dobj.objType = DO_TABLE;
tblinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_reltableoid));
tblinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_reloid));
@@ -7233,7 +7248,8 @@ getTables(Archive *fout, int *numTables)
/* Add statistics */
if (tblinfo[i].interesting)
- getRelationStatistics(fout, &tblinfo[i].dobj, tblinfo[i].relkind);
+ getRelationStatistics(fout, &tblinfo[i].dobj, tblinfo[i].relpages,
+ reltuples, relallvisible, tblinfo[i].relkind);
/*
* Read-lock target tables to make sure they aren't DROPPED or altered
@@ -7499,6 +7515,9 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
i_oid,
i_indrelid,
i_indexname,
+ i_relpages,
+ i_reltuples,
+ i_relallvisible,
i_parentidx,
i_indexdef,
i_indnkeyatts,
@@ -7552,6 +7571,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
appendPQExpBufferStr(query,
"SELECT t.tableoid, t.oid, i.indrelid, "
"t.relname AS indexname, "
+ "t.relpages, t.reltuples, t.relallvisible, "
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
"i.indkey, i.indisclustered, "
"c.contype, c.conname, "
@@ -7659,6 +7679,9 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
i_oid = PQfnumber(res, "oid");
i_indrelid = PQfnumber(res, "indrelid");
i_indexname = PQfnumber(res, "indexname");
+ i_relpages = PQfnumber(res, "relpages");
+ i_reltuples = PQfnumber(res, "reltuples");
+ i_relallvisible = PQfnumber(res, "relallvisible");
i_parentidx = PQfnumber(res, "parentidx");
i_indexdef = PQfnumber(res, "indexdef");
i_indnkeyatts = PQfnumber(res, "indnkeyatts");
@@ -7725,6 +7748,9 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
char contype;
char indexkind;
RelStatsInfo *relstats;
+ int32 relpages = atoi(PQgetvalue(res, j, i_relpages));
+ float reltuples = strtof(PQgetvalue(res, j, i_reltuples), NULL);
+ int32 relallvisible = atoi(PQgetvalue(res, j, i_relallvisible));
indxinfo[j].dobj.objType = DO_INDEX;
indxinfo[j].dobj.catId.tableoid = atooid(PQgetvalue(res, j, i_tableoid));
@@ -7759,7 +7785,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
indexkind = RELKIND_PARTITIONED_INDEX;
contype = *(PQgetvalue(res, j, i_contype));
- relstats = getRelationStatistics(fout, &indxinfo[j].dobj, indexkind);
+ relstats = getRelationStatistics(fout, &indxinfo[j].dobj, relpages,
+ reltuples, relallvisible, indexkind);
if (contype == 'p' || contype == 'u' || contype == 'x')
{
@@ -10383,18 +10410,6 @@ dumpComment(Archive *fout, const char *type,
catalogId, subid, dumpId, NULL);
}
-/*
- * Tabular description of the parameters to pg_restore_relation_stats()
- * param_name, param_type
- */
-static const char *rel_stats_arginfo[][2] = {
- {"relation", "regclass"},
- {"version", "integer"},
- {"relpages", "integer"},
- {"reltuples", "real"},
- {"relallvisible", "integer"},
-};
-
/*
* Tabular description of the parameters to pg_restore_attribute_stats()
* param_name, param_type
@@ -10419,30 +10434,6 @@ static const char *att_stats_arginfo[][2] = {
{"range_bounds_histogram", "text"},
};
-/*
- * getRelStatsExportQuery --
- *
- * Generate a query that will fetch all relation (e.g. pg_class)
- * stats for a given relation.
- */
-static void
-getRelStatsExportQuery(PQExpBuffer query, Archive *fout,
- const char *schemaname, const char *relname)
-{
- resetPQExpBuffer(query);
- appendPQExpBufferStr(query,
- "SELECT c.oid::regclass AS relation, "
- "current_setting('server_version_num') AS version, "
- "c.relpages, c.reltuples, c.relallvisible "
- "FROM pg_class c "
- "JOIN pg_namespace n "
- "ON n.oid = c.relnamespace "
- "WHERE n.nspname = ");
- appendStringLiteralAH(query, schemaname, fout);
- appendPQExpBufferStr(query, " AND c.relname = ");
- appendStringLiteralAH(query, relname, fout);
-}
-
/*
* getAttStatsExportQuery --
*
@@ -10454,21 +10445,22 @@ getAttStatsExportQuery(PQExpBuffer query, Archive *fout,
const char *schemaname, const char *relname)
{
resetPQExpBuffer(query);
- appendPQExpBufferStr(query,
- "SELECT c.oid::regclass AS relation, "
- "s.attname,"
- "s.inherited,"
- "current_setting('server_version_num') AS version, "
- "s.null_frac,"
- "s.avg_width,"
- "s.n_distinct,"
- "s.most_common_vals,"
- "s.most_common_freqs,"
- "s.histogram_bounds,"
- "s.correlation,"
- "s.most_common_elems,"
- "s.most_common_elem_freqs,"
- "s.elem_count_histogram,");
+ appendPQExpBuffer(query,
+ "SELECT c.oid::regclass AS relation, "
+ "s.attname,"
+ "s.inherited,"
+ "'%u'::integer AS version, "
+ "s.null_frac,"
+ "s.avg_width,"
+ "s.n_distinct,"
+ "s.most_common_vals,"
+ "s.most_common_freqs,"
+ "s.histogram_bounds,"
+ "s.correlation,"
+ "s.most_common_elems,"
+ "s.most_common_elem_freqs,"
+ "s.elem_count_histogram,",
+ fout->remoteVersion);
if (fout->remoteVersion >= 170000)
appendPQExpBufferStr(query,
@@ -10521,34 +10513,21 @@ appendNamedArgument(PQExpBuffer out, Archive *fout, const char *argname,
* Append a formatted pg_restore_relation_stats statement.
*/
static void
-appendRelStatsImport(PQExpBuffer out, Archive *fout, PGresult *res)
+appendRelStatsImport(PQExpBuffer out, Archive *fout, const RelStatsInfo *rsinfo)
{
- const char *sep = "";
+ const char *qualname = fmtQualifiedId(rsinfo->dobj.namespace->dobj.name, rsinfo->dobj.name);
+ char reltuples_str[FLOAT_SHORTEST_DECIMAL_LEN];
- if (PQntuples(res) == 0)
- return;
+ float_to_shortest_decimal_buf(rsinfo->reltuples, reltuples_str);
appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
-
- for (int argno = 0; argno < lengthof(rel_stats_arginfo); argno++)
- {
- const char *argname = rel_stats_arginfo[argno][0];
- const char *argtype = rel_stats_arginfo[argno][1];
- int fieldno = PQfnumber(res, argname);
-
- if (fieldno < 0)
- pg_fatal("relation stats export query missing field '%s'",
- argname);
-
- if (PQgetisnull(res, 0, fieldno))
- continue;
-
- appendPQExpBufferStr(out, sep);
- appendNamedArgument(out, fout, argname, PQgetvalue(res, 0, fieldno), argtype);
-
- sep = ",\n";
- }
- appendPQExpBufferStr(out, "\n);\n");
+ appendPQExpBuffer(out, "\t'relation', '%s'::regclass,\n", qualname);
+ appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+ fout->remoteVersion);
+ appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
+ appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", reltuples_str);
+ appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer\n);\n",
+ rsinfo->relallvisible);
}
/*
@@ -10643,15 +10622,11 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
tag = createPQExpBuffer();
appendPQExpBufferStr(tag, fmtId(dobj->name));
- query = createPQExpBuffer();
out = createPQExpBuffer();
- getRelStatsExportQuery(query, fout, dobj->namespace->dobj.name,
- dobj->name);
- res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
- appendRelStatsImport(out, fout, res);
- PQclear(res);
+ appendRelStatsImport(out, fout, rsinfo);
+ query = createPQExpBuffer();
getAttStatsExportQuery(query, fout, dobj->namespace->dobj.name,
dobj->name);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f08f5905aa3..9d6a4857c4b 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -328,7 +328,7 @@ typedef struct _tableInfo
Oid owning_tab; /* OID of table owning sequence */
int owning_col; /* attr # of column owning sequence */
bool is_identity_sequence;
- int relpages; /* table's size in pages (from pg_class) */
+ int32 relpages; /* table's size in pages (from pg_class) */
int toastpages; /* toast table's size in pages, if any */
bool interesting; /* true if need to collect more data */
@@ -438,6 +438,9 @@ typedef struct _indexAttachInfo
typedef struct _relStatsInfo
{
DumpableObject dobj;
+ int32 relpages;
+ float reltuples;
+ int32 relallvisible;
char relkind; /* 'r', 'm', 'i', etc */
bool postponed_def; /* stats must be postponed into post-data */
} RelStatsInfo;
--
2.34.1
[text/x-patch] v3j-0002-pg_dump-prepare-attribute-stats-query.patch (7.7K, ../../[email protected]/3-v3j-0002-pg_dump-prepare-attribute-stats-query.patch)
download | inline diff:
From 33cde5fdbb207a99bce678015e1e45df0210bb56 Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Tue, 25 Feb 2025 17:15:47 -0800
Subject: [PATCH v3j 2/2] pg_dump: prepare attribute stats query.
Follow precedent in pg_dump for preparing queries to improve
performance. Also, simplify the query by removing unnecessary joins.
Reported-by: Andres Freund <[email protected]>
Co-authored-by: Corey Huinker <[email protected]>
Co-authored-by: Jeff Davis <[email protected]>
Discussion: https://postgr.es/m/CADkLM=dRMC6t8gp9GVf6y6E_r5EChQjMAAh_vPyih_zMiq0zvA@mail.gmail.com
---
src/bin/pg_dump/pg_backup.h | 1 +
src/bin/pg_dump/pg_dump.c | 123 +++++++++++++++++-------------------
2 files changed, 58 insertions(+), 66 deletions(-)
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index 350cf659c41..e783cc68d89 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -72,6 +72,7 @@ enum _dumpPreparedQueries
PREPQUERY_DUMPOPR,
PREPQUERY_DUMPRANGETYPE,
PREPQUERY_DUMPTABLEATTACH,
+ PREPQUERY_GETATTRIBUTESTATS,
PREPQUERY_GETCOLUMNACLS,
PREPQUERY_GETDOMAINCONSTRAINTS,
};
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a1823914656..0de6c959bb0 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -10415,10 +10415,8 @@ dumpComment(Archive *fout, const char *type,
* param_name, param_type
*/
static const char *att_stats_arginfo[][2] = {
- {"relation", "regclass"},
{"attname", "name"},
{"inherited", "boolean"},
- {"version", "integer"},
{"null_frac", "float4"},
{"avg_width", "integer"},
{"n_distinct", "float4"},
@@ -10434,60 +10432,6 @@ static const char *att_stats_arginfo[][2] = {
{"range_bounds_histogram", "text"},
};
-/*
- * getAttStatsExportQuery --
- *
- * Generate a query that will fetch all attribute (e.g. pg_statistic)
- * stats for a given relation.
- */
-static void
-getAttStatsExportQuery(PQExpBuffer query, Archive *fout,
- const char *schemaname, const char *relname)
-{
- resetPQExpBuffer(query);
- appendPQExpBuffer(query,
- "SELECT c.oid::regclass AS relation, "
- "s.attname,"
- "s.inherited,"
- "'%u'::integer AS version, "
- "s.null_frac,"
- "s.avg_width,"
- "s.n_distinct,"
- "s.most_common_vals,"
- "s.most_common_freqs,"
- "s.histogram_bounds,"
- "s.correlation,"
- "s.most_common_elems,"
- "s.most_common_elem_freqs,"
- "s.elem_count_histogram,",
- fout->remoteVersion);
-
- if (fout->remoteVersion >= 170000)
- appendPQExpBufferStr(query,
- "s.range_length_histogram,"
- "s.range_empty_frac,"
- "s.range_bounds_histogram ");
- else
- appendPQExpBufferStr(query,
- "NULL AS range_length_histogram,"
- "NULL AS range_empty_frac,"
- "NULL AS range_bounds_histogram ");
-
- appendPQExpBufferStr(query,
- "FROM pg_stats s "
- "JOIN pg_namespace n "
- "ON n.nspname = s.schemaname "
- "JOIN pg_class c "
- "ON c.relname = s.tablename "
- "AND c.relnamespace = n.oid "
- "WHERE s.schemaname = ");
- appendStringLiteralAH(query, schemaname, fout);
- appendPQExpBufferStr(query, " AND s.tablename = ");
- appendStringLiteralAH(query, relname, fout);
- appendPQExpBufferStr(query, " ORDER BY s.attname, s.inherited");
-}
-
-
/*
* appendNamedArgument --
*
@@ -10513,17 +10457,17 @@ appendNamedArgument(PQExpBuffer out, Archive *fout, const char *argname,
* Append a formatted pg_restore_relation_stats statement.
*/
static void
-appendRelStatsImport(PQExpBuffer out, Archive *fout, const RelStatsInfo *rsinfo)
+appendRelStatsImport(PQExpBuffer out, Archive *fout, const RelStatsInfo *rsinfo,
+ const char *qualified_name)
{
- const char *qualname = fmtQualifiedId(rsinfo->dobj.namespace->dobj.name, rsinfo->dobj.name);
char reltuples_str[FLOAT_SHORTEST_DECIMAL_LEN];
float_to_shortest_decimal_buf(rsinfo->reltuples, reltuples_str);
appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
- appendPQExpBuffer(out, "\t'relation', '%s'::regclass,\n", qualname);
appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
fout->remoteVersion);
+ appendPQExpBuffer(out, "\t'relation', '%s'::regclass,\n", qualified_name);
appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", reltuples_str);
appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer\n);\n",
@@ -10536,13 +10480,18 @@ appendRelStatsImport(PQExpBuffer out, Archive *fout, const RelStatsInfo *rsinfo)
* Append a series of formatted pg_restore_attribute_stats statements.
*/
static void
-appendAttStatsImport(PQExpBuffer out, Archive *fout, PGresult *res)
+appendAttStatsImport(PQExpBuffer out, Archive *fout, PGresult *res,
+ const char *qualified_name)
{
for (int rownum = 0; rownum < PQntuples(res); rownum++)
{
const char *sep = "";
appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
+ appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+ fout->remoteVersion);
+ appendPQExpBuffer(out, "\t'relation', '%s'::regclass,\n",
+ qualified_name);
for (int argno = 0; argno < lengthof(att_stats_arginfo); argno++)
{
const char *argname = att_stats_arginfo[argno][0];
@@ -10607,6 +10556,7 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
DumpableObject *dobj = (DumpableObject *) &rsinfo->dobj;
DumpId *deps = NULL;
int ndeps = 0;
+ const char *qualified_name;
/* nothing to do if we are not dumping statistics */
if (!fout->dopt->dumpStatistics)
@@ -10622,15 +10572,56 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
tag = createPQExpBuffer();
appendPQExpBufferStr(tag, fmtId(dobj->name));
- out = createPQExpBuffer();
+ query = createPQExpBuffer();
+ if (!fout->is_prepared[PREPQUERY_GETATTRIBUTESTATS])
+ {
+ appendPQExpBufferStr(query,
+ "PREPARE getAttributeStats(pg_catalog.name, pg_catalog.name) AS\n"
+ "SELECT s.attname, s.inherited, "
+ "s.null_frac, s.avg_width, s.n_distinct, "
+ "s.most_common_vals, s.most_common_freqs, "
+ "s.histogram_bounds, s.correlation, "
+ "s.most_common_elems, s.most_common_elem_freqs, "
+ "s.elem_count_histogram, ");
+
+ if (fout->remoteVersion >= 170000)
+ appendPQExpBufferStr(query,
+ "s.range_length_histogram, s.range_empty_frac, "
+ "s.range_bounds_histogram ");
+ else
+ appendPQExpBufferStr(query,
+ "NULL AS range_length_histogram,"
+ "NULL AS range_empty_frac,"
+ "NULL AS range_bounds_histogram ");
- appendRelStatsImport(out, fout, rsinfo);
+ appendPQExpBufferStr(query,
+ "FROM pg_stats s "
+ "WHERE s.schemaname = $1 "
+ "AND s.tablename = $2 "
+ "ORDER BY s.attname, s.inherited");
+
+ ExecuteSqlStatement(fout, query->data);
+
+ fout->is_prepared[PREPQUERY_GETATTRIBUTESTATS] = true;
+ resetPQExpBuffer(query);
+ }
+
+ appendPQExpBufferStr(query, "EXECUTE getAttributeStats(");
+ appendStringLiteralAH(query, dobj->namespace->dobj.name, fout);
+ appendPQExpBufferStr(query, ", ");
+ appendStringLiteralAH(query, dobj->name, fout);
+ appendPQExpBufferStr(query, "); ");
- query = createPQExpBuffer();
- getAttStatsExportQuery(query, fout, dobj->namespace->dobj.name,
- dobj->name);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
- appendAttStatsImport(out, fout, res);
+
+ out = createPQExpBuffer();
+
+ qualified_name = fmtQualifiedId(rsinfo->dobj.namespace->dobj.name,
+ rsinfo->dobj.name);
+
+ appendRelStatsImport(out, fout, rsinfo, qualified_name);
+ appendAttStatsImport(out, fout, res, qualified_name);
+
PQclear(res);
ArchiveEntry(fout, nilCatalogId, createDumpId(),
--
2.34.1
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-26 02:29 Corey Huinker <[email protected]>
parent: Jeff Davis <[email protected]>
1 sibling, 2 replies; 199+ messages in thread
From: Corey Huinker @ 2025-02-26 02:29 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Tue, Feb 25, 2025 at 9:00 PM Jeff Davis <[email protected]> wrote:
> On Mon, 2025-02-24 at 09:54 -0500, Andres Freund wrote:
> > Have you compared performance of with/without stats after these
> > optimizations?
>
> On unoptimized build with asserts enabled, dumping the regression
> database:
>
> --no-statistics: 1.0s
> master: 3.6s
> v3j-0001: 3.0s
> v3j-0002: 1.7s
>
> I plan to commit the patches soon.
>
> Regards,
> Jeff Davis
>
>
+1 from me
We can still convert the "EXECUTE getAttributeStats" call to a Params call,
but that involves creating an ExecuteSqlQueryParams(), which starts to
snowball in the changes required.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-26 03:40 Tom Lane <[email protected]>
parent: Corey Huinker <[email protected]>
1 sibling, 2 replies; 199+ messages in thread
From: Tom Lane @ 2025-02-26 03:40 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Jeff Davis <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Corey Huinker <[email protected]> writes:
> We can still convert the "EXECUTE getAttributeStats" call to a Params call,
> but that involves creating an ExecuteSqlQueryParams(), which starts to
> snowball in the changes required.
Yeah, let's leave that for some other day. It's not really apparent
that it'd buy us much performance-wise, though maybe the code would
net out cleaner.
To my mind the next task is to get the buildfarm green again by
fixing the expression-index-stats problem. I can have a go at
that once Jeff pushes these patches, unless one of you are already
on it?
regards, tom lane
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-26 03:55 Jeff Davis <[email protected]>
parent: Tom Lane <[email protected]>
1 sibling, 0 replies; 199+ messages in thread
From: Jeff Davis @ 2025-02-26 03:55 UTC (permalink / raw)
To: Tom Lane <[email protected]>; Corey Huinker <[email protected]>; +Cc: Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Tue, 2025-02-25 at 22:40 -0500, Tom Lane wrote:
> To my mind the next task is to get the buildfarm green again by
> fixing the expression-index-stats problem. I can have a go at
> that once Jeff pushes these patches, unless one of you are already
> on it?
I just committed them.
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-26 04:05 Corey Huinker <[email protected]>
parent: Tom Lane <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Corey Huinker @ 2025-02-26 04:05 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Jeff Davis <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
>
> To my mind the next task is to get the buildfarm green again by
> fixing the expression-index-stats problem. I can have a go at
> that once Jeff pushes these patches, unless one of you are already
> on it?
>
Already on it, but I can step aside if you've got a clearer vision of how
to solve it.
My solution so far is to take allo the v11+ (SELECT array_agg...) functions
and put them into a LATERAL, two of them filtered by attstattarget > 0 and
a new one aggregating attnames with no filter.
An alternative would be a new subselect for array_agg(attname) WHERE
in.indexprs IS NOT NULL, thus removing the extra compute for the indexes
that lack an index expression (i.e. most of them), and thus lack settable
stats (at least for now) and wouldn't be affected by the name-jitter issue
anyway.
I'm on the fence about how to handle pg_clear_attribute_stats(), leaning
toward overloaded functions.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-26 04:36 Tom Lane <[email protected]>
parent: Corey Huinker <[email protected]>
0 siblings, 2 replies; 199+ messages in thread
From: Tom Lane @ 2025-02-26 04:36 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Jeff Davis <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Corey Huinker <[email protected]> writes:
> My solution so far is to take allo the v11+ (SELECT array_agg...) functions
> and put them into a LATERAL, two of them filtered by attstattarget > 0 and
> a new one aggregating attnames with no filter.
> An alternative would be a new subselect for array_agg(attname) WHERE
> in.indexprs IS NOT NULL, thus removing the extra compute for the indexes
> that lack an index expression (i.e. most of them), and thus lack settable
> stats (at least for now) and wouldn't be affected by the name-jitter issue
> anyway.
Yeah, I've been thinking about that. I think that the idea of the
current design is that relatively few indexes will have explicit stats
targets set on them, so most of the time the sub-SELECTs produce no
data. (Which is not to say that they're cheap to execute.) If we
pull all the column names for all indexes then we'll likely bloat
pg_dump's working storage quite a bit. Pulling them only for indexes
with expression columns should fix that, and as you say we don't need
the names otherwise.
I still fear that those sub-selects are pretty expensive in aggregate
-- they are basically forcing a nestloop join -- and maybe we need to
rethink that whole idea.
BTW, just as a point of order: it is not the case that non-expression
indexes are free of name-jitter problems. That's because we don't
bother to rename index columns when the underlying table column is
renamed, thus:
regression=# create table t1 (id int primary key);
CREATE TABLE
regression=# \d t1_pkey
Index "public.t1_pkey"
Column | Type | Key? | Definition
--------+---------+------+------------
id | integer | yes | id
primary key, btree, for table "public.t1"
regression=# alter table t1 rename column id to xx;
ALTER TABLE
regression=# \d t1_pkey
Index "public.t1_pkey"
Column | Type | Key? | Definition
--------+---------+------+------------
id | integer | yes | xx
primary key, btree, for table "public.t1"
After dump-n-reload, this index's column will be named "xx".
That's not relevant to our current problem as long as we
don't store stats on such index columns, but it's plenty
relevant to the ALTER INDEX ... SET STATISTICS code.
> I'm on the fence about how to handle pg_clear_attribute_stats(), leaning
> toward overloaded functions.
I kinda felt that we didn't need to bother with an attnum-based
variant of pg_clear_attribute_stats(), since pg_dump has no
use for that. I won't stand in the way if you're desperate to
do it, though.
regards, tom lane
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-26 04:57 Corey Huinker <[email protected]>
parent: Tom Lane <[email protected]>
1 sibling, 0 replies; 199+ messages in thread
From: Corey Huinker @ 2025-02-26 04:57 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Jeff Davis <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Tue, Feb 25, 2025 at 11:36 PM Tom Lane <[email protected]> wrote:
> Corey Huinker <[email protected]> writes:
> > My solution so far is to take allo the v11+ (SELECT array_agg...)
> functions
> > and put them into a LATERAL, two of them filtered by attstattarget > 0
> and
> > a new one aggregating attnames with no filter.
>
> > An alternative would be a new subselect for array_agg(attname) WHERE
> > in.indexprs IS NOT NULL, thus removing the extra compute for the indexes
> > that lack an index expression (i.e. most of them), and thus lack settable
> > stats (at least for now) and wouldn't be affected by the name-jitter
> issue
> > anyway.
>
> Yeah, I've been thinking about that. I think that the idea of the
> current design is that relatively few indexes will have explicit stats
> targets set on them, so most of the time the sub-SELECTs produce no
> data. (Which is not to say that they're cheap to execute.) If we
> pull all the column names for all indexes then we'll likely bloat
> pg_dump's working storage quite a bit. Pulling them only for indexes
> with expression columns should fix that, and as you say we don't need
> the names otherwise.
>
> I still fear that those sub-selects are pretty expensive in aggregate
> -- they are basically forcing a nestloop join -- and maybe we need to
> rethink that whole idea.
>
> BTW, just as a point of order: it is not the case that non-expression
> indexes are free of name-jitter problems. That's because we don't
> bother to rename index columns when the underlying table column is
> renamed, thus:
>
Ouch.
> After dump-n-reload, this index's column will be named "xx".
> That's not relevant to our current problem as long as we
> don't store stats on such index columns, but it's plenty
> relevant to the ALTER INDEX ... SET STATISTICS code.
>
The only way I can imagine those columns getting their own stats is if we
start adding stats for columns of partial indexes, in which case we'd just
bump the predicate to WHERE (i.indexprs IS NOT NULL OR i.indpred IS NOT
NULL)
Just to confirm, we ARE able to assume dense packing of attributes in an
index, and thus we can infer the attnum from the position of the attname in
the aggregated array, and there's no need to do a parallel array_agg of
attnums, yes?
>
> > I'm on the fence about how to handle pg_clear_attribute_stats(), leaning
> > toward overloaded functions.
>
> I kinda felt that we didn't need to bother with an attnum-based
> variant of pg_clear_attribute_stats(), since pg_dump has no
> use for that. I won't stand in the way if you're desperate to
> do it, though.
>
I'm not desperate to slow this thread down, no. We'll stick with
attname-only.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-26 05:05 Tom Lane <[email protected]>
parent: Tom Lane <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Tom Lane @ 2025-02-26 05:05 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Jeff Davis <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Corey Huinker <[email protected]> writes:
> Just to confirm, we ARE able to assume dense packing of attributes in an
> index, and thus we can infer the attnum from the position of the attname in
> the aggregated array, and there's no need to do a parallel array_agg of
> attnums, yes?
Yes, absolutely, there are no dropped columns in indexes. See
upthread discussion.
We could have avoided two sub-selects for attstattarget too,
on the same principle: just collect them all and sort it out
later. That'd risk bloating pg_dump's storage, although maybe
we could have handled that by doing additional processing
while inspecting the results of getIndexes' query, so as not
to store anything in the common case.
regards, tom lane
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-26 09:25 Corey Huinker <[email protected]>
parent: Tom Lane <[email protected]>
0 siblings, 2 replies; 199+ messages in thread
From: Corey Huinker @ 2025-02-26 09:25 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Jeff Davis <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Wed, Feb 26, 2025 at 12:05 AM Tom Lane <[email protected]> wrote:
> Corey Huinker <[email protected]> writes:
> > Just to confirm, we ARE able to assume dense packing of attributes in an
> > index, and thus we can infer the attnum from the position of the attname
> in
> > the aggregated array, and there's no need to do a parallel array_agg of
> > attnums, yes?
>
> Yes, absolutely, there are no dropped columns in indexes. See
> upthread discussion.
>
> We could have avoided two sub-selects for attstattarget too,
> on the same principle: just collect them all and sort it out
> later. That'd risk bloating pg_dump's storage, although maybe
> we could have handled that by doing additional processing
> while inspecting the results of getIndexes' query, so as not
> to store anything in the common case.
>
> regards, tom lane
>
0001 - Add attnum support to attribute_statistics_update
* Basically what Tom posted earlier, minus the pg_set_attribute_stats
stuff, obviously.
0002 - Add attnum support to pg_dump.
* Removed att_stats_arginfo
* Folds appendRelStatsImport and appendAttStatsImport
into dumpRelationStats
Attachments:
[text/x-patch] vViaDellaAttnums-0001-Add-ability-to-reference-columns-by.patch (7.2K, ../../CADkLM=crPjbcyvYEpKwd1hHkaSYgCtiYbN3+Zk3zoj4GrC-g3g@mail.gmail.com/3-vViaDellaAttnums-0001-Add-ability-to-reference-columns-by.patch)
download | inline diff:
From 9dd7129318804ede9e412d886f3124e6d452a2d9 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Wed, 26 Feb 2025 00:39:17 -0500
Subject: [PATCH vViaDellaAttnums 1/2] Add ability to reference columns by
attnum to pg_restore_attribute_stats().
We need this because pg_dump needs to address index expression column
statistics by attnum because the attribute name is not stable across
upgrades.
---
src/backend/statistics/attribute_stats.c | 84 +++++++++++++++++-----
src/test/regress/expected/stats_import.out | 2 +-
2 files changed, 67 insertions(+), 19 deletions(-)
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index 66a5676c810..71a7a175d1f 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -38,6 +38,7 @@ enum attribute_stats_argnum
{
ATTRELATION_ARG = 0,
ATTNAME_ARG,
+ ATTNUM_ARG,
INHERITED_ARG,
NULL_FRAC_ARG,
AVG_WIDTH_ARG,
@@ -59,6 +60,7 @@ static struct StatsArgInfo attarginfo[] =
{
[ATTRELATION_ARG] = {"relation", REGCLASSOID},
[ATTNAME_ARG] = {"attname", NAMEOID},
+ [ATTNUM_ARG] = {"attnum", INT2OID},
[INHERITED_ARG] = {"inherited", BOOLOID},
[NULL_FRAC_ARG] = {"null_frac", FLOAT4OID},
[AVG_WIDTH_ARG] = {"avg_width", INT4OID},
@@ -76,6 +78,22 @@ static struct StatsArgInfo attarginfo[] =
[NUM_ATTRIBUTE_STATS_ARGS] = {0}
};
+enum clear_attribute_stats_argnum
+{
+ C_ATTRELATION_ARG = 0,
+ C_ATTNAME_ARG,
+ C_INHERITED_ARG,
+ C_NUM_ATTRIBUTE_STATS_ARGS
+};
+
+static struct StatsArgInfo cleararginfo[] =
+{
+ [C_ATTRELATION_ARG] = {"relation", REGCLASSOID},
+ [C_ATTNAME_ARG] = {"attname", NAMEOID},
+ [C_INHERITED_ARG] = {"inherited", BOOLOID},
+ [C_NUM_ATTRIBUTE_STATS_ARGS] = {0}
+};
+
static bool attribute_statistics_update(FunctionCallInfo fcinfo);
static Node *get_attr_expr(Relation rel, int attnum);
static void get_attr_stat_type(Oid reloid, AttrNumber attnum,
@@ -116,9 +134,9 @@ static bool
attribute_statistics_update(FunctionCallInfo fcinfo)
{
Oid reloid;
- Name attname;
- bool inherited;
+ char *attname;
AttrNumber attnum;
+ bool inherited;
Relation starel;
HeapTuple statup;
@@ -164,21 +182,51 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
/* lock before looking up attribute */
stats_lock_check_privileges(reloid);
- stats_check_required_arg(fcinfo, attarginfo, ATTNAME_ARG);
- attname = PG_GETARG_NAME(ATTNAME_ARG);
- attnum = get_attnum(reloid, NameStr(*attname));
+ /* user can specify either attname or attnum, but not both */
+ if (!PG_ARGISNULL(ATTNAME_ARG))
+ {
+ Name attnamename;
+
+ if (!PG_ARGISNULL(ATTNUM_ARG))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("must specify one of attname and attnum")));
+ attnamename = PG_GETARG_NAME(ATTNAME_ARG);
+ attname = NameStr(*attnamename);
+ attnum = get_attnum(reloid, attname);
+ /* note that this test covers attisdropped cases too: */
+ if (attnum == InvalidAttrNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ attname, get_rel_name(reloid))));
+ }
+ else if (!PG_ARGISNULL(ATTNUM_ARG))
+ {
+ attnum = PG_GETARG_INT16(ATTNUM_ARG);
+ attname = get_attname(reloid, attnum, true);
+ /* Annoyingly, get_attname doesn't check attisdropped */
+ if (attname == NULL ||
+ !SearchSysCacheExistsAttName(reloid, attname))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column %d of relation \"%s\" does not exist",
+ attnum, get_rel_name(reloid))));
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("must specify one of attname and attnum")));
+ attname = NULL; /* keep compiler quiet */
+ attnum = 0;
+ }
if (attnum < 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot modify statistics on system column \"%s\"",
- NameStr(*attname))));
-
- if (attnum == InvalidAttrNumber)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
- NameStr(*attname), get_rel_name(reloid))));
+ attname)));
stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG);
inherited = PG_GETARG_BOOL(INHERITED_ARG);
@@ -241,7 +289,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
&elemtypid, &elem_eq_opr))
{
ereport(WARNING,
- (errmsg("unable to determine element type of attribute \"%s\"", NameStr(*attname)),
+ (errmsg("unable to determine element type of attribute \"%s\"", attname),
errdetail("Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.")));
elemtypid = InvalidOid;
elem_eq_opr = InvalidOid;
@@ -257,7 +305,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
{
ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not determine less-than operator for attribute \"%s\"", NameStr(*attname)),
+ errmsg("could not determine less-than operator for attribute \"%s\"", attname),
errdetail("Cannot set STATISTIC_KIND_HISTOGRAM or STATISTIC_KIND_CORRELATION.")));
do_histogram = false;
@@ -271,7 +319,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
{
ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("attribute \"%s\" is not a range type", NameStr(*attname)),
+ errmsg("attribute \"%s\" is not a range type", attname),
errdetail("Cannot set STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM or STATISTIC_KIND_BOUNDS_HISTOGRAM.")));
do_bounds_histogram = false;
@@ -857,7 +905,7 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
AttrNumber attnum;
bool inherited;
- stats_check_required_arg(fcinfo, attarginfo, ATTRELATION_ARG);
+ stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELATION_ARG);
reloid = PG_GETARG_OID(ATTRELATION_ARG);
if (RecoveryInProgress())
@@ -868,7 +916,7 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
stats_lock_check_privileges(reloid);
- stats_check_required_arg(fcinfo, attarginfo, ATTNAME_ARG);
+ stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
attname = PG_GETARG_NAME(ATTNAME_ARG);
attnum = get_attnum(reloid, NameStr(*attname));
@@ -884,7 +932,7 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
errmsg("column \"%s\" of relation \"%s\" does not exist",
NameStr(*attname), get_rel_name(reloid))));
- stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG);
+ stats_check_required_arg(fcinfo, attarginfo, C_INHERITED_ARG);
inherited = PG_GETARG_BOOL(INHERITED_ARG);
delete_pg_statistic(reloid, attnum, inherited);
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 7e8b7f429c9..b8fc2fcce46 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -1250,7 +1250,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'null_frac', 0.1::real,
'avg_width', 2::integer,
'n_distinct', 0.3::real);
-ERROR: "attname" cannot be NULL
+ERROR: must specify one of attname and attnum
-- error: attname doesn't exist
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
base-commit: 0e42d31b0b2273c376ce9de946b59d155fac589c
--
2.48.1
[text/x-patch] vViaDellaAttnums-0002-Dump-expression-index-stats-by-attn.patch (12.8K, ../../CADkLM=crPjbcyvYEpKwd1hHkaSYgCtiYbN3+Zk3zoj4GrC-g3g@mail.gmail.com/4-vViaDellaAttnums-0002-Dump-expression-index-stats-by-attn.patch)
download | inline diff:
From 92f75fc9835e7982216d32329caa34b26347a6b3 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Wed, 26 Feb 2025 04:08:31 -0500
Subject: [PATCH vViaDellaAttnums 2/2] Dump expression index stats by attnum,
not attname.
Names of columns in index expressions are not stable across major
versions, so we are forced to dump those by attnum instead.
---
src/bin/pg_dump/pg_dump.c | 219 +++++++++++++++++++++-----------------
src/bin/pg_dump/pg_dump.h | 2 +
2 files changed, 125 insertions(+), 96 deletions(-)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 0de6c959bb0..65413e07899 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6819,7 +6819,8 @@ getFuncs(Archive *fout)
*/
static RelStatsInfo *
getRelationStatistics(Archive *fout, DumpableObject *rel, int32 relpages,
- float reltuples, int32 relallvisible, char relkind)
+ float reltuples, int32 relallvisible, char relkind,
+ char **indexprattnames, int nindexprattnames)
{
if (!fout->dopt->dumpStatistics)
return NULL;
@@ -6844,6 +6845,8 @@ getRelationStatistics(Archive *fout, DumpableObject *rel, int32 relpages,
dobj->components |= DUMP_COMPONENT_STATISTICS;
dobj->name = pg_strdup(rel->name);
dobj->namespace = rel->namespace;
+ info->indexprattnames = indexprattnames;
+ info->nindexprattnames = nindexprattnames;
info->relpages = relpages;
info->reltuples = reltuples;
info->relallvisible = relallvisible;
@@ -7249,7 +7252,8 @@ getTables(Archive *fout, int *numTables)
/* Add statistics */
if (tblinfo[i].interesting)
getRelationStatistics(fout, &tblinfo[i].dobj, tblinfo[i].relpages,
- reltuples, relallvisible, tblinfo[i].relkind);
+ reltuples, relallvisible, tblinfo[i].relkind,
+ NULL, 0);
/*
* Read-lock target tables to make sure they aren't DROPPED or altered
@@ -7537,7 +7541,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
i_tablespace,
i_indreloptions,
i_indstatcols,
- i_indstatvals;
+ i_indstatvals,
+ i_indexprattnames;
/*
* We want to perform just one query against pg_index. However, we
@@ -7579,6 +7584,10 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
+ "(SELECT pg_catalog.array_agg(attname ORDER BY attnum) "
+ " FROM pg_catalog.pg_attribute "
+ " WHERE attrelid = i.indexrelid AND "
+ " i.indexprs IS NOT NULL) AS indexprattnames, "
"(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
"t.reloptions AS indreloptions, ");
@@ -7702,6 +7711,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
i_indreloptions = PQfnumber(res, "indreloptions");
i_indstatcols = PQfnumber(res, "indstatcols");
i_indstatvals = PQfnumber(res, "indstatvals");
+ i_indexprattnames = PQfnumber(res, "indexprattnames");
indxinfo = (IndxInfo *) pg_malloc(ntups * sizeof(IndxInfo));
@@ -7715,6 +7725,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
Oid indrelid = atooid(PQgetvalue(res, j, i_indrelid));
TableInfo *tbinfo = NULL;
int numinds;
+ char **indexprattnames = NULL; /* attnames for expression indexes only */
+ int nindexprattnames = 0; /* number of attnames for expression indexes only */
/* Count rows for this table */
for (numinds = 1; numinds < ntups - j; numinds++)
@@ -7784,9 +7796,16 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
else
indexkind = RELKIND_PARTITIONED_INDEX;
- contype = *(PQgetvalue(res, j, i_contype));
+ if (!PQgetisnull(res, j, i_indexprattnames))
+ if (!parsePGArray(PQgetvalue(res, j, i_indexprattnames),
+ &indexprattnames, &nindexprattnames))
+ pg_fatal("could not parse %s array", "indattnames");
+
relstats = getRelationStatistics(fout, &indxinfo[j].dobj, relpages,
- reltuples, relallvisible, indexkind);
+ reltuples, relallvisible, indexkind,
+ indexprattnames, nindexprattnames);
+
+ contype = *(PQgetvalue(res, j, i_contype));
if (contype == 'p' || contype == 'u' || contype == 'x')
{
@@ -10410,28 +10429,6 @@ dumpComment(Archive *fout, const char *type,
catalogId, subid, dumpId, NULL);
}
-/*
- * Tabular description of the parameters to pg_restore_attribute_stats()
- * param_name, param_type
- */
-static const char *att_stats_arginfo[][2] = {
- {"attname", "name"},
- {"inherited", "boolean"},
- {"null_frac", "float4"},
- {"avg_width", "integer"},
- {"n_distinct", "float4"},
- {"most_common_vals", "text"},
- {"most_common_freqs", "float4[]"},
- {"histogram_bounds", "text"},
- {"correlation", "float4"},
- {"most_common_elems", "text"},
- {"most_common_elem_freqs", "float4[]"},
- {"elem_count_histogram", "float4[]"},
- {"range_length_histogram", "text"},
- {"range_empty_frac", "float4"},
- {"range_bounds_histogram", "text"},
-};
-
/*
* appendNamedArgument --
*
@@ -10440,9 +10437,9 @@ static const char *att_stats_arginfo[][2] = {
*/
static void
appendNamedArgument(PQExpBuffer out, Archive *fout, const char *argname,
- const char *argval, const char *argtype)
+ const char *argtype, const char *argval)
{
- appendPQExpBufferStr(out, "\t");
+ appendPQExpBufferStr(out, ",\n\t");
appendStringLiteralAH(out, argname, fout);
appendPQExpBufferStr(out, ", ");
@@ -10451,68 +10448,6 @@ appendNamedArgument(PQExpBuffer out, Archive *fout, const char *argname,
appendPQExpBuffer(out, "::%s", argtype);
}
-/*
- * appendRelStatsImport --
- *
- * Append a formatted pg_restore_relation_stats statement.
- */
-static void
-appendRelStatsImport(PQExpBuffer out, Archive *fout, const RelStatsInfo *rsinfo,
- const char *qualified_name)
-{
- char reltuples_str[FLOAT_SHORTEST_DECIMAL_LEN];
-
- float_to_shortest_decimal_buf(rsinfo->reltuples, reltuples_str);
-
- appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
- appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
- fout->remoteVersion);
- appendPQExpBuffer(out, "\t'relation', '%s'::regclass,\n", qualified_name);
- appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
- appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", reltuples_str);
- appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer\n);\n",
- rsinfo->relallvisible);
-}
-
-/*
- * appendAttStatsImport --
- *
- * Append a series of formatted pg_restore_attribute_stats statements.
- */
-static void
-appendAttStatsImport(PQExpBuffer out, Archive *fout, PGresult *res,
- const char *qualified_name)
-{
- for (int rownum = 0; rownum < PQntuples(res); rownum++)
- {
- const char *sep = "";
-
- appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
- appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
- fout->remoteVersion);
- appendPQExpBuffer(out, "\t'relation', '%s'::regclass,\n",
- qualified_name);
- for (int argno = 0; argno < lengthof(att_stats_arginfo); argno++)
- {
- const char *argname = att_stats_arginfo[argno][0];
- const char *argtype = att_stats_arginfo[argno][1];
- int fieldno = PQfnumber(res, argname);
-
- if (fieldno < 0)
- pg_fatal("attribute stats export query missing field '%s'",
- argname);
-
- if (PQgetisnull(res, rownum, fieldno))
- continue;
-
- appendPQExpBufferStr(out, sep);
- appendNamedArgument(out, fout, argname, PQgetvalue(res, rownum, fieldno), argtype);
- sep = ",\n";
- }
- appendPQExpBufferStr(out, "\n);\n");
- }
-}
-
/*
* Decide which section to use based on the relkind of the parent object.
*
@@ -10557,6 +10492,7 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
DumpId *deps = NULL;
int ndeps = 0;
const char *qualified_name;
+ char reltuples_str[FLOAT_SHORTEST_DECIMAL_LEN];
/* nothing to do if we are not dumping statistics */
if (!fout->dopt->dumpStatistics)
@@ -10606,6 +10542,22 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
resetPQExpBuffer(query);
}
+ out = createPQExpBuffer();
+
+ qualified_name = fmtQualifiedId(rsinfo->dobj.namespace->dobj.name,
+ rsinfo->dobj.name);
+
+ /* restore relation stats */
+ float_to_shortest_decimal_buf(rsinfo->reltuples, reltuples_str);
+ appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
+ appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+ fout->remoteVersion);
+ appendPQExpBuffer(out, "\t'relation', '%s'::regclass,\n", qualified_name);
+ appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
+ appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", reltuples_str);
+ appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer\n);\n",
+ rsinfo->relallvisible);
+
appendPQExpBufferStr(query, "EXECUTE getAttributeStats(");
appendStringLiteralAH(query, dobj->namespace->dobj.name, fout);
appendPQExpBufferStr(query, ", ");
@@ -10614,13 +10566,88 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
- out = createPQExpBuffer();
+ /* restore attribute stats */
+ for (int rownum = 0; rownum < PQntuples(res); rownum++)
+ {
+ const char *attname;
- qualified_name = fmtQualifiedId(rsinfo->dobj.namespace->dobj.name,
- rsinfo->dobj.name);
+ appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
+ appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+ fout->remoteVersion);
+ appendPQExpBuffer(out, "\t'relation', '%s'::regclass",
+ qualified_name);
- appendRelStatsImport(out, fout, rsinfo, qualified_name);
- appendAttStatsImport(out, fout, res, qualified_name);
+
+ if (PQgetisnull(res, rownum, 0))
+ pg_fatal("attname cannot be NULL");
+ attname = PQgetvalue(res, rownum, 0);
+
+ /*
+ * Expression indexes look up attname in attnames to derive attnum,
+ * all others use attname directly.
+ */
+ if (rsinfo->nindexprattnames == 0)
+ appendNamedArgument(out, fout, "attname", "name", attname);
+ else
+ {
+ bool found = false;
+
+ for (int i = 0; i < rsinfo->nindexprattnames; i++)
+ if (strcmp(attname, rsinfo->indexprattnames[i]) == 0)
+ {
+ appendPQExpBuffer(out, ",\n\t'attnum', '%d'::smallint", (AttrNumber) i + 1);
+ found = true;
+ break;
+ }
+
+ if (!found)
+ pg_fatal("unable to find attname '%s'", attname);
+ }
+
+ if (!PQgetisnull(res, rownum, 1))
+ appendNamedArgument(out, fout, "inherited", "boolean",
+ PQgetvalue(res, rownum, 1));
+ if (!PQgetisnull(res, rownum, 2))
+ appendNamedArgument(out, fout, "null_frac", "float4",
+ PQgetvalue(res, rownum, 2));
+ if (!PQgetisnull(res, rownum, 3))
+ appendNamedArgument(out, fout, "avg_width", "integer",
+ PQgetvalue(res, rownum, 3));
+ if (!PQgetisnull(res, rownum, 4))
+ appendNamedArgument(out, fout, "n_distinct", "float4",
+ PQgetvalue(res, rownum, 4));
+ if (!PQgetisnull(res, rownum, 5))
+ appendNamedArgument(out, fout, "most_common_vals", "text",
+ PQgetvalue(res, rownum, 5));
+ if (!PQgetisnull(res, rownum, 6))
+ appendNamedArgument(out, fout, "most_common_freqs", "float4[]",
+ PQgetvalue(res, rownum, 6));
+ if (!PQgetisnull(res, rownum, 7))
+ appendNamedArgument(out, fout, "histogram_bounds", "text",
+ PQgetvalue(res, rownum, 7));
+ if (!PQgetisnull(res, rownum, 8))
+ appendNamedArgument(out, fout, "correlation", "float4",
+ PQgetvalue(res, rownum, 8));
+ if (!PQgetisnull(res, rownum, 9))
+ appendNamedArgument(out, fout, "most_common_elems", "text",
+ PQgetvalue(res, rownum, 9));
+ if (!PQgetisnull(res, rownum, 10))
+ appendNamedArgument(out, fout, "most_common_elem_freqs", "float4[]",
+ PQgetvalue(res, rownum, 10));
+ if (!PQgetisnull(res, rownum, 11))
+ appendNamedArgument(out, fout, "elem_count_histogram", "float4[]",
+ PQgetvalue(res, rownum, 11));
+ if (!PQgetisnull(res, rownum, 12))
+ appendNamedArgument(out, fout, "range_length_histogram", "text",
+ PQgetvalue(res, rownum, 12));
+ if (!PQgetisnull(res, rownum, 13))
+ appendNamedArgument(out, fout, "range_empty_frac", "float4",
+ PQgetvalue(res, rownum, 13));
+ if (!PQgetisnull(res, rownum, 14))
+ appendNamedArgument(out, fout, "range_bounds_histogram", "text",
+ PQgetvalue(res, rownum, 14));
+ appendPQExpBufferStr(out, "\n);\n");
+ }
PQclear(res);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 9d6a4857c4b..0584b1c7abb 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -438,6 +438,8 @@ typedef struct _indexAttachInfo
typedef struct _relStatsInfo
{
DumpableObject dobj;
+ char **indexprattnames; /* attnames in an expression index */
+ int32 nindexprattnames; /* number of attnames for expression indexes, else 0 */
int32 relpages;
float reltuples;
int32 relallvisible;
--
2.48.1
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-26 16:13 Tom Lane <[email protected]>
parent: Corey Huinker <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Tom Lane @ 2025-02-26 16:13 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Jeff Davis <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Corey Huinker <[email protected]> writes:
> 0001 - Add attnum support to attribute_statistics_update
> * Basically what Tom posted earlier, minus the pg_set_attribute_stats
> stuff, obviously.
> 0002 - Add attnum support to pg_dump.
> * Removed att_stats_arginfo
> * Folds appendRelStatsImport and appendAttStatsImport
> into dumpRelationStats
Cool. Jeff, are you taking these, or shall I?
regards, tom lane
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-26 16:16 Jeff Davis <[email protected]>
parent: Tom Lane <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Jeff Davis @ 2025-02-26 16:16 UTC (permalink / raw)
To: Tom Lane <[email protected]>; Corey Huinker <[email protected]>; +Cc: Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Wed, 2025-02-26 at 11:13 -0500, Tom Lane wrote:
> Cool. Jeff, are you taking these, or shall I?
Please go ahead.
I think you had mentioned upthread something about getting rid of the
table-driven logic, which is fine with me. Did you mean for that to
happen in this patch as well?
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-26 16:23 Tom Lane <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Tom Lane @ 2025-02-26 16:23 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Jeff Davis <[email protected]> writes:
> I think you had mentioned upthread something about getting rid of the
> table-driven logic, which is fine with me. Did you mean for that to
> happen in this patch as well?
Per Corey's description of the patch (I didn't read it yet), some
of that already happened. I want to get to buildfarm-green ASAP,
so I'm content to leave other cosmetic changes for later.
BTW, one cosmetic change that I'd like to see is that any tables that
don't go away get marked "const". I tried to make that happen with
attribute_stats.c's tables in my WIP patch upthread, but found that
the need for const-ness would propagate to some utility functions and
such, so I put the idea on the back burner.
regards, tom lane
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-26 16:43 Corey Huinker <[email protected]>
parent: Tom Lane <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Corey Huinker @ 2025-02-26 16:43 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Jeff Davis <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Wed, Feb 26, 2025 at 11:23 AM Tom Lane <[email protected]> wrote:
> Jeff Davis <[email protected]> writes:
> > I think you had mentioned upthread something about getting rid of the
> > table-driven logic, which is fine with me. Did you mean for that to
> > happen in this patch as well?
>
> Per Corey's description of the patch (I didn't read it yet), some
> of that already happened. I want to get to buildfarm-green ASAP,
> so I'm content to leave other cosmetic changes for later.
>
The structs attarginfo and cleararginfo remain, which is notable but not
quite no-table.
> BTW, one cosmetic change that I'd like to see is that any tables that
> don't go away get marked "const". I tried to make that happen with
> attribute_stats.c's tables in my WIP patch upthread, but found that
> the need for const-ness would propagate to some utility functions and
> such, so I put the idea on the back burner.
>
I didn't even think to try const-ing those structs.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-26 18:02 Jeff Davis <[email protected]>
parent: Corey Huinker <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Jeff Davis @ 2025-02-26 18:02 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; Tom Lane <[email protected]>; +Cc: Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Wed, 2025-02-26 at 04:25 -0500, Corey Huinker wrote:
> 0001 - Add attnum support to attribute_statistics_update
>
> * Basically what Tom posted earlier, minus the pg_set_attribute_stats
> stuff, obviously.
Should have a couple simple tests.
And I would use two different error message wordings:
"must specify either attname or attnum"
"cannot specify both attname and attnum"
(or similar)
The "one of attname and attnum" is a bit awkward.
The new struct for pg_clear_attribute_stats() isn't great, but as
discussed we can get rid of that in a subsequent commit.
Otherwise LGTM.
> 0002 - Add attnum support to pg_dump.
>
> * Removed att_stats_arginfo
> * Folds appendRelStatsImport and appendAttStatsImport
> into dumpRelationStats
Can we add a test here, too, to check that tables dump the attname and
indexes dump the attnum?
Everything else in the file uses i_fieldname = PQfnumber(), but in this
patch you're just using raw numbers.
Some of the fields from pg_stats are NOT NULL, so we could consider
issuing a warning in that case rather than just skipping it.
And it could use a pgindent.
I ran a quick measurement and it appears within the noise of the
numbers I posted here:
https://www.postgresql.org/message-id/[email protected]
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-26 18:06 Tom Lane <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Tom Lane @ 2025-02-26 18:06 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Jeff Davis <[email protected]> writes:
> I ran a quick measurement and it appears within the noise of the
> numbers I posted here:
> https://www.postgresql.org/message-id/[email protected]
Thanks for doing that. I agree with your other comments and
will incorporate them.
regards, tom lane
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-26 18:44 Jeff Davis <[email protected]>
parent: Tom Lane <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Jeff Davis @ 2025-02-26 18:44 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Wed, 2025-02-26 at 13:06 -0500, Tom Lane wrote:
> Jeff Davis <[email protected]> writes:
> > I ran a quick measurement and it appears within the noise of the
> > numbers I posted here:
> > https://www.postgresql.org/message-id/[email protected]
>
> Thanks for doing that. I agree with your other comments and
> will incorporate them.
Also, here are the numbers with an optimized build:
--no-statistics: 0.21 s
pre-optimization (6c349d83b6): 0.75
v3j-0001 (8f427187db): 0.65
v3j-0002 (6ee3b91bad): 0.27
new patch 0001+0002: 0.26
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-26 19:54 Melanie Plageman <[email protected]>
parent: Corey Huinker <[email protected]>
0 siblings, 2 replies; 199+ messages in thread
From: Melanie Plageman @ 2025-02-26 19:54 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Jeff Davis <[email protected]>; Tom Lane <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Tue, Feb 25, 2025 at 6:41 PM Corey Huinker <[email protected]> wrote:
>
> 0003 - converting some of the deleted pg_set* tests into pg_restore* tests to keep the error coverage that they had.
I haven't really followed this thread and am not sure where the right
place is to drop this question, so I'll just do it here.
I have a patch that is getting thwacked around by the churn in
stats_import.sql, and it occurred to me that I don't see why all the
negative tests for pg_restore_relation_stats() need to have all the
parameters provided. For example, in both of these tests, you are
testing the relation parameter but including all these other fields.
It's fine if there is a reason to do that, but otherwise, it makes the
test file longer and makes the test case less clear IMO.
-- error: argument name is NULL
SELECT pg_restore_relation_stats(
'relation', '0'::oid::regclass,
'version', 150000::integer,
NULL, '17'::integer,
'reltuples', 400::real,
'relallvisible', 4::integer);
-- error: argument name is an integer
SELECT pg_restore_relation_stats(
'relation', '0'::oid::regclass,
'version', 150000::integer,
17, '17'::integer,
'reltuples', 400::real,
'relallvisible', 4::integer);
- Melanie
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-26 20:15 Robert Haas <[email protected]>
parent: Andres Freund <[email protected]>
2 siblings, 1 reply; 199+ messages in thread
From: Robert Haas @ 2025-02-26 20:15 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Corey Huinker <[email protected]>; Tom Lane <[email protected]>; Jeff Davis <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Mon, Feb 24, 2025 at 3:36 PM Andres Freund <[email protected]> wrote:
> I suspect that this is a *really* bad idea. It's very very hard to get inplace
> updates right. We have several unfixed correctness bugs that are related to
> the use of inplace updates. I really don't think it's wise to add additional
> interfaces that can reach inplace updates unless there's really no other
> alternative (like not being able to assign an xid in VACUUM to be able to deal
> with anti-xid-wraparound-shutdown systems).
I strongly agree. I think shipping this feature in any form that uses
in-place updates is a bad idea. I believe that the chances that we
will regret that decision are high. I take Corey's point that bloating
pg_class isn't great either ... but if that's a big problem, I believe
the solution is to find a way to get the right values into those rows
when they are first created, not to use in-place updates after the
fact.
Honestly, I'd go further than Andres did: even when there's really no
alternative, that doesn't mean in-place updates are a good idea. It
just means they're the best thing we've been able to come up with so
far. I think we're going to keep finding bugs until we remove every
last in-place update in the system.
--
Robert Haas
EDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-26 20:37 Jeff Davis <[email protected]>
parent: Robert Haas <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Jeff Davis @ 2025-02-26 20:37 UTC (permalink / raw)
To: Robert Haas <[email protected]>; Andres Freund <[email protected]>; +Cc: Corey Huinker <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Wed, 2025-02-26 at 15:15 -0500, Robert Haas wrote:
> I strongly agree. I think shipping this feature in any form that uses
> in-place updates is a bad idea.
Removed already in commit f3dae2ae58.
The reason they were added was mostly for consistency with ANALYZE, and
(at least for me) secondarily about churn on pg_class. The bloat was
never terrible.
With that in mind, should we remove the in-place updates from ANALYZE
as well?
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-26 20:57 Robert Haas <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Robert Haas @ 2025-02-26 20:57 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Andres Freund <[email protected]>; Corey Huinker <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Wed, Feb 26, 2025 at 3:37 PM Jeff Davis <[email protected]> wrote:
> On Wed, 2025-02-26 at 15:15 -0500, Robert Haas wrote:
> > I strongly agree. I think shipping this feature in any form that uses
> > in-place updates is a bad idea.
>
> Removed already in commit f3dae2ae58.
Cool.
> The reason they were added was mostly for consistency with ANALYZE, and
> (at least for me) secondarily about churn on pg_class. The bloat was
> never terrible.
>
> With that in mind, should we remove the in-place updates from ANALYZE
> as well?
While I generally think fewer in-place updates are better than more,
I'm not sure what code we're talking about here and I definitely
haven't studied it, so I don't want to make excessively strong
statements. If you feel it can be done without breaking anything else,
or you have a way to repair the breakage, I'd definitely be interested
in hearing more about that.
--
Robert Haas
EDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-26 21:32 Jeff Davis <[email protected]>
parent: Robert Haas <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Jeff Davis @ 2025-02-26 21:32 UTC (permalink / raw)
To: Robert Haas <[email protected]>; +Cc: Andres Freund <[email protected]>; Corey Huinker <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Wed, 2025-02-26 at 15:57 -0500, Robert Haas wrote:
> If you feel it can be done without breaking anything else,
> or you have a way to repair the breakage, I'd definitely be
> interested
> in hearing more about that.
That would be a separate thread, but it's good to know that there is a
general consensus that we don't want to use in-place updates for non-
critical things like stats (and perhaps eliminate them entirely). In
other words, the inconcistency likely won't last forever.
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-26 21:44 Tom Lane <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Tom Lane @ 2025-02-26 21:44 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Robert Haas <[email protected]>; Andres Freund <[email protected]>; Corey Huinker <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Jeff Davis <[email protected]> writes:
> That would be a separate thread, but it's good to know that there is a
> general consensus that we don't want to use in-place updates for non-
> critical things like stats (and perhaps eliminate them entirely). In
> other words, the inconcistency likely won't last forever.
I'm quite sure that the original argument for using in-place updates
for this was not wanting a full-database VACUUM or ANALYZE to update
every tuple in pg_class. At the time that definitely did lead to
more-or-less 2x bloat. The new information we have now is that that's
no longer the case, and thus the decision can and should be revisited.
regards, tom lane
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-26 21:46 Tom Lane <[email protected]>
parent: Melanie Plageman <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Tom Lane @ 2025-02-26 21:46 UTC (permalink / raw)
To: Melanie Plageman <[email protected]>; +Cc: Corey Huinker <[email protected]>; Jeff Davis <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Melanie Plageman <[email protected]> writes:
> I have a patch that is getting thwacked around by the churn in
> stats_import.sql, and it occurred to me that I don't see why all the
> negative tests for pg_restore_relation_stats() need to have all the
> parameters provided. For example, in both of these tests, you are
> testing the relation parameter but including all these other fields.
> It's fine if there is a reason to do that, but otherwise, it makes the
> test file longer and makes the test case less clear IMO.
+1, let's shorten those queries. The coast is probably pretty
clear now if you want to go do that.
regards, tom lane
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-26 21:57 Corey Huinker <[email protected]>
parent: Melanie Plageman <[email protected]>
1 sibling, 0 replies; 199+ messages in thread
From: Corey Huinker @ 2025-02-26 21:57 UTC (permalink / raw)
To: Melanie Plageman <[email protected]>; +Cc: Jeff Davis <[email protected]>; Tom Lane <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
>
> I have a patch that is getting thwacked around by the churn in
> stats_import.sql, and it occurred to me that I don't see why all the
> negative tests for pg_restore_relation_stats() need to have all the
> parameters provided. For example, in both of these tests, you are
> testing the relation parameter but including all these other fields.
> It's fine if there is a reason to do that, but otherwise, it makes the
> test file longer and makes the test case less clear IMO.
>
It's a known issue, and I intend to do a culling. Things have been
changing a lot with the pg_set* functions going away, and some of the tests
were covered by set* functions but not restore* functions. I'll give it a
pass once the buildfarm goes green again, and then I'm immediately shifting
gears to your patchset so that the additional tests you'll require are
smooth and minimal.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-26 21:58 Corey Huinker <[email protected]>
parent: Tom Lane <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Corey Huinker @ 2025-02-26 21:58 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Melanie Plageman <[email protected]>; Jeff Davis <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Wed, Feb 26, 2025 at 4:46 PM Tom Lane <[email protected]> wrote:
> Melanie Plageman <[email protected]> writes:
> > I have a patch that is getting thwacked around by the churn in
> > stats_import.sql, and it occurred to me that I don't see why all the
> > negative tests for pg_restore_relation_stats() need to have all the
> > parameters provided. For example, in both of these tests, you are
> > testing the relation parameter but including all these other fields.
> > It's fine if there is a reason to do that, but otherwise, it makes the
> > test file longer and makes the test case less clear IMO.
>
> +1, let's shorten those queries. The coast is probably pretty
> clear now if you want to go do that.
On it.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-27 02:19 Corey Huinker <[email protected]>
parent: Corey Huinker <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Corey Huinker @ 2025-02-27 02:19 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Melanie Plageman <[email protected]>; Jeff Davis <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
>
> +1, let's shorten those queries. The coast is probably pretty
>> clear now if you want to go do that.
>
>
> On it.
>
The earlier conversion of pg_set_attribute_stats (which once had many
not-null params) to pg_restore_* tests (where only the columns that
identify the stat row are actually required) meant that a lot of parameters
which were previously required were now inconsequential to the test.
However, it is important to demonstrate cases where the rest of the restore
operation completed after given bad statistic was encountered, but that can
be adequately done by one "bystander" parameter rather than the whole fleet.
Other notes:
* organized the tests into roughly three groups: relation tests, attribute
tests, and set-difference tests.
* tests that raise an error bubble up to the top of their respective groups
* tests that would have multiple warnings are reduced to having just one
wherever possible
* each test gets a comment about what is to be demonstrated
* attention paid to parameter values to avoid coincidental values that
could mislead someone into thinking the value was written somewhere when
that just happened to be what was already there, etc.
* the set difference tests remain, as they proved extremely useful in
detecting undesirable side-effects during development
Attachments:
[text/x-patch] vTestReorg-0001-Organize-and-deduplicate-statistics-impor.patch (91.6K, ../../CADkLM=fyN6RjWYZNTmPyVCPsfR0VkLC9yWZR_bbz1_bTPTQxaQ@mail.gmail.com/3-vTestReorg-0001-Organize-and-deduplicate-statistics-impor.patch)
download | inline diff:
From f3087b04784d6853970bf41eb619281d72ce94bd Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Wed, 26 Feb 2025 21:02:44 -0500
Subject: [PATCH vTestReorg] Organize and deduplicate statistics import tests.
Many changes, refactorings, and rebasings have taken their toll on the
statistics import tests. Now that things appear more stable and the
pg_set_* functions are gone in favor of using pg_restore_* in all cases,
it's safe to remove duplicates, combine tests where possible, and make
the test descriptions a bit more descriptive and uniform.
---
src/test/regress/expected/stats_import.out | 672 +++++++++------------
src/test/regress/sql/stats_import.sql | 546 +++++++----------
2 files changed, 500 insertions(+), 718 deletions(-)
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 1f150f7b08d..eaa3fe0f812 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -13,17 +13,58 @@ CREATE TABLE stats_import.test(
tags text[]
) WITH (autovacuum_enabled = false);
CREATE INDEX test_i ON stats_import.test(id);
+--
+-- relstats tests
+--
+--- error: relation is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 0::oid,
+ 'relpages', 17::integer,
+ 'reltuples', 400.0::real,
+ 'relallvisible', 4::integer);
+WARNING: argument "relation" has type "oid", expected type "regclass"
+ERROR: "relation" cannot be NULL
+-- error: relation not found
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 0::oid::regclass,
+ 'relpages', 17::integer,
+ 'reltuples', 400.0::real,
+ 'relallvisible', 4::integer);
+ERROR: could not open relation with OID 0
+-- error: odd number of variadic arguments cannot be pairs
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'version', 150000::integer,
+ 'relpages', '17'::integer,
+ 'reltuples', 400::real,
+ 'relallvisible');
+ERROR: variadic arguments must be name/value pairs
+HINT: Provide an even number of variadic arguments that can be divided into pairs.
+-- error: argument name is NULL
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'version', 150000::integer,
+ NULL, '17'::integer,
+ 'relallvisible', 14::integer);
+ERROR: name at variadic position 5 is NULL
+-- error: argument name is not a text type
+SELECT pg_restore_relation_stats(
+ 'relation', '0'::oid::regclass,
+ 'version', 150000::integer,
+ 17, '17'::integer,
+ 'relallvisible', 44::integer);
+ERROR: name at variadic position 5 has type "integer", expected type "text"
-- starting stats
SELECT relpages, reltuples, relallvisible
FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
+WHERE oid = 'stats_import.test_i'::regclass;
relpages | reltuples | relallvisible
----------+-----------+---------------
- 0 | -1 | 0
+ 1 | 0 | 0
(1 row)
-BEGIN;
-- regular indexes have special case locking rules
+BEGIN;
SELECT
pg_catalog.pg_restore_relation_stats(
'relation', 'stats_import.test_i'::regclass,
@@ -50,32 +91,6 @@ WHERE relation = 'stats_import.test_i'::regclass AND
(1 row)
COMMIT;
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.test_i'::regclass,
- 'relpages', 19::integer );
- pg_restore_relation_stats
----------------------------
- t
-(1 row)
-
--- clear
-SELECT
- pg_catalog.pg_clear_relation_stats(
- 'stats_import.test'::regclass);
- pg_clear_relation_stats
--------------------------
-
-(1 row)
-
-SELECT relpages, reltuples, relallvisible
-FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
- relpages | reltuples | relallvisible
-----------+-----------+---------------
- 0 | -1 | 0
-(1 row)
-
-- relpages may be -1 for partitioned tables
CREATE TABLE stats_import.part_parent ( i integer ) PARTITION BY RANGE(i);
CREATE TABLE stats_import.part_child_1
@@ -92,26 +107,6 @@ WHERE oid = 'stats_import.part_parent'::regclass;
-1
(1 row)
--- although partitioned tables have no storage, setting relpages to a
--- positive value is still allowed
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent_i'::regclass,
- 'relpages', 2::integer);
- pg_restore_relation_stats
----------------------------
- t
-(1 row)
-
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent'::regclass,
- 'relpages', 2::integer);
- pg_restore_relation_stats
----------------------------
- t
-(1 row)
-
--
-- Partitioned indexes aren't analyzed but it is possible to set
-- stats. The locking rules are different from normal indexes due to
@@ -145,30 +140,19 @@ WHERE relation = 'stats_import.part_parent_i'::regclass AND
(1 row)
COMMIT;
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent_i'::regclass,
- 'relpages', 2::integer);
- pg_restore_relation_stats
----------------------------
- t
+SELECT relpages
+FROM pg_class
+WHERE oid = 'stats_import.part_parent_i'::regclass;
+ relpages
+----------
+ 2
(1 row)
--- nothing stops us from setting it to -1
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent'::regclass,
- 'relpages', -1::integer);
- pg_restore_relation_stats
----------------------------
- t
-(1 row)
-
--- ok: set all stats
+-- ok: set all stats, no bounds checking
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
'version', 150000::integer,
- 'relpages', '17'::integer,
+ 'relpages', '-17'::integer,
'reltuples', 400::real,
'relallvisible', 4::integer);
pg_restore_relation_stats
@@ -181,10 +165,10 @@ FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
relpages | reltuples | relallvisible
----------+-----------+---------------
- 17 | 400 | 4
+ -17 | 400 | 4
(1 row)
--- ok: just relpages
+-- ok: set just relpages, rest stay same
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
'version', 150000::integer,
@@ -202,7 +186,7 @@ WHERE oid = 'stats_import.test'::regclass;
16 | 400 | 4
(1 row)
--- ok: just reltuples
+-- ok: set just reltuples, rest stay same
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
'version', 150000::integer,
@@ -220,7 +204,7 @@ WHERE oid = 'stats_import.test'::regclass;
16 | 500 | 4
(1 row)
--- ok: just relallvisible
+-- ok: set just relallvisible, rest stay same
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
'version', 150000::integer,
@@ -238,7 +222,7 @@ WHERE oid = 'stats_import.test'::regclass;
16 | 500 | 5
(1 row)
--- warn: bad relpages type
+-- warn: bad relpages type, rest updated
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
'version', 150000::integer,
@@ -259,20 +243,136 @@ WHERE oid = 'stats_import.test'::regclass;
16 | 400 | 4
(1 row)
+-- unrecognized argument name, rest ok
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'version', 150000::integer,
+ 'relpages', '171'::integer,
+ 'nope', 10::integer);
+WARNING: unrecognized argument name: "nope"
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+ relpages | reltuples | relallvisible
+----------+-----------+---------------
+ 171 | 400 | 4
+(1 row)
+
+-- ok: clear stats
+SELECT pg_catalog.pg_clear_relation_stats(
+ relation => 'stats_import.test'::regclass);
+ pg_clear_relation_stats
+-------------------------
+
+(1 row)
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+ relpages | reltuples | relallvisible
+----------+-----------+---------------
+ 0 | -1 | 0
+(1 row)
+
-- invalid relkinds for statistics
CREATE SEQUENCE stats_import.testseq;
-CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
-SELECT
- pg_catalog.pg_clear_relation_stats(
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.testseq'::regclass);
+ERROR: cannot modify statistics for relation "testseq"
+DETAIL: This operation is not supported for sequences.
+SELECT pg_catalog.pg_clear_relation_stats(
'stats_import.testseq'::regclass);
ERROR: cannot modify statistics for relation "testseq"
DETAIL: This operation is not supported for sequences.
-SELECT
- pg_catalog.pg_clear_relation_stats(
+CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.testview'::regclass);
+ERROR: cannot modify statistics for relation "testview"
+DETAIL: This operation is not supported for views.
+SELECT pg_catalog.pg_clear_relation_stats(
'stats_import.testview'::regclass);
ERROR: cannot modify statistics for relation "testview"
DETAIL: This operation is not supported for views.
--- ok: no stakinds
+--
+-- attribute stats
+--
+-- error: object does not exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', '0'::oid::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.1::real);
+ERROR: could not open relation with OID 0
+-- error: relation null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', NULL::oid::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.1::real);
+ERROR: "relation" cannot be NULL
+-- error: NULL attname
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', NULL::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.1::real);
+ERROR: must specify either attname or attnum
+-- error: attname doesn't exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'nope'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+ERROR: column "nope" of relation "test" does not exist
+-- error: both attname and attnum
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'attnum', 1::smallint,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.1::real);
+ERROR: cannot specify both attname and attnum
+-- error: neither attname nor attnum
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.1::real);
+ERROR: must specify either attname or attnum
+-- error: attribute is system column
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'xmin'::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: cannot modify statistics on system column "xmin"
+-- error: inherited null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', NULL::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.1::real);
+ERROR: "inherited" cannot be NULL
+-- error: attribute is system column
+SELECT pg_catalog.pg_clear_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'ctid'::name,
+ inherited => false::boolean);
+ERROR: cannot clear statistics on system column "ctid"
+-- ok: just the fixed values, no stakinds
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
@@ -297,15 +397,17 @@ AND attname = 'id';
stats_import | test | id | f | 0.2 | 5 | 0.6 | | | | | | | | | |
(1 row)
--- ok: restore by attnum
+--
+-- ok: restore by attnum, we normally reserve this for
+-- indexes, but there is no reason it shouldn't work
+-- for any stat-having relation.
+--
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attnum', 1::smallint,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.4::real,
- 'avg_width', 5::integer,
- 'n_distinct', 0.6::real);
+ 'null_frac', 0.4::real);
pg_restore_attribute_stats
----------------------------
t
@@ -322,14 +424,13 @@ AND attname = 'id';
stats_import | test | id | f | 0.4 | 5 | 0.6 | | | | | | | | | |
(1 row)
--- warn: unrecognized argument name
+-- warn: unrecognized argument name, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
'version', 150000::integer,
'null_frac', 0.2::real,
- 'avg_width', NULL::integer,
'nope', 0.5::real);
WARNING: unrecognized argument name: "nope"
pg_restore_attribute_stats
@@ -348,15 +449,13 @@ AND attname = 'id';
stats_import | test | id | f | 0.2 | 5 | 0.6 | | | | | | | | | |
(1 row)
--- warn: mcv / mcf null mismatch part 1
+-- warn: mcv / mcf null mismatch part 1, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.6::real,
- 'avg_width', 7::integer,
- 'n_distinct', -0.7::real,
+ 'null_frac', 0.21::real,
'most_common_freqs', '{0.1,0.2,0.3}'::real[]
);
WARNING: "most_common_vals" must be specified when "most_common_freqs" is specified
@@ -373,18 +472,16 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.6 | 7 | -0.7 | | | | | | | | | |
+ stats_import | test | id | f | 0.21 | 5 | 0.6 | | | | | | | | | |
(1 row)
--- warn: mcv / mcf null mismatch part 2
+-- warn: mcv / mcf null mismatch part 2, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.7::real,
- 'avg_width', 8::integer,
- 'n_distinct', -0.8::real,
+ 'null_frac', 0.21::real,
'most_common_vals', '{1,2,3}'::text
);
WARNING: "most_common_freqs" must be specified when "most_common_vals" is specified
@@ -401,18 +498,16 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.7 | 8 | -0.8 | | | | | | | | | |
+ stats_import | test | id | f | 0.21 | 5 | 0.6 | | | | | | | | | |
(1 row)
--- warn: mcv / mcf type mismatch
+-- warn: mcf type mismatch, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.8::real,
- 'avg_width', 9::integer,
- 'n_distinct', -0.9::real,
+ 'null_frac', 0.22::real,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.2,0.1}'::double precision[]
);
@@ -431,18 +526,16 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.8 | 9 | -0.9 | | | | | | | | | |
+ stats_import | test | id | f | 0.22 | 5 | 0.6 | | | | | | | | | |
(1 row)
--- warn: mcv cast failure
+-- warn: mcv cast failure, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.9::real,
- 'avg_width', 10::integer,
- 'n_distinct', -0.4::real,
+ 'null_frac', 0.23::real,
'most_common_vals', '{2,four,3}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
);
@@ -460,7 +553,7 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.9 | 10 | -0.4 | | | | | | | | | |
+ stats_import | test | id | f | 0.23 | 5 | 0.6 | | | | | | | | | |
(1 row)
-- ok: mcv+mcf
@@ -469,9 +562,6 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'attname', 'id'::name,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 1::integer,
- 'n_distinct', -0.1::real,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
);
@@ -488,18 +578,16 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.1 | 1 | -0.1 | {2,1,3} | {0.3,0.25,0.05} | | | | | | | |
+ stats_import | test | id | f | 0.23 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | | | | | | | |
(1 row)
--- warn: NULL in histogram array
+-- warn: NULL in histogram array, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.2::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.2::real,
+ 'null_frac', 0.24::real,
'histogram_bounds', '{1,NULL,3,4}'::text
);
WARNING: "histogram_bounds" array cannot contain NULL values
@@ -516,7 +604,7 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.2 | 2 | -0.2 | {2,1,3} | {0.3,0.25,0.05} | | | | | | | |
+ stats_import | test | id | f | 0.24 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | | | | | | | |
(1 row)
-- ok: histogram_bounds
@@ -525,10 +613,8 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'attname', 'id'::name,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.3::real,
- 'avg_width', 3::integer,
- 'n_distinct', -0.3::real,
- 'histogram_bounds', '{1,2,3,4}'::text );
+ 'histogram_bounds', '{1,2,3,4}'::text
+ );
pg_restore_attribute_stats
----------------------------
t
@@ -542,19 +628,17 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.3 | 3 | -0.3 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
+ stats_import | test | id | f | 0.24 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
(1 row)
--- warn: elem_count_histogram null element
+-- warn: elem_count_histogram null element, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.4::real,
- 'avg_width', 5::integer,
- 'n_distinct', -0.4::real,
- 'elem_count_histogram', '{1,1,NULL,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
+ 'null_frac', 0.25::real,
+ 'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[]
);
WARNING: "elem_count_histogram" array cannot contain NULL values
pg_restore_attribute_stats
@@ -570,7 +654,7 @@ AND inherited = false
AND attname = 'tags';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | tags | f | 0.4 | 5 | -0.4 | | | | | | | | | |
+ stats_import | test | tags | f | 0.25 | 0 | 0 | | | | | | | | | |
(1 row)
-- ok: elem_count_histogram
@@ -579,9 +663,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'attname', 'tags'::name,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.5::real,
- 'avg_width', 6::integer,
- 'n_distinct', -0.55::real,
+ 'null_frac', 0.26::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
);
pg_restore_attribute_stats
@@ -597,18 +679,16 @@ AND inherited = false
AND attname = 'tags';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------+------------------+------------------------
- stats_import | test | tags | f | 0.5 | 6 | -0.55 | | | | | | | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
+ stats_import | test | tags | f | 0.26 | 0 | 0 | | | | | | | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
(1 row)
--- range stats on a scalar type
+-- warn: range stats on a scalar type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.6::real,
- 'avg_width', 7::integer,
- 'n_distinct', -0.15::real,
+ 'null_frac', 0.27::real,
'range_empty_frac', 0.5::real,
'range_length_histogram', '{399,499,Infinity}'::text
);
@@ -627,18 +707,16 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.6 | 7 | -0.15 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
+ stats_import | test | id | f | 0.27 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
(1 row)
--- warn: range_empty_frac range_length_hist null mismatch
+-- warn: range_empty_frac range_length_hist null mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.7::real,
- 'avg_width', 8::integer,
- 'n_distinct', -0.25::real,
+ 'null_frac', 0.28::real,
'range_length_histogram', '{399,499,Infinity}'::text
);
WARNING: "range_empty_frac" must be specified when "range_length_histogram" is specified
@@ -655,18 +733,16 @@ AND inherited = false
AND attname = 'arange';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | arange | f | 0.7 | 8 | -0.25 | | | | | | | | | |
+ stats_import | test | arange | f | 0.28 | 0 | 0 | | | | | | | | | |
(1 row)
--- warn: range_empty_frac range_length_hist null mismatch part 2
+-- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.8::real,
- 'avg_width', 9::integer,
- 'n_distinct', -0.35::real,
+ 'null_frac', 0.29::real,
'range_empty_frac', 0.5::real
);
WARNING: "range_length_histogram" must be specified when "range_empty_frac" is specified
@@ -683,7 +759,7 @@ AND inherited = false
AND attname = 'arange';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | arange | f | 0.8 | 9 | -0.35 | | | | | | | | | |
+ stats_import | test | arange | f | 0.29 | 0 | 0 | | | | | | | | | |
(1 row)
-- ok: range_empty_frac + range_length_hist
@@ -692,9 +768,6 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'attname', 'arange'::name,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.9::real,
- 'avg_width', 1::integer,
- 'n_distinct', -0.19::real,
'range_empty_frac', 0.5::real,
'range_length_histogram', '{399,499,Infinity}'::text
);
@@ -711,18 +784,16 @@ AND inherited = false
AND attname = 'arange';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | arange | f | 0.9 | 1 | -0.19 | | | | | | | | {399,499,Infinity} | 0.5 |
+ stats_import | test | arange | f | 0.29 | 0 | 0 | | | | | | | | {399,499,Infinity} | 0.5 |
(1 row)
--- warn: range bounds histogram on scalar
+-- warn: range bounds histogram on scalar, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.29::real,
+ 'null_frac', 0.31::real,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
);
WARNING: attribute "id" is not a range type
@@ -740,7 +811,7 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.1 | 2 | -0.29 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
+ stats_import | test | id | f | 0.31 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
(1 row)
-- ok: range_bounds_histogram
@@ -749,9 +820,6 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'attname', 'arange'::name,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.2::real,
- 'avg_width', 3::integer,
- 'n_distinct', -0.39::real,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
);
pg_restore_attribute_stats
@@ -767,26 +835,17 @@ AND inherited = false
AND attname = 'arange';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+--------------------------------------
- stats_import | test | arange | f | 0.2 | 3 | -0.39 | | | | | | | | {399,499,Infinity} | 0.5 | {"[-1,1)","[0,4)","[1,4)","[1,100)"}
+ stats_import | test | arange | f | 0.29 | 0 | 0 | | | | | | | | {399,499,Infinity} | 0.5 | {"[-1,1)","[0,4)","[1,4)","[1,100)"}
(1 row)
--- warn: cannot set most_common_elems for range type
+-- warn: cannot set most_common_elems for range type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
- 'most_common_vals', '{"[2,3)","[1,2)","[3,4)"}'::text,
- 'most_common_freqs', '{0.3,0.25,0.05}'::real[],
- 'histogram_bounds', '{"[1,2)","[2,3)","[3,4)","[4,5)"}'::text,
- 'correlation', 1.1::real,
+ 'null_frac', 0.32::real,
'most_common_elems', '{3,1}'::text,
- 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[],
- 'range_empty_frac', -0.5::real,
- 'range_length_histogram', '{399,499,Infinity}'::text,
- 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
+ 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
);
WARNING: unable to determine element type of attribute "arange"
DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.
@@ -801,19 +860,17 @@ WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'arange';
- schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
---------------+-----------+---------+-----------+-----------+-----------+------------+---------------------------+-------------------+-----------------------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+--------------------------------------
- stats_import | test | arange | f | 0.5 | 2 | -0.1 | {"[2,3)","[1,2)","[3,4)"} | {0.3,0.25,0.05} | {"[1,2)","[2,3)","[3,4)","[4,5)"} | 1.1 | | | | {399,499,Infinity} | -0.5 | {"[-1,1)","[0,4)","[1,4)","[1,100)"}
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+--------------------------------------
+ stats_import | test | arange | f | 0.32 | 0 | 0 | | | | | | | | {399,499,Infinity} | 0.5 | {"[-1,1)","[0,4)","[1,4)","[1,100)"}
(1 row)
--- warn: scalars can't have mcelem
+-- warn: scalars can't have mcelem, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
+ 'null_frac', 0.33::real,
'most_common_elems', '{1,3}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
);
@@ -832,17 +889,15 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.5 | 2 | -0.1 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
+ stats_import | test | id | f | 0.33 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
(1 row)
--- warn: mcelem / mcelem mismatch
+-- warn: mcelem / mcelem mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
+ 'null_frac', 0.34::real,
'most_common_elems', '{one,two}'::text
);
WARNING: "most_common_elem_freqs" must be specified when "most_common_elems" is specified
@@ -859,17 +914,15 @@ AND inherited = false
AND attname = 'tags';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------+------------------+------------------------
- stats_import | test | tags | f | 0.5 | 2 | -0.1 | | | | | | | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
+ stats_import | test | tags | f | 0.34 | 0 | 0 | | | | | | | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
(1 row)
--- warn: mcelem / mcelem null mismatch part 2
+-- warn: mcelem / mcelem null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
+ 'null_frac', 0.35::real,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
);
WARNING: "most_common_elems" must be specified when "most_common_elem_freqs" is specified
@@ -878,14 +931,22 @@ WARNING: "most_common_elems" must be specified when "most_common_elem_freqs" is
f
(1 row)
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'tags';
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------+------------------+------------------------
+ stats_import | test | tags | f | 0.35 | 0 | 0 | | | | | | | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
+(1 row)
+
-- ok: mcelem
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
'most_common_elems', '{one,three}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
);
@@ -902,18 +963,16 @@ AND inherited = false
AND attname = 'tags';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------+------------------+------------------------
- stats_import | test | tags | f | 0.5 | 2 | -0.1 | | | | | {one,three} | {0.3,0.2,0.2,0.3,0} | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
+ stats_import | test | tags | f | 0.35 | 0 | 0 | | | | | {one,three} | {0.3,0.2,0.2,0.3,0} | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
(1 row)
--- warn: scalars can't have elem_count_histogram
+-- warn: scalars can't have elem_count_histogram, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
- 'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
+ 'null_frac', 0.36::real,
+ 'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[]
);
WARNING: unable to determine element type of attribute "id"
DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.
@@ -930,43 +989,7 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.5 | 2 | -0.1 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
-(1 row)
-
--- warn: too many stat kinds
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
- 'most_common_vals', '{"[2,3)","[1,3)","[3,9)"}'::text,
- 'most_common_freqs', '{0.3,0.25,0.05}'::real[],
- 'histogram_bounds', '{"[1,2)","[2,3)","[3,4)","[4,)"}'::text,
- 'correlation', 1.1::real,
- 'most_common_elems', '{3,1}'::text,
- 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[],
- 'range_empty_frac', -0.5::real,
- 'range_length_histogram', '{399,499,Infinity}'::text,
- 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text);
-WARNING: unable to determine element type of attribute "arange"
-DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.
- pg_restore_attribute_stats
-----------------------------
- f
-(1 row)
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'arange';
- schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
---------------+-----------+---------+-----------+-----------+-----------+------------+---------------------------+-------------------+----------------------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+--------------------------------------
- stats_import | test | arange | f | 0.5 | 2 | -0.1 | {"[2,3)","[1,3)","[3,9)"} | {0.3,0.25,0.05} | {"[1,2)","[2,3)","[3,4)","[4,)"} | 1.1 | | | | {399,499,Infinity} | -0.5 | {"[-1,1)","[0,4)","[1,4)","[1,100)"}
+ stats_import | test | id | f | 0.36 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
(1 row)
--
@@ -986,19 +1009,6 @@ SELECT 3, 'tre', (3, 3.3, 'TRE', '2003-03-03', NULL)::stats_import.complex_type,
UNION ALL
SELECT 4, 'four', NULL, int4range(0,100), NULL;
CREATE INDEX is_odd ON stats_import.test(((comp).a % 2 = 1));
--- restoring stats on index
-SELECT * FROM pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.is_odd'::regclass,
- 'version', '180000'::integer,
- 'relpages', '11'::integer,
- 'reltuples', '10000'::real,
- 'relallvisible', '0'::integer
-);
- pg_restore_relation_stats
----------------------------
- t
-(1 row)
-
-- Generate statistics on table with data
ANALYZE stats_import.test;
CREATE TABLE stats_import.test_clone ( LIKE stats_import.test )
@@ -1176,7 +1186,18 @@ WHERE s.starelid = 'stats_import.is_odd'::regclass;
---------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+-----------
(0 rows)
--- ok
+-- attribute stats exist before a clear, but not after
+SELECT COUNT(*)
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'arange';
+ count
+-------
+ 1
+(1 row)
+
SELECT pg_catalog.pg_clear_attribute_stats(
relation => 'stats_import.test'::regclass,
attname => 'arange'::name,
@@ -1186,154 +1207,17 @@ SELECT pg_catalog.pg_clear_attribute_stats(
(1 row)
---
--- Negative tests
---
---- error: relation is wrong type
-SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid,
- 'relpages', 17::integer,
- 'reltuples', 400.0::real,
- 'relallvisible', 4::integer);
-WARNING: argument "relation" has type "oid", expected type "regclass"
-ERROR: "relation" cannot be NULL
---- error: relation not found
-SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::regclass,
- 'relpages', 17::integer,
- 'reltuples', 400.0::real,
- 'relallvisible', 4::integer);
-ERROR: could not open relation with OID 0
--- warn and error: unrecognized argument name
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 'relpages', '17'::integer,
- 'reltuples', 400::real,
- 'nope', 4::integer);
-WARNING: unrecognized argument name: "nope"
-ERROR: could not open relation with OID 0
--- error: argument name is NULL
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- NULL, '17'::integer,
- 'reltuples', 400::real,
- 'relallvisible', 4::integer);
-ERROR: name at variadic position 5 is NULL
--- error: argument name is an integer
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 17, '17'::integer,
- 'reltuples', 400::real,
- 'relallvisible', 4::integer);
-ERROR: name at variadic position 5 has type "integer", expected type "text"
--- error: odd number of variadic arguments cannot be pairs
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 'relpages', '17'::integer,
- 'reltuples', 400::real,
- 'relallvisible');
-ERROR: variadic arguments must be name/value pairs
-HINT: Provide an even number of variadic arguments that can be divided into pairs.
--- error: object doesn't exist
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 'relpages', '17'::integer,
- 'reltuples', 400::real,
- 'relallvisible', 4::integer);
-ERROR: could not open relation with OID 0
--- error: object does not exist
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', '0'::oid::regclass,
- 'attname', 'id'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-ERROR: could not open relation with OID 0
--- error: relation null
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', NULL::oid,
- 'attname', 'id'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-ERROR: "relation" cannot be NULL
--- error: missing attname
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', NULL::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-ERROR: must specify either attname or attnum
--- error: both attname and attnum
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
- 'attnum', 1::smallint,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-ERROR: cannot specify both attname and attnum
--- error: attname doesn't exist
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'nope'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-ERROR: column "nope" of relation "test" does not exist
--- error: attribute is system column
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'xmin'::name,
- 'inherited', false::boolean,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-ERROR: cannot modify statistics on system column "xmin"
--- error: inherited null
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
- 'inherited', NULL::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-ERROR: "inherited" cannot be NULL
--- error: relation not found
-SELECT pg_catalog.pg_clear_relation_stats(
- relation => 'stats_import.nope'::regclass);
-ERROR: relation "stats_import.nope" does not exist
-LINE 2: relation => 'stats_import.nope'::regclass);
- ^
--- error: attribute is system column
-SELECT pg_catalog.pg_clear_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'ctid'::name,
- inherited => false::boolean);
-ERROR: cannot clear statistics on system column "ctid"
--- error: attname doesn't exist
-SELECT pg_catalog.pg_clear_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'nope'::name,
- inherited => false::boolean);
-ERROR: column "nope" of relation "test" does not exist
+SELECT COUNT(*)
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'arange';
+ count
+-------
+ 0
+(1 row)
+
DROP SCHEMA stats_import CASCADE;
NOTICE: drop cascades to 6 other objects
DETAIL: drop cascades to type stats_import.complex_type
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index 8c183bceb8a..69e6cc960de 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -17,13 +17,53 @@ CREATE TABLE stats_import.test(
CREATE INDEX test_i ON stats_import.test(id);
+--
+-- relstats tests
+--
+
+--- error: relation is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 0::oid,
+ 'relpages', 17::integer,
+ 'reltuples', 400.0::real,
+ 'relallvisible', 4::integer);
+
+-- error: relation not found
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 0::oid::regclass,
+ 'relpages', 17::integer,
+ 'reltuples', 400.0::real,
+ 'relallvisible', 4::integer);
+
+-- error: odd number of variadic arguments cannot be pairs
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'version', 150000::integer,
+ 'relpages', '17'::integer,
+ 'reltuples', 400::real,
+ 'relallvisible');
+
+-- error: argument name is NULL
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'version', 150000::integer,
+ NULL, '17'::integer,
+ 'relallvisible', 14::integer);
+
+-- error: argument name is not a text type
+SELECT pg_restore_relation_stats(
+ 'relation', '0'::oid::regclass,
+ 'version', 150000::integer,
+ 17, '17'::integer,
+ 'relallvisible', 44::integer);
+
-- starting stats
SELECT relpages, reltuples, relallvisible
FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
+WHERE oid = 'stats_import.test_i'::regclass;
-BEGIN;
-- regular indexes have special case locking rules
+BEGIN;
SELECT
pg_catalog.pg_restore_relation_stats(
'relation', 'stats_import.test_i'::regclass,
@@ -39,20 +79,6 @@ WHERE relation = 'stats_import.test_i'::regclass AND
COMMIT;
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.test_i'::regclass,
- 'relpages', 19::integer );
-
--- clear
-SELECT
- pg_catalog.pg_clear_relation_stats(
- 'stats_import.test'::regclass);
-
-SELECT relpages, reltuples, relallvisible
-FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
-
-- relpages may be -1 for partitioned tables
CREATE TABLE stats_import.part_parent ( i integer ) PARTITION BY RANGE(i);
CREATE TABLE stats_import.part_child_1
@@ -68,18 +94,6 @@ SELECT relpages
FROM pg_class
WHERE oid = 'stats_import.part_parent'::regclass;
--- although partitioned tables have no storage, setting relpages to a
--- positive value is still allowed
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent_i'::regclass,
- 'relpages', 2::integer);
-
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent'::regclass,
- 'relpages', 2::integer);
-
--
-- Partitioned indexes aren't analyzed but it is possible to set
-- stats. The locking rules are different from normal indexes due to
@@ -103,22 +117,15 @@ WHERE relation = 'stats_import.part_parent_i'::regclass AND
COMMIT;
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent_i'::regclass,
- 'relpages', 2::integer);
+SELECT relpages
+FROM pg_class
+WHERE oid = 'stats_import.part_parent_i'::regclass;
--- nothing stops us from setting it to -1
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent'::regclass,
- 'relpages', -1::integer);
-
--- ok: set all stats
+-- ok: set all stats, no bounds checking
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
'version', 150000::integer,
- 'relpages', '17'::integer,
+ 'relpages', '-17'::integer,
'reltuples', 400::real,
'relallvisible', 4::integer);
@@ -126,7 +133,7 @@ SELECT relpages, reltuples, relallvisible
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
--- ok: just relpages
+-- ok: set just relpages, rest stay same
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
'version', 150000::integer,
@@ -136,7 +143,7 @@ SELECT relpages, reltuples, relallvisible
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
--- ok: just reltuples
+-- ok: set just reltuples, rest stay same
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
'version', 150000::integer,
@@ -146,7 +153,7 @@ SELECT relpages, reltuples, relallvisible
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
--- ok: just relallvisible
+-- ok: set just relallvisible, rest stay same
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
'version', 150000::integer,
@@ -156,7 +163,7 @@ SELECT relpages, reltuples, relallvisible
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
--- warn: bad relpages type
+-- warn: bad relpages type, rest updated
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
'version', 150000::integer,
@@ -168,17 +175,118 @@ SELECT relpages, reltuples, relallvisible
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
+-- unrecognized argument name, rest ok
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'version', 150000::integer,
+ 'relpages', '171'::integer,
+ 'nope', 10::integer);
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+
+-- ok: clear stats
+SELECT pg_catalog.pg_clear_relation_stats(
+ relation => 'stats_import.test'::regclass);
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+
-- invalid relkinds for statistics
CREATE SEQUENCE stats_import.testseq;
-CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
-SELECT
- pg_catalog.pg_clear_relation_stats(
+
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.testseq'::regclass);
+
+SELECT pg_catalog.pg_clear_relation_stats(
'stats_import.testseq'::regclass);
-SELECT
- pg_catalog.pg_clear_relation_stats(
+
+CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
+
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.testview'::regclass);
+
+SELECT pg_catalog.pg_clear_relation_stats(
'stats_import.testview'::regclass);
--- ok: no stakinds
+--
+-- attribute stats
+--
+
+-- error: object does not exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', '0'::oid::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.1::real);
+
+-- error: relation null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', NULL::oid::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.1::real);
+
+-- error: NULL attname
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', NULL::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.1::real);
+
+-- error: attname doesn't exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'nope'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+
+-- error: both attname and attnum
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'attnum', 1::smallint,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.1::real);
+
+-- error: neither attname nor attnum
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.1::real);
+
+-- error: attribute is system column
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'xmin'::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: inherited null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', NULL::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.1::real);
+
+-- error: attribute is system column
+SELECT pg_catalog.pg_clear_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'ctid'::name,
+ inherited => false::boolean);
+
+-- ok: just the fixed values, no stakinds
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
@@ -195,15 +303,17 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- ok: restore by attnum
+--
+-- ok: restore by attnum, we normally reserve this for
+-- indexes, but there is no reason it shouldn't work
+-- for any stat-having relation.
+--
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attnum', 1::smallint,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.4::real,
- 'avg_width', 5::integer,
- 'n_distinct', 0.6::real);
+ 'null_frac', 0.4::real);
SELECT *
FROM pg_stats
@@ -212,14 +322,13 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: unrecognized argument name
+-- warn: unrecognized argument name, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
'version', 150000::integer,
'null_frac', 0.2::real,
- 'avg_width', NULL::integer,
'nope', 0.5::real);
SELECT *
@@ -229,15 +338,13 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: mcv / mcf null mismatch part 1
+-- warn: mcv / mcf null mismatch part 1, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.6::real,
- 'avg_width', 7::integer,
- 'n_distinct', -0.7::real,
+ 'null_frac', 0.21::real,
'most_common_freqs', '{0.1,0.2,0.3}'::real[]
);
@@ -248,15 +355,13 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: mcv / mcf null mismatch part 2
+-- warn: mcv / mcf null mismatch part 2, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.7::real,
- 'avg_width', 8::integer,
- 'n_distinct', -0.8::real,
+ 'null_frac', 0.21::real,
'most_common_vals', '{1,2,3}'::text
);
@@ -267,15 +372,13 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: mcv / mcf type mismatch
+-- warn: mcf type mismatch, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.8::real,
- 'avg_width', 9::integer,
- 'n_distinct', -0.9::real,
+ 'null_frac', 0.22::real,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.2,0.1}'::double precision[]
);
@@ -287,15 +390,13 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: mcv cast failure
+-- warn: mcv cast failure, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.9::real,
- 'avg_width', 10::integer,
- 'n_distinct', -0.4::real,
+ 'null_frac', 0.23::real,
'most_common_vals', '{2,four,3}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
);
@@ -313,9 +414,6 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'attname', 'id'::name,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 1::integer,
- 'n_distinct', -0.1::real,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
);
@@ -327,15 +425,13 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: NULL in histogram array
+-- warn: NULL in histogram array, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.2::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.2::real,
+ 'null_frac', 0.24::real,
'histogram_bounds', '{1,NULL,3,4}'::text
);
@@ -352,10 +448,8 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'attname', 'id'::name,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.3::real,
- 'avg_width', 3::integer,
- 'n_distinct', -0.3::real,
- 'histogram_bounds', '{1,2,3,4}'::text );
+ 'histogram_bounds', '{1,2,3,4}'::text
+ );
SELECT *
FROM pg_stats
@@ -364,16 +458,14 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: elem_count_histogram null element
+-- warn: elem_count_histogram null element, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.4::real,
- 'avg_width', 5::integer,
- 'n_distinct', -0.4::real,
- 'elem_count_histogram', '{1,1,NULL,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
+ 'null_frac', 0.25::real,
+ 'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[]
);
SELECT *
@@ -389,9 +481,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'attname', 'tags'::name,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.5::real,
- 'avg_width', 6::integer,
- 'n_distinct', -0.55::real,
+ 'null_frac', 0.26::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
);
@@ -402,15 +492,13 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'tags';
--- range stats on a scalar type
+-- warn: range stats on a scalar type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.6::real,
- 'avg_width', 7::integer,
- 'n_distinct', -0.15::real,
+ 'null_frac', 0.27::real,
'range_empty_frac', 0.5::real,
'range_length_histogram', '{399,499,Infinity}'::text
);
@@ -422,15 +510,13 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: range_empty_frac range_length_hist null mismatch
+-- warn: range_empty_frac range_length_hist null mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.7::real,
- 'avg_width', 8::integer,
- 'n_distinct', -0.25::real,
+ 'null_frac', 0.28::real,
'range_length_histogram', '{399,499,Infinity}'::text
);
@@ -441,15 +527,13 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'arange';
--- warn: range_empty_frac range_length_hist null mismatch part 2
+-- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.8::real,
- 'avg_width', 9::integer,
- 'n_distinct', -0.35::real,
+ 'null_frac', 0.29::real,
'range_empty_frac', 0.5::real
);
@@ -466,9 +550,6 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'attname', 'arange'::name,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.9::real,
- 'avg_width', 1::integer,
- 'n_distinct', -0.19::real,
'range_empty_frac', 0.5::real,
'range_length_histogram', '{399,499,Infinity}'::text
);
@@ -480,15 +561,13 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'arange';
--- warn: range bounds histogram on scalar
+-- warn: range bounds histogram on scalar, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.29::real,
+ 'null_frac', 0.31::real,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
);
@@ -505,9 +584,6 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'attname', 'arange'::name,
'inherited', false::boolean,
'version', 150000::integer,
- 'null_frac', 0.2::real,
- 'avg_width', 3::integer,
- 'n_distinct', -0.39::real,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
);
@@ -518,23 +594,14 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'arange';
--- warn: cannot set most_common_elems for range type
+-- warn: cannot set most_common_elems for range type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
- 'most_common_vals', '{"[2,3)","[1,2)","[3,4)"}'::text,
- 'most_common_freqs', '{0.3,0.25,0.05}'::real[],
- 'histogram_bounds', '{"[1,2)","[2,3)","[3,4)","[4,5)"}'::text,
- 'correlation', 1.1::real,
+ 'null_frac', 0.32::real,
'most_common_elems', '{3,1}'::text,
- 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[],
- 'range_empty_frac', -0.5::real,
- 'range_length_histogram', '{399,499,Infinity}'::text,
- 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
+ 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
);
SELECT *
@@ -544,14 +611,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'arange';
--- warn: scalars can't have mcelem
+-- warn: scalars can't have mcelem, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
+ 'null_frac', 0.33::real,
'most_common_elems', '{1,3}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
);
@@ -563,14 +628,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: mcelem / mcelem mismatch
+-- warn: mcelem / mcelem mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
+ 'null_frac', 0.34::real,
'most_common_elems', '{one,two}'::text
);
@@ -581,25 +644,27 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'tags';
--- warn: mcelem / mcelem null mismatch part 2
+-- warn: mcelem / mcelem null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
+ 'null_frac', 0.35::real,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
);
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'tags';
+
-- ok: mcelem
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
'most_common_elems', '{one,three}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
);
@@ -611,15 +676,13 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'tags';
--- warn: scalars can't have elem_count_histogram
+-- warn: scalars can't have elem_count_histogram, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
- 'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
+ 'null_frac', 0.36::real,
+ 'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[]
);
SELECT *
@@ -629,32 +692,6 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: too many stat kinds
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
- 'most_common_vals', '{"[2,3)","[1,3)","[3,9)"}'::text,
- 'most_common_freqs', '{0.3,0.25,0.05}'::real[],
- 'histogram_bounds', '{"[1,2)","[2,3)","[3,4)","[4,)"}'::text,
- 'correlation', 1.1::real,
- 'most_common_elems', '{3,1}'::text,
- 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[],
- 'range_empty_frac', -0.5::real,
- 'range_length_histogram', '{399,499,Infinity}'::text,
- 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text);
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'arange';
-
--
-- Test the ability to exactly copy data from one table to an identical table,
-- correctly reconstructing the stakind order as well as the staopN and
@@ -674,15 +711,6 @@ SELECT 4, 'four', NULL, int4range(0,100), NULL;
CREATE INDEX is_odd ON stats_import.test(((comp).a % 2 = 1));
--- restoring stats on index
-SELECT * FROM pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.is_odd'::regclass,
- 'version', '180000'::integer,
- 'relpages', '11'::integer,
- 'reltuples', '10000'::real,
- 'relallvisible', '0'::integer
-);
-
-- Generate statistics on table with data
ANALYZE stats_import.test;
@@ -835,154 +863,24 @@ FROM pg_statistic s
JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
WHERE s.starelid = 'stats_import.is_odd'::regclass;
--- ok
+-- attribute stats exist before a clear, but not after
+SELECT COUNT(*)
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'arange';
+
SELECT pg_catalog.pg_clear_attribute_stats(
relation => 'stats_import.test'::regclass,
attname => 'arange'::name,
inherited => false::boolean);
---
--- Negative tests
---
-
---- error: relation is wrong type
-SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid,
- 'relpages', 17::integer,
- 'reltuples', 400.0::real,
- 'relallvisible', 4::integer);
-
---- error: relation not found
-SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::regclass,
- 'relpages', 17::integer,
- 'reltuples', 400.0::real,
- 'relallvisible', 4::integer);
-
--- warn and error: unrecognized argument name
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 'relpages', '17'::integer,
- 'reltuples', 400::real,
- 'nope', 4::integer);
-
--- error: argument name is NULL
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- NULL, '17'::integer,
- 'reltuples', 400::real,
- 'relallvisible', 4::integer);
-
--- error: argument name is an integer
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 17, '17'::integer,
- 'reltuples', 400::real,
- 'relallvisible', 4::integer);
-
--- error: odd number of variadic arguments cannot be pairs
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 'relpages', '17'::integer,
- 'reltuples', 400::real,
- 'relallvisible');
-
--- error: object doesn't exist
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 'relpages', '17'::integer,
- 'reltuples', 400::real,
- 'relallvisible', 4::integer);
-
--- error: object does not exist
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', '0'::oid::regclass,
- 'attname', 'id'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-
--- error: relation null
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', NULL::oid,
- 'attname', 'id'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-
--- error: missing attname
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', NULL::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-
--- error: both attname and attnum
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
- 'attnum', 1::smallint,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-
--- error: attname doesn't exist
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'nope'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-
--- error: attribute is system column
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'xmin'::name,
- 'inherited', false::boolean,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-
--- error: inherited null
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
- 'inherited', NULL::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-
--- error: relation not found
-SELECT pg_catalog.pg_clear_relation_stats(
- relation => 'stats_import.nope'::regclass);
-
--- error: attribute is system column
-SELECT pg_catalog.pg_clear_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'ctid'::name,
- inherited => false::boolean);
-
--- error: attname doesn't exist
-SELECT pg_catalog.pg_clear_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'nope'::name,
- inherited => false::boolean);
+SELECT COUNT(*)
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'arange';
DROP SCHEMA stats_import CASCADE;
base-commit: 62ec3e1f6786181431210643a2d427b9a98b8af8
--
2.48.1
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-27 23:27 Melanie Plageman <[email protected]>
parent: Corey Huinker <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Melanie Plageman @ 2025-02-27 23:27 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Tom Lane <[email protected]>; Jeff Davis <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Wed, Feb 26, 2025 at 9:19 PM Corey Huinker <[email protected]> wrote:
>>>
>>> +1, let's shorten those queries. The coast is probably pretty
>>> clear now if you want to go do that.
>>
>>
>> On it.
So, I started reviewing this and my original thought about shortening
the queries testing pg_restore_relation_stats() wasn't included in
your patch.
For example:
+--- error: relation is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 0::oid,
+ 'relpages', 17::integer,
+ 'reltuples', 400.0::real,
+ 'relallvisible', 4::integer);
Why do you need to specify all the stats (relpages, reltuples, etc)?
To exercise this you could just do:
select pg_catalog.pg_restore_relation_stats('relation', 0::oid);
Since I haven't been following along with this feature development, I
don't think I can get comfortable enough with all of the changes in
this test diff to commit them. I can't really say if this is the set
of tests that is representative and sufficient for this feature.
If you agree with me that the failure tests could be shorter, I'm
happy to commit that, but I don't really feel comfortable assessing
what the right set of full tests is.
- Melanie
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-28 02:32 Jeff Davis <[email protected]>
parent: Ashutosh Bapat <[email protected]>
0 siblings, 2 replies; 199+ messages in thread
From: Jeff Davis @ 2025-02-28 02:32 UTC (permalink / raw)
To: Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; +Cc: Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>
On Tue, 2025-02-25 at 11:11 +0530, Ashutosh Bapat wrote:
> So the dumped statistics are not restored exactly. The reason for
> this
> is the table statistics is dumped before dumping ALTER TABLE ... ADD
> CONSTRAINT command which changes the statistics. I think all the
> pg_restore_relation_stats() calls should be dumped after all the
> schema and data modifications have been done. OR what's the point in
> dumping statistics only to get rewritten even before restore
> finishes.
In your example, it's not so bad because the stats are actually better:
the index is built after the data is present, and therefore relpages
and reltuples are correct.
The problem is more clear if you use --no-data. If you load data,
ANALYZE, pg_dump --no-data, then reload the sql file, then the stats
are lost.
That workflow is very close to what pg_upgrade does. We solved the
problem for pg_upgrade in commit 71b66171d0 by simply not updating the
statistics when building an index and IsBinaryUpgrade.
To solve the issue with dump --no-data, I propose that we change the
test in 71b66171d0 to only update the stats if the physical relpages is
non-zero.
Patch attached:
* If the dump is --no-data, or during pg_upgrade, the table will be
empty, so the physical relpages will be zero and the restored stats
won't be overwritten.
* If (like in your example) the dump includes data, the new stats are
based on real data, so they are better anyway. This is sort of like the
case where autoanalyze kicks in.
* If the dump is --statistics-only, then there won't be any indexes
created in the SQL file, so when you restore the stats, they will
remain until you do something else to change them.
* If your example really is a problem, you'd need to dump first with -
-no-statistics, and then with --statistics-only, and restore the two
SQL files in order.
Alternatively, we could put stats into SECTION_POST_DATA, which was
already discussed[*], and we decided against it (though there was not a
clear consensus).
Regards,
Jeff Davis
*:
https://www.postgresql.org/message-id/1798867.1712376328%40sss.pgh.pa.us
Attachments:
[text/x-patch] v1-0001-Do-not-update-stats-on-empty-table-when-building-.patch (4.1K, ../../[email protected]/2-v1-0001-Do-not-update-stats-on-empty-table-when-building-.patch)
download | inline diff:
From 86c03cb525b49d24019c5c0ea8ec36bb82b3c58a Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Thu, 27 Feb 2025 17:06:00 -0800
Subject: [PATCH v1] Do not update stats on empty table when building index.
We previously fixed this for binary upgrade in 71b66171d0, but a
similar problem exists when using pg_dump --no-data without pg_upgrade
involved. Fix both problems by not updating the stats when the table
has no pages.
Reported-by: Ashutosh Bapat <[email protected]>
Discussion: https://postgr.es/m/CAExHW5vf9D+8-a5_BEX3y=2y_xY9hiCxV1=C+FnxDvfprWvkng@mail.gmail.com
---
src/backend/catalog/index.c | 17 +++++++++++------
src/test/regress/expected/stats_import.out | 22 +++++++++++++++++++++-
src/test/regress/sql/stats_import.sql | 12 ++++++++++++
3 files changed, 44 insertions(+), 7 deletions(-)
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index f37b990c81d..1a3fdeab350 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2833,11 +2833,7 @@ index_update_stats(Relation rel,
if (reltuples == 0 && rel->rd_rel->reltuples < 0)
reltuples = -1;
- /*
- * Don't update statistics during binary upgrade, because the indexes are
- * created before the data is moved into place.
- */
- update_stats = reltuples >= 0 && !IsBinaryUpgrade;
+ update_stats = reltuples >= 0;
/*
* Finish I/O and visibility map buffer locks before
@@ -2850,7 +2846,16 @@ index_update_stats(Relation rel,
{
relpages = RelationGetNumberOfBlocks(rel);
- if (rel->rd_rel->relkind != RELKIND_INDEX)
+ /*
+ * Don't update statistics when the relation is completely empty. This
+ * is important during binary upgrade, because at the time the schema
+ * is loaded, the data has not yet been moved into place. It's also
+ * useful when restoring a dump containing only schema and statistics.
+ */
+ if (relpages == 0)
+ update_stats = false;
+
+ if (update_stats && rel->rd_rel->relkind != RELKIND_INDEX)
visibilitymap_count(rel, &relallvisible, NULL);
}
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 1f150f7b08d..4c81fb60c91 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -12,14 +12,34 @@ CREATE TABLE stats_import.test(
arange int4range,
tags text[]
) WITH (autovacuum_enabled = false);
+SELECT
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'relpages', 18::integer,
+ 'reltuples', 21::real,
+ 'relallvisible', 24::integer);
+ pg_restore_relation_stats
+---------------------------
+ t
+(1 row)
+
+-- creating an index on an empty table shouldn't overwrite stats
CREATE INDEX test_i ON stats_import.test(id);
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+ relpages | reltuples | relallvisible
+----------+-----------+---------------
+ 18 | 21 | 24
+(1 row)
+
-- starting stats
SELECT relpages, reltuples, relallvisible
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
relpages | reltuples | relallvisible
----------+-----------+---------------
- 0 | -1 | 0
+ 18 | 21 | 24
(1 row)
BEGIN;
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index 8c183bceb8a..c8abb715130 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -15,8 +15,20 @@ CREATE TABLE stats_import.test(
tags text[]
) WITH (autovacuum_enabled = false);
+SELECT
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'relpages', 18::integer,
+ 'reltuples', 21::real,
+ 'relallvisible', 24::integer);
+
+-- creating an index on an empty table shouldn't overwrite stats
CREATE INDEX test_i ON stats_import.test(id);
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+
-- starting stats
SELECT relpages, reltuples, relallvisible
FROM pg_class
--
2.34.1
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-28 03:01 Corey Huinker <[email protected]>
parent: Melanie Plageman <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Corey Huinker @ 2025-02-28 03:01 UTC (permalink / raw)
To: Melanie Plageman <[email protected]>; +Cc: Tom Lane <[email protected]>; Jeff Davis <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
>
> +--- error: relation is wrong type
> +SELECT pg_catalog.pg_restore_relation_stats(
> + 'relation', 0::oid,
> + 'relpages', 17::integer,
> + 'reltuples', 400.0::real,
> + 'relallvisible', 4::integer);
>
> Why do you need to specify all the stats (relpages, reltuples, etc)?
> To exercise this you could just do:
> select pg_catalog.pg_restore_relation_stats('relation', 0::oid);
>
In the above case, it's historical inertia in that the pg_set_* call
required all those parameters, as well as a fear that the code - now or in
the future - might evaluate "can anything actually change from this call"
and short circuit out before actually trying to make sense of the reg_class
oid. But we can assuage that fear with just one of the three stat
parameters, and I'll adjust accordingly.
> Since I haven't been following along with this feature development, I
> don't think I can get comfortable enough with all of the changes in
> this test diff to commit them. I can't really say if this is the set
> of tests that is representative and sufficient for this feature.
>
That's fine, I hadn't anticipated that you'd review this patch, let alone
commit it.
> If you agree with me that the failure tests could be shorter, I'm
> happy to commit that, but I don't really feel comfortable assessing
> what the right set of full tests is.
The set of tests is as short as I feel comfortable with. I'll give the
parameter lists one more pass and repost.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-28 03:42 Greg Sabino Mullane <[email protected]>
parent: Jeff Davis <[email protected]>
1 sibling, 2 replies; 199+ messages in thread
From: Greg Sabino Mullane @ 2025-02-28 03:42 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>
I know I'm coming late to this, but I would like us to rethink having
statistics dumped by default. I was caught by this today, as I was doing
two dumps in a row, but the output changed between runs solely because the
stats got updated. It got me thinking about all the use cases of pg_dump
I've seen over the years. I think this has the potential to cause a lot of
problems for things like automated scripts. It certainly violates the
principle of least astonishment to have dumps change when no user
interaction has happened.
Alternatively, we could put stats into SECTION_POST_DATA,
No, that would make the above-mentioned problem much worse.
Cheers,
Greg
--
Crunchy Data - https://www.crunchydata.com
Enterprise Postgres Software Products & Tech Support
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-28 04:34 Corey Huinker <[email protected]>
parent: Corey Huinker <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Corey Huinker @ 2025-02-28 04:34 UTC (permalink / raw)
To: Melanie Plageman <[email protected]>; +Cc: Tom Lane <[email protected]>; Jeff Davis <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Thu, Feb 27, 2025 at 10:01 PM Corey Huinker <[email protected]>
wrote:
> +--- error: relation is wrong type
>> +SELECT pg_catalog.pg_restore_relation_stats(
>> + 'relation', 0::oid,
>> + 'relpages', 17::integer,
>> + 'reltuples', 400.0::real,
>> + 'relallvisible', 4::integer);
>>
>> Why do you need to specify all the stats (relpages, reltuples, etc)?
>> To exercise this you could just do:
>> select pg_catalog.pg_restore_relation_stats('relation', 0::oid);
>>
>
> In the above case, it's historical inertia in that the pg_set_* call
> required all those parameters, as well as a fear that the code - now or in
> the future - might evaluate "can anything actually change from this call"
> and short circuit out before actually trying to make sense of the reg_class
> oid. But we can assuage that fear with just one of the three stat
> parameters, and I'll adjust accordingly.
>
* reduced relstats parameters specified to the minimum needed to verify the
error and avoid a theoretical future logic short-circuit described above.
* version parameter usage reduced to absolute minimum - verifying that it
is accepted and ignored, though Melanie's patch may introduce a need to
bring it back in a place or two.
84 lines deleted. Not great, not terrible.
I suppose if we really trusted the TAP test databases to have "one of
everything" in terms of tables with all the datatypes, and sufficient rows
to generate interesting stats, plus some indexes of each, then we could get
rid of those two, but I feel very strongly that it would be a minor savings
at a major cost to clarity.
Attachments:
[text/x-patch] v2-0001-Organize-and-deduplicate-statistics-import-tests.patch (92.3K, ../../CADkLM=drSHpmJ_qiHQgsAKuSaWDZLVyOXZrB2=q9tqpmQDERAg@mail.gmail.com/3-v2-0001-Organize-and-deduplicate-statistics-import-tests.patch)
download | inline diff:
From d0dfca62eb8ce27fb4bfce77bd2ee835738899a8 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Wed, 26 Feb 2025 21:02:44 -0500
Subject: [PATCH v2] Organize and deduplicate statistics import tests.
Many changes, refactorings, and rebasings have taken their toll on the
statistics import tests. Now that things appear more stable and the
pg_set_* functions are gone in favor of using pg_restore_* in all cases,
it's safe to remove duplicates, combine tests where possible, and make
the test descriptions a bit more descriptive and uniform.
Additionally, parameters that were not strictly needed to demonstrate
the purpose(s) of a test were removed to reduce clutter.
---
src/test/regress/expected/stats_import.out | 680 ++++++++-------------
src/test/regress/sql/stats_import.sql | 554 +++++++----------
2 files changed, 466 insertions(+), 768 deletions(-)
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 1f150f7b08d..7bd7bfb3e7b 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -13,19 +13,48 @@ CREATE TABLE stats_import.test(
tags text[]
) WITH (autovacuum_enabled = false);
CREATE INDEX test_i ON stats_import.test(id);
+--
+-- relstats tests
+--
+--- error: relation is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 0::oid,
+ 'relpages', 17::integer);
+WARNING: argument "relation" has type "oid", expected type "regclass"
+ERROR: "relation" cannot be NULL
+-- error: relation not found
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 0::oid::regclass,
+ 'relpages', 17::integer);
+ERROR: could not open relation with OID 0
+-- error: odd number of variadic arguments cannot be pairs
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'relallvisible');
+ERROR: variadic arguments must be name/value pairs
+HINT: Provide an even number of variadic arguments that can be divided into pairs.
+-- error: argument name is NULL
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ NULL, '17'::integer);
+ERROR: name at variadic position 3 is NULL
+-- error: argument name is not a text type
+SELECT pg_restore_relation_stats(
+ 'relation', '0'::oid::regclass,
+ 17, '17'::integer);
+ERROR: name at variadic position 3 has type "integer", expected type "text"
-- starting stats
SELECT relpages, reltuples, relallvisible
FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
+WHERE oid = 'stats_import.test_i'::regclass;
relpages | reltuples | relallvisible
----------+-----------+---------------
- 0 | -1 | 0
+ 1 | 0 | 0
(1 row)
-BEGIN;
-- regular indexes have special case locking rules
-SELECT
- pg_catalog.pg_restore_relation_stats(
+BEGIN;
+SELECT pg_catalog.pg_restore_relation_stats(
'relation', 'stats_import.test_i'::regclass,
'relpages', 18::integer);
pg_restore_relation_stats
@@ -50,32 +79,6 @@ WHERE relation = 'stats_import.test_i'::regclass AND
(1 row)
COMMIT;
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.test_i'::regclass,
- 'relpages', 19::integer );
- pg_restore_relation_stats
----------------------------
- t
-(1 row)
-
--- clear
-SELECT
- pg_catalog.pg_clear_relation_stats(
- 'stats_import.test'::regclass);
- pg_clear_relation_stats
--------------------------
-
-(1 row)
-
-SELECT relpages, reltuples, relallvisible
-FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
- relpages | reltuples | relallvisible
-----------+-----------+---------------
- 0 | -1 | 0
-(1 row)
-
-- relpages may be -1 for partitioned tables
CREATE TABLE stats_import.part_parent ( i integer ) PARTITION BY RANGE(i);
CREATE TABLE stats_import.part_child_1
@@ -92,26 +95,6 @@ WHERE oid = 'stats_import.part_parent'::regclass;
-1
(1 row)
--- although partitioned tables have no storage, setting relpages to a
--- positive value is still allowed
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent_i'::regclass,
- 'relpages', 2::integer);
- pg_restore_relation_stats
----------------------------
- t
-(1 row)
-
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent'::regclass,
- 'relpages', 2::integer);
- pg_restore_relation_stats
----------------------------
- t
-(1 row)
-
--
-- Partitioned indexes aren't analyzed but it is possible to set
-- stats. The locking rules are different from normal indexes due to
@@ -119,8 +102,7 @@ SELECT
-- partitioned index are locked in ShareUpdateExclusive mode.
--
BEGIN;
-SELECT
- pg_catalog.pg_restore_relation_stats(
+SELECT pg_catalog.pg_restore_relation_stats(
'relation', 'stats_import.part_parent_i'::regclass,
'relpages', 2::integer);
pg_restore_relation_stats
@@ -145,30 +127,19 @@ WHERE relation = 'stats_import.part_parent_i'::regclass AND
(1 row)
COMMIT;
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent_i'::regclass,
- 'relpages', 2::integer);
- pg_restore_relation_stats
----------------------------
- t
+SELECT relpages
+FROM pg_class
+WHERE oid = 'stats_import.part_parent_i'::regclass;
+ relpages
+----------
+ 2
(1 row)
--- nothing stops us from setting it to -1
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent'::regclass,
- 'relpages', -1::integer);
- pg_restore_relation_stats
----------------------------
- t
-(1 row)
-
--- ok: set all stats
+-- ok: set all relstats, with version, no bounds checking
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
'version', 150000::integer,
- 'relpages', '17'::integer,
+ 'relpages', '-17'::integer,
'reltuples', 400::real,
'relallvisible', 4::integer);
pg_restore_relation_stats
@@ -181,13 +152,12 @@ FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
relpages | reltuples | relallvisible
----------+-----------+---------------
- 17 | 400 | 4
+ -17 | 400 | 4
(1 row)
--- ok: just relpages
+-- ok: set just relpages, rest stay same
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
- 'version', 150000::integer,
'relpages', '16'::integer);
pg_restore_relation_stats
---------------------------
@@ -202,10 +172,9 @@ WHERE oid = 'stats_import.test'::regclass;
16 | 400 | 4
(1 row)
--- ok: just reltuples
+-- ok: set just reltuples, rest stay same
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
- 'version', 150000::integer,
'reltuples', '500'::real);
pg_restore_relation_stats
---------------------------
@@ -220,10 +189,9 @@ WHERE oid = 'stats_import.test'::regclass;
16 | 500 | 4
(1 row)
--- ok: just relallvisible
+-- ok: set just relallvisible, rest stay same
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
- 'version', 150000::integer,
'relallvisible', 5::integer);
pg_restore_relation_stats
---------------------------
@@ -238,10 +206,9 @@ WHERE oid = 'stats_import.test'::regclass;
16 | 500 | 5
(1 row)
--- warn: bad relpages type
+-- warn: bad relpages type, rest updated
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
- 'version', 150000::integer,
'relpages', 'nope'::text,
'reltuples', 400.0::real,
'relallvisible', 4::integer);
@@ -259,20 +226,128 @@ WHERE oid = 'stats_import.test'::regclass;
16 | 400 | 4
(1 row)
+-- unrecognized argument name, rest ok
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'relpages', '171'::integer,
+ 'nope', 10::integer);
+WARNING: unrecognized argument name: "nope"
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+ relpages | reltuples | relallvisible
+----------+-----------+---------------
+ 171 | 400 | 4
+(1 row)
+
+-- ok: clear stats
+SELECT pg_catalog.pg_clear_relation_stats(
+ relation => 'stats_import.test'::regclass);
+ pg_clear_relation_stats
+-------------------------
+
+(1 row)
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+ relpages | reltuples | relallvisible
+----------+-----------+---------------
+ 0 | -1 | 0
+(1 row)
+
-- invalid relkinds for statistics
CREATE SEQUENCE stats_import.testseq;
-CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
-SELECT
- pg_catalog.pg_clear_relation_stats(
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.testseq'::regclass);
+ERROR: cannot modify statistics for relation "testseq"
+DETAIL: This operation is not supported for sequences.
+SELECT pg_catalog.pg_clear_relation_stats(
'stats_import.testseq'::regclass);
ERROR: cannot modify statistics for relation "testseq"
DETAIL: This operation is not supported for sequences.
-SELECT
- pg_catalog.pg_clear_relation_stats(
+CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.testview'::regclass);
+ERROR: cannot modify statistics for relation "testview"
+DETAIL: This operation is not supported for views.
+SELECT pg_catalog.pg_clear_relation_stats(
'stats_import.testview'::regclass);
ERROR: cannot modify statistics for relation "testview"
DETAIL: This operation is not supported for views.
--- ok: no stakinds
+--
+-- attribute stats
+--
+-- error: object does not exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', '0'::oid::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: could not open relation with OID 0
+-- error: relation null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', NULL::oid::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: "relation" cannot be NULL
+-- error: NULL attname
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', NULL::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: must specify either attname or attnum
+-- error: attname doesn't exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'nope'::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+ERROR: column "nope" of relation "test" does not exist
+-- error: both attname and attnum
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'attnum', 1::smallint,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: cannot specify both attname and attnum
+-- error: neither attname nor attnum
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: must specify either attname or attnum
+-- error: attribute is system column
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'xmin'::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: cannot modify statistics on system column "xmin"
+-- error: inherited null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', NULL::boolean,
+ 'null_frac', 0.1::real);
+ERROR: "inherited" cannot be NULL
+-- error: attribute is system column
+SELECT pg_catalog.pg_clear_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'ctid'::name,
+ inherited => false::boolean);
+ERROR: cannot clear statistics on system column "ctid"
+-- ok: just the fixed values, with version, no stakinds
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
@@ -297,15 +372,16 @@ AND attname = 'id';
stats_import | test | id | f | 0.2 | 5 | 0.6 | | | | | | | | | |
(1 row)
--- ok: restore by attnum
+--
+-- ok: restore by attnum, we normally reserve this for
+-- indexes, but there is no reason it shouldn't work
+-- for any stat-having relation.
+--
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attnum', 1::smallint,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.4::real,
- 'avg_width', 5::integer,
- 'n_distinct', 0.6::real);
+ 'null_frac', 0.4::real);
pg_restore_attribute_stats
----------------------------
t
@@ -322,14 +398,12 @@ AND attname = 'id';
stats_import | test | id | f | 0.4 | 5 | 0.6 | | | | | | | | | |
(1 row)
--- warn: unrecognized argument name
+-- warn: unrecognized argument name, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
'null_frac', 0.2::real,
- 'avg_width', NULL::integer,
'nope', 0.5::real);
WARNING: unrecognized argument name: "nope"
pg_restore_attribute_stats
@@ -348,15 +422,12 @@ AND attname = 'id';
stats_import | test | id | f | 0.2 | 5 | 0.6 | | | | | | | | | |
(1 row)
--- warn: mcv / mcf null mismatch part 1
+-- warn: mcv / mcf null mismatch part 1, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.6::real,
- 'avg_width', 7::integer,
- 'n_distinct', -0.7::real,
+ 'null_frac', 0.21::real,
'most_common_freqs', '{0.1,0.2,0.3}'::real[]
);
WARNING: "most_common_vals" must be specified when "most_common_freqs" is specified
@@ -373,18 +444,15 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.6 | 7 | -0.7 | | | | | | | | | |
+ stats_import | test | id | f | 0.21 | 5 | 0.6 | | | | | | | | | |
(1 row)
--- warn: mcv / mcf null mismatch part 2
+-- warn: mcv / mcf null mismatch part 2, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.7::real,
- 'avg_width', 8::integer,
- 'n_distinct', -0.8::real,
+ 'null_frac', 0.21::real,
'most_common_vals', '{1,2,3}'::text
);
WARNING: "most_common_freqs" must be specified when "most_common_vals" is specified
@@ -401,18 +469,15 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.7 | 8 | -0.8 | | | | | | | | | |
+ stats_import | test | id | f | 0.21 | 5 | 0.6 | | | | | | | | | |
(1 row)
--- warn: mcv / mcf type mismatch
+-- warn: mcf type mismatch, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.8::real,
- 'avg_width', 9::integer,
- 'n_distinct', -0.9::real,
+ 'null_frac', 0.22::real,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.2,0.1}'::double precision[]
);
@@ -431,18 +496,15 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.8 | 9 | -0.9 | | | | | | | | | |
+ stats_import | test | id | f | 0.22 | 5 | 0.6 | | | | | | | | | |
(1 row)
--- warn: mcv cast failure
+-- warn: mcv cast failure, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.9::real,
- 'avg_width', 10::integer,
- 'n_distinct', -0.4::real,
+ 'null_frac', 0.23::real,
'most_common_vals', '{2,four,3}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
);
@@ -460,7 +522,7 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.9 | 10 | -0.4 | | | | | | | | | |
+ stats_import | test | id | f | 0.23 | 5 | 0.6 | | | | | | | | | |
(1 row)
-- ok: mcv+mcf
@@ -468,10 +530,6 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 1::integer,
- 'n_distinct', -0.1::real,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
);
@@ -488,18 +546,15 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.1 | 1 | -0.1 | {2,1,3} | {0.3,0.25,0.05} | | | | | | | |
+ stats_import | test | id | f | 0.23 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | | | | | | | |
(1 row)
--- warn: NULL in histogram array
+-- warn: NULL in histogram array, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.2::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.2::real,
+ 'null_frac', 0.24::real,
'histogram_bounds', '{1,NULL,3,4}'::text
);
WARNING: "histogram_bounds" array cannot contain NULL values
@@ -516,7 +571,7 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.2 | 2 | -0.2 | {2,1,3} | {0.3,0.25,0.05} | | | | | | | |
+ stats_import | test | id | f | 0.24 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | | | | | | | |
(1 row)
-- ok: histogram_bounds
@@ -524,11 +579,8 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.3::real,
- 'avg_width', 3::integer,
- 'n_distinct', -0.3::real,
- 'histogram_bounds', '{1,2,3,4}'::text );
+ 'histogram_bounds', '{1,2,3,4}'::text
+ );
pg_restore_attribute_stats
----------------------------
t
@@ -542,19 +594,16 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.3 | 3 | -0.3 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
+ stats_import | test | id | f | 0.24 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
(1 row)
--- warn: elem_count_histogram null element
+-- warn: elem_count_histogram null element, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.4::real,
- 'avg_width', 5::integer,
- 'n_distinct', -0.4::real,
- 'elem_count_histogram', '{1,1,NULL,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
+ 'null_frac', 0.25::real,
+ 'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[]
);
WARNING: "elem_count_histogram" array cannot contain NULL values
pg_restore_attribute_stats
@@ -570,7 +619,7 @@ AND inherited = false
AND attname = 'tags';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | tags | f | 0.4 | 5 | -0.4 | | | | | | | | | |
+ stats_import | test | tags | f | 0.25 | 0 | 0 | | | | | | | | | |
(1 row)
-- ok: elem_count_histogram
@@ -578,10 +627,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.5::real,
- 'avg_width', 6::integer,
- 'n_distinct', -0.55::real,
+ 'null_frac', 0.26::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
);
pg_restore_attribute_stats
@@ -597,18 +643,15 @@ AND inherited = false
AND attname = 'tags';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------+------------------+------------------------
- stats_import | test | tags | f | 0.5 | 6 | -0.55 | | | | | | | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
+ stats_import | test | tags | f | 0.26 | 0 | 0 | | | | | | | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
(1 row)
--- range stats on a scalar type
+-- warn: range stats on a scalar type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.6::real,
- 'avg_width', 7::integer,
- 'n_distinct', -0.15::real,
+ 'null_frac', 0.27::real,
'range_empty_frac', 0.5::real,
'range_length_histogram', '{399,499,Infinity}'::text
);
@@ -627,18 +670,15 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.6 | 7 | -0.15 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
+ stats_import | test | id | f | 0.27 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
(1 row)
--- warn: range_empty_frac range_length_hist null mismatch
+-- warn: range_empty_frac range_length_hist null mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.7::real,
- 'avg_width', 8::integer,
- 'n_distinct', -0.25::real,
+ 'null_frac', 0.28::real,
'range_length_histogram', '{399,499,Infinity}'::text
);
WARNING: "range_empty_frac" must be specified when "range_length_histogram" is specified
@@ -655,18 +695,15 @@ AND inherited = false
AND attname = 'arange';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | arange | f | 0.7 | 8 | -0.25 | | | | | | | | | |
+ stats_import | test | arange | f | 0.28 | 0 | 0 | | | | | | | | | |
(1 row)
--- warn: range_empty_frac range_length_hist null mismatch part 2
+-- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.8::real,
- 'avg_width', 9::integer,
- 'n_distinct', -0.35::real,
+ 'null_frac', 0.29::real,
'range_empty_frac', 0.5::real
);
WARNING: "range_length_histogram" must be specified when "range_empty_frac" is specified
@@ -683,7 +720,7 @@ AND inherited = false
AND attname = 'arange';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | arange | f | 0.8 | 9 | -0.35 | | | | | | | | | |
+ stats_import | test | arange | f | 0.29 | 0 | 0 | | | | | | | | | |
(1 row)
-- ok: range_empty_frac + range_length_hist
@@ -691,10 +728,6 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.9::real,
- 'avg_width', 1::integer,
- 'n_distinct', -0.19::real,
'range_empty_frac', 0.5::real,
'range_length_histogram', '{399,499,Infinity}'::text
);
@@ -711,18 +744,15 @@ AND inherited = false
AND attname = 'arange';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | arange | f | 0.9 | 1 | -0.19 | | | | | | | | {399,499,Infinity} | 0.5 |
+ stats_import | test | arange | f | 0.29 | 0 | 0 | | | | | | | | {399,499,Infinity} | 0.5 |
(1 row)
--- warn: range bounds histogram on scalar
+-- warn: range bounds histogram on scalar, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.29::real,
+ 'null_frac', 0.31::real,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
);
WARNING: attribute "id" is not a range type
@@ -740,7 +770,7 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.1 | 2 | -0.29 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
+ stats_import | test | id | f | 0.31 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
(1 row)
-- ok: range_bounds_histogram
@@ -748,10 +778,6 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.2::real,
- 'avg_width', 3::integer,
- 'n_distinct', -0.39::real,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
);
pg_restore_attribute_stats
@@ -767,26 +793,17 @@ AND inherited = false
AND attname = 'arange';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+--------------------------------------
- stats_import | test | arange | f | 0.2 | 3 | -0.39 | | | | | | | | {399,499,Infinity} | 0.5 | {"[-1,1)","[0,4)","[1,4)","[1,100)"}
+ stats_import | test | arange | f | 0.29 | 0 | 0 | | | | | | | | {399,499,Infinity} | 0.5 | {"[-1,1)","[0,4)","[1,4)","[1,100)"}
(1 row)
--- warn: cannot set most_common_elems for range type
+-- warn: cannot set most_common_elems for range type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
- 'most_common_vals', '{"[2,3)","[1,2)","[3,4)"}'::text,
- 'most_common_freqs', '{0.3,0.25,0.05}'::real[],
- 'histogram_bounds', '{"[1,2)","[2,3)","[3,4)","[4,5)"}'::text,
- 'correlation', 1.1::real,
+ 'null_frac', 0.32::real,
'most_common_elems', '{3,1}'::text,
- 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[],
- 'range_empty_frac', -0.5::real,
- 'range_length_histogram', '{399,499,Infinity}'::text,
- 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
+ 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
);
WARNING: unable to determine element type of attribute "arange"
DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.
@@ -801,19 +818,17 @@ WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'arange';
- schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
---------------+-----------+---------+-----------+-----------+-----------+------------+---------------------------+-------------------+-----------------------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+--------------------------------------
- stats_import | test | arange | f | 0.5 | 2 | -0.1 | {"[2,3)","[1,2)","[3,4)"} | {0.3,0.25,0.05} | {"[1,2)","[2,3)","[3,4)","[4,5)"} | 1.1 | | | | {399,499,Infinity} | -0.5 | {"[-1,1)","[0,4)","[1,4)","[1,100)"}
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+--------------------------------------
+ stats_import | test | arange | f | 0.32 | 0 | 0 | | | | | | | | {399,499,Infinity} | 0.5 | {"[-1,1)","[0,4)","[1,4)","[1,100)"}
(1 row)
--- warn: scalars can't have mcelem
+-- warn: scalars can't have mcelem, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
+ 'null_frac', 0.33::real,
'most_common_elems', '{1,3}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
);
@@ -832,17 +847,15 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.5 | 2 | -0.1 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
+ stats_import | test | id | f | 0.33 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
(1 row)
--- warn: mcelem / mcelem mismatch
+-- warn: mcelem / mcelem mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
+ 'null_frac', 0.34::real,
'most_common_elems', '{one,two}'::text
);
WARNING: "most_common_elem_freqs" must be specified when "most_common_elems" is specified
@@ -859,17 +872,15 @@ AND inherited = false
AND attname = 'tags';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------+------------------+------------------------
- stats_import | test | tags | f | 0.5 | 2 | -0.1 | | | | | | | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
+ stats_import | test | tags | f | 0.34 | 0 | 0 | | | | | | | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
(1 row)
--- warn: mcelem / mcelem null mismatch part 2
+-- warn: mcelem / mcelem null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
+ 'null_frac', 0.35::real,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
);
WARNING: "most_common_elems" must be specified when "most_common_elem_freqs" is specified
@@ -878,14 +889,22 @@ WARNING: "most_common_elems" must be specified when "most_common_elem_freqs" is
f
(1 row)
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'tags';
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------+------------------+------------------------
+ stats_import | test | tags | f | 0.35 | 0 | 0 | | | | | | | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
+(1 row)
+
-- ok: mcelem
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
'most_common_elems', '{one,three}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
);
@@ -902,18 +921,16 @@ AND inherited = false
AND attname = 'tags';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------+------------------+------------------------
- stats_import | test | tags | f | 0.5 | 2 | -0.1 | | | | | {one,three} | {0.3,0.2,0.2,0.3,0} | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
+ stats_import | test | tags | f | 0.35 | 0 | 0 | | | | | {one,three} | {0.3,0.2,0.2,0.3,0} | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
(1 row)
--- warn: scalars can't have elem_count_histogram
+-- warn: scalars can't have elem_count_histogram, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
- 'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
+ 'null_frac', 0.36::real,
+ 'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[]
);
WARNING: unable to determine element type of attribute "id"
DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.
@@ -930,43 +947,7 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.5 | 2 | -0.1 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
-(1 row)
-
--- warn: too many stat kinds
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
- 'most_common_vals', '{"[2,3)","[1,3)","[3,9)"}'::text,
- 'most_common_freqs', '{0.3,0.25,0.05}'::real[],
- 'histogram_bounds', '{"[1,2)","[2,3)","[3,4)","[4,)"}'::text,
- 'correlation', 1.1::real,
- 'most_common_elems', '{3,1}'::text,
- 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[],
- 'range_empty_frac', -0.5::real,
- 'range_length_histogram', '{399,499,Infinity}'::text,
- 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text);
-WARNING: unable to determine element type of attribute "arange"
-DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.
- pg_restore_attribute_stats
-----------------------------
- f
-(1 row)
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'arange';
- schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
---------------+-----------+---------+-----------+-----------+-----------+------------+---------------------------+-------------------+----------------------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+--------------------------------------
- stats_import | test | arange | f | 0.5 | 2 | -0.1 | {"[2,3)","[1,3)","[3,9)"} | {0.3,0.25,0.05} | {"[1,2)","[2,3)","[3,4)","[4,)"} | 1.1 | | | | {399,499,Infinity} | -0.5 | {"[-1,1)","[0,4)","[1,4)","[1,100)"}
+ stats_import | test | id | f | 0.36 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
(1 row)
--
@@ -986,19 +967,6 @@ SELECT 3, 'tre', (3, 3.3, 'TRE', '2003-03-03', NULL)::stats_import.complex_type,
UNION ALL
SELECT 4, 'four', NULL, int4range(0,100), NULL;
CREATE INDEX is_odd ON stats_import.test(((comp).a % 2 = 1));
--- restoring stats on index
-SELECT * FROM pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.is_odd'::regclass,
- 'version', '180000'::integer,
- 'relpages', '11'::integer,
- 'reltuples', '10000'::real,
- 'relallvisible', '0'::integer
-);
- pg_restore_relation_stats
----------------------------
- t
-(1 row)
-
-- Generate statistics on table with data
ANALYZE stats_import.test;
CREATE TABLE stats_import.test_clone ( LIKE stats_import.test )
@@ -1176,7 +1144,18 @@ WHERE s.starelid = 'stats_import.is_odd'::regclass;
---------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+-----------
(0 rows)
--- ok
+-- attribute stats exist before a clear, but not after
+SELECT COUNT(*)
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'arange';
+ count
+-------
+ 1
+(1 row)
+
SELECT pg_catalog.pg_clear_attribute_stats(
relation => 'stats_import.test'::regclass,
attname => 'arange'::name,
@@ -1186,154 +1165,17 @@ SELECT pg_catalog.pg_clear_attribute_stats(
(1 row)
---
--- Negative tests
---
---- error: relation is wrong type
-SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid,
- 'relpages', 17::integer,
- 'reltuples', 400.0::real,
- 'relallvisible', 4::integer);
-WARNING: argument "relation" has type "oid", expected type "regclass"
-ERROR: "relation" cannot be NULL
---- error: relation not found
-SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::regclass,
- 'relpages', 17::integer,
- 'reltuples', 400.0::real,
- 'relallvisible', 4::integer);
-ERROR: could not open relation with OID 0
--- warn and error: unrecognized argument name
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 'relpages', '17'::integer,
- 'reltuples', 400::real,
- 'nope', 4::integer);
-WARNING: unrecognized argument name: "nope"
-ERROR: could not open relation with OID 0
--- error: argument name is NULL
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- NULL, '17'::integer,
- 'reltuples', 400::real,
- 'relallvisible', 4::integer);
-ERROR: name at variadic position 5 is NULL
--- error: argument name is an integer
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 17, '17'::integer,
- 'reltuples', 400::real,
- 'relallvisible', 4::integer);
-ERROR: name at variadic position 5 has type "integer", expected type "text"
--- error: odd number of variadic arguments cannot be pairs
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 'relpages', '17'::integer,
- 'reltuples', 400::real,
- 'relallvisible');
-ERROR: variadic arguments must be name/value pairs
-HINT: Provide an even number of variadic arguments that can be divided into pairs.
--- error: object doesn't exist
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 'relpages', '17'::integer,
- 'reltuples', 400::real,
- 'relallvisible', 4::integer);
-ERROR: could not open relation with OID 0
--- error: object does not exist
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', '0'::oid::regclass,
- 'attname', 'id'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-ERROR: could not open relation with OID 0
--- error: relation null
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', NULL::oid,
- 'attname', 'id'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-ERROR: "relation" cannot be NULL
--- error: missing attname
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', NULL::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-ERROR: must specify either attname or attnum
--- error: both attname and attnum
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
- 'attnum', 1::smallint,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-ERROR: cannot specify both attname and attnum
--- error: attname doesn't exist
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'nope'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-ERROR: column "nope" of relation "test" does not exist
--- error: attribute is system column
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'xmin'::name,
- 'inherited', false::boolean,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-ERROR: cannot modify statistics on system column "xmin"
--- error: inherited null
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
- 'inherited', NULL::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-ERROR: "inherited" cannot be NULL
--- error: relation not found
-SELECT pg_catalog.pg_clear_relation_stats(
- relation => 'stats_import.nope'::regclass);
-ERROR: relation "stats_import.nope" does not exist
-LINE 2: relation => 'stats_import.nope'::regclass);
- ^
--- error: attribute is system column
-SELECT pg_catalog.pg_clear_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'ctid'::name,
- inherited => false::boolean);
-ERROR: cannot clear statistics on system column "ctid"
--- error: attname doesn't exist
-SELECT pg_catalog.pg_clear_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'nope'::name,
- inherited => false::boolean);
-ERROR: column "nope" of relation "test" does not exist
+SELECT COUNT(*)
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'arange';
+ count
+-------
+ 0
+(1 row)
+
DROP SCHEMA stats_import CASCADE;
NOTICE: drop cascades to 6 other objects
DETAIL: drop cascades to type stats_import.complex_type
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index 8c183bceb8a..7b2c7d6617f 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -17,15 +17,43 @@ CREATE TABLE stats_import.test(
CREATE INDEX test_i ON stats_import.test(id);
+--
+-- relstats tests
+--
+
+--- error: relation is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 0::oid,
+ 'relpages', 17::integer);
+
+-- error: relation not found
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 0::oid::regclass,
+ 'relpages', 17::integer);
+
+-- error: odd number of variadic arguments cannot be pairs
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'relallvisible');
+
+-- error: argument name is NULL
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ NULL, '17'::integer);
+
+-- error: argument name is not a text type
+SELECT pg_restore_relation_stats(
+ 'relation', '0'::oid::regclass,
+ 17, '17'::integer);
+
-- starting stats
SELECT relpages, reltuples, relallvisible
FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
+WHERE oid = 'stats_import.test_i'::regclass;
-BEGIN;
-- regular indexes have special case locking rules
-SELECT
- pg_catalog.pg_restore_relation_stats(
+BEGIN;
+SELECT pg_catalog.pg_restore_relation_stats(
'relation', 'stats_import.test_i'::regclass,
'relpages', 18::integer);
@@ -39,20 +67,6 @@ WHERE relation = 'stats_import.test_i'::regclass AND
COMMIT;
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.test_i'::regclass,
- 'relpages', 19::integer );
-
--- clear
-SELECT
- pg_catalog.pg_clear_relation_stats(
- 'stats_import.test'::regclass);
-
-SELECT relpages, reltuples, relallvisible
-FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
-
-- relpages may be -1 for partitioned tables
CREATE TABLE stats_import.part_parent ( i integer ) PARTITION BY RANGE(i);
CREATE TABLE stats_import.part_child_1
@@ -68,18 +82,6 @@ SELECT relpages
FROM pg_class
WHERE oid = 'stats_import.part_parent'::regclass;
--- although partitioned tables have no storage, setting relpages to a
--- positive value is still allowed
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent_i'::regclass,
- 'relpages', 2::integer);
-
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent'::regclass,
- 'relpages', 2::integer);
-
--
-- Partitioned indexes aren't analyzed but it is possible to set
-- stats. The locking rules are different from normal indexes due to
@@ -88,8 +90,7 @@ SELECT
--
BEGIN;
-SELECT
- pg_catalog.pg_restore_relation_stats(
+SELECT pg_catalog.pg_restore_relation_stats(
'relation', 'stats_import.part_parent_i'::regclass,
'relpages', 2::integer);
@@ -103,22 +104,15 @@ WHERE relation = 'stats_import.part_parent_i'::regclass AND
COMMIT;
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent_i'::regclass,
- 'relpages', 2::integer);
+SELECT relpages
+FROM pg_class
+WHERE oid = 'stats_import.part_parent_i'::regclass;
--- nothing stops us from setting it to -1
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent'::regclass,
- 'relpages', -1::integer);
-
--- ok: set all stats
+-- ok: set all relstats, with version, no bounds checking
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
'version', 150000::integer,
- 'relpages', '17'::integer,
+ 'relpages', '-17'::integer,
'reltuples', 400::real,
'relallvisible', 4::integer);
@@ -126,40 +120,36 @@ SELECT relpages, reltuples, relallvisible
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
--- ok: just relpages
+-- ok: set just relpages, rest stay same
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
- 'version', 150000::integer,
'relpages', '16'::integer);
SELECT relpages, reltuples, relallvisible
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
--- ok: just reltuples
+-- ok: set just reltuples, rest stay same
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
- 'version', 150000::integer,
'reltuples', '500'::real);
SELECT relpages, reltuples, relallvisible
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
--- ok: just relallvisible
+-- ok: set just relallvisible, rest stay same
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
- 'version', 150000::integer,
'relallvisible', 5::integer);
SELECT relpages, reltuples, relallvisible
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
--- warn: bad relpages type
+-- warn: bad relpages type, rest updated
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
- 'version', 150000::integer,
'relpages', 'nope'::text,
'reltuples', 400.0::real,
'relallvisible', 4::integer);
@@ -168,17 +158,110 @@ SELECT relpages, reltuples, relallvisible
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
+-- unrecognized argument name, rest ok
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'relpages', '171'::integer,
+ 'nope', 10::integer);
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+
+-- ok: clear stats
+SELECT pg_catalog.pg_clear_relation_stats(
+ relation => 'stats_import.test'::regclass);
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+
-- invalid relkinds for statistics
CREATE SEQUENCE stats_import.testseq;
-CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
-SELECT
- pg_catalog.pg_clear_relation_stats(
+
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.testseq'::regclass);
+
+SELECT pg_catalog.pg_clear_relation_stats(
'stats_import.testseq'::regclass);
-SELECT
- pg_catalog.pg_clear_relation_stats(
+
+CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
+
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.testview'::regclass);
+
+SELECT pg_catalog.pg_clear_relation_stats(
'stats_import.testview'::regclass);
--- ok: no stakinds
+--
+-- attribute stats
+--
+
+-- error: object does not exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', '0'::oid::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: relation null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', NULL::oid::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: NULL attname
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', NULL::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: attname doesn't exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'nope'::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+
+-- error: both attname and attnum
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'attnum', 1::smallint,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: neither attname nor attnum
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: attribute is system column
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'xmin'::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: inherited null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', NULL::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: attribute is system column
+SELECT pg_catalog.pg_clear_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'ctid'::name,
+ inherited => false::boolean);
+
+-- ok: just the fixed values, with version, no stakinds
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
@@ -195,15 +278,16 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- ok: restore by attnum
+--
+-- ok: restore by attnum, we normally reserve this for
+-- indexes, but there is no reason it shouldn't work
+-- for any stat-having relation.
+--
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attnum', 1::smallint,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.4::real,
- 'avg_width', 5::integer,
- 'n_distinct', 0.6::real);
+ 'null_frac', 0.4::real);
SELECT *
FROM pg_stats
@@ -212,14 +296,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: unrecognized argument name
+-- warn: unrecognized argument name, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
'null_frac', 0.2::real,
- 'avg_width', NULL::integer,
'nope', 0.5::real);
SELECT *
@@ -229,15 +311,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: mcv / mcf null mismatch part 1
+-- warn: mcv / mcf null mismatch part 1, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.6::real,
- 'avg_width', 7::integer,
- 'n_distinct', -0.7::real,
+ 'null_frac', 0.21::real,
'most_common_freqs', '{0.1,0.2,0.3}'::real[]
);
@@ -248,15 +327,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: mcv / mcf null mismatch part 2
+-- warn: mcv / mcf null mismatch part 2, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.7::real,
- 'avg_width', 8::integer,
- 'n_distinct', -0.8::real,
+ 'null_frac', 0.21::real,
'most_common_vals', '{1,2,3}'::text
);
@@ -267,15 +343,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: mcv / mcf type mismatch
+-- warn: mcf type mismatch, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.8::real,
- 'avg_width', 9::integer,
- 'n_distinct', -0.9::real,
+ 'null_frac', 0.22::real,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.2,0.1}'::double precision[]
);
@@ -287,15 +360,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: mcv cast failure
+-- warn: mcv cast failure, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.9::real,
- 'avg_width', 10::integer,
- 'n_distinct', -0.4::real,
+ 'null_frac', 0.23::real,
'most_common_vals', '{2,four,3}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
);
@@ -312,10 +382,6 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 1::integer,
- 'n_distinct', -0.1::real,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
);
@@ -327,15 +393,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: NULL in histogram array
+-- warn: NULL in histogram array, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.2::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.2::real,
+ 'null_frac', 0.24::real,
'histogram_bounds', '{1,NULL,3,4}'::text
);
@@ -351,11 +414,8 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.3::real,
- 'avg_width', 3::integer,
- 'n_distinct', -0.3::real,
- 'histogram_bounds', '{1,2,3,4}'::text );
+ 'histogram_bounds', '{1,2,3,4}'::text
+ );
SELECT *
FROM pg_stats
@@ -364,16 +424,13 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: elem_count_histogram null element
+-- warn: elem_count_histogram null element, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.4::real,
- 'avg_width', 5::integer,
- 'n_distinct', -0.4::real,
- 'elem_count_histogram', '{1,1,NULL,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
+ 'null_frac', 0.25::real,
+ 'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[]
);
SELECT *
@@ -388,10 +445,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.5::real,
- 'avg_width', 6::integer,
- 'n_distinct', -0.55::real,
+ 'null_frac', 0.26::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
);
@@ -402,15 +456,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'tags';
--- range stats on a scalar type
+-- warn: range stats on a scalar type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.6::real,
- 'avg_width', 7::integer,
- 'n_distinct', -0.15::real,
+ 'null_frac', 0.27::real,
'range_empty_frac', 0.5::real,
'range_length_histogram', '{399,499,Infinity}'::text
);
@@ -422,15 +473,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: range_empty_frac range_length_hist null mismatch
+-- warn: range_empty_frac range_length_hist null mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.7::real,
- 'avg_width', 8::integer,
- 'n_distinct', -0.25::real,
+ 'null_frac', 0.28::real,
'range_length_histogram', '{399,499,Infinity}'::text
);
@@ -441,15 +489,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'arange';
--- warn: range_empty_frac range_length_hist null mismatch part 2
+-- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.8::real,
- 'avg_width', 9::integer,
- 'n_distinct', -0.35::real,
+ 'null_frac', 0.29::real,
'range_empty_frac', 0.5::real
);
@@ -465,10 +510,6 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.9::real,
- 'avg_width', 1::integer,
- 'n_distinct', -0.19::real,
'range_empty_frac', 0.5::real,
'range_length_histogram', '{399,499,Infinity}'::text
);
@@ -480,15 +521,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'arange';
--- warn: range bounds histogram on scalar
+-- warn: range bounds histogram on scalar, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.29::real,
+ 'null_frac', 0.31::real,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
);
@@ -504,10 +542,6 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.2::real,
- 'avg_width', 3::integer,
- 'n_distinct', -0.39::real,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
);
@@ -518,23 +552,14 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'arange';
--- warn: cannot set most_common_elems for range type
+-- warn: cannot set most_common_elems for range type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
- 'most_common_vals', '{"[2,3)","[1,2)","[3,4)"}'::text,
- 'most_common_freqs', '{0.3,0.25,0.05}'::real[],
- 'histogram_bounds', '{"[1,2)","[2,3)","[3,4)","[4,5)"}'::text,
- 'correlation', 1.1::real,
+ 'null_frac', 0.32::real,
'most_common_elems', '{3,1}'::text,
- 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[],
- 'range_empty_frac', -0.5::real,
- 'range_length_histogram', '{399,499,Infinity}'::text,
- 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
+ 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
);
SELECT *
@@ -544,14 +569,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'arange';
--- warn: scalars can't have mcelem
+-- warn: scalars can't have mcelem, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
+ 'null_frac', 0.33::real,
'most_common_elems', '{1,3}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
);
@@ -563,14 +586,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: mcelem / mcelem mismatch
+-- warn: mcelem / mcelem mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
+ 'null_frac', 0.34::real,
'most_common_elems', '{one,two}'::text
);
@@ -581,25 +602,27 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'tags';
--- warn: mcelem / mcelem null mismatch part 2
+-- warn: mcelem / mcelem null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
+ 'null_frac', 0.35::real,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
);
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'tags';
+
-- ok: mcelem
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
'most_common_elems', '{one,three}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
);
@@ -611,15 +634,13 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'tags';
--- warn: scalars can't have elem_count_histogram
+-- warn: scalars can't have elem_count_histogram, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
- 'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
+ 'null_frac', 0.36::real,
+ 'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[]
);
SELECT *
@@ -629,32 +650,6 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: too many stat kinds
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
- 'most_common_vals', '{"[2,3)","[1,3)","[3,9)"}'::text,
- 'most_common_freqs', '{0.3,0.25,0.05}'::real[],
- 'histogram_bounds', '{"[1,2)","[2,3)","[3,4)","[4,)"}'::text,
- 'correlation', 1.1::real,
- 'most_common_elems', '{3,1}'::text,
- 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[],
- 'range_empty_frac', -0.5::real,
- 'range_length_histogram', '{399,499,Infinity}'::text,
- 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text);
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'arange';
-
--
-- Test the ability to exactly copy data from one table to an identical table,
-- correctly reconstructing the stakind order as well as the staopN and
@@ -674,15 +669,6 @@ SELECT 4, 'four', NULL, int4range(0,100), NULL;
CREATE INDEX is_odd ON stats_import.test(((comp).a % 2 = 1));
--- restoring stats on index
-SELECT * FROM pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.is_odd'::regclass,
- 'version', '180000'::integer,
- 'relpages', '11'::integer,
- 'reltuples', '10000'::real,
- 'relallvisible', '0'::integer
-);
-
-- Generate statistics on table with data
ANALYZE stats_import.test;
@@ -835,154 +821,24 @@ FROM pg_statistic s
JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
WHERE s.starelid = 'stats_import.is_odd'::regclass;
--- ok
+-- attribute stats exist before a clear, but not after
+SELECT COUNT(*)
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'arange';
+
SELECT pg_catalog.pg_clear_attribute_stats(
relation => 'stats_import.test'::regclass,
attname => 'arange'::name,
inherited => false::boolean);
---
--- Negative tests
---
-
---- error: relation is wrong type
-SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid,
- 'relpages', 17::integer,
- 'reltuples', 400.0::real,
- 'relallvisible', 4::integer);
-
---- error: relation not found
-SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::regclass,
- 'relpages', 17::integer,
- 'reltuples', 400.0::real,
- 'relallvisible', 4::integer);
-
--- warn and error: unrecognized argument name
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 'relpages', '17'::integer,
- 'reltuples', 400::real,
- 'nope', 4::integer);
-
--- error: argument name is NULL
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- NULL, '17'::integer,
- 'reltuples', 400::real,
- 'relallvisible', 4::integer);
-
--- error: argument name is an integer
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 17, '17'::integer,
- 'reltuples', 400::real,
- 'relallvisible', 4::integer);
-
--- error: odd number of variadic arguments cannot be pairs
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 'relpages', '17'::integer,
- 'reltuples', 400::real,
- 'relallvisible');
-
--- error: object doesn't exist
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 'relpages', '17'::integer,
- 'reltuples', 400::real,
- 'relallvisible', 4::integer);
-
--- error: object does not exist
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', '0'::oid::regclass,
- 'attname', 'id'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-
--- error: relation null
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', NULL::oid,
- 'attname', 'id'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-
--- error: missing attname
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', NULL::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-
--- error: both attname and attnum
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
- 'attnum', 1::smallint,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-
--- error: attname doesn't exist
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'nope'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-
--- error: attribute is system column
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'xmin'::name,
- 'inherited', false::boolean,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-
--- error: inherited null
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
- 'inherited', NULL::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-
--- error: relation not found
-SELECT pg_catalog.pg_clear_relation_stats(
- relation => 'stats_import.nope'::regclass);
-
--- error: attribute is system column
-SELECT pg_catalog.pg_clear_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'ctid'::name,
- inherited => false::boolean);
-
--- error: attname doesn't exist
-SELECT pg_catalog.pg_clear_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'nope'::name,
- inherited => false::boolean);
+SELECT COUNT(*)
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'arange';
DROP SCHEMA stats_import CASCADE;
base-commit: c2a50ac678eb5ccee271aef3e7ed146ac395a32b
--
2.48.1
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export: difference in statistics dumped
@ 2025-02-28 09:21 Ashutosh Bapat <[email protected]>
parent: Jeff Davis <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Ashutosh Bapat @ 2025-02-28 09:21 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; Alvaro Herrera <[email protected]>; jian he <[email protected]>
Hi Jeff,
I am changing the subject on this email and thus creating a new thread
to discuss this issue.
On Fri, Feb 28, 2025 at 8:02 AM Jeff Davis <[email protected]> wrote:
>
> On Tue, 2025-02-25 at 11:11 +0530, Ashutosh Bapat wrote:
> > So the dumped statistics are not restored exactly. The reason for
> > this
> > is the table statistics is dumped before dumping ALTER TABLE ... ADD
> > CONSTRAINT command which changes the statistics. I think all the
> > pg_restore_relation_stats() calls should be dumped after all the
> > schema and data modifications have been done. OR what's the point in
> > dumping statistics only to get rewritten even before restore
> > finishes.
>
> In your example, it's not so bad because the stats are actually better:
> the index is built after the data is present, and therefore relpages
> and reltuples are correct.
>
> The problem is more clear if you use --no-data. If you load data,
> ANALYZE, pg_dump --no-data, then reload the sql file, then the stats
> are lost.
>
> That workflow is very close to what pg_upgrade does. We solved the
> problem for pg_upgrade in commit 71b66171d0 by simply not updating the
> statistics when building an index and IsBinaryUpgrade.
>
> To solve the issue with dump --no-data, I propose that we change the
> test in 71b66171d0 to only update the stats if the physical relpages is
> non-zero.
I don't think I understand the patch well, but here's one question: If
a table is truncated and index is rebuilt would the code in patch stop
it from updating the stats? If yes, that looks problematic.
>
> Patch attached:
>
> * If the dump is --no-data, or during pg_upgrade, the table will be
> empty, so the physical relpages will be zero and the restored stats
> won't be overwritten.
>
> * If (like in your example) the dump includes data, the new stats are
> based on real data, so they are better anyway. This is sort of like the
> case where autoanalyze kicks in.
>
> * If the dump is --statistics-only, then there won't be any indexes
> created in the SQL file, so when you restore the stats, they will
> remain until you do something else to change them.
>
> * If your example really is a problem, you'd need to dump first with -
> -no-statistics, and then with --statistics-only, and restore the two
> SQL files in order.
There are few problems
1. If there are thousands of tables with primary key constraints, we
have twice the number of calls to pg_restore_relation_stats() of which
only half will be useful. The stats written by the first set of calls
will be overwritten by the second set of calls. The time spent in
executing the first set of calls can be saved completely and to some
extent time dumping the calls as well. It will be some measurable
improvement I think.
2. We aren't restoring the statistics faithfully - as mentioned in
Greg's reply. If users dump and restore with autovacuum turned off,
they will be surprised to see the statistics to be different on the
original and restored database - which may have other effects like
change in plans.
3. The test I am building over at [1] is aimed at testing whether the
objects dumped get restored faithfully by comparing dumps from the
original and restored database. That's a bit crude method but is being
used by some of our tests. I think it will be good to test statistics
as well in that test. But if it's not going to be same on the original
and the restored database we can not test it. For now, I have used
--no-statistics.
>
>
> Alternatively, we could put stats into SECTION_POST_DATA, which was
> already discussed[*], and we decided against it (though there was not a
> clear consensus).
I haven't looked at the code which dumps the statistics, but it does
seem simple dump the statistics after the constraint creation command
for the tables with primary key constraint. That will dump
not-up-to-date statistics and might overwrite the statistics
[1] https://www.postgresql.org/message-id/CAExHW5sBbMki6Xs4XxFQQF3C4Wx3wxkLAcySrtuW3vrnOxXDNQ%40mail.gma...
--
Best Wishes,
Ashutosh Bapat
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export: difference in statistics dumped
@ 2025-02-28 20:10 Jeff Davis <[email protected]>
parent: Ashutosh Bapat <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Jeff Davis @ 2025-02-28 20:10 UTC (permalink / raw)
To: Ashutosh Bapat <[email protected]>; +Cc: Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; Alvaro Herrera <[email protected]>; jian he <[email protected]>
On Fri, 2025-02-28 at 14:51 +0530, Ashutosh Bapat wrote:
> 2. We aren't restoring the statistics faithfully - as mentioned in
> Greg's reply. If users dump and restore with autovacuum turned off,
> they will be surprised to see the statistics to be different on the
> original and restored database - which may have other effects like
> change in plans.
Then let's just address that concern directly: disable updating stats
implicitly if autovacuum is off. If autovacuum is on, the user
shouldn't have an expectation of stable stats anyway. Patch attached.
Regards,
Jeff Davis
Attachments:
[text/x-patch] v2-0001-During-CREATE-INDEX-don-t-update-stats-if-autovac.patch (6.8K, ../../[email protected]/2-v2-0001-During-CREATE-INDEX-don-t-update-stats-if-autovac.patch)
download | inline diff:
From a8945b9ce4e358f4a79d3065c07f3b42a94fd387 Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Thu, 27 Feb 2025 17:06:00 -0800
Subject: [PATCH v2] During CREATE INDEX, don't update stats if autovacuum is
off.
We previously fixed this for binary upgrade in 71b66171d0, but a
similar problem existed when using pg_dump --no-data without
pg_upgrade involved.
Fix by not implicitly updating stats during create index when
autovacuum is disabled.
Reported-by: Ashutosh Bapat <[email protected]>
Discussion: https://postgr.es/m/CAExHW5vf9D+8-a5_BEX3y=2y_xY9hiCxV1=C+FnxDvfprWvkng@mail.gmail.com
---
src/backend/catalog/index.c | 31 +++++++++++++++--
src/test/regress/expected/stats_import.out | 39 ++++++++++++++++++----
src/test/regress/sql/stats_import.sql | 22 ++++++++++--
3 files changed, 82 insertions(+), 10 deletions(-)
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index f37b990c81d..318a44e1e1d 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -63,6 +63,7 @@
#include "optimizer/optimizer.h"
#include "parser/parser.h"
#include "pgstat.h"
+#include "postmaster/autovacuum.h"
#include "rewrite/rewriteManip.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
@@ -121,6 +122,7 @@ static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
bool isready);
static void index_update_stats(Relation rel,
bool hasindex,
+ bool update_stats,
double reltuples);
static void IndexCheckExclusion(Relation heapRelation,
Relation indexRelation,
@@ -1261,6 +1263,17 @@ index_create(Relation heapRelation,
}
else if ((flags & INDEX_CREATE_SKIP_BUILD) != 0)
{
+ bool update_stats = true;
+
+ /*
+ * If autovacuum is disabled, don't implicitly update stats as a part
+ * of index creation.
+ */
+ if (!AutoVacuumingActive() ||
+ (heapRelation->rd_options != NULL &&
+ !((StdRdOptions *) heapRelation->rd_options)->autovacuum.enabled))
+ update_stats = false;
+
/*
* Caller is responsible for filling the index later on. However,
* we'd better make sure that the heap relation is correctly marked as
@@ -1268,6 +1281,7 @@ index_create(Relation heapRelation,
*/
index_update_stats(heapRelation,
true,
+ update_stats,
-1.0);
/* Make the above update visible */
CommandCounterIncrement();
@@ -2807,9 +2821,9 @@ FormIndexDatum(IndexInfo *indexInfo,
static void
index_update_stats(Relation rel,
bool hasindex,
+ bool update_stats,
double reltuples)
{
- bool update_stats;
BlockNumber relpages = 0; /* keep compiler quiet */
BlockNumber relallvisible = 0;
Oid relid = RelationGetRelid(rel);
@@ -2837,7 +2851,8 @@ index_update_stats(Relation rel,
* Don't update statistics during binary upgrade, because the indexes are
* created before the data is moved into place.
*/
- update_stats = reltuples >= 0 && !IsBinaryUpgrade;
+ if (reltuples < 0 || IsBinaryUpgrade)
+ update_stats = false;
/*
* Finish I/O and visibility map buffer locks before
@@ -2981,6 +2996,7 @@ index_build(Relation heapRelation,
Oid save_userid;
int save_sec_context;
int save_nestlevel;
+ bool update_stats = true;
/*
* sanity checks
@@ -3121,15 +3137,26 @@ index_build(Relation heapRelation,
table_close(pg_index, RowExclusiveLock);
}
+ /*
+ * If autovacuum is disabled, don't implicitly update stats as a part
+ * of index creation.
+ */
+ if (!AutoVacuumingActive() ||
+ (heapRelation->rd_options != NULL &&
+ !((StdRdOptions *) heapRelation->rd_options)->autovacuum.enabled))
+ update_stats = false;
+
/*
* Update heap and index pg_class rows
*/
index_update_stats(heapRelation,
true,
+ update_stats,
stats->heap_tuples);
index_update_stats(indexRelation,
false,
+ update_stats,
stats->index_tuples);
/* Make the updated catalog row versions visible */
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 1f150f7b08d..abd391181e4 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -12,15 +12,42 @@ CREATE TABLE stats_import.test(
arange int4range,
tags text[]
) WITH (autovacuum_enabled = false);
+SELECT
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'relpages', 18::integer,
+ 'reltuples', 21::real,
+ 'relallvisible', 24::integer);
+ pg_restore_relation_stats
+---------------------------
+ t
+(1 row)
+
+-- CREATE INDEX on a table with autovac disabled should not overwrite
+-- stats
CREATE INDEX test_i ON stats_import.test(id);
+SELECT
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.test_i'::regclass,
+ 'relpages', 28::integer,
+ 'reltuples', 35::real,
+ 'relallvisible', 42::integer);
+ pg_restore_relation_stats
+---------------------------
+ t
+(1 row)
+
-- starting stats
-SELECT relpages, reltuples, relallvisible
+SELECT relname, relpages, reltuples, relallvisible
FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
- relpages | reltuples | relallvisible
-----------+-----------+---------------
- 0 | -1 | 0
-(1 row)
+WHERE oid = 'stats_import.test'::regclass
+ OR oid = 'stats_import.test_i'::regclass
+ORDER BY relname;
+ relname | relpages | reltuples | relallvisible
+---------+----------+-----------+---------------
+ test | 18 | 21 | 24
+ test_i | 28 | 35 | 42
+(2 rows)
BEGIN;
-- regular indexes have special case locking rules
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index 8c183bceb8a..f8907504de1 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -15,12 +15,30 @@ CREATE TABLE stats_import.test(
tags text[]
) WITH (autovacuum_enabled = false);
+SELECT
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'relpages', 18::integer,
+ 'reltuples', 21::real,
+ 'relallvisible', 24::integer);
+
+-- CREATE INDEX on a table with autovac disabled should not overwrite
+-- stats
CREATE INDEX test_i ON stats_import.test(id);
+SELECT
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.test_i'::regclass,
+ 'relpages', 28::integer,
+ 'reltuples', 35::real,
+ 'relallvisible', 42::integer);
+
-- starting stats
-SELECT relpages, reltuples, relallvisible
+SELECT relname, relpages, reltuples, relallvisible
FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
+WHERE oid = 'stats_import.test'::regclass
+ OR oid = 'stats_import.test_i'::regclass
+ORDER BY relname;
BEGIN;
-- regular indexes have special case locking rules
--
2.34.1
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-02-28 20:54 Jeff Davis <[email protected]>
parent: Greg Sabino Mullane <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Jeff Davis @ 2025-02-28 20:54 UTC (permalink / raw)
To: Greg Sabino Mullane <[email protected]>; +Cc: Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>
On Thu, 2025-02-27 at 22:42 -0500, Greg Sabino Mullane wrote:
> I know I'm coming late to this, but I would like us to rethink having
> statistics dumped by default. I was caught by this today, as I was
> doing two dumps in a row, but the output changed between runs solely
> because the stats got updated. It got me thinking about all the use
> cases of pg_dump I've seen over the years. I think this has the
> potential to cause a lot of problems for things like automated
> scripts.
Can you expand on some of those cases?
There are some good reasons to make dumping stats the default:
* The argument here[1] seemed compelling: pg_dump has always dumped
everything by default, so not doing so for stats could be surprising.
* When dumping into the custom format, we'd almost certainly want to
include the stats so you can decide later whether to restore them or
not.
* For most of the cases I'm aware of, if you encounter a diff related
to stats, it would be obvious what the problem is and the fix would be
easy. I can imagine cases where it might not be easy, but I can't
recall any, so if you can then it would be helpful to list them.
so we will need to weigh the costs and benefits.
Unless there's a consensus to change it, I'm inclined to keep it the
default at least into beta, so that we can get feedback from users and
make a more informed decision.
(Aside: I assume everyone here agrees that pg_upgrade should transfer
the stats by default.)
Regards,
Jeff Davis
[1]
https://www.postgresql.org/message-id/3228677.1713844341%40sss.pgh.pa.us
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-01 17:00 Alexander Lakhin <[email protected]>
parent: Jeff Davis <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Alexander Lakhin @ 2025-03-01 17:00 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; Andres Freund <[email protected]>; Corey Huinker <[email protected]>; +Cc: Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Hello Jeff,
26.02.2025 04:00, Jeff Davis wrote:
> I plan to commit the patches soon.
It looks like 8f427187d broke pg_dump on Cygwin:
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=fairywren&dt=2025-02-26%2010%3A03%3A07
As far as I can see, it exits prematurely here:
float reltuples = strtof(PQgetvalue(res, i, i_reltuples), NULL);
because of:
/*
* Cygwin has a strtof() which is literally just (float)strtod(), which means
* we get misrounding _and_ silent over/underflow. Using our wrapper doesn't
* fix the misrounding but does fix the error checks, which cuts down on the
* number of test variant files needed.
*/
#define HAVE_BUGGY_STRTOF 1
...
#ifdef HAVE_BUGGY_STRTOF
extern float pg_strtof(const char *nptr, char **endptr);
#define strtof(a,b) (pg_strtof((a),(b)))
#endif
and:
float
pg_strtof(const char *nptr, char **endptr)
{
...
if (errno)
{
/* On error, just return the error to the caller. */
return fresult;
}
else if ((*endptr == nptr) || isnan(fresult) ||
...
Best regards,
Alexander Lakhin
Neon (https://neon.tech)
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-01 18:04 Tom Lane <[email protected]>
parent: Alexander Lakhin <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Tom Lane @ 2025-03-01 18:04 UTC (permalink / raw)
To: Alexander Lakhin <[email protected]>; +Cc: Jeff Davis <[email protected]>; Andres Freund <[email protected]>; Corey Huinker <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Alexander Lakhin <[email protected]> writes:
> It looks like 8f427187d broke pg_dump on Cygwin:
> https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=fairywren&dt=2025-02-26%2010%3A03%3A07
Yeah, Andrew and I have been puzzling over that off-list. pg_dump
is definitely exiting unceremoniously.
> As far as I can see, it exits prematurely here:
> float reltuples = strtof(PQgetvalue(res, i, i_reltuples), NULL);
I was suspecting those float conversions as a likely cause, but
what do you think is wrong exactly? I see nothing obviously
buggy in pg_strtof().
But I'm not sure it's worth running to ground. I don't love any of
the portability-related hacks that 8f427187d made: the setlocale()
call looks like something with an undesirably large blast radius,
and pg_dump has never made use of strtof or f2s.c before. Sure,
those *ought* to work, but they evidently don't work everywhere,
and I don't especially want to expend more brain cells figuring out
what's wrong here. I think we ought to cut our losses and store
reltuples in string form, as Corey wanted to do originally.
regards, tom lane
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-01 18:20 Alexander Lakhin <[email protected]>
parent: Tom Lane <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Alexander Lakhin @ 2025-03-01 18:20 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Jeff Davis <[email protected]>; Andres Freund <[email protected]>; Corey Huinker <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Hello Tom,
01.03.2025 20:04, Tom Lane wrote:
> Alexander Lakhin <[email protected]> writes:
>> It looks like 8f427187d broke pg_dump on Cygwin:
>> https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=fairywren&dt=2025-02-26%2010%3A03%3A07
> Yeah, Andrew and I have been puzzling over that off-list. pg_dump
> is definitely exiting unceremoniously.
>
>> As far as I can see, it exits prematurely here:
>> float reltuples = strtof(PQgetvalue(res, i, i_reltuples), NULL);
> I was suspecting those float conversions as a likely cause, but
> what do you think is wrong exactly? I see nothing obviously
> buggy in pg_strtof().
From my understanding, pg_strtof () can't stand against endptr == NULL.
I have changed that line to:
char *tptr;
float reltuples = strtof(PQgetvalue(res, i, i_reltuples), &tptr);
and 002_compare_backups passed for me.
Best regards,
Alexander Lakhin
Neon (https://neon.tech)
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-01 18:52 Greg Sabino Mullane <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Greg Sabino Mullane @ 2025-03-01 18:52 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>
> Can you expand on some of those cases?
Certainly. I think one of the problems is that because this patch is
solving a pg_upgrade issue, the focus is on the "dump and restore"
scenarios. But pg_dump is used for much more than that, especially "dump
and examine".
Although pg_dump is meant to be a canonical, logical representation of your
schema and data, the stats add a non-determinant element to that.
Statistical sampling is random, so pg_dump output changes with each run.
(yes, COPY can also change, but much less so, as I argue later).
One use case is a program that is simply using pg_dump to verify that
nothing has modified your table data (I'll use a single table for these
examples, but obviously this applies to a whole database as well). So let's
say we create a table and populate it at time X, then check back at a later
time to verify things are still exactly as we left them.
dropdb gregtest
createdb gregtest
pgbench gregtest -i 2> /dev/null
pg_dump gregtest -t pgbench_accounts > a1
sleep 10
pg_dump gregtest -t pgbench_accounts > a2
diff a1 a2 | cut -c1-50
100078c100078
< 'histogram_bounds', '{2,964,1921,2917,3892,4935
---
> 'histogram_bounds', '{7,989,1990,2969,3973,4977
While COPY is not going to promise a particular output order, the order
should not change except for manual things: insert, update, delete,
truncate, vacuum full, cluster (off the top of my head). What should not
change the output is a background process gathering some metadata. Or
someone running a database-wide ANALYZE.
Another use case is someone rolling out their schema to a QA box. All the
table definitions and data are checked into a git repository, with a
checksum. They want to roll it out, and then verify that everything is
exactly as they expect it to be. Or the program is part of a test suite
that does a sanity check that the database is in an exact known state
before starting.
(Our system catalogs are very difficult when reverse engineering objects.
Thus, many programs rely on pg_dump to do the heavy lifting for them.
Parsing the text file generated by pg_dump is much easier than trying to
manipulate the system catalogs.)
So let's say the process is to create a new database, load things into it,
and then checksum the result. We can simulate that with pg_bench:
dropdb qa1; dropdb qa2
createdb qa1; createdb qa2
pgbench qa1 -i 2>/dev/null
pgbench qa2 -i 2>/dev/null
pg_dump qa1 > dump1; pg_dump qa2 > dump2
$ md5sum dump1
39a2da5e51e8541e9a2c025c918bf463 dump1
This md5sum does not match our repo! It doesn't even match the other one:
$ md5sum dump2
4a977657dfdf910cb66c875d29cfebf2 dump2
It's the stats, or course, which has added a dose of randomness that was
not there before, and makes our checksums useless:
$ diff dump1 dump2 | cut -c1-50
100172c100172
< 'histogram_bounds', '{1,979,1974,2952,3973,4900
---
> 'histogram_bounds', '{8,1017,2054,3034,4045,513
With --no-statistics, the diff shows no difference, and the md5sum is
always the same.
Just to be clear, I love this patch, and I love the fact that one of our
major upgrade warts is finally getting fixed. I've tried fixing it myself a
few times over the last decade or so, but lacked the skills to do so. :) So
I am thrilled to have this finally done. I just don't think it should be
enabled by default for everything using pg_dump. For the record, I would
not strongly object to having stats on by default for binary dumps,
although I would prefer them off.
So why not just expect people to modify their programs to use
--no-statistics for cases like this? That's certainly an option, but it's
going to break a lot of existing things, and create branching code:
old code:
pg_dump mydb -f pg.dump
new code:
if pg_dump.version >= 18
pg_dump --no-statistics mydb -f pg.dump
else
pg_dump mydb -f pg.dump
Also, anything trained to parse pg_dump output will have to learn about the
new SELECT pg_restore_ calls with their multi-line formats (not 100% sure
we don't have that anywhere, as things like "SELECT setval" and "SELECT
set_config" are single line, but there may be existing things)
Cheers,
Greg
--
Crunchy Data - https://www.crunchydata.com
Enterprise Postgres Software Products & Tech Support
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-01 18:56 Tom Lane <[email protected]>
parent: Alexander Lakhin <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Tom Lane @ 2025-03-01 18:56 UTC (permalink / raw)
To: Alexander Lakhin <[email protected]>; +Cc: Andrew Dunstan <[email protected]>; Jeff Davis <[email protected]>; Andres Freund <[email protected]>; Corey Huinker <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Alexander Lakhin <[email protected]> writes:
> 01.03.2025 20:04, Tom Lane wrote:
>> I was suspecting those float conversions as a likely cause, but
>> what do you think is wrong exactly? I see nothing obviously
>> buggy in pg_strtof().
> From my understanding, pg_strtof () can't stand against endptr == NULL.
D'oh! I'm blind as a bat today.
> I have changed that line to:
> char *tptr;
> float reltuples = strtof(PQgetvalue(res, i, i_reltuples), &tptr);
> and 002_compare_backups passed for me.
Cool, but surely the right fix is to make pg_strtof() adhere to
the POSIX specification, so we don't have to learn this lesson
again elsewhere. I'll go make it so.
Independently of that, do we want to switch over to storing
reltuples as a string instead of converting it? I still feel
uncomfortable about the amount of baggage we added to pg_dump
to avoid that.
regards, tom lane
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-01 20:48 Jeff Davis <[email protected]>
parent: Greg Sabino Mullane <[email protected]>
0 siblings, 2 replies; 199+ messages in thread
From: Jeff Davis @ 2025-03-01 20:48 UTC (permalink / raw)
To: Greg Sabino Mullane <[email protected]>; +Cc: Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>
On Sat, 2025-03-01 at 13:52 -0500, Greg Sabino Mullane wrote:
> > Can you expand on some of those cases?
>
> Certainly. I think one of the problems is that because this patch is
> solving a pg_upgrade issue, the focus is on the "dump and restore"
> scenarios. But pg_dump is used for much more than that, especially
> "dump and examine".
Thank you for going through these examples.
> I just don't think it should be enabled by default for everything
> using pg_dump. For the record, I would not strongly object to having
> stats on by default for binary dumps, although I would prefer them
> off.
I am open to that idea, I just want to get it right, because probably
whatever the default is in 18 will stay that way.
Also, we will need to think through the set of pg_dump options again. A
lot of our tools seem to assume that "if it's the default, we don't
need a way to ask for it explicitly", which makes it a lot harder to
ever change the default and keep a coherent set of options.
> So why not just expect people to modify their programs to use --no-
> statistics for cases like this? That's certainly an option, but it's
> going to break a lot of existing things, and create branching code:
I suggest that we wait a bit to see what additional feedback we get
early in beta.
> Also, anything trained to parse pg_dump output will have to learn
> about the new SELECT pg_restore_ calls with their multi-line formats
> (not 100% sure we don't have that anywhere, as things like "SELECT
> setval" and "SELECT set_config" are single line, but there may be
> existing things)
That's an interesting point. What tools are currrently trying to parse
pg_dump output?
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-01 21:23 Tom Lane <[email protected]>
parent: Jeff Davis <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Tom Lane @ 2025-03-01 21:23 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Greg Sabino Mullane <[email protected]>; Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>
Jeff Davis <[email protected]> writes:
> On Sat, 2025-03-01 at 13:52 -0500, Greg Sabino Mullane wrote:
>> Also, anything trained to parse pg_dump output will have to learn
>> about the new SELECT pg_restore_ calls with their multi-line formats
>> (not 100% sure we don't have that anywhere, as things like "SELECT
>> setval" and "SELECT set_config" are single line, but there may be
>> existing things)
> That's an interesting point. What tools are currrently trying to parse
> pg_dump output?
That particular argument needs to be rejected vociferously. Otherwise
we could never make any change at all in what pg_dump emits. I think
the standard has to be "if you parse pg_dump output, it's on you to
cope with any legal SQL".
I do grasp Greg's larger point that this is a big change in pg_dump's
behavior and will certainly break some expectations. I kind of lean
to the position that we'll be sad in the long run if we don't change
the default, though. What other part of pg_dump's output is not
produced by default?
regards, tom lane
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-02 01:55 Corey Huinker <[email protected]>
parent: Tom Lane <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Corey Huinker @ 2025-03-02 01:55 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Alexander Lakhin <[email protected]>; Andrew Dunstan <[email protected]>; Jeff Davis <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
>
> Independently of that, do we want to switch over to storing
> reltuples as a string instead of converting it? I still feel
> uncomfortable about the amount of baggage we added to pg_dump
> to avoid that.
I'm obviously a 'yes' vote for string, either fixed width buffer or
pg_strdup'd, for the reduced complexity. I'm not dismissing concerns about
memory usage, and we could free the RelStatsInfo structure after use, but
we're already not freeing the parent structures tbinfo or indxinfo,
probably because they're needed right up til the end of the program, and
there's no subsequent consumer for the memory that we'd be freeing up.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-02 14:38 Magnus Hagander <[email protected]>
parent: Jeff Davis <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Magnus Hagander @ 2025-03-02 14:38 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Greg Sabino Mullane <[email protected]>; Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>
On Sat, Mar 1, 2025 at 9:48 PM Jeff Davis <[email protected]> wrote:
> On Sat, 2025-03-01 at 13:52 -0500, Greg Sabino Mullane wrote:
> > > Can you expand on some of those cases?
> >
> > Certainly. I think one of the problems is that because this patch is
> > solving a pg_upgrade issue, the focus is on the "dump and restore"
> > scenarios. But pg_dump is used for much more than that, especially
> > "dump and examine".
>
> Thank you for going through these examples.
>
> > I just don't think it should be enabled by default for everything
> > using pg_dump. For the record, I would not strongly object to having
> > stats on by default for binary dumps, although I would prefer them
> > off.
>
> I am open to that idea, I just want to get it right, because probably
> whatever the default is in 18 will stay that way.
>
> Also, we will need to think through the set of pg_dump options again. A
> lot of our tools seem to assume that "if it's the default, we don't
> need a way to ask for it explicitly", which makes it a lot harder to
> ever change the default and keep a coherent set of options.
>
That's a good point in general, and definitely something we should think
through, independently of his patch.
> > So why not just expect people to modify their programs to use --no-
> > statistics for cases like this? That's certainly an option, but it's
> > going to break a lot of existing things, and create branching code:
>
> I suggest that we wait a bit to see what additional feedback we get
> early in beta.
>
I definitely thing it should be on by default.
FWIW, I've seen many cases of people using automated tools to verify the
*schema* between two databases. I'd say that's quite common. But they use
pg_dump -s, which I believe is not affected by this one.
I don't think I've ever come across an automated tool to verify the
contents of an entire database this way. That doesn't mean it's not out
there of course, just that it's not so common. The cases I've seen pg_dump
used to verify the contents that's always been in combination with a myriad
of other switches such as include/exclude of specific tables etc, and
adding just one more switch to those seems like a small price to pay for
having the default behaviour be a big improvement for the majority of
usecases.
> Also, anything trained to parse pg_dump output will have to learn
> > about the new SELECT pg_restore_ calls with their multi-line formats
> > (not 100% sure we don't have that anywhere, as things like "SELECT
> > setval" and "SELECT set_config" are single line, but there may be
> > existing things)
>
That's going to be true every time we add something to pg_dump. And for
that matter, anything new to *postgresql*, since surely we'd want pg_dump
to dump objects by default. Any tool that parses the pg_dump output
directly will always have to carefully analyze each new version. And
probably shouldn't be using the plaintext format in the first place - and
if using pg_restore it comes out as it's own type of object, making it easy
to exclude at that level.
--
Magnus Hagander
Me: https://www.hagander.net/ <http://www.hagander.net/;
Work: https://www.redpill-linpro.com/ <http://www.redpill-linpro.com/;
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-02 20:29 Corey Huinker <[email protected]>
parent: Magnus Hagander <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Corey Huinker @ 2025-03-02 20:29 UTC (permalink / raw)
To: Magnus Hagander <[email protected]>; +Cc: Jeff Davis <[email protected]>; Greg Sabino Mullane <[email protected]>; Ashutosh Bapat <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>
>
> Also, we will need to think through the set of pg_dump options again. A
>> lot of our tools seem to assume that "if it's the default, we don't
>> need a way to ask for it explicitly", which makes it a lot harder to
>> ever change the default and keep a coherent set of options.
>>
>
> That's a good point in general, and definitely something we should think
> through, independently of his patch.
>
I agree. There was a --with-statistics option in earlier patchsets, which
was effectively a no-op because statistics are the default, and it was
removed when its existence was questioned. I mention this only to say that
consensus for those options will have to be built.
> FWIW, I've seen many cases of people using automated tools to verify the
> *schema* between two databases. I'd say that's quite common. But they use
> pg_dump -s, which I believe is not affected by this one.
>
Correct, -s behaves as before, as does --data-only. Schema, data, and
statistics are independent, each has their own -only flag, each each has
their own --no- flag.
If you were using --no-schema to mean data-only, or --no-data to mean
schema-only, then you'll have to add --no-statistics to that call, but I'd
argue that they already had a better option of getting what they wanted.
If you thought you saw major changes in the patchsets around those flags,
you weren't imagining it. There was a lot of internal logic that worked on
the assumptions like "If schema_only is false then we must want data" but
that's no longer strictly true, so we resolved all the user flags to
dumpSchema/dumpData/dumpStatistics at the very start, and now the internal
logic work is based on those affirmative flags rather than the bankshot
absence-of-the-opposite logic that was there before.
>
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export: difference in statistics dumped
@ 2025-03-03 16:34 Ashutosh Bapat <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Ashutosh Bapat @ 2025-03-03 16:34 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; Alvaro Herrera <[email protected]>; jian he <[email protected]>
On Sat, Mar 1, 2025 at 1:40 AM Jeff Davis <[email protected]> wrote:
>
> On Fri, 2025-02-28 at 14:51 +0530, Ashutosh Bapat wrote:
> > 2. We aren't restoring the statistics faithfully - as mentioned in
> > Greg's reply. If users dump and restore with autovacuum turned off,
> > they will be surprised to see the statistics to be different on the
> > original and restored database - which may have other effects like
> > change in plans.
>
> Then let's just address that concern directly: disable updating stats
> implicitly if autovacuum is off. If autovacuum is on, the user
> shouldn't have an expectation of stable stats anyway. Patch attached.
The fact that statistics gets updated is not documented at least under
CREATE INDEX page. So at least users should not rely on that
behaviour. But while we have hold of reltuples wasting a chance to
update it in pg_class does not look right to me. Changing regular
behaviour for the sake of pg_dump/pg_restore doesn't seem right to me.
I think the solution should be on the pg_dump/restore side and not on
the server side.
--
Best Wishes,
Ashutosh Bapat
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-03 19:00 Greg Sabino Mullane <[email protected]>
parent: Tom Lane <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Greg Sabino Mullane @ 2025-03-03 19:00 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Jeff Davis <[email protected]>; Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>
On Sat, Mar 1, 2025 at 4:23 PM Tom Lane <[email protected]> wrote:
> That particular argument needs to be rejected vociferously.
Okay, I will concede that part of my argument. And for the record, I've
written pg_dump output parsing programs many times over the years, and seen
others in the wild. It's not uncommon as some in this thread think.
> What other part of pg_dump's output is not produced by default?
>
None, but that's kind of the point - this is a very special class of data
(so much so, that we've been arguing about where it fits in our usual
pre/data/post paradigm). So I don't think it's unreasonable that something
this unique (and non-deterministic) gets excluded by default. It's still
only a flag away if people require it.
Cheers,
Greg
--
Crunchy Data - https://www.crunchydata.com
Enterprise Postgres Software Products & Tech Support
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export: difference in statistics dumped
@ 2025-03-04 00:55 Jeff Davis <[email protected]>
parent: Ashutosh Bapat <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Jeff Davis @ 2025-03-04 00:55 UTC (permalink / raw)
To: Ashutosh Bapat <[email protected]>; +Cc: Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; Alvaro Herrera <[email protected]>; jian he <[email protected]>
On Mon, 2025-03-03 at 22:04 +0530, Ashutosh Bapat wrote:
> But while we have hold of reltuples wasting a chance to
> update it in pg_class does not look right to me.
To me, autovacuum=off is a pretty clear signal that the user doesn't
want this kind of side-effect to happen. Am I missing something?
> I think the solution should be on the pg_dump/restore side and not on
> the server side.
What solution are you suggesting? The only one that comes to mind is
moving everything to SECTION_POST_DATA, which is possible, but it seems
like a big design change to satisfy a small detail.
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export: difference in statistics dumped
@ 2025-03-04 04:58 Ashutosh Bapat <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Ashutosh Bapat @ 2025-03-04 04:58 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; Alvaro Herrera <[email protected]>; jian he <[email protected]>
On Tue, Mar 4, 2025 at 6:25 AM Jeff Davis <[email protected]> wrote:
>
> On Mon, 2025-03-03 at 22:04 +0530, Ashutosh Bapat wrote:
> > But while we have hold of reltuples wasting a chance to
> > update it in pg_class does not look right to me.
>
> To me, autovacuum=off is a pretty clear signal that the user doesn't
> want this kind of side-effect to happen. Am I missing something?
Documentation of autovacuum says "Controls whether the server should
run the autovacuum launcher daemon." It doesn't talk about updates
happening as a side-effect. With autovacuum there is an extra scan and
resources are consumed but with index creation all that cost is
already paid. I wouldn't compare those two.
The case with IsBinaryUpdate is straight, statistics is not updated
only when run in binary upgrade mode. If we could devise a way to not
update statistics only when the index is created as part of restoring
a dump, that will be easily acceptable. But I don't know
>
> > I think the solution should be on the pg_dump/restore side and not on
> > the server side.
>
> What solution are you suggesting? The only one that comes to mind is
> moving everything to SECTION_POST_DATA, which is possible, but it seems
> like a big design change to satisfy a small detail.
We don't have to do that. We can manage it by making statistics of
index dependent upon the indexes on the table. As far as dump is
concerned, they are dependent since index creation rewrites the
statistics so we would like to add statistics after index creation.
For that we will need to track the statistics dumpable object in the
TableInfo. When adding indexes to TableInfo in getIndexes, we add
dependency between the index and the table statistics. The dependency
based sorting will automatically take care ordering statistics objects
after all the index objects and thus print it after all CREATE INDEX
commands. I have not tried to code this. Do you see any problems with
that?
--
Best Wishes,
Ashutosh Bapat
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export: difference in statistics dumped
@ 2025-03-04 18:15 Jeff Davis <[email protected]>
parent: Ashutosh Bapat <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Jeff Davis @ 2025-03-04 18:15 UTC (permalink / raw)
To: Ashutosh Bapat <[email protected]>; +Cc: Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; Alvaro Herrera <[email protected]>; jian he <[email protected]>
On Tue, 2025-03-04 at 10:28 +0530, Ashutosh Bapat wrote:
> >
> > What solution are you suggesting? The only one that comes to mind
> > is
> > moving everything to SECTION_POST_DATA, which is possible, but it
> > seems
> > like a big design change to satisfy a small detail.
>
> We don't have to do that. We can manage it by making statistics of
> index dependent upon the indexes on the table.
The index relstats are already dependent on the index definition. If
you have a simple database like:
CREATE TABLE t(i INT);
INSERT INTO t SELECT generate_series(1,10);
CREATE INDEX t_idx ON t (i);
ANALYZE;
and then you dump it, you get:
------- SECTION_PRE_DATA -------
CREATE TABLE public.t ...
------- SECTION_DATA -----------
COPY public.t (i) FROM stdin;
...
SELECT * FROM pg_catalog.pg_restore_relation_stats(
'version', '180000'::integer,
'relation', 'public.t'::regclass,
'relpages', '1'::integer,
'reltuples', '10'::real,
'relallvisible', '0'::integer
);
...
------- SECTION_POST_DATA ------
CREATE INDEX t_idx ON public.t USING btree (i);
SELECT * FROM pg_catalog.pg_restore_relation_stats(
'version', '180000'::integer,
'relation', 'public.t_idx'::regclass,
'relpages', '2'::integer,
'reltuples', '10'::real,
'relallvisible', '0'::integer
);
(section annotations added for clarity)
There is no problem with the index relstats, because they are already
dependent on the index definition, and will be restored after the
CREATE INDEX.
The issue is when the table's restored relstats are different from what
CREATE INDEX calculates, and then the CREATE INDEX overwrites the
table's just-restored relation stats. The easiest way to see this is
when restoring with --no-data, because CREATE INDEX will see an empty
table and overwrite the table's restored relstats with zeros.
If we view this issue as a dependency problem, then we'd have to make
the *table relstats* depend on the *index definition*. If a table has
any indexes, the relstats would need to go after the last index
definition, effectively moving most relstats to SECTION_POST_DATA. The
table's attribute stats would not be dependent on the index definition
(because CREATE INDEX doesn't touch those), so they could stay in
SECTION_DATA. And if the table doesn't have any indexes, then its
relstats could also stay in SECTION_DATA. But then we have a mess, so
we might as well just put all stats in SECTION_POST_DATA.
But I don't see it as a dependency problem. When I look at the above
SQL, it reads nicely to me and there's no obvious problem with it.
If we want stats to be stable, we need some kind of mode to tell the
server not to apply these kind of helpful optimizations, otherwise the
issue will resurface in some form no matter what we do with pg_dump. We
could invent a new mode, but autovacuum=off seems close enough to me.
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-05 08:08 Corey Huinker <[email protected]>
parent: Greg Sabino Mullane <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Corey Huinker @ 2025-03-05 08:08 UTC (permalink / raw)
To: Greg Sabino Mullane <[email protected]>; +Cc: Tom Lane <[email protected]>; Jeff Davis <[email protected]>; Ashutosh Bapat <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>
Attached are a couple updates that fell by the wayside and I'd like to
bring focus back to them, plus one potential change, and a recap of where
things stand.
0001 is a patch from Jian He [1] which removes a logic deduplication and I
believe should be committed.
0002 is some attempt to cull the regression tests, eliminating extraneous
parameters where possible, and reorganizing the tests
0003 is a bit more experimental.
In the interest of reducing potential ERRORs raised by
pg_restore_relation_stats and pg_restore_attribute_stats within a restore
or upgrade, the possibility that we attempt to restore stats to a relation
that does not yet exist means that the 'foo.bar'::regclass call will fail
with an ERROR. If, however, we replace the relation regclass parameter with
text parameters schemaname and relname, we have the flexibility to catch
this particular scenario and turn it into a WARNING instead. Likewise, if
we change the attname parameter to text, we avoid those casts as well.
I'm seeking feedback on whether people think this is a positive change.
TODO
1. If the schemaname/relname change is amenable, there are some other
conditions where we currently raise errors but could instead issue a
WARNING instead and return false. We should settle on our preference
soon-ish.
2. Commit 99f8f3fbbc8f74 introduced relallfrozen to pg_class, and pg_dump
does not presently dump relallfrozen stats. I can implement this pending
the feedback on schemaname/relname vs relation regclass.
3. We still have a gap in functionality in that we do not currently dump
and restore extended stats. That patchset was recently updated and is
covered in thread [3]. I know time is getting short, but having this at the
same time would
reduce the number of customers who need to use the new vacuumdb option
under development in [4], and reduce customer confusion concerning whether
they are in need of post-upgrade analyzing of anything.
[1]
https://www.postgresql.org/message-id/flat/CACJufxFVq%3Dtq9u1zrHWYSbMi1T07gS9Ff0LJScMco4HZmtZ1xw%40m...
[2]
https://www.postgresql.org/message-id/flat/CAExHW5sNgxkqkyscm9KRrcwvi%2B_Hg%3DPRe_u%2BxZYJzX%2Bw4XAM...
[3]
https://www.postgresql.org/message-id/flat/CADkLM%3DdnFKZMAo7MwqD2X6JjiiLCoVXHmszqtZp8sycYmoCcMQ%40m...
[4]
https://www.postgresql.org/message-id/flat/CANWCAZZf_jNn2i%2B6-JfQt_j909DBk-U6Dg0M7iArZPLgdXCAmw%40m...
Attachments:
[text/x-patch] v5-0001-refactor-_tocEntryRequired-for-STATISTICS-DATA.patch (2.1K, ../../CADkLM=ct-qo25r-GYO3xq8-f+ZDJCOxmC+FrwhcnFep53LnFkg@mail.gmail.com/3-v5-0001-refactor-_tocEntryRequired-for-STATISTICS-DATA.patch)
download | inline diff:
From 0ed0773d920f96e2f1d2e054a81f364d45d4826d Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Tue, 25 Feb 2025 14:13:24 +0800
Subject: [PATCH v5 1/3] refactor _tocEntryRequired for STATISTICS DATA.
we don't dump STATISTICS DATA in --section=pre-data.
so in _tocEntryRequired, we evaulate "(strcmp(te->desc, "STATISTICS DATA") == 0)"
after ``switch (curSection)``.
---
src/bin/pg_dump/pg_backup_archiver.c | 21 +++++++++------------
1 file changed, 9 insertions(+), 12 deletions(-)
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 632077113a4..0fc4ba04ba4 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2932,14 +2932,6 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
strcmp(te->desc, "SEARCHPATH") == 0)
return REQ_SPECIAL;
- if (strcmp(te->desc, "STATISTICS DATA") == 0)
- {
- if (!ropt->dumpStatistics)
- return 0;
- else
- res = REQ_STATS;
- }
-
/*
* DATABASE and DATABASE PROPERTIES also have a special rule: they are
* restored in createDB mode, and not restored otherwise, independently of
@@ -2984,10 +2976,6 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
if (ropt->no_subscriptions && strcmp(te->desc, "SUBSCRIPTION") == 0)
return 0;
- /* If it's statistics and we don't want statistics, maybe ignore it */
- if (!ropt->dumpStatistics && strcmp(te->desc, "STATISTICS DATA") == 0)
- return 0;
-
/* Ignore it if section is not to be dumped/restored */
switch (curSection)
{
@@ -3008,6 +2996,15 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
return 0;
}
+ /* If it's statistics and we don't want statistics, ignore it */
+ if (strcmp(te->desc, "STATISTICS DATA") == 0)
+ {
+ if (!ropt->dumpStatistics)
+ return 0;
+ else
+ return REQ_STATS;
+ }
+
/* Ignore it if rejected by idWanted[] (cf. SortTocFromFile) */
if (ropt->idWanted && !ropt->idWanted[te->dumpId - 1])
return 0;
base-commit: f4694e0f35b218238cbc87bcf8f8f5c6639bb1d4
--
2.48.1
[text/x-patch] v5-0002-Organize-and-deduplicate-statistics-import-tests.patch (92.7K, ../../CADkLM=ct-qo25r-GYO3xq8-f+ZDJCOxmC+FrwhcnFep53LnFkg@mail.gmail.com/4-v5-0002-Organize-and-deduplicate-statistics-import-tests.patch)
download | inline diff:
From 4fb95c2dc726d666fe32a59595d5dc2478b80465 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Wed, 26 Feb 2025 21:02:44 -0500
Subject: [PATCH v5 2/3] Organize and deduplicate statistics import tests.
Many changes, refactorings, and rebasings have taken their toll on the
statistics import tests. Now that things appear more stable and the
pg_set_* functions are gone in favor of using pg_restore_* in all cases,
it's safe to remove duplicates, combine tests where possible, and make
the test descriptions a bit more descriptive and uniform.
Additionally, parameters that were not strictly needed to demonstrate
the purpose(s) of a test were removed to reduce clutter.
---
src/test/regress/expected/stats_import.out | 681 ++++++++-------------
src/test/regress/sql/stats_import.sql | 555 ++++++-----------
2 files changed, 454 insertions(+), 782 deletions(-)
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 4df287e547f..ebba14c6a1d 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -13,19 +13,48 @@ CREATE TABLE stats_import.test(
tags text[]
) WITH (autovacuum_enabled = false);
CREATE INDEX test_i ON stats_import.test(id);
+--
+-- relstats tests
+--
+--- error: relation is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 0::oid,
+ 'relpages', 17::integer);
+WARNING: argument "relation" has type "oid", expected type "regclass"
+ERROR: "relation" cannot be NULL
+-- error: relation not found
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 0::oid::regclass,
+ 'relpages', 17::integer);
+ERROR: could not open relation with OID 0
+-- error: odd number of variadic arguments cannot be pairs
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'relallvisible');
+ERROR: variadic arguments must be name/value pairs
+HINT: Provide an even number of variadic arguments that can be divided into pairs.
+-- error: argument name is NULL
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ NULL, '17'::integer);
+ERROR: name at variadic position 3 is NULL
+-- error: argument name is not a text type
+SELECT pg_restore_relation_stats(
+ 'relation', '0'::oid::regclass,
+ 17, '17'::integer);
+ERROR: name at variadic position 3 has type "integer", expected type "text"
-- starting stats
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
+WHERE oid = 'stats_import.test_i'::regclass;
relpages | reltuples | relallvisible | relallfrozen
----------+-----------+---------------+--------------
- 0 | -1 | 0 | 0
+ 1 | 0 | 0 | 0
(1 row)
-BEGIN;
-- regular indexes have special case locking rules
-SELECT
- pg_catalog.pg_restore_relation_stats(
+BEGIN;
+SELECT pg_catalog.pg_restore_relation_stats(
'relation', 'stats_import.test_i'::regclass,
'relpages', 18::integer);
pg_restore_relation_stats
@@ -50,32 +79,6 @@ WHERE relation = 'stats_import.test_i'::regclass AND
(1 row)
COMMIT;
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.test_i'::regclass,
- 'relpages', 19::integer );
- pg_restore_relation_stats
----------------------------
- t
-(1 row)
-
--- clear
-SELECT
- pg_catalog.pg_clear_relation_stats(
- 'stats_import.test'::regclass);
- pg_clear_relation_stats
--------------------------
-
-(1 row)
-
-SELECT relpages, reltuples, relallvisible, relallfrozen
-FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
- relpages | reltuples | relallvisible | relallfrozen
-----------+-----------+---------------+--------------
- 0 | -1 | 0 | 0
-(1 row)
-
-- relpages may be -1 for partitioned tables
CREATE TABLE stats_import.part_parent ( i integer ) PARTITION BY RANGE(i);
CREATE TABLE stats_import.part_child_1
@@ -92,26 +95,6 @@ WHERE oid = 'stats_import.part_parent'::regclass;
-1
(1 row)
--- although partitioned tables have no storage, setting relpages to a
--- positive value is still allowed
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent_i'::regclass,
- 'relpages', 2::integer);
- pg_restore_relation_stats
----------------------------
- t
-(1 row)
-
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent'::regclass,
- 'relpages', 2::integer);
- pg_restore_relation_stats
----------------------------
- t
-(1 row)
-
--
-- Partitioned indexes aren't analyzed but it is possible to set
-- stats. The locking rules are different from normal indexes due to
@@ -119,8 +102,7 @@ SELECT
-- partitioned index are locked in ShareUpdateExclusive mode.
--
BEGIN;
-SELECT
- pg_catalog.pg_restore_relation_stats(
+SELECT pg_catalog.pg_restore_relation_stats(
'relation', 'stats_import.part_parent_i'::regclass,
'relpages', 2::integer);
pg_restore_relation_stats
@@ -145,30 +127,19 @@ WHERE relation = 'stats_import.part_parent_i'::regclass AND
(1 row)
COMMIT;
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent_i'::regclass,
- 'relpages', 2::integer);
- pg_restore_relation_stats
----------------------------
- t
+SELECT relpages
+FROM pg_class
+WHERE oid = 'stats_import.part_parent_i'::regclass;
+ relpages
+----------
+ 2
(1 row)
--- nothing stops us from setting it to -1
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent'::regclass,
- 'relpages', -1::integer);
- pg_restore_relation_stats
----------------------------
- t
-(1 row)
-
--- ok: set all stats
+-- ok: set all relstats, with version, no bounds checking
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
'version', 150000::integer,
- 'relpages', '17'::integer,
+ 'relpages', '-17'::integer,
'reltuples', 400::real,
'relallvisible', 4::integer,
'relallfrozen', 2::integer);
@@ -182,13 +153,12 @@ FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
relpages | reltuples | relallvisible | relallfrozen
----------+-----------+---------------+--------------
- 17 | 400 | 4 | 2
+ -17 | 400 | 4 | 2
(1 row)
--- ok: just relpages
+-- ok: set just relpages, rest stay same
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
- 'version', 150000::integer,
'relpages', '16'::integer);
pg_restore_relation_stats
---------------------------
@@ -203,10 +173,9 @@ WHERE oid = 'stats_import.test'::regclass;
16 | 400 | 4 | 2
(1 row)
--- ok: just reltuples
+-- ok: set just reltuples, rest stay same
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
- 'version', 150000::integer,
'reltuples', '500'::real);
pg_restore_relation_stats
---------------------------
@@ -221,10 +190,9 @@ WHERE oid = 'stats_import.test'::regclass;
16 | 500 | 4 | 2
(1 row)
--- ok: just relallvisible
+-- ok: set just relallvisible, rest stay same
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
- 'version', 150000::integer,
'relallvisible', 5::integer);
pg_restore_relation_stats
---------------------------
@@ -257,10 +225,9 @@ WHERE oid = 'stats_import.test'::regclass;
16 | 500 | 5 | 3
(1 row)
--- warn: bad relpages type
+-- warn: bad relpages type, rest updated
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
- 'version', 150000::integer,
'relpages', 'nope'::text,
'reltuples', 400.0::real,
'relallvisible', 4::integer,
@@ -279,20 +246,122 @@ WHERE oid = 'stats_import.test'::regclass;
16 | 400 | 4 | 3
(1 row)
+-- unrecognized argument name, rest ok
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'relpages', '171'::integer,
+ 'nope', 10::integer);
+WARNING: unrecognized argument name: "nope"
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+ relpages | reltuples | relallvisible
+----------+-----------+---------------
+ 171 | 400 | 4
+(1 row)
+
+-- ok: clear stats
+SELECT pg_catalog.pg_clear_relation_stats(
+ relation => 'stats_import.test'::regclass);
+ pg_clear_relation_stats
+-------------------------
+
+(1 row)
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+ relpages | reltuples | relallvisible
+----------+-----------+---------------
+ 0 | -1 | 0
+(1 row)
+
-- invalid relkinds for statistics
CREATE SEQUENCE stats_import.testseq;
-CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
-SELECT
- pg_catalog.pg_clear_relation_stats(
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.testseq'::regclass);
+ERROR: cannot modify statistics for relation "testseq"
+DETAIL: This operation is not supported for sequences.
+SELECT pg_catalog.pg_clear_relation_stats(
'stats_import.testseq'::regclass);
ERROR: cannot modify statistics for relation "testseq"
DETAIL: This operation is not supported for sequences.
-SELECT
- pg_catalog.pg_clear_relation_stats(
+CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.testview'::regclass);
+ERROR: cannot modify statistics for relation "testview"
+DETAIL: This operation is not supported for views.
+SELECT pg_catalog.pg_clear_relation_stats(
'stats_import.testview'::regclass);
ERROR: cannot modify statistics for relation "testview"
DETAIL: This operation is not supported for views.
--- ok: no stakinds
+--
+-- attribute stats
+--
+-- error: object does not exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', '0'::oid::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: could not open relation with OID 0
+-- error: relation null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', NULL::oid::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: "relation" cannot be NULL
+-- error: NULL attname
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', NULL::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: must specify either attname or attnum
+-- error: attname doesn't exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'nope'::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+ERROR: column "nope" of relation "test" does not exist
+-- error: both attname and attnum
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'attnum', 1::smallint,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: cannot specify both attname and attnum
+-- error: neither attname nor attnum
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: must specify either attname or attnum
+-- error: attribute is system column
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'xmin'::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: cannot modify statistics on system column "xmin"
+-- error: inherited null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', NULL::boolean,
+ 'null_frac', 0.1::real);
+ERROR: "inherited" cannot be NULL
+-- ok: just the fixed values, with version, no stakinds
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
@@ -317,15 +386,16 @@ AND attname = 'id';
stats_import | test | id | f | 0.2 | 5 | 0.6 | | | | | | | | | |
(1 row)
--- ok: restore by attnum
+--
+-- ok: restore by attnum, we normally reserve this for
+-- indexes, but there is no reason it shouldn't work
+-- for any stat-having relation.
+--
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attnum', 1::smallint,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.4::real,
- 'avg_width', 5::integer,
- 'n_distinct', 0.6::real);
+ 'null_frac', 0.4::real);
pg_restore_attribute_stats
----------------------------
t
@@ -342,14 +412,12 @@ AND attname = 'id';
stats_import | test | id | f | 0.4 | 5 | 0.6 | | | | | | | | | |
(1 row)
--- warn: unrecognized argument name
+-- warn: unrecognized argument name, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
'null_frac', 0.2::real,
- 'avg_width', NULL::integer,
'nope', 0.5::real);
WARNING: unrecognized argument name: "nope"
pg_restore_attribute_stats
@@ -368,15 +436,12 @@ AND attname = 'id';
stats_import | test | id | f | 0.2 | 5 | 0.6 | | | | | | | | | |
(1 row)
--- warn: mcv / mcf null mismatch part 1
+-- warn: mcv / mcf null mismatch part 1, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.6::real,
- 'avg_width', 7::integer,
- 'n_distinct', -0.7::real,
+ 'null_frac', 0.21::real,
'most_common_freqs', '{0.1,0.2,0.3}'::real[]
);
WARNING: "most_common_vals" must be specified when "most_common_freqs" is specified
@@ -393,18 +458,15 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.6 | 7 | -0.7 | | | | | | | | | |
+ stats_import | test | id | f | 0.21 | 5 | 0.6 | | | | | | | | | |
(1 row)
--- warn: mcv / mcf null mismatch part 2
+-- warn: mcv / mcf null mismatch part 2, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.7::real,
- 'avg_width', 8::integer,
- 'n_distinct', -0.8::real,
+ 'null_frac', 0.21::real,
'most_common_vals', '{1,2,3}'::text
);
WARNING: "most_common_freqs" must be specified when "most_common_vals" is specified
@@ -421,18 +483,15 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.7 | 8 | -0.8 | | | | | | | | | |
+ stats_import | test | id | f | 0.21 | 5 | 0.6 | | | | | | | | | |
(1 row)
--- warn: mcv / mcf type mismatch
+-- warn: mcf type mismatch, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.8::real,
- 'avg_width', 9::integer,
- 'n_distinct', -0.9::real,
+ 'null_frac', 0.22::real,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.2,0.1}'::double precision[]
);
@@ -451,18 +510,15 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.8 | 9 | -0.9 | | | | | | | | | |
+ stats_import | test | id | f | 0.22 | 5 | 0.6 | | | | | | | | | |
(1 row)
--- warn: mcv cast failure
+-- warn: mcv cast failure, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.9::real,
- 'avg_width', 10::integer,
- 'n_distinct', -0.4::real,
+ 'null_frac', 0.23::real,
'most_common_vals', '{2,four,3}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
);
@@ -480,7 +536,7 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.9 | 10 | -0.4 | | | | | | | | | |
+ stats_import | test | id | f | 0.23 | 5 | 0.6 | | | | | | | | | |
(1 row)
-- ok: mcv+mcf
@@ -488,10 +544,6 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 1::integer,
- 'n_distinct', -0.1::real,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
);
@@ -508,18 +560,15 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.1 | 1 | -0.1 | {2,1,3} | {0.3,0.25,0.05} | | | | | | | |
+ stats_import | test | id | f | 0.23 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | | | | | | | |
(1 row)
--- warn: NULL in histogram array
+-- warn: NULL in histogram array, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.2::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.2::real,
+ 'null_frac', 0.24::real,
'histogram_bounds', '{1,NULL,3,4}'::text
);
WARNING: "histogram_bounds" array cannot contain NULL values
@@ -536,7 +585,7 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.2 | 2 | -0.2 | {2,1,3} | {0.3,0.25,0.05} | | | | | | | |
+ stats_import | test | id | f | 0.24 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | | | | | | | |
(1 row)
-- ok: histogram_bounds
@@ -544,11 +593,8 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.3::real,
- 'avg_width', 3::integer,
- 'n_distinct', -0.3::real,
- 'histogram_bounds', '{1,2,3,4}'::text );
+ 'histogram_bounds', '{1,2,3,4}'::text
+ );
pg_restore_attribute_stats
----------------------------
t
@@ -562,19 +608,16 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.3 | 3 | -0.3 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
+ stats_import | test | id | f | 0.24 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
(1 row)
--- warn: elem_count_histogram null element
+-- warn: elem_count_histogram null element, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.4::real,
- 'avg_width', 5::integer,
- 'n_distinct', -0.4::real,
- 'elem_count_histogram', '{1,1,NULL,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
+ 'null_frac', 0.25::real,
+ 'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[]
);
WARNING: "elem_count_histogram" array cannot contain NULL values
pg_restore_attribute_stats
@@ -590,7 +633,7 @@ AND inherited = false
AND attname = 'tags';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | tags | f | 0.4 | 5 | -0.4 | | | | | | | | | |
+ stats_import | test | tags | f | 0.25 | 0 | 0 | | | | | | | | | |
(1 row)
-- ok: elem_count_histogram
@@ -598,10 +641,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.5::real,
- 'avg_width', 6::integer,
- 'n_distinct', -0.55::real,
+ 'null_frac', 0.26::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
);
pg_restore_attribute_stats
@@ -617,18 +657,15 @@ AND inherited = false
AND attname = 'tags';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------+------------------+------------------------
- stats_import | test | tags | f | 0.5 | 6 | -0.55 | | | | | | | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
+ stats_import | test | tags | f | 0.26 | 0 | 0 | | | | | | | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
(1 row)
--- range stats on a scalar type
+-- warn: range stats on a scalar type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.6::real,
- 'avg_width', 7::integer,
- 'n_distinct', -0.15::real,
+ 'null_frac', 0.27::real,
'range_empty_frac', 0.5::real,
'range_length_histogram', '{399,499,Infinity}'::text
);
@@ -647,18 +684,15 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.6 | 7 | -0.15 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
+ stats_import | test | id | f | 0.27 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
(1 row)
--- warn: range_empty_frac range_length_hist null mismatch
+-- warn: range_empty_frac range_length_hist null mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.7::real,
- 'avg_width', 8::integer,
- 'n_distinct', -0.25::real,
+ 'null_frac', 0.28::real,
'range_length_histogram', '{399,499,Infinity}'::text
);
WARNING: "range_empty_frac" must be specified when "range_length_histogram" is specified
@@ -675,18 +709,15 @@ AND inherited = false
AND attname = 'arange';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | arange | f | 0.7 | 8 | -0.25 | | | | | | | | | |
+ stats_import | test | arange | f | 0.28 | 0 | 0 | | | | | | | | | |
(1 row)
--- warn: range_empty_frac range_length_hist null mismatch part 2
+-- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.8::real,
- 'avg_width', 9::integer,
- 'n_distinct', -0.35::real,
+ 'null_frac', 0.29::real,
'range_empty_frac', 0.5::real
);
WARNING: "range_length_histogram" must be specified when "range_empty_frac" is specified
@@ -703,7 +734,7 @@ AND inherited = false
AND attname = 'arange';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | arange | f | 0.8 | 9 | -0.35 | | | | | | | | | |
+ stats_import | test | arange | f | 0.29 | 0 | 0 | | | | | | | | | |
(1 row)
-- ok: range_empty_frac + range_length_hist
@@ -711,10 +742,6 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.9::real,
- 'avg_width', 1::integer,
- 'n_distinct', -0.19::real,
'range_empty_frac', 0.5::real,
'range_length_histogram', '{399,499,Infinity}'::text
);
@@ -731,18 +758,15 @@ AND inherited = false
AND attname = 'arange';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | arange | f | 0.9 | 1 | -0.19 | | | | | | | | {399,499,Infinity} | 0.5 |
+ stats_import | test | arange | f | 0.29 | 0 | 0 | | | | | | | | {399,499,Infinity} | 0.5 |
(1 row)
--- warn: range bounds histogram on scalar
+-- warn: range bounds histogram on scalar, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.29::real,
+ 'null_frac', 0.31::real,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
);
WARNING: attribute "id" is not a range type
@@ -760,7 +784,7 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.1 | 2 | -0.29 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
+ stats_import | test | id | f | 0.31 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
(1 row)
-- ok: range_bounds_histogram
@@ -768,10 +792,6 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.2::real,
- 'avg_width', 3::integer,
- 'n_distinct', -0.39::real,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
);
pg_restore_attribute_stats
@@ -787,26 +807,17 @@ AND inherited = false
AND attname = 'arange';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+--------------------------------------
- stats_import | test | arange | f | 0.2 | 3 | -0.39 | | | | | | | | {399,499,Infinity} | 0.5 | {"[-1,1)","[0,4)","[1,4)","[1,100)"}
+ stats_import | test | arange | f | 0.29 | 0 | 0 | | | | | | | | {399,499,Infinity} | 0.5 | {"[-1,1)","[0,4)","[1,4)","[1,100)"}
(1 row)
--- warn: cannot set most_common_elems for range type
+-- warn: cannot set most_common_elems for range type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
- 'most_common_vals', '{"[2,3)","[1,2)","[3,4)"}'::text,
- 'most_common_freqs', '{0.3,0.25,0.05}'::real[],
- 'histogram_bounds', '{"[1,2)","[2,3)","[3,4)","[4,5)"}'::text,
- 'correlation', 1.1::real,
+ 'null_frac', 0.32::real,
'most_common_elems', '{3,1}'::text,
- 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[],
- 'range_empty_frac', -0.5::real,
- 'range_length_histogram', '{399,499,Infinity}'::text,
- 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
+ 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
);
WARNING: unable to determine element type of attribute "arange"
DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.
@@ -821,19 +832,17 @@ WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'arange';
- schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
---------------+-----------+---------+-----------+-----------+-----------+------------+---------------------------+-------------------+-----------------------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+--------------------------------------
- stats_import | test | arange | f | 0.5 | 2 | -0.1 | {"[2,3)","[1,2)","[3,4)"} | {0.3,0.25,0.05} | {"[1,2)","[2,3)","[3,4)","[4,5)"} | 1.1 | | | | {399,499,Infinity} | -0.5 | {"[-1,1)","[0,4)","[1,4)","[1,100)"}
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+--------------------------------------
+ stats_import | test | arange | f | 0.32 | 0 | 0 | | | | | | | | {399,499,Infinity} | 0.5 | {"[-1,1)","[0,4)","[1,4)","[1,100)"}
(1 row)
--- warn: scalars can't have mcelem
+-- warn: scalars can't have mcelem, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
+ 'null_frac', 0.33::real,
'most_common_elems', '{1,3}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
);
@@ -852,17 +861,15 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.5 | 2 | -0.1 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
+ stats_import | test | id | f | 0.33 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
(1 row)
--- warn: mcelem / mcelem mismatch
+-- warn: mcelem / mcelem mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
+ 'null_frac', 0.34::real,
'most_common_elems', '{one,two}'::text
);
WARNING: "most_common_elem_freqs" must be specified when "most_common_elems" is specified
@@ -879,17 +886,15 @@ AND inherited = false
AND attname = 'tags';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------+------------------+------------------------
- stats_import | test | tags | f | 0.5 | 2 | -0.1 | | | | | | | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
+ stats_import | test | tags | f | 0.34 | 0 | 0 | | | | | | | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
(1 row)
--- warn: mcelem / mcelem null mismatch part 2
+-- warn: mcelem / mcelem null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
+ 'null_frac', 0.35::real,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
);
WARNING: "most_common_elems" must be specified when "most_common_elem_freqs" is specified
@@ -898,14 +903,22 @@ WARNING: "most_common_elems" must be specified when "most_common_elem_freqs" is
f
(1 row)
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'tags';
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------+------------------+------------------------
+ stats_import | test | tags | f | 0.35 | 0 | 0 | | | | | | | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
+(1 row)
+
-- ok: mcelem
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
'most_common_elems', '{one,three}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
);
@@ -922,18 +935,16 @@ AND inherited = false
AND attname = 'tags';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------+------------------+------------------------
- stats_import | test | tags | f | 0.5 | 2 | -0.1 | | | | | {one,three} | {0.3,0.2,0.2,0.3,0} | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
+ stats_import | test | tags | f | 0.35 | 0 | 0 | | | | | {one,three} | {0.3,0.2,0.2,0.3,0} | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
(1 row)
--- warn: scalars can't have elem_count_histogram
+-- warn: scalars can't have elem_count_histogram, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
- 'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
+ 'null_frac', 0.36::real,
+ 'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[]
);
WARNING: unable to determine element type of attribute "id"
DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.
@@ -950,43 +961,7 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.5 | 2 | -0.1 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
-(1 row)
-
--- warn: too many stat kinds
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
- 'most_common_vals', '{"[2,3)","[1,3)","[3,9)"}'::text,
- 'most_common_freqs', '{0.3,0.25,0.05}'::real[],
- 'histogram_bounds', '{"[1,2)","[2,3)","[3,4)","[4,)"}'::text,
- 'correlation', 1.1::real,
- 'most_common_elems', '{3,1}'::text,
- 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[],
- 'range_empty_frac', -0.5::real,
- 'range_length_histogram', '{399,499,Infinity}'::text,
- 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text);
-WARNING: unable to determine element type of attribute "arange"
-DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.
- pg_restore_attribute_stats
-----------------------------
- f
-(1 row)
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'arange';
- schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
---------------+-----------+---------+-----------+-----------+-----------+------------+---------------------------+-------------------+----------------------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+--------------------------------------
- stats_import | test | arange | f | 0.5 | 2 | -0.1 | {"[2,3)","[1,3)","[3,9)"} | {0.3,0.25,0.05} | {"[1,2)","[2,3)","[3,4)","[4,)"} | 1.1 | | | | {399,499,Infinity} | -0.5 | {"[-1,1)","[0,4)","[1,4)","[1,100)"}
+ stats_import | test | id | f | 0.36 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
(1 row)
--
@@ -1006,20 +981,6 @@ SELECT 3, 'tre', (3, 3.3, 'TRE', '2003-03-03', NULL)::stats_import.complex_type,
UNION ALL
SELECT 4, 'four', NULL, int4range(0,100), NULL;
CREATE INDEX is_odd ON stats_import.test(((comp).a % 2 = 1));
--- restoring stats on index
-SELECT * FROM pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.is_odd'::regclass,
- 'version', '180000'::integer,
- 'relpages', '11'::integer,
- 'reltuples', '10000'::real,
- 'relallvisible', '0'::integer,
- 'relallfrozen', '0'::integer
-);
- pg_restore_relation_stats
----------------------------
- t
-(1 row)
-
-- Generate statistics on table with data
ANALYZE stats_import.test;
CREATE TABLE stats_import.test_clone ( LIKE stats_import.test )
@@ -1197,7 +1158,18 @@ WHERE s.starelid = 'stats_import.is_odd'::regclass;
---------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+-----------
(0 rows)
--- ok
+-- attribute stats exist before a clear, but not after
+SELECT COUNT(*)
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'arange';
+ count
+-------
+ 1
+(1 row)
+
SELECT pg_catalog.pg_clear_attribute_stats(
relation => 'stats_import.test'::regclass,
attname => 'arange'::name,
@@ -1207,160 +1179,17 @@ SELECT pg_catalog.pg_clear_attribute_stats(
(1 row)
---
--- Negative tests
---
---- error: relation is wrong type
-SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid,
- 'relpages', 17::integer,
- 'reltuples', 400.0::real,
- 'relallvisible', 4::integer,
- 'relallfrozen', 3::integer);
-WARNING: argument "relation" has type "oid", expected type "regclass"
-ERROR: "relation" cannot be NULL
---- error: relation not found
-SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::regclass,
- 'relpages', 17::integer,
- 'reltuples', 400.0::real,
- 'relallvisible', 4::integer,
- 'relallfrozen', 3::integer);
-ERROR: could not open relation with OID 0
--- warn and error: unrecognized argument name
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 'relpages', '17'::integer,
- 'reltuples', 400::real,
- 'nope', 4::integer);
-WARNING: unrecognized argument name: "nope"
-ERROR: could not open relation with OID 0
--- error: argument name is NULL
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- NULL, '17'::integer,
- 'reltuples', 400::real,
- 'relallvisible', 4::integer,
- 'relallfrozen', 3::integer);
-ERROR: name at variadic position 5 is NULL
--- error: argument name is an integer
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 17, '17'::integer,
- 'reltuples', 400::real,
- 'relallvisible', 4::integer,
- 'relallfrozen', 3::integer);
-ERROR: name at variadic position 5 has type "integer", expected type "text"
--- error: odd number of variadic arguments cannot be pairs
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 'relpages', '17'::integer,
- 'reltuples', 400::real,
- 'relallfrozen', 3::integer,
- 'relallvisible');
-ERROR: variadic arguments must be name/value pairs
-HINT: Provide an even number of variadic arguments that can be divided into pairs.
--- error: object doesn't exist
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 'relpages', '17'::integer,
- 'reltuples', 400::real,
- 'relallvisible', 4::integer,
- 'relallfrozen', 3::integer);
-ERROR: could not open relation with OID 0
--- error: object does not exist
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', '0'::oid::regclass,
- 'attname', 'id'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-ERROR: could not open relation with OID 0
--- error: relation null
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', NULL::oid,
- 'attname', 'id'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-ERROR: "relation" cannot be NULL
--- error: missing attname
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', NULL::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-ERROR: must specify either attname or attnum
--- error: both attname and attnum
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
- 'attnum', 1::smallint,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-ERROR: cannot specify both attname and attnum
--- error: attname doesn't exist
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'nope'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-ERROR: column "nope" of relation "test" does not exist
--- error: attribute is system column
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'xmin'::name,
- 'inherited', false::boolean,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-ERROR: cannot modify statistics on system column "xmin"
--- error: inherited null
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
- 'inherited', NULL::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-ERROR: "inherited" cannot be NULL
--- error: relation not found
-SELECT pg_catalog.pg_clear_relation_stats(
- relation => 'stats_import.nope'::regclass);
-ERROR: relation "stats_import.nope" does not exist
-LINE 2: relation => 'stats_import.nope'::regclass);
- ^
--- error: attribute is system column
-SELECT pg_catalog.pg_clear_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'ctid'::name,
- inherited => false::boolean);
-ERROR: cannot clear statistics on system column "ctid"
--- error: attname doesn't exist
-SELECT pg_catalog.pg_clear_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'nope'::name,
- inherited => false::boolean);
-ERROR: column "nope" of relation "test" does not exist
+SELECT COUNT(*)
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'arange';
+ count
+-------
+ 0
+(1 row)
+
DROP SCHEMA stats_import CASCADE;
NOTICE: drop cascades to 6 other objects
DETAIL: drop cascades to type stats_import.complex_type
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index febda3d18d9..8d04ff4f378 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -17,15 +17,43 @@ CREATE TABLE stats_import.test(
CREATE INDEX test_i ON stats_import.test(id);
+--
+-- relstats tests
+--
+
+--- error: relation is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 0::oid,
+ 'relpages', 17::integer);
+
+-- error: relation not found
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 0::oid::regclass,
+ 'relpages', 17::integer);
+
+-- error: odd number of variadic arguments cannot be pairs
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'relallvisible');
+
+-- error: argument name is NULL
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ NULL, '17'::integer);
+
+-- error: argument name is not a text type
+SELECT pg_restore_relation_stats(
+ 'relation', '0'::oid::regclass,
+ 17, '17'::integer);
+
-- starting stats
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
+WHERE oid = 'stats_import.test_i'::regclass;
-BEGIN;
-- regular indexes have special case locking rules
-SELECT
- pg_catalog.pg_restore_relation_stats(
+BEGIN;
+SELECT pg_catalog.pg_restore_relation_stats(
'relation', 'stats_import.test_i'::regclass,
'relpages', 18::integer);
@@ -39,20 +67,6 @@ WHERE relation = 'stats_import.test_i'::regclass AND
COMMIT;
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.test_i'::regclass,
- 'relpages', 19::integer );
-
--- clear
-SELECT
- pg_catalog.pg_clear_relation_stats(
- 'stats_import.test'::regclass);
-
-SELECT relpages, reltuples, relallvisible, relallfrozen
-FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
-
-- relpages may be -1 for partitioned tables
CREATE TABLE stats_import.part_parent ( i integer ) PARTITION BY RANGE(i);
CREATE TABLE stats_import.part_child_1
@@ -68,18 +82,6 @@ SELECT relpages
FROM pg_class
WHERE oid = 'stats_import.part_parent'::regclass;
--- although partitioned tables have no storage, setting relpages to a
--- positive value is still allowed
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent_i'::regclass,
- 'relpages', 2::integer);
-
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent'::regclass,
- 'relpages', 2::integer);
-
--
-- Partitioned indexes aren't analyzed but it is possible to set
-- stats. The locking rules are different from normal indexes due to
@@ -88,8 +90,7 @@ SELECT
--
BEGIN;
-SELECT
- pg_catalog.pg_restore_relation_stats(
+SELECT pg_catalog.pg_restore_relation_stats(
'relation', 'stats_import.part_parent_i'::regclass,
'relpages', 2::integer);
@@ -103,22 +104,15 @@ WHERE relation = 'stats_import.part_parent_i'::regclass AND
COMMIT;
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent_i'::regclass,
- 'relpages', 2::integer);
+SELECT relpages
+FROM pg_class
+WHERE oid = 'stats_import.part_parent_i'::regclass;
--- nothing stops us from setting it to -1
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent'::regclass,
- 'relpages', -1::integer);
-
--- ok: set all stats
+-- ok: set all relstats, with version, no bounds checking
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
'version', 150000::integer,
- 'relpages', '17'::integer,
+ 'relpages', '-17'::integer,
'reltuples', 400::real,
'relallvisible', 4::integer,
'relallfrozen', 2::integer);
@@ -127,30 +121,27 @@ SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
--- ok: just relpages
+-- ok: set just relpages, rest stay same
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
- 'version', 150000::integer,
'relpages', '16'::integer);
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
--- ok: just reltuples
+-- ok: set just reltuples, rest stay same
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
- 'version', 150000::integer,
'reltuples', '500'::real);
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
--- ok: just relallvisible
+-- ok: set just relallvisible, rest stay same
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
- 'version', 150000::integer,
'relallvisible', 5::integer);
SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -167,10 +158,9 @@ SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
--- warn: bad relpages type
+-- warn: bad relpages type, rest updated
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
- 'version', 150000::integer,
'relpages', 'nope'::text,
'reltuples', 400.0::real,
'relallvisible', 4::integer,
@@ -180,17 +170,104 @@ SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
+-- unrecognized argument name, rest ok
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'relpages', '171'::integer,
+ 'nope', 10::integer);
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+
+-- ok: clear stats
+SELECT pg_catalog.pg_clear_relation_stats(
+ relation => 'stats_import.test'::regclass);
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+
-- invalid relkinds for statistics
CREATE SEQUENCE stats_import.testseq;
-CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
-SELECT
- pg_catalog.pg_clear_relation_stats(
+
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.testseq'::regclass);
+
+SELECT pg_catalog.pg_clear_relation_stats(
'stats_import.testseq'::regclass);
-SELECT
- pg_catalog.pg_clear_relation_stats(
+
+CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
+
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.testview'::regclass);
+
+SELECT pg_catalog.pg_clear_relation_stats(
'stats_import.testview'::regclass);
--- ok: no stakinds
+--
+-- attribute stats
+--
+
+-- error: object does not exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', '0'::oid::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: relation null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', NULL::oid::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: NULL attname
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', NULL::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: attname doesn't exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'nope'::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+
+-- error: both attname and attnum
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'attnum', 1::smallint,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: neither attname nor attnum
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: attribute is system column
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'xmin'::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: inherited null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', NULL::boolean,
+ 'null_frac', 0.1::real);
+
+-- ok: just the fixed values, with version, no stakinds
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
@@ -207,15 +284,16 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- ok: restore by attnum
+--
+-- ok: restore by attnum, we normally reserve this for
+-- indexes, but there is no reason it shouldn't work
+-- for any stat-having relation.
+--
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attnum', 1::smallint,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.4::real,
- 'avg_width', 5::integer,
- 'n_distinct', 0.6::real);
+ 'null_frac', 0.4::real);
SELECT *
FROM pg_stats
@@ -224,14 +302,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: unrecognized argument name
+-- warn: unrecognized argument name, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
'null_frac', 0.2::real,
- 'avg_width', NULL::integer,
'nope', 0.5::real);
SELECT *
@@ -241,15 +317,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: mcv / mcf null mismatch part 1
+-- warn: mcv / mcf null mismatch part 1, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.6::real,
- 'avg_width', 7::integer,
- 'n_distinct', -0.7::real,
+ 'null_frac', 0.21::real,
'most_common_freqs', '{0.1,0.2,0.3}'::real[]
);
@@ -260,15 +333,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: mcv / mcf null mismatch part 2
+-- warn: mcv / mcf null mismatch part 2, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.7::real,
- 'avg_width', 8::integer,
- 'n_distinct', -0.8::real,
+ 'null_frac', 0.21::real,
'most_common_vals', '{1,2,3}'::text
);
@@ -279,15 +349,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: mcv / mcf type mismatch
+-- warn: mcf type mismatch, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.8::real,
- 'avg_width', 9::integer,
- 'n_distinct', -0.9::real,
+ 'null_frac', 0.22::real,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.2,0.1}'::double precision[]
);
@@ -299,15 +366,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: mcv cast failure
+-- warn: mcv cast failure, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.9::real,
- 'avg_width', 10::integer,
- 'n_distinct', -0.4::real,
+ 'null_frac', 0.23::real,
'most_common_vals', '{2,four,3}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
);
@@ -324,10 +388,6 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 1::integer,
- 'n_distinct', -0.1::real,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
);
@@ -339,15 +399,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: NULL in histogram array
+-- warn: NULL in histogram array, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.2::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.2::real,
+ 'null_frac', 0.24::real,
'histogram_bounds', '{1,NULL,3,4}'::text
);
@@ -363,11 +420,8 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.3::real,
- 'avg_width', 3::integer,
- 'n_distinct', -0.3::real,
- 'histogram_bounds', '{1,2,3,4}'::text );
+ 'histogram_bounds', '{1,2,3,4}'::text
+ );
SELECT *
FROM pg_stats
@@ -376,16 +430,13 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: elem_count_histogram null element
+-- warn: elem_count_histogram null element, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.4::real,
- 'avg_width', 5::integer,
- 'n_distinct', -0.4::real,
- 'elem_count_histogram', '{1,1,NULL,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
+ 'null_frac', 0.25::real,
+ 'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[]
);
SELECT *
@@ -400,10 +451,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.5::real,
- 'avg_width', 6::integer,
- 'n_distinct', -0.55::real,
+ 'null_frac', 0.26::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
);
@@ -414,15 +462,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'tags';
--- range stats on a scalar type
+-- warn: range stats on a scalar type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.6::real,
- 'avg_width', 7::integer,
- 'n_distinct', -0.15::real,
+ 'null_frac', 0.27::real,
'range_empty_frac', 0.5::real,
'range_length_histogram', '{399,499,Infinity}'::text
);
@@ -434,15 +479,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: range_empty_frac range_length_hist null mismatch
+-- warn: range_empty_frac range_length_hist null mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.7::real,
- 'avg_width', 8::integer,
- 'n_distinct', -0.25::real,
+ 'null_frac', 0.28::real,
'range_length_histogram', '{399,499,Infinity}'::text
);
@@ -453,15 +495,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'arange';
--- warn: range_empty_frac range_length_hist null mismatch part 2
+-- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.8::real,
- 'avg_width', 9::integer,
- 'n_distinct', -0.35::real,
+ 'null_frac', 0.29::real,
'range_empty_frac', 0.5::real
);
@@ -477,10 +516,6 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.9::real,
- 'avg_width', 1::integer,
- 'n_distinct', -0.19::real,
'range_empty_frac', 0.5::real,
'range_length_histogram', '{399,499,Infinity}'::text
);
@@ -492,15 +527,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'arange';
--- warn: range bounds histogram on scalar
+-- warn: range bounds histogram on scalar, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.29::real,
+ 'null_frac', 0.31::real,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
);
@@ -516,10 +548,6 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.2::real,
- 'avg_width', 3::integer,
- 'n_distinct', -0.39::real,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
);
@@ -530,23 +558,14 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'arange';
--- warn: cannot set most_common_elems for range type
+-- warn: cannot set most_common_elems for range type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
- 'most_common_vals', '{"[2,3)","[1,2)","[3,4)"}'::text,
- 'most_common_freqs', '{0.3,0.25,0.05}'::real[],
- 'histogram_bounds', '{"[1,2)","[2,3)","[3,4)","[4,5)"}'::text,
- 'correlation', 1.1::real,
+ 'null_frac', 0.32::real,
'most_common_elems', '{3,1}'::text,
- 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[],
- 'range_empty_frac', -0.5::real,
- 'range_length_histogram', '{399,499,Infinity}'::text,
- 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
+ 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
);
SELECT *
@@ -556,14 +575,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'arange';
--- warn: scalars can't have mcelem
+-- warn: scalars can't have mcelem, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
+ 'null_frac', 0.33::real,
'most_common_elems', '{1,3}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
);
@@ -575,14 +592,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: mcelem / mcelem mismatch
+-- warn: mcelem / mcelem mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
+ 'null_frac', 0.34::real,
'most_common_elems', '{one,two}'::text
);
@@ -593,25 +608,27 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'tags';
--- warn: mcelem / mcelem null mismatch part 2
+-- warn: mcelem / mcelem null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
+ 'null_frac', 0.35::real,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
);
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'tags';
+
-- ok: mcelem
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
'most_common_elems', '{one,three}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
);
@@ -623,15 +640,13 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'tags';
--- warn: scalars can't have elem_count_histogram
+-- warn: scalars can't have elem_count_histogram, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
- 'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
+ 'null_frac', 0.36::real,
+ 'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[]
);
SELECT *
@@ -641,32 +656,6 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: too many stat kinds
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
- 'most_common_vals', '{"[2,3)","[1,3)","[3,9)"}'::text,
- 'most_common_freqs', '{0.3,0.25,0.05}'::real[],
- 'histogram_bounds', '{"[1,2)","[2,3)","[3,4)","[4,)"}'::text,
- 'correlation', 1.1::real,
- 'most_common_elems', '{3,1}'::text,
- 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[],
- 'range_empty_frac', -0.5::real,
- 'range_length_histogram', '{399,499,Infinity}'::text,
- 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text);
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'arange';
-
--
-- Test the ability to exactly copy data from one table to an identical table,
-- correctly reconstructing the stakind order as well as the staopN and
@@ -686,16 +675,6 @@ SELECT 4, 'four', NULL, int4range(0,100), NULL;
CREATE INDEX is_odd ON stats_import.test(((comp).a % 2 = 1));
--- restoring stats on index
-SELECT * FROM pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.is_odd'::regclass,
- 'version', '180000'::integer,
- 'relpages', '11'::integer,
- 'reltuples', '10000'::real,
- 'relallvisible', '0'::integer,
- 'relallfrozen', '0'::integer
-);
-
-- Generate statistics on table with data
ANALYZE stats_import.test;
@@ -848,160 +827,24 @@ FROM pg_statistic s
JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
WHERE s.starelid = 'stats_import.is_odd'::regclass;
--- ok
+-- attribute stats exist before a clear, but not after
+SELECT COUNT(*)
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'arange';
+
SELECT pg_catalog.pg_clear_attribute_stats(
relation => 'stats_import.test'::regclass,
attname => 'arange'::name,
inherited => false::boolean);
---
--- Negative tests
---
-
---- error: relation is wrong type
-SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid,
- 'relpages', 17::integer,
- 'reltuples', 400.0::real,
- 'relallvisible', 4::integer,
- 'relallfrozen', 3::integer);
-
---- error: relation not found
-SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::regclass,
- 'relpages', 17::integer,
- 'reltuples', 400.0::real,
- 'relallvisible', 4::integer,
- 'relallfrozen', 3::integer);
-
--- warn and error: unrecognized argument name
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 'relpages', '17'::integer,
- 'reltuples', 400::real,
- 'nope', 4::integer);
-
--- error: argument name is NULL
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- NULL, '17'::integer,
- 'reltuples', 400::real,
- 'relallvisible', 4::integer,
- 'relallfrozen', 3::integer);
-
--- error: argument name is an integer
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 17, '17'::integer,
- 'reltuples', 400::real,
- 'relallvisible', 4::integer,
- 'relallfrozen', 3::integer);
-
--- error: odd number of variadic arguments cannot be pairs
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 'relpages', '17'::integer,
- 'reltuples', 400::real,
- 'relallfrozen', 3::integer,
- 'relallvisible');
-
--- error: object doesn't exist
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 'relpages', '17'::integer,
- 'reltuples', 400::real,
- 'relallvisible', 4::integer,
- 'relallfrozen', 3::integer);
-
--- error: object does not exist
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', '0'::oid::regclass,
- 'attname', 'id'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-
--- error: relation null
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', NULL::oid,
- 'attname', 'id'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-
--- error: missing attname
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', NULL::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-
--- error: both attname and attnum
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
- 'attnum', 1::smallint,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-
--- error: attname doesn't exist
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'nope'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-
--- error: attribute is system column
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'xmin'::name,
- 'inherited', false::boolean,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-
--- error: inherited null
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
- 'inherited', NULL::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-
--- error: relation not found
-SELECT pg_catalog.pg_clear_relation_stats(
- relation => 'stats_import.nope'::regclass);
-
--- error: attribute is system column
-SELECT pg_catalog.pg_clear_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'ctid'::name,
- inherited => false::boolean);
-
--- error: attname doesn't exist
-SELECT pg_catalog.pg_clear_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'nope'::name,
- inherited => false::boolean);
+SELECT COUNT(*)
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'arange';
DROP SCHEMA stats_import CASCADE;
--
2.48.1
[text/x-patch] v5-0003-Split-relation-into-schemaname-and-relname.patch (62.3K, ../../CADkLM=ct-qo25r-GYO3xq8-f+ZDJCOxmC+FrwhcnFep53LnFkg@mail.gmail.com/5-v5-0003-Split-relation-into-schemaname-and-relname.patch)
download | inline diff:
From 87ab5a7a5061ba1b064bf81f115bb5c4204d0fc6 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Tue, 4 Mar 2025 22:16:52 -0500
Subject: [PATCH v5 3/3] Split relation into schemaname and relname.
In order to further reduce potential error-failures in restores and
upgrades, replace the numerous casts of fully qualified relation names
into their schema+relname text components.
Further remove the ::name casts on attname and change the expected
datatype to text.
---
src/include/catalog/pg_proc.dat | 8 +-
src/backend/statistics/attribute_stats.c | 98 +++++--
src/backend/statistics/relation_stats.c | 70 +++--
src/bin/pg_dump/pg_dump.c | 25 +-
src/bin/pg_dump/t/002_pg_dump.pl | 6 +-
src/test/regress/expected/stats_import.out | 302 +++++++++++++--------
src/test/regress/sql/stats_import.sql | 271 +++++++++++-------
doc/src/sgml/func.sgml | 41 +--
8 files changed, 537 insertions(+), 284 deletions(-)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index cd9422d0bac..25a63f279aa 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12424,8 +12424,8 @@
descr => 'clear statistics on relation',
proname => 'pg_clear_relation_stats', provolatile => 'v', proisstrict => 'f',
proparallel => 'u', prorettype => 'void',
- proargtypes => 'regclass',
- proargnames => '{relation}',
+ proargtypes => 'text text',
+ proargnames => '{schemaname,relname}',
prosrc => 'pg_clear_relation_stats' },
{ oid => '8461',
descr => 'restore statistics on attribute',
@@ -12440,8 +12440,8 @@
descr => 'clear statistics on attribute',
proname => 'pg_clear_attribute_stats', provolatile => 'v', proisstrict => 'f',
proparallel => 'u', prorettype => 'void',
- proargtypes => 'regclass name bool',
- proargnames => '{relation,attname,inherited}',
+ proargtypes => 'text text text bool',
+ proargnames => '{schemaname,relname,attname,inherited}',
prosrc => 'pg_clear_attribute_stats' },
# GiST stratnum implementations
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index 6bcbee0edba..65456a04ae5 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -19,6 +19,7 @@
#include "access/heapam.h"
#include "catalog/indexing.h"
+#include "catalog/namespace.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_operator.h"
#include "nodes/nodeFuncs.h"
@@ -36,7 +37,8 @@
enum attribute_stats_argnum
{
- ATTRELATION_ARG = 0,
+ ATTRELSCHEMA_ARG = 0,
+ ATTRELNAME_ARG,
ATTNAME_ARG,
ATTNUM_ARG,
INHERITED_ARG,
@@ -58,8 +60,9 @@ enum attribute_stats_argnum
static struct StatsArgInfo attarginfo[] =
{
- [ATTRELATION_ARG] = {"relation", REGCLASSOID},
- [ATTNAME_ARG] = {"attname", NAMEOID},
+ [ATTRELSCHEMA_ARG] = {"schemaname", TEXTOID},
+ [ATTRELNAME_ARG] = {"relname", TEXTOID},
+ [ATTNAME_ARG] = {"attname", TEXTOID},
[ATTNUM_ARG] = {"attnum", INT2OID},
[INHERITED_ARG] = {"inherited", BOOLOID},
[NULL_FRAC_ARG] = {"null_frac", FLOAT4OID},
@@ -80,7 +83,8 @@ static struct StatsArgInfo attarginfo[] =
enum clear_attribute_stats_argnum
{
- C_ATTRELATION_ARG = 0,
+ C_ATTRELSCHEMA_ARG = 0,
+ C_ATTRELNAME_ARG,
C_ATTNAME_ARG,
C_INHERITED_ARG,
C_NUM_ATTRIBUTE_STATS_ARGS
@@ -88,8 +92,9 @@ enum clear_attribute_stats_argnum
static struct StatsArgInfo cleararginfo[] =
{
- [C_ATTRELATION_ARG] = {"relation", REGCLASSOID},
- [C_ATTNAME_ARG] = {"attname", NAMEOID},
+ [C_ATTRELSCHEMA_ARG] = {"relation", TEXTOID},
+ [C_ATTRELNAME_ARG] = {"relation", TEXTOID},
+ [C_ATTNAME_ARG] = {"attname", TEXTOID},
[C_INHERITED_ARG] = {"inherited", BOOLOID},
[C_NUM_ATTRIBUTE_STATS_ARGS] = {0}
};
@@ -133,6 +138,9 @@ static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
static bool
attribute_statistics_update(FunctionCallInfo fcinfo)
{
+ char *nspname;
+ Oid nspoid;
+ char *relname;
Oid reloid;
char *attname;
AttrNumber attnum;
@@ -170,8 +178,28 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
bool result = true;
- stats_check_required_arg(fcinfo, attarginfo, ATTRELATION_ARG);
- reloid = PG_GETARG_OID(ATTRELATION_ARG);
+ stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG);
+ stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG);
+
+ nspname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELSCHEMA_ARG));
+ nspoid = get_namespace_oid(nspname, true);
+ if (nspoid == InvalidOid)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("Namespace \"%s\" not found.", nspname)));
+ return false;
+ }
+
+ relname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELNAME_ARG));
+ reloid = get_relname_relid(relname, nspoid);
+ if (reloid == InvalidOid)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
+ return false;
+ }
if (RecoveryInProgress())
ereport(ERROR,
@@ -185,21 +213,18 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
/* user can specify either attname or attnum, but not both */
if (!PG_ARGISNULL(ATTNAME_ARG))
{
- Name attnamename;
-
if (!PG_ARGISNULL(ATTNUM_ARG))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("cannot specify both attname and attnum")));
- attnamename = PG_GETARG_NAME(ATTNAME_ARG);
- attname = NameStr(*attnamename);
+ attname = TextDatumGetCString(PG_GETARG_DATUM(ATTNAME_ARG));
attnum = get_attnum(reloid, attname);
/* note that this test covers attisdropped cases too: */
if (attnum == InvalidAttrNumber)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
- attname, get_rel_name(reloid))));
+ errmsg("column \"%s\" of relation \"%s\".\"%s\" does not exist",
+ attname, nspname, relname)));
}
else if (!PG_ARGISNULL(ATTNUM_ARG))
{
@@ -210,8 +235,8 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
!SearchSysCacheExistsAttName(reloid, attname))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column %d of relation \"%s\" does not exist",
- attnum, get_rel_name(reloid))));
+ errmsg("column %d of relation \"%s\".\"%s\" does not exist",
+ attnum, nspname, relname)));
}
else
{
@@ -900,13 +925,38 @@ init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
Datum
pg_clear_attribute_stats(PG_FUNCTION_ARGS)
{
+ char *nspname;
+ Oid nspoid;
+ char *relname;
Oid reloid;
- Name attname;
+ char *attname;
AttrNumber attnum;
bool inherited;
- stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELATION_ARG);
- reloid = PG_GETARG_OID(C_ATTRELATION_ARG);
+ stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG);
+ stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG);
+ stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
+ stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
+
+ nspname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELSCHEMA_ARG));
+ nspoid = get_namespace_oid(nspname, true);
+ if (nspoid == InvalidOid)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("Namespace \"%s\" not found.", nspname)));
+ return false;
+ }
+
+ relname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELNAME_ARG));
+ reloid = get_relname_relid(relname, nspoid);
+ if (reloid == InvalidOid)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
+ return false;
+ }
if (RecoveryInProgress())
ereport(ERROR,
@@ -916,23 +966,21 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
stats_lock_check_privileges(reloid);
- stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
- attname = PG_GETARG_NAME(C_ATTNAME_ARG);
- attnum = get_attnum(reloid, NameStr(*attname));
+ attname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTNAME_ARG));
+ attnum = get_attnum(reloid, attname);
if (attnum < 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot clear statistics on system column \"%s\"",
- NameStr(*attname))));
+ attname)));
if (attnum == InvalidAttrNumber)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
- NameStr(*attname), get_rel_name(reloid))));
+ attname, get_rel_name(reloid))));
- stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
inherited = PG_GETARG_BOOL(C_INHERITED_ARG);
delete_pg_statistic(reloid, attnum, inherited);
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index 2c1cea3fc80..4c1e75a3c78 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -19,9 +19,12 @@
#include "access/heapam.h"
#include "catalog/indexing.h"
+#include "catalog/namespace.h"
#include "statistics/stat_utils.h"
+#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/fmgrprotos.h"
+#include "utils/lsyscache.h"
#include "utils/syscache.h"
@@ -32,7 +35,8 @@
enum relation_stats_argnum
{
- RELATION_ARG = 0,
+ RELSCHEMA_ARG = 0,
+ RELNAME_ARG,
RELPAGES_ARG,
RELTUPLES_ARG,
RELALLVISIBLE_ARG,
@@ -42,7 +46,8 @@ enum relation_stats_argnum
static struct StatsArgInfo relarginfo[] =
{
- [RELATION_ARG] = {"relation", REGCLASSOID},
+ [RELSCHEMA_ARG] = {"schemaname", TEXTOID},
+ [RELNAME_ARG] = {"relname", TEXTOID},
[RELPAGES_ARG] = {"relpages", INT4OID},
[RELTUPLES_ARG] = {"reltuples", FLOAT4OID},
[RELALLVISIBLE_ARG] = {"relallvisible", INT4OID},
@@ -59,6 +64,9 @@ static bool
relation_statistics_update(FunctionCallInfo fcinfo)
{
bool result = true;
+ char *nspname;
+ Oid nspoid;
+ char *relname;
Oid reloid;
Relation crel;
BlockNumber relpages = 0;
@@ -76,6 +84,37 @@ relation_statistics_update(FunctionCallInfo fcinfo)
bool nulls[4] = {0};
int nreplaces = 0;
+ stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG);
+ stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG);
+
+ nspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
+ nspoid = get_namespace_oid(nspname, true);
+ if (nspoid == InvalidOid)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("Namespace \"%s\" not found.", nspname)));
+ return false;
+ }
+
+ relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
+ reloid = get_relname_relid(relname, nspoid);
+ if (reloid == InvalidOid)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
+ return false;
+ }
+
+ if (RecoveryInProgress())
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("recovery is in progress"),
+ errhint("Statistics cannot be modified during recovery.")));
+
+ stats_lock_check_privileges(reloid);
+
if (!PG_ARGISNULL(RELPAGES_ARG))
{
relpages = PG_GETARG_UINT32(RELPAGES_ARG);
@@ -108,17 +147,6 @@ relation_statistics_update(FunctionCallInfo fcinfo)
update_relallfrozen = true;
}
- stats_check_required_arg(fcinfo, relarginfo, RELATION_ARG);
- reloid = PG_GETARG_OID(RELATION_ARG);
-
- if (RecoveryInProgress())
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("recovery is in progress"),
- errhint("Statistics cannot be modified during recovery.")));
-
- stats_lock_check_privileges(reloid);
-
/*
* Take RowExclusiveLock on pg_class, consistent with
* vac_update_relstats().
@@ -193,20 +221,22 @@ relation_statistics_update(FunctionCallInfo fcinfo)
Datum
pg_clear_relation_stats(PG_FUNCTION_ARGS)
{
- LOCAL_FCINFO(newfcinfo, 5);
+ LOCAL_FCINFO(newfcinfo, 6);
- InitFunctionCallInfoData(*newfcinfo, NULL, 5, InvalidOid, NULL, NULL);
+ InitFunctionCallInfoData(*newfcinfo, NULL, 6, InvalidOid, NULL, NULL);
- newfcinfo->args[0].value = PG_GETARG_OID(0);
+ newfcinfo->args[0].value = PG_GETARG_DATUM(0);
newfcinfo->args[0].isnull = PG_ARGISNULL(0);
- newfcinfo->args[1].value = UInt32GetDatum(0);
- newfcinfo->args[1].isnull = false;
- newfcinfo->args[2].value = Float4GetDatum(-1.0);
+ newfcinfo->args[1].value = PG_GETARG_DATUM(1);
+ newfcinfo->args[1].isnull = PG_ARGISNULL(1);
+ newfcinfo->args[2].value = UInt32GetDatum(0);
newfcinfo->args[2].isnull = false;
- newfcinfo->args[3].value = UInt32GetDatum(0);
+ newfcinfo->args[3].value = Float4GetDatum(-1.0);
newfcinfo->args[3].isnull = false;
newfcinfo->args[4].value = UInt32GetDatum(0);
newfcinfo->args[4].isnull = false;
+ newfcinfo->args[5].value = UInt32GetDatum(0);
+ newfcinfo->args[5].isnull = false;
relation_statistics_update(newfcinfo);
PG_RETURN_VOID();
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4f4ad2ee150..6cf2c7d1fe4 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -10492,7 +10492,6 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
PQExpBuffer out;
DumpId *deps = NULL;
int ndeps = 0;
- char *qualified_name;
char reltuples_str[FLOAT_SHORTEST_DECIMAL_LEN];
int i_attname;
int i_inherited;
@@ -10558,15 +10557,16 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
out = createPQExpBuffer();
- qualified_name = pg_strdup(fmtQualifiedDumpable(rsinfo));
-
/* restore relation stats */
appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
fout->remoteVersion);
- appendPQExpBufferStr(out, "\t'relation', ");
- appendStringLiteralAH(out, qualified_name, fout);
- appendPQExpBufferStr(out, "::regclass,\n");
+ appendPQExpBufferStr(out, "\t'schemaname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+ appendPQExpBufferStr(out, ",\n");
+ appendPQExpBufferStr(out, "\t'relname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+ appendPQExpBufferStr(out, ",\n");
appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
float_to_shortest_decimal_buf(rsinfo->reltuples, reltuples_str);
appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", reltuples_str);
@@ -10606,9 +10606,10 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
fout->remoteVersion);
- appendPQExpBufferStr(out, "\t'relation', ");
- appendStringLiteralAH(out, qualified_name, fout);
- appendPQExpBufferStr(out, "::regclass");
+ appendPQExpBufferStr(out, "\t'schemaname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+ appendPQExpBufferStr(out, ",\n\t'relname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.name, fout);
if (PQgetisnull(res, rownum, i_attname))
pg_fatal("attname cannot be NULL");
@@ -10620,7 +10621,10 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
* their attnames are not necessarily stable across dump/reload.
*/
if (rsinfo->nindAttNames == 0)
- appendNamedArgument(out, fout, "attname", "name", attname);
+ {
+ appendPQExpBuffer(out, ",\n\t'attname', ");
+ appendStringLiteralAH(out, attname, fout);
+ }
else
{
bool found = false;
@@ -10700,7 +10704,6 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
.deps = deps,
.nDeps = ndeps));
- free(qualified_name);
destroyPQExpBuffer(out);
destroyPQExpBuffer(query);
}
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c7bffc1b045..b037f239136 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -4725,14 +4725,16 @@ my %tests = (
regexp => qr/^
\QSELECT * FROM pg_catalog.pg_restore_relation_stats(\E\s+
'version',\s'\d+'::integer,\s+
- 'relation',\s'dump_test.dup_test_post_data_ix'::regclass,\s+
+ 'schemaname',\s'dump_test',\s+
+ 'relname',\s'dup_test_post_data_ix',\s+
'relpages',\s'\d+'::integer,\s+
'reltuples',\s'\d+'::real,\s+
'relallvisible',\s'\d+'::integer\s+
\);\s+
\QSELECT * FROM pg_catalog.pg_restore_attribute_stats(\E\s+
'version',\s'\d+'::integer,\s+
- 'relation',\s'dump_test.dup_test_post_data_ix'::regclass,\s+
+ 'schemaname',\s'dump_test',\s+
+ 'relname',\s'dup_test_post_data_ix',\s+
'attnum',\s'2'::smallint,\s+
'inherited',\s'f'::boolean,\s+
'null_frac',\s'0'::real,\s+
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index ebba14c6a1d..ca7fe39660e 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -16,33 +16,54 @@ CREATE INDEX test_i ON stats_import.test(id);
--
-- relstats tests
--
---- error: relation is wrong type
+-- error: schemaname missing
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid,
+ 'relname', 'test',
'relpages', 17::integer);
-WARNING: argument "relation" has type "oid", expected type "regclass"
-ERROR: "relation" cannot be NULL
+ERROR: "schemaname" cannot be NULL
+-- error: relname missing
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 'stats_import',
+ 'relpages', 17::integer);
+ERROR: "relname" cannot be NULL
+--- error: schemaname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 3.6::float,
+ 'relname', 'test',
+ 'relpages', 17::integer);
+WARNING: argument "schemaname" has type "double precision", expected type "text"
+ERROR: "schemaname" cannot be NULL
+--- error: relname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 0::oid,
+ 'relpages', 17::integer);
+WARNING: argument "relname" has type "oid", expected type "text"
+ERROR: "relname" cannot be NULL
-- error: relation not found
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'nope',
'relpages', 17::integer);
-ERROR: could not open relation with OID 0
+WARNING: Relation "stats_import"."nope" not found.
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
-- error: odd number of variadic arguments cannot be pairs
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relallvisible');
ERROR: variadic arguments must be name/value pairs
HINT: Provide an even number of variadic arguments that can be divided into pairs.
-- error: argument name is NULL
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
NULL, '17'::integer);
-ERROR: name at variadic position 3 is NULL
--- error: argument name is not a text type
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 17, '17'::integer);
-ERROR: name at variadic position 3 has type "integer", expected type "text"
+ERROR: name at variadic position 5 is NULL
-- starting stats
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
@@ -55,7 +76,8 @@ WHERE oid = 'stats_import.test_i'::regclass;
-- regular indexes have special case locking rules
BEGIN;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.test_i'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test_i',
'relpages', 18::integer);
pg_restore_relation_stats
---------------------------
@@ -103,7 +125,8 @@ WHERE oid = 'stats_import.part_parent'::regclass;
--
BEGIN;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent_i'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'part_parent_i',
'relpages', 2::integer);
pg_restore_relation_stats
---------------------------
@@ -137,7 +160,8 @@ WHERE oid = 'stats_import.part_parent_i'::regclass;
-- ok: set all relstats, with version, no bounds checking
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'version', 150000::integer,
'relpages', '-17'::integer,
'reltuples', 400::real,
@@ -158,7 +182,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just relpages, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', '16'::integer);
pg_restore_relation_stats
---------------------------
@@ -175,7 +200,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just reltuples, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'reltuples', '500'::real);
pg_restore_relation_stats
---------------------------
@@ -192,7 +218,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just relallvisible, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relallvisible', 5::integer);
pg_restore_relation_stats
---------------------------
@@ -209,7 +236,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: just relallfrozen
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'version', 150000::integer,
'relallfrozen', 3::integer);
pg_restore_relation_stats
@@ -227,7 +255,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- warn: bad relpages type, rest updated
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', 'nope'::text,
'reltuples', 400.0::real,
'relallvisible', 4::integer,
@@ -248,7 +277,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- unrecognized argument name, rest ok
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', '171'::integer,
'nope', 10::integer);
WARNING: unrecognized argument name: "nope"
@@ -266,8 +296,7 @@ WHERE oid = 'stats_import.test'::regclass;
(1 row)
-- ok: clear stats
-SELECT pg_catalog.pg_clear_relation_stats(
- relation => 'stats_import.test'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'test');
pg_clear_relation_stats
-------------------------
@@ -284,87 +313,123 @@ WHERE oid = 'stats_import.test'::regclass;
-- invalid relkinds for statistics
CREATE SEQUENCE stats_import.testseq;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.testseq'::regclass);
+ 'schemaname', 'stats_import',
+ 'relname', 'testseq');
ERROR: cannot modify statistics for relation "testseq"
DETAIL: This operation is not supported for sequences.
-SELECT pg_catalog.pg_clear_relation_stats(
- 'stats_import.testseq'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
ERROR: cannot modify statistics for relation "testseq"
DETAIL: This operation is not supported for sequences.
CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
-SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.testview'::regclass);
-ERROR: cannot modify statistics for relation "testview"
-DETAIL: This operation is not supported for views.
-SELECT pg_catalog.pg_clear_relation_stats(
- 'stats_import.testview'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
ERROR: cannot modify statistics for relation "testview"
DETAIL: This operation is not supported for views.
--
-- attribute stats
--
--- error: object does not exist
+-- error: schemaname missing
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', '0'::oid::regclass,
- 'attname', 'id'::name,
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: could not open relation with OID 0
--- error: relation null
+ERROR: "schemaname" cannot be NULL
+-- error: schema does not exist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', NULL::oid::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'nope',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: "relation" cannot be NULL
+WARNING: Namespace "nope" not found.
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- error: relname missing
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: "relname" cannot be NULL
+-- error: relname does not exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'nope',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+WARNING: Relation "stats_import"."nope" not found.
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- error: relname null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'relname', NULL,
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: "relname" cannot be NULL
-- error: NULL attname
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', NULL::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', NULL,
'inherited', false::boolean,
'null_frac', 0.1::real);
ERROR: must specify either attname or attnum
-- error: attname doesn't exist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'nope'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'nope',
'inherited', false::boolean,
'null_frac', 0.1::real,
'avg_width', 2::integer,
'n_distinct', 0.3::real);
-ERROR: column "nope" of relation "test" does not exist
+ERROR: column "nope" of relation "stats_import"."test" does not exist
-- error: both attname and attnum
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.1::real);
ERROR: cannot specify both attname and attnum
-- error: neither attname nor attnum
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'inherited', false::boolean,
'null_frac', 0.1::real);
ERROR: must specify either attname or attnum
-- error: attribute is system column
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'xmin'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'xmin',
'inherited', false::boolean,
'null_frac', 0.1::real);
ERROR: cannot modify statistics on system column "xmin"
-- error: inherited null
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', NULL::boolean,
'null_frac', 0.1::real);
ERROR: "inherited" cannot be NULL
-- ok: just the fixed values, with version, no stakinds
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'version', 150000::integer,
'null_frac', 0.2::real,
@@ -392,7 +457,8 @@ AND attname = 'id';
-- for any stat-having relation.
--
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.4::real);
@@ -414,8 +480,9 @@ AND attname = 'id';
-- warn: unrecognized argument name, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.2::real,
'nope', 0.5::real);
@@ -438,8 +505,9 @@ AND attname = 'id';
-- warn: mcv / mcf null mismatch part 1, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.21::real,
'most_common_freqs', '{0.1,0.2,0.3}'::real[]
@@ -463,8 +531,9 @@ AND attname = 'id';
-- warn: mcv / mcf null mismatch part 2, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.21::real,
'most_common_vals', '{1,2,3}'::text
@@ -488,8 +557,9 @@ AND attname = 'id';
-- warn: mcf type mismatch, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.22::real,
'most_common_vals', '{2,1,3}'::text,
@@ -515,8 +585,9 @@ AND attname = 'id';
-- warn: mcv cast failure, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.23::real,
'most_common_vals', '{2,four,3}'::text,
@@ -541,8 +612,9 @@ AND attname = 'id';
-- ok: mcv+mcf
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
@@ -565,8 +637,9 @@ AND attname = 'id';
-- warn: NULL in histogram array, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.24::real,
'histogram_bounds', '{1,NULL,3,4}'::text
@@ -590,8 +663,9 @@ AND attname = 'id';
-- ok: histogram_bounds
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'histogram_bounds', '{1,2,3,4}'::text
);
@@ -613,8 +687,9 @@ AND attname = 'id';
-- warn: elem_count_histogram null element, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.25::real,
'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[]
@@ -638,8 +713,9 @@ AND attname = 'tags';
-- ok: elem_count_histogram
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.26::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -662,8 +738,9 @@ AND attname = 'tags';
-- warn: range stats on a scalar type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.27::real,
'range_empty_frac', 0.5::real,
@@ -689,8 +766,9 @@ AND attname = 'id';
-- warn: range_empty_frac range_length_hist null mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.28::real,
'range_length_histogram', '{399,499,Infinity}'::text
@@ -714,8 +792,9 @@ AND attname = 'arange';
-- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.29::real,
'range_empty_frac', 0.5::real
@@ -739,8 +818,9 @@ AND attname = 'arange';
-- ok: range_empty_frac + range_length_hist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'range_empty_frac', 0.5::real,
'range_length_histogram', '{399,499,Infinity}'::text
@@ -763,8 +843,9 @@ AND attname = 'arange';
-- warn: range bounds histogram on scalar, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.31::real,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
@@ -789,8 +870,9 @@ AND attname = 'id';
-- ok: range_bounds_histogram
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
);
@@ -812,8 +894,9 @@ AND attname = 'arange';
-- warn: cannot set most_common_elems for range type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.32::real,
'most_common_elems', '{3,1}'::text,
@@ -839,8 +922,9 @@ AND attname = 'arange';
-- warn: scalars can't have mcelem, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.33::real,
'most_common_elems', '{1,3}'::text,
@@ -866,8 +950,9 @@ AND attname = 'id';
-- warn: mcelem / mcelem mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.34::real,
'most_common_elems', '{one,two}'::text
@@ -891,8 +976,9 @@ AND attname = 'tags';
-- warn: mcelem / mcelem null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.35::real,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
@@ -916,8 +1002,9 @@ AND attname = 'tags';
-- ok: mcelem
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'most_common_elems', '{one,three}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
@@ -940,8 +1027,9 @@ AND attname = 'tags';
-- warn: scalars can't have elem_count_histogram, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.36::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -993,8 +1081,9 @@ SELECT s.schemaname, s.tablename, s.attname, s.inherited, r.*
FROM pg_catalog.pg_stats AS s
CROSS JOIN LATERAL
pg_catalog.pg_restore_attribute_stats(
- 'relation', ('stats_import.' || s.tablename || '_clone')::regclass,
- 'attname', s.attname,
+ 'schemaname', 'stats_import',
+ 'relname', s.tablename::text || '_clone',
+ 'attname', s.attname::text,
'inherited', s.inherited,
'version', 150000,
'null_frac', s.null_frac,
@@ -1171,9 +1260,10 @@ AND attname = 'arange';
(1 row)
SELECT pg_catalog.pg_clear_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean);
+ schemaname => 'stats_import',
+ relname => 'test',
+ attname => 'arange',
+ inherited => false);
pg_clear_attribute_stats
--------------------------
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index 8d04ff4f378..3c330387243 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -21,31 +21,46 @@ CREATE INDEX test_i ON stats_import.test(id);
-- relstats tests
--
---- error: relation is wrong type
+-- error: schemaname missing
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid,
+ 'relname', 'test',
+ 'relpages', 17::integer);
+
+-- error: relname missing
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 'stats_import',
+ 'relpages', 17::integer);
+
+--- error: schemaname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 3.6::float,
+ 'relname', 'test',
+ 'relpages', 17::integer);
+
+--- error: relname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 0::oid,
'relpages', 17::integer);
-- error: relation not found
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'nope',
'relpages', 17::integer);
-- error: odd number of variadic arguments cannot be pairs
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relallvisible');
-- error: argument name is NULL
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
NULL, '17'::integer);
--- error: argument name is not a text type
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 17, '17'::integer);
-
-- starting stats
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
@@ -54,7 +69,8 @@ WHERE oid = 'stats_import.test_i'::regclass;
-- regular indexes have special case locking rules
BEGIN;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.test_i'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test_i',
'relpages', 18::integer);
SELECT mode FROM pg_locks
@@ -91,7 +107,8 @@ WHERE oid = 'stats_import.part_parent'::regclass;
BEGIN;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent_i'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'part_parent_i',
'relpages', 2::integer);
SELECT mode FROM pg_locks
@@ -110,7 +127,8 @@ WHERE oid = 'stats_import.part_parent_i'::regclass;
-- ok: set all relstats, with version, no bounds checking
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'version', 150000::integer,
'relpages', '-17'::integer,
'reltuples', 400::real,
@@ -123,7 +141,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just relpages, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', '16'::integer);
SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -132,7 +151,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just reltuples, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'reltuples', '500'::real);
SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -141,7 +161,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just relallvisible, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relallvisible', 5::integer);
SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -150,7 +171,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: just relallfrozen
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'version', 150000::integer,
'relallfrozen', 3::integer);
@@ -160,7 +182,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- warn: bad relpages type, rest updated
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', 'nope'::text,
'reltuples', 400.0::real,
'relallvisible', 4::integer,
@@ -172,7 +195,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- unrecognized argument name, rest ok
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', '171'::integer,
'nope', 10::integer);
@@ -181,8 +205,7 @@ FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
-- ok: clear stats
-SELECT pg_catalog.pg_clear_relation_stats(
- relation => 'stats_import.test'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'test');
SELECT relpages, reltuples, relallvisible
FROM pg_class
@@ -192,48 +215,70 @@ WHERE oid = 'stats_import.test'::regclass;
CREATE SEQUENCE stats_import.testseq;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.testseq'::regclass);
+ 'schemaname', 'stats_import',
+ 'relname', 'testseq');
-SELECT pg_catalog.pg_clear_relation_stats(
- 'stats_import.testseq'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
-SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.testview'::regclass);
-
-SELECT pg_catalog.pg_clear_relation_stats(
- 'stats_import.testview'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
--
-- attribute stats
--
--- error: object does not exist
+-- error: schemaname missing
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', '0'::oid::regclass,
- 'attname', 'id'::name,
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: relation null
+-- error: schema does not exist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', NULL::oid::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'nope',
+ 'relname', 'test',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: relname missing
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: relname does not exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'nope',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: relname null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'relname', NULL,
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: NULL attname
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', NULL::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', NULL,
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: attname doesn't exist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'nope'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'nope',
'inherited', false::boolean,
'null_frac', 0.1::real,
'avg_width', 2::integer,
@@ -241,36 +286,41 @@ SELECT pg_catalog.pg_restore_attribute_stats(
-- error: both attname and attnum
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: neither attname nor attnum
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: attribute is system column
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'xmin'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'xmin',
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: inherited null
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', NULL::boolean,
'null_frac', 0.1::real);
-- ok: just the fixed values, with version, no stakinds
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'version', 150000::integer,
'null_frac', 0.2::real,
@@ -290,7 +340,8 @@ AND attname = 'id';
-- for any stat-having relation.
--
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.4::real);
@@ -304,8 +355,9 @@ AND attname = 'id';
-- warn: unrecognized argument name, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.2::real,
'nope', 0.5::real);
@@ -319,8 +371,9 @@ AND attname = 'id';
-- warn: mcv / mcf null mismatch part 1, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.21::real,
'most_common_freqs', '{0.1,0.2,0.3}'::real[]
@@ -335,8 +388,9 @@ AND attname = 'id';
-- warn: mcv / mcf null mismatch part 2, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.21::real,
'most_common_vals', '{1,2,3}'::text
@@ -351,8 +405,9 @@ AND attname = 'id';
-- warn: mcf type mismatch, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.22::real,
'most_common_vals', '{2,1,3}'::text,
@@ -368,8 +423,9 @@ AND attname = 'id';
-- warn: mcv cast failure, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.23::real,
'most_common_vals', '{2,four,3}'::text,
@@ -385,8 +441,9 @@ AND attname = 'id';
-- ok: mcv+mcf
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
@@ -401,8 +458,9 @@ AND attname = 'id';
-- warn: NULL in histogram array, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.24::real,
'histogram_bounds', '{1,NULL,3,4}'::text
@@ -417,8 +475,9 @@ AND attname = 'id';
-- ok: histogram_bounds
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'histogram_bounds', '{1,2,3,4}'::text
);
@@ -432,8 +491,9 @@ AND attname = 'id';
-- warn: elem_count_histogram null element, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.25::real,
'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[]
@@ -448,8 +508,9 @@ AND attname = 'tags';
-- ok: elem_count_histogram
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.26::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -464,8 +525,9 @@ AND attname = 'tags';
-- warn: range stats on a scalar type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.27::real,
'range_empty_frac', 0.5::real,
@@ -481,8 +543,9 @@ AND attname = 'id';
-- warn: range_empty_frac range_length_hist null mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.28::real,
'range_length_histogram', '{399,499,Infinity}'::text
@@ -497,8 +560,9 @@ AND attname = 'arange';
-- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.29::real,
'range_empty_frac', 0.5::real
@@ -513,8 +577,9 @@ AND attname = 'arange';
-- ok: range_empty_frac + range_length_hist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'range_empty_frac', 0.5::real,
'range_length_histogram', '{399,499,Infinity}'::text
@@ -529,8 +594,9 @@ AND attname = 'arange';
-- warn: range bounds histogram on scalar, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.31::real,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
@@ -545,8 +611,9 @@ AND attname = 'id';
-- ok: range_bounds_histogram
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
);
@@ -560,8 +627,9 @@ AND attname = 'arange';
-- warn: cannot set most_common_elems for range type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.32::real,
'most_common_elems', '{3,1}'::text,
@@ -577,8 +645,9 @@ AND attname = 'arange';
-- warn: scalars can't have mcelem, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.33::real,
'most_common_elems', '{1,3}'::text,
@@ -594,8 +663,9 @@ AND attname = 'id';
-- warn: mcelem / mcelem mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.34::real,
'most_common_elems', '{one,two}'::text
@@ -610,8 +680,9 @@ AND attname = 'tags';
-- warn: mcelem / mcelem null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.35::real,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
@@ -626,8 +697,9 @@ AND attname = 'tags';
-- ok: mcelem
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'most_common_elems', '{one,three}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
@@ -642,8 +714,9 @@ AND attname = 'tags';
-- warn: scalars can't have elem_count_histogram, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.36::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -690,8 +763,9 @@ SELECT s.schemaname, s.tablename, s.attname, s.inherited, r.*
FROM pg_catalog.pg_stats AS s
CROSS JOIN LATERAL
pg_catalog.pg_restore_attribute_stats(
- 'relation', ('stats_import.' || s.tablename || '_clone')::regclass,
- 'attname', s.attname,
+ 'schemaname', 'stats_import',
+ 'relname', s.tablename::text || '_clone',
+ 'attname', s.attname::text,
'inherited', s.inherited,
'version', 150000,
'null_frac', s.null_frac,
@@ -836,9 +910,10 @@ AND inherited = false
AND attname = 'arange';
SELECT pg_catalog.pg_clear_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean);
+ schemaname => 'stats_import',
+ relname => 'test',
+ attname => 'arange',
+ inherited => false);
SELECT COUNT(*)
FROM pg_stats
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index bf31b1f3eee..fc5daa5c4b1 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -30324,22 +30324,24 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<structname>mytable</structname>:
<programlisting>
SELECT pg_restore_relation_stats(
- 'relation', 'mytable'::regclass,
- 'relpages', 173::integer,
- 'reltuples', 10000::real);
+ 'schemaname', 'myschema',
+ 'relname', 'mytable',
+ 'relpages', 173::integer,
+ 'reltuples', 10000::real);
</programlisting>
</para>
<para>
- The argument <literal>relation</literal> with a value of type
- <type>regclass</type> is required, and specifies the table. Other
- arguments are the names and values of statistics corresponding to
- certain columns in <link
+ The arguments <literal>schemaname</literal> with a value of type
+ <type>regclass</type> and <literal>relname</literal> are required,
+ and specifies the table. Other arguments are the names and values
+ of statistics corresponding to certain columns in <link
linkend="catalog-pg-class"><structname>pg_class</structname></link>.
The currently-supported relation statistics are
<literal>relpages</literal> with a value of type
<type>integer</type>, <literal>reltuples</literal> with a value of
- type <type>real</type>, and <literal>relallvisible</literal> with a
- value of type <type>integer</type>.
+ type <type>real</type>, <literal>relallvisible</literal> with a
+ value of type <type>integer</type>, and <literal>relallfrozen</literal>
+ with a value of type <type>integer</type>.
</para>
<para>
Additionally, this function accepts argument name
@@ -30367,7 +30369,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<indexterm>
<primary>pg_clear_relation_stats</primary>
</indexterm>
- <function>pg_clear_relation_stats</function> ( <parameter>relation</parameter> <type>regclass</type> )
+ <function>pg_clear_relation_stats</function> ( <parameter>schemaname</parameter> <type>text</type>, <parameter>relname</parameter> <type>text</type> )
<returnvalue>void</returnvalue>
</para>
<para>
@@ -30416,16 +30418,18 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<structname>mytable</structname>:
<programlisting>
SELECT pg_restore_attribute_stats(
- 'relation', 'mytable'::regclass,
- 'attname', 'col1'::name,
- 'inherited', false,
- 'avg_width', 125::integer,
- 'null_frac', 0.5::real);
+ 'schemaname', 'myschema',
+ 'relname', 'mytable',
+ 'attname', 'col1',
+ 'inherited', false,
+ 'avg_width', 125::integer,
+ 'null_frac', 0.5::real);
</programlisting>
</para>
<para>
- The required arguments are <literal>relation</literal> with a value
- of type <type>regclass</type>, which specifies the table; either
+ The required arguments are <literal>schemaname</literal> with a value
+ of type <type>regclass</type> and <literal>relname</literal> with a value
+ of type <type>text</type> which specify the table; either
<literal>attname</literal> with a value of type <type>name</type> or
<literal>attnum</literal> with a value of type <type>smallint</type>,
which specifies the column; and <literal>inherited</literal>, which
@@ -30461,7 +30465,8 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<primary>pg_clear_attribute_stats</primary>
</indexterm>
<function>pg_clear_attribute_stats</function> (
- <parameter>relation</parameter> <type>regclass</type>,
+ <parameter>schemaname</parameter> <type>text</type>,
+ <parameter>relname</parameter> <type>text</type>,
<parameter>attname</parameter> <type>name</type>,
<parameter>inherited</parameter> <type>boolean</type> )
<returnvalue>void</returnvalue>
--
2.48.1
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export: difference in statistics dumped
@ 2025-03-05 09:52 Ashutosh Bapat <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Ashutosh Bapat @ 2025-03-05 09:52 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; Alvaro Herrera <[email protected]>; jian he <[email protected]>
On Tue, Mar 4, 2025 at 11:45 PM Jeff Davis <[email protected]> wrote:
>
> On Tue, 2025-03-04 at 10:28 +0530, Ashutosh Bapat wrote:
>
> > >
> > > What solution are you suggesting? The only one that comes to mind
> > > is
> > > moving everything to SECTION_POST_DATA, which is possible, but it
> > > seems
> > > like a big design change to satisfy a small detail.
> >
> > We don't have to do that. We can manage it by making statistics of
> > index dependent upon the indexes on the table.
>
> The index relstats are already dependent on the index definition. If
> you have a simple database like:
>
> CREATE TABLE t(i INT);
> INSERT INTO t SELECT generate_series(1,10);
> CREATE INDEX t_idx ON t (i);
> ANALYZE;
>
> and then you dump it, you get:
>
>
> ------- SECTION_PRE_DATA -------
>
> CREATE TABLE public.t ...
>
> ------- SECTION_DATA -----------
>
> COPY public.t (i) FROM stdin;
> ...
> SELECT * FROM pg_catalog.pg_restore_relation_stats(
> 'version', '180000'::integer,
> 'relation', 'public.t'::regclass,
> 'relpages', '1'::integer,
> 'reltuples', '10'::real,
> 'relallvisible', '0'::integer
> );
> ...
>
> ------- SECTION_POST_DATA ------
>
> CREATE INDEX t_idx ON public.t USING btree (i);
> SELECT * FROM pg_catalog.pg_restore_relation_stats(
> 'version', '180000'::integer,
> 'relation', 'public.t_idx'::regclass,
> 'relpages', '2'::integer,
> 'reltuples', '10'::real,
> 'relallvisible', '0'::integer
> );
>
> (section annotations added for clarity)
>
> There is no problem with the index relstats, because they are already
> dependent on the index definition, and will be restored after the
> CREATE INDEX.
>
> The issue is when the table's restored relstats are different from what
> CREATE INDEX calculates, and then the CREATE INDEX overwrites the
> table's just-restored relation stats. The easiest way to see this is
> when restoring with --no-data, because CREATE INDEX will see an empty
> table and overwrite the table's restored relstats with zeros.
>
> If we view this issue as a dependency problem, then we'd have to make
> the *table relstats* depend on the *index definition*. If a table has
> any indexes, the relstats would need to go after the last index
> definition, effectively moving most relstats to SECTION_POST_DATA. The
> table's attribute stats would not be dependent on the index definition
> (because CREATE INDEX doesn't touch those), so they could stay in
> SECTION_DATA. And if the table doesn't have any indexes, then its
> relstats could also stay in SECTION_DATA. But then we have a mess, so
> we might as well just put all stats in SECTION_POST_DATA.
>
> But I don't see it as a dependency problem. When I look at the above
> SQL, it reads nicely to me and there's no obvious problem with it.
Thanks for explaining it. I
>
> If we want stats to be stable, we need some kind of mode to tell the
> server not to apply these kind of helpful optimizations, otherwise the
> issue will resurface in some form no matter what we do with pg_dump. We
> could invent a new mode, but autovacuum=off seems close enough to me.
Hmm. Updating the statistics without consuming more CPU is more
valuable when autovacuum is off it improves query plans with no extra
efforts. But if adding a new mode is some significant work, riding it
on top of autovacuum=off might ok. It's not documented either way, so
we could change that behaviour later if we find it troublesome.
--
Best Wishes,
Ashutosh Bapat
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 01:17 Andres Freund <[email protected]>
parent: Corey Huinker <[email protected]>
1 sibling, 2 replies; 199+ messages in thread
From: Andres Freund @ 2025-03-06 01:17 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Jeff Davis <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Hi,
On 2025-02-25 21:29:56 -0500, Corey Huinker wrote:
> On Tue, Feb 25, 2025 at 9:00 PM Jeff Davis <[email protected]> wrote:
>
> > On Mon, 2025-02-24 at 09:54 -0500, Andres Freund wrote:
> > > Have you compared performance of with/without stats after these
> > > optimizations?
> >
> > On unoptimized build with asserts enabled, dumping the regression
> > database:
> >
> > --no-statistics: 1.0s
> > master: 3.6s
> > v3j-0001: 3.0s
> > v3j-0002: 1.7s
> >
> > I plan to commit the patches soon.
I think these have all been committed, but I still see a larger performance
difference than what you observed. I just checked because I was noticing that
the tests are still considerably slower than they used to be.
An optimized pg_dump against an unoptimized assert-enabled server:
time ./src/bin/pg_dump/pg_dump --no-data --quote-all-identifiers --binary-upgrade --format=custom --no-sync regression > /dev/null
real 0m2.778s
user 0m0.167s
sys 0m0.057s
$ time ./src/bin/pg_dump/pg_dump --no-data --quote-all-identifiers --binary-upgrade --format=custom --no-sync --no-statistics regression > /dev/null
real 0m1.290s
user 0m0.097s
sys 0m0.026s
I thought it might be interesting to look at the set of queries arriving on
the server side, so I enabled pg-stat_statements and ran a dump:
regression[4041753][1]=# SELECT total_exec_time, total_plan_time, calls, plans, substring(query, 1, 30) FROM pg_stat_statements ORDER BY calls DESC LIMIT 15;
┌─────────────────────┬─────────────────────┬───────┬───────┬────────────────────────────────┐
│ total_exec_time │ total_plan_time │ calls │ plans │ substring │
├─────────────────────┼─────────────────────┼───────┼───────┼────────────────────────────────┤
│ 239.9672189999998 │ 12.5725 │ 981 │ 6 │ PREPARE getAttributeStats(pg_c │
│ 15.330405000000004 │ 1.836712 │ 282 │ 6 │ PREPARE dumpFunc(pg_catalog.oi │
│ 10.129114000000003 │ 0.39834800000000004 │ 199 │ 6 │ PREPARE dumpTableAttach(pg_cat │
│ 9.887489000000002 │ 0.9332620000000001 │ 84 │ 84 │ SELECT pg_get_partkeydef($1) │
│ 14.350725000000006 │ 0.691071 │ 60 │ 60 │ SELECT pg_catalog.pg_get_viewd │
│ 5.1174219999999995 │ 1.4604219999999999 │ 47 │ 6 │ PREPARE dumpAgg(pg_catalog.oid │
│ 0.24036199999999996 │ 0.545125 │ 41 │ 41 │ SELECT pg_catalog.format_type( │
│ 7.099635000000002 │ 0.47031800000000007 │ 39 │ 39 │ SELECT pg_catalog.pg_get_ruled │
│ 0.672752 │ 1.9036320000000002 │ 21 │ 6 │ PREPARE dumpDomain(pg_catalog. │
│ 1.6519299999999997 │ 3.1480380000000006 │ 21 │ 22 │ PREPARE getDomainConstraints(p │
│ 1.085548 │ 3.9647630000000005 │ 16 │ 6 │ PREPARE dumpCompositeType(pg_c │
│ 0.196259 │ 0.602291 │ 11 │ 6 │ PREPARE dumpOpr(pg_catalog.oid │
│ 0.265461 │ 4.428914 │ 10 │ 10 │ SELECT amprocnum, amproc::pg_c │
│ 0.39591399999999993 │ 9.345471 │ 10 │ 10 │ SELECT amopstrategy, amopopr:: │
│ 0.35752100000000003 │ 2.128437 │ 9 │ 9 │ SELECT nspname, tmplname FROM │
└─────────────────────┴─────────────────────┴───────┴───────┴────────────────────────────────┘
It looks a lot less bad with an optimized build:
regression[4042057][1]=# SELECT total_exec_time, total_plan_time, calls, plans, substring(query, 1, 30) FROM pg_stat_statements ORDER BY calls DESC LIMIT 15;
┌─────────────────────┬─────────────────────┬───────┬───────┬────────────────────────────────┐
│ total_exec_time │ total_plan_time │ calls │ plans │ substring │
├─────────────────────┼─────────────────────┼───────┼───────┼────────────────────────────────┤
│ 50.63764299999999 │ 2.503585 │ 981 │ 6 │ PREPARE getAttributeStats(pg_c │
│ 3.5241990000000007 │ 0.478541 │ 282 │ 6 │ PREPARE dumpFunc(pg_catalog.oi │
│ 2.3170359999999985 │ 0.126379 │ 199 │ 6 │ PREPARE dumpTableAttach(pg_cat │
│ 2.291331 │ 0.25360400000000005 │ 84 │ 84 │ SELECT pg_get_partkeydef($1) │
│ 4.678433000000003 │ 0.202578 │ 60 │ 60 │ SELECT pg_catalog.pg_get_viewd │
│ 1.1288440000000004 │ 0.30976200000000004 │ 47 │ 6 │ PREPARE dumpAgg(pg_catalog.oid │
│ 0.06619 │ 0.16813600000000004 │ 41 │ 41 │ SELECT pg_catalog.format_type( │
│ 2.102865 │ 0.115169 │ 39 │ 39 │ SELECT pg_catalog.pg_get_ruled │
│ 0.16163 │ 0.439991 │ 21 │ 6 │ PREPARE dumpDomain(pg_catalog. │
│ 0.5335120000000001 │ 0.727573 │ 21 │ 22 │ PREPARE getDomainConstraints(p │
│ 0.28177 │ 0.894156 │ 16 │ 6 │ PREPARE dumpCompositeType(pg_c │
│ 0.038558 │ 0.140807 │ 11 │ 6 │ PREPARE dumpOpr(pg_catalog.oid │
│ 0.082078 │ 0.9654280000000001 │ 10 │ 10 │ SELECT amprocnum, amproc::pg_c │
│ 0.136964 │ 2.1140120000000002 │ 10 │ 10 │ SELECT amopstrategy, amopopr:: │
│ 0.11634699999999999 │ 0.48550499999999996 │ 9 │ 9 │ SELECT nspname, tmplname FROM │
└─────────────────────┴─────────────────────┴───────┴───────┴────────────────────────────────┘
(15 rows)
This isn't even *remotely* an adversarial case, there are lots of workloads
with folks have a handful of indexes on each table and many many tables.
Right now --statistics more than doubles the number of queries that pg_dump
issues. That's oviously noticeable locally, but it's going to be really
noticeable when dumping across the network.
I think we need to do more to lessen the impact. Even leaving regression test
performance aside, the time increase for the default pg_dump invocation will
be painful for folks, particularly due to this being enabled by default.
One fairly easy win would be to stop issuing getAttributeStats() for
non-expression indexes. In most cases that'll already drastically cut down on
the extra queries.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 01:30 Corey Huinker <[email protected]>
parent: Andres Freund <[email protected]>
1 sibling, 0 replies; 199+ messages in thread
From: Corey Huinker @ 2025-03-06 01:30 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Jeff Davis <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
>
> One fairly easy win would be to stop issuing getAttributeStats() for
> non-expression indexes. In most cases that'll already drastically cut down
> on
> the extra queries.
That does seem like an easy win, especially since we're already using
indexprs for some filters. I am concerned that, down the road, if we ever
start storing non-expression stats for, say, partial indexes, we would
overlook that a corresponding change needed to happen in pg_dump. If you
can think of any safeguards we can create for that, I'm listening.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 01:36 Nathan Bossart <[email protected]>
parent: Andres Freund <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Nathan Bossart @ 2025-03-06 01:36 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Corey Huinker <[email protected]>; Jeff Davis <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Wed, Mar 05, 2025 at 08:17:53PM -0500, Andres Freund wrote:
> Right now --statistics more than doubles the number of queries that pg_dump
> issues. That's oviously noticeable locally, but it's going to be really
> noticeable when dumping across the network.
>
> I think we need to do more to lessen the impact. Even leaving regression test
> performance aside, the time increase for the default pg_dump invocation will
> be painful for folks, particularly due to this being enabled by default.
>
> One fairly easy win would be to stop issuing getAttributeStats() for
> non-expression indexes. In most cases that'll already drastically cut down on
> the extra queries.
Apologies if this has already been considered upthread, but would it be
possible to use one query to gather all the required information into a
sorted table? At a glance, it looks to me like it might be feasible. I
had a lot of luck with reducing the number per-object queries with that
approach recently (e.g., commit 2329cad).
--
nathan
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 01:54 Corey Huinker <[email protected]>
parent: Nathan Bossart <[email protected]>
0 siblings, 2 replies; 199+ messages in thread
From: Corey Huinker @ 2025-03-06 01:54 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: Andres Freund <[email protected]>; Jeff Davis <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
>
> Apologies if this has already been considered upthread, but would it be
> possible to use one query to gather all the required information into a
> sorted table? At a glance, it looks to me like it might be feasible. I
> had a lot of luck with reducing the number per-object queries with that
> approach recently (e.g., commit 2329cad).
It's been considered and not ruled out, with a "let's see how the simple
thing works, first" approach. Considerations are:
* pg_stats is keyed on schemaname + tablename (which can also be indexes)
and we need to use that because of the security barrier
* Joining pg_class and pg_namespace to pg_stats was specifically singled
out as a thing to remove.
* The stats data is kinda heavy (most common value lists, most common
elements lists, esp for high stattargets), which would be a considerable
memory impact and some of those stats might not even be needed (example,
index stats for a table that is filtered out)
So it's not impossible, but it's trickier than just, say, tables or indexes.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 02:18 Andres Freund <[email protected]>
parent: Corey Huinker <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Andres Freund @ 2025-03-06 02:18 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Jeff Davis <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Hi,
On 2025-03-05 20:54:35 -0500, Corey Huinker wrote:
> It's been considered and not ruled out, with a "let's see how the simple
> thing works, first" approach. Considerations are:
>
> * pg_stats is keyed on schemaname + tablename (which can also be indexes)
> and we need to use that because of the security barrier
I don't think that has to be a big issue, you can just make the the query
query multiple tables at once using an = ANY(ARRAY[]) expression or such.
> * The stats data is kinda heavy (most common value lists, most common
> elements lists, esp for high stattargets), which would be a considerable
> memory impact and some of those stats might not even be needed (example,
> index stats for a table that is filtered out)
Doesn't the code currently have this problem already? Afaict the stats are
currently all stored in memory inside pg_dump.
$ for opt in '' --no-statistics; do echo "using option $opt"; for dbname in pgbench_part_100 pgbench_part_1000 pgbench_part_10000; do echo $dbname; /usr/bin/time -f 'Max RSS kB: %M' ./src/bin/pg_dump/pg_dump --no-data --quote-all-identifiers --no-sync --no-data $opt $dbname -Fp > /dev/null;done;done
using option
pgbench_part_100
Max RSS kB: 12780
pgbench_part_1000
Max RSS kB: 22700
pgbench_part_10000
Max RSS kB: 124224
using option --no-statistics
pgbench_part_100
Max RSS kB: 12648
pgbench_part_1000
Max RSS kB: 19124
pgbench_part_10000
Max RSS kB: 85068
I don't think the query itself would be a problem, a query querying all the
required stats should probably use PQsetSingleRowMode() or
PQsetChunkedRowsMode().
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 02:19 Nathan Bossart <[email protected]>
parent: Corey Huinker <[email protected]>
1 sibling, 0 replies; 199+ messages in thread
From: Nathan Bossart @ 2025-03-06 02:19 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Andres Freund <[email protected]>; Jeff Davis <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Wed, Mar 05, 2025 at 08:54:35PM -0500, Corey Huinker wrote:
> * The stats data is kinda heavy (most common value lists, most common
> elements lists, esp for high stattargets), which would be a considerable
> memory impact and some of those stats might not even be needed (example,
> index stats for a table that is filtered out)
Understood. Looking closer, I can see why that's a concern in this case.
You'd need 128 bytes just for the schema and table name.
--
nathan
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 03:00 Corey Huinker <[email protected]>
parent: Andres Freund <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Corey Huinker @ 2025-03-06 03:00 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Jeff Davis <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Wed, Mar 5, 2025 at 9:18 PM Andres Freund <[email protected]> wrote:
> Hi,
>
> On 2025-03-05 20:54:35 -0500, Corey Huinker wrote:
> > It's been considered and not ruled out, with a "let's see how the simple
> > thing works, first" approach. Considerations are:
> >
> > * pg_stats is keyed on schemaname + tablename (which can also be indexes)
> > and we need to use that because of the security barrier
>
> I don't think that has to be a big issue, you can just make the the query
> query multiple tables at once using an = ANY(ARRAY[]) expression or such.
>
I'm uncertain how we'd do that with (schemaname,tablename) pairs. Are you
suggesting we back the joins from pg_stats to pg_namespace and pg_class and
then filter by oids?
> > * The stats data is kinda heavy (most common value lists, most common
> > elements lists, esp for high stattargets), which would be a considerable
> > memory impact and some of those stats might not even be needed (example,
> > index stats for a table that is filtered out)
>
> Doesn't the code currently have this problem already? Afaict the stats are
> currently all stored in memory inside pg_dump.
>
Each call to getAttributeStats() fetches the pg_stats for one and only one
relation and then writes the SQL call to fout, then discards the result set
once all the attributes of the relation are done.
I don't think the query itself would be a problem, a query querying all the
> required stats should probably use PQsetSingleRowMode() or
> PQsetChunkedRowsMode().
That makes sense if we get the attribute stats from the result set in the
order that we need them, and I don't know how we could possibly do that.
We'd still need a table to bsearch() and that would be huge.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 03:41 Andres Freund <[email protected]>
parent: Corey Huinker <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Andres Freund @ 2025-03-06 03:41 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Jeff Davis <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Hi,
On 2025-03-05 22:00:42 -0500, Corey Huinker wrote:
> On Wed, Mar 5, 2025 at 9:18 PM Andres Freund <[email protected]> wrote:
> > On 2025-03-05 20:54:35 -0500, Corey Huinker wrote:
> > > It's been considered and not ruled out, with a "let's see how the simple
> > > thing works, first" approach. Considerations are:
> > >
> > > * pg_stats is keyed on schemaname + tablename (which can also be indexes)
> > > and we need to use that because of the security barrier
> >
> > I don't think that has to be a big issue, you can just make the the query
> > query multiple tables at once using an = ANY(ARRAY[]) expression or such.
> >
>
> I'm uncertain how we'd do that with (schemaname,tablename) pairs. Are you
> suggesting we back the joins from pg_stats to pg_namespace and pg_class and
> then filter by oids?
I was thinking of one query per schema or something like that. But yea, a
query to pg_namespace and pg_class wouldn't be a problem if we did it far
fewer times than before. Or you could put the list of catalogs / tables to
be queried into an unnest() with two arrays or such.
Not sure how good the query plan for that would be, but it may be worth
looking at.
> > > * The stats data is kinda heavy (most common value lists, most common
> > > elements lists, esp for high stattargets), which would be a considerable
> > > memory impact and some of those stats might not even be needed (example,
> > > index stats for a table that is filtered out)
> >
> > Doesn't the code currently have this problem already? Afaict the stats are
> > currently all stored in memory inside pg_dump.
> >
>
> Each call to getAttributeStats() fetches the pg_stats for one and only one
> relation and then writes the SQL call to fout, then discards the result set
> once all the attributes of the relation are done.
I don't think that's true. For one my example demonstrated that it increases
the peak memory usage substantially. That'd not be the case if the data was
just written out to stdout or such.
Looking at the code confirms that. The ArchiveEntry() in dumpRelationStats()
is never freed, afaict. And ArchiveEntry() strdups ->createStmt, which
contains the "SELECT pg_restore_attribute_stats(...)".
> I don't think the query itself would be a problem, a query querying all the
> > required stats should probably use PQsetSingleRowMode() or
> > PQsetChunkedRowsMode().
>
>
> That makes sense if we get the attribute stats from the result set in the
> order that we need them, and I don't know how we could possibly do that.
> We'd still need a table to bsearch() and that would be huge.
I'm not following - what would be the problem with a bsearch()? Compared to
the stats data an array to map from oid to an index in an array of stats data
data would be very small.
But with the unnest() idea from above it wouldn't even be needed, you could
use
SELECT ...
FROM unnest(schema_array, table_array) WITH ORDINALITY AS src(schemaname, tablename)
...
ORDER BY ordinality
or something along those lines.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 04:04 Corey Huinker <[email protected]>
parent: Andres Freund <[email protected]>
0 siblings, 3 replies; 199+ messages in thread
From: Corey Huinker @ 2025-03-06 04:04 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Jeff Davis <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
>
>
> > I'm uncertain how we'd do that with (schemaname,tablename) pairs. Are you
> > suggesting we back the joins from pg_stats to pg_namespace and pg_class
> and
> > then filter by oids?
>
> I was thinking of one query per schema or something like that. But yea, a
> query to pg_namespace and pg_class wouldn't be a problem if we did it far
> fewer times than before. Or you could put the list of catalogs / tables
> to
> be queried into an unnest() with two arrays or such.
>
> Not sure how good the query plan for that would be, but it may be worth
> looking at.
>
Ok, so we're willing to take the pg_class/pg_namespace join hit for one or
a handful of queries, good to know.
> > Each call to getAttributeStats() fetches the pg_stats for one and only
> one
> > relation and then writes the SQL call to fout, then discards the result
> set
> > once all the attributes of the relation are done.
>
> I don't think that's true. For one my example demonstrated that it
> increases
> the peak memory usage substantially. That'd not be the case if the data was
> just written out to stdout or such.
>
> Looking at the code confirms that. The ArchiveEntry() in
> dumpRelationStats()
> is never freed, afaict. And ArchiveEntry() strdups ->createStmt, which
> contains the "SELECT pg_restore_attribute_stats(...)".
>
Pardon my inexperience, but aren't the ArchiveEntry records needed right up
until the program's run? If there's value in freeing them, why isn't it
being done already? What other thing would consume this freed memory?
>
>
> > I don't think the query itself would be a problem, a query querying all
> the
> > > required stats should probably use PQsetSingleRowMode() or
> > > PQsetChunkedRowsMode().
> >
> >
> > That makes sense if we get the attribute stats from the result set in the
> > order that we need them, and I don't know how we could possibly do that.
> > We'd still need a table to bsearch() and that would be huge.
>
> I'm not following - what would be the problem with a bsearch()? Compared to
> the stats data an array to map from oid to an index in an array of stats
> data
> data would be very small.
>
If we can do oid bsearch lookups, then we might be in business, but even
then we have to maintain a data structure in memory of all pg_stats records
relevant to this dump, either in PGresult form, an intermediate data
structure like tblinfo/indxinfo, or in the resolved string of
pg_restore_attribute_stats() calls for that relation...which then get
strdup'd into the ArchiveEntry that we have to maintain anyway.
>
>
> But with the unnest() idea from above it wouldn't even be needed, you could
> use
>
> SELECT ...
> FROM unnest(schema_array, table_array) WITH ORDINALITY AS src(schemaname,
> tablename)
> ...
> ORDER BY ordinality
>
> or something along those lines.
>
This still seems like there is some ability to generate a batch of these
rows and then discard them, and then go to the next logical batch (perhaps
by schema, as you suggested earlier), and I don't know that we have that
freedom. Perhaps we would have that freedom if stats were the absolute last
thing loaded in a dump.
Anyway, here's a rebased set of the existing up-for-consideration patches,
plus the optimization of avoiding querying on non-expression indexes.
I should add that this set presently doesn't include a patch that reverts
the set locale and strtof() call in favor of storing reltuples as a string.
As far as I know that idea is still on the table.
Attachments:
[text/x-patch] v6-0001-refactor-_tocEntryRequired-for-STATISTICS-DATA.patch (2.1K, ../../CADkLM=f1n2_Vomq0gKab7xdxDHmJGgn=DE48P8fzQOp3Mrs1Qg@mail.gmail.com/3-v6-0001-refactor-_tocEntryRequired-for-STATISTICS-DATA.patch)
download | inline diff:
From 87ec2ad2f7dba7cce39a959ef02413be68caf4fb Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Tue, 25 Feb 2025 14:13:24 +0800
Subject: [PATCH v6 1/4] refactor _tocEntryRequired for STATISTICS DATA.
we don't dump STATISTICS DATA in --section=pre-data.
so in _tocEntryRequired, we evaulate "(strcmp(te->desc, "STATISTICS DATA") == 0)"
after ``switch (curSection)``.
---
src/bin/pg_dump/pg_backup_archiver.c | 21 +++++++++------------
1 file changed, 9 insertions(+), 12 deletions(-)
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 632077113a4..0fc4ba04ba4 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2932,14 +2932,6 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
strcmp(te->desc, "SEARCHPATH") == 0)
return REQ_SPECIAL;
- if (strcmp(te->desc, "STATISTICS DATA") == 0)
- {
- if (!ropt->dumpStatistics)
- return 0;
- else
- res = REQ_STATS;
- }
-
/*
* DATABASE and DATABASE PROPERTIES also have a special rule: they are
* restored in createDB mode, and not restored otherwise, independently of
@@ -2984,10 +2976,6 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
if (ropt->no_subscriptions && strcmp(te->desc, "SUBSCRIPTION") == 0)
return 0;
- /* If it's statistics and we don't want statistics, maybe ignore it */
- if (!ropt->dumpStatistics && strcmp(te->desc, "STATISTICS DATA") == 0)
- return 0;
-
/* Ignore it if section is not to be dumped/restored */
switch (curSection)
{
@@ -3008,6 +2996,15 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
return 0;
}
+ /* If it's statistics and we don't want statistics, ignore it */
+ if (strcmp(te->desc, "STATISTICS DATA") == 0)
+ {
+ if (!ropt->dumpStatistics)
+ return 0;
+ else
+ return REQ_STATS;
+ }
+
/* Ignore it if rejected by idWanted[] (cf. SortTocFromFile) */
if (ropt->idWanted && !ropt->idWanted[te->dumpId - 1])
return 0;
base-commit: 39de4f157d3ac0b889bb276c2487fe160578f967
--
2.48.1
[text/x-patch] v6-0002-Organize-and-deduplicate-statistics-import-tests.patch (92.7K, ../../CADkLM=f1n2_Vomq0gKab7xdxDHmJGgn=DE48P8fzQOp3Mrs1Qg@mail.gmail.com/4-v6-0002-Organize-and-deduplicate-statistics-import-tests.patch)
download | inline diff:
From 2b4bce20e5e9f1b1fc68057e649dacb91f6bb6c9 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Wed, 26 Feb 2025 21:02:44 -0500
Subject: [PATCH v6 2/4] Organize and deduplicate statistics import tests.
Many changes, refactorings, and rebasings have taken their toll on the
statistics import tests. Now that things appear more stable and the
pg_set_* functions are gone in favor of using pg_restore_* in all cases,
it's safe to remove duplicates, combine tests where possible, and make
the test descriptions a bit more descriptive and uniform.
Additionally, parameters that were not strictly needed to demonstrate
the purpose(s) of a test were removed to reduce clutter.
---
src/test/regress/expected/stats_import.out | 681 ++++++++-------------
src/test/regress/sql/stats_import.sql | 555 ++++++-----------
2 files changed, 454 insertions(+), 782 deletions(-)
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 4df287e547f..ebba14c6a1d 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -13,19 +13,48 @@ CREATE TABLE stats_import.test(
tags text[]
) WITH (autovacuum_enabled = false);
CREATE INDEX test_i ON stats_import.test(id);
+--
+-- relstats tests
+--
+--- error: relation is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 0::oid,
+ 'relpages', 17::integer);
+WARNING: argument "relation" has type "oid", expected type "regclass"
+ERROR: "relation" cannot be NULL
+-- error: relation not found
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 0::oid::regclass,
+ 'relpages', 17::integer);
+ERROR: could not open relation with OID 0
+-- error: odd number of variadic arguments cannot be pairs
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'relallvisible');
+ERROR: variadic arguments must be name/value pairs
+HINT: Provide an even number of variadic arguments that can be divided into pairs.
+-- error: argument name is NULL
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ NULL, '17'::integer);
+ERROR: name at variadic position 3 is NULL
+-- error: argument name is not a text type
+SELECT pg_restore_relation_stats(
+ 'relation', '0'::oid::regclass,
+ 17, '17'::integer);
+ERROR: name at variadic position 3 has type "integer", expected type "text"
-- starting stats
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
+WHERE oid = 'stats_import.test_i'::regclass;
relpages | reltuples | relallvisible | relallfrozen
----------+-----------+---------------+--------------
- 0 | -1 | 0 | 0
+ 1 | 0 | 0 | 0
(1 row)
-BEGIN;
-- regular indexes have special case locking rules
-SELECT
- pg_catalog.pg_restore_relation_stats(
+BEGIN;
+SELECT pg_catalog.pg_restore_relation_stats(
'relation', 'stats_import.test_i'::regclass,
'relpages', 18::integer);
pg_restore_relation_stats
@@ -50,32 +79,6 @@ WHERE relation = 'stats_import.test_i'::regclass AND
(1 row)
COMMIT;
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.test_i'::regclass,
- 'relpages', 19::integer );
- pg_restore_relation_stats
----------------------------
- t
-(1 row)
-
--- clear
-SELECT
- pg_catalog.pg_clear_relation_stats(
- 'stats_import.test'::regclass);
- pg_clear_relation_stats
--------------------------
-
-(1 row)
-
-SELECT relpages, reltuples, relallvisible, relallfrozen
-FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
- relpages | reltuples | relallvisible | relallfrozen
-----------+-----------+---------------+--------------
- 0 | -1 | 0 | 0
-(1 row)
-
-- relpages may be -1 for partitioned tables
CREATE TABLE stats_import.part_parent ( i integer ) PARTITION BY RANGE(i);
CREATE TABLE stats_import.part_child_1
@@ -92,26 +95,6 @@ WHERE oid = 'stats_import.part_parent'::regclass;
-1
(1 row)
--- although partitioned tables have no storage, setting relpages to a
--- positive value is still allowed
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent_i'::regclass,
- 'relpages', 2::integer);
- pg_restore_relation_stats
----------------------------
- t
-(1 row)
-
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent'::regclass,
- 'relpages', 2::integer);
- pg_restore_relation_stats
----------------------------
- t
-(1 row)
-
--
-- Partitioned indexes aren't analyzed but it is possible to set
-- stats. The locking rules are different from normal indexes due to
@@ -119,8 +102,7 @@ SELECT
-- partitioned index are locked in ShareUpdateExclusive mode.
--
BEGIN;
-SELECT
- pg_catalog.pg_restore_relation_stats(
+SELECT pg_catalog.pg_restore_relation_stats(
'relation', 'stats_import.part_parent_i'::regclass,
'relpages', 2::integer);
pg_restore_relation_stats
@@ -145,30 +127,19 @@ WHERE relation = 'stats_import.part_parent_i'::regclass AND
(1 row)
COMMIT;
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent_i'::regclass,
- 'relpages', 2::integer);
- pg_restore_relation_stats
----------------------------
- t
+SELECT relpages
+FROM pg_class
+WHERE oid = 'stats_import.part_parent_i'::regclass;
+ relpages
+----------
+ 2
(1 row)
--- nothing stops us from setting it to -1
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent'::regclass,
- 'relpages', -1::integer);
- pg_restore_relation_stats
----------------------------
- t
-(1 row)
-
--- ok: set all stats
+-- ok: set all relstats, with version, no bounds checking
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
'version', 150000::integer,
- 'relpages', '17'::integer,
+ 'relpages', '-17'::integer,
'reltuples', 400::real,
'relallvisible', 4::integer,
'relallfrozen', 2::integer);
@@ -182,13 +153,12 @@ FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
relpages | reltuples | relallvisible | relallfrozen
----------+-----------+---------------+--------------
- 17 | 400 | 4 | 2
+ -17 | 400 | 4 | 2
(1 row)
--- ok: just relpages
+-- ok: set just relpages, rest stay same
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
- 'version', 150000::integer,
'relpages', '16'::integer);
pg_restore_relation_stats
---------------------------
@@ -203,10 +173,9 @@ WHERE oid = 'stats_import.test'::regclass;
16 | 400 | 4 | 2
(1 row)
--- ok: just reltuples
+-- ok: set just reltuples, rest stay same
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
- 'version', 150000::integer,
'reltuples', '500'::real);
pg_restore_relation_stats
---------------------------
@@ -221,10 +190,9 @@ WHERE oid = 'stats_import.test'::regclass;
16 | 500 | 4 | 2
(1 row)
--- ok: just relallvisible
+-- ok: set just relallvisible, rest stay same
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
- 'version', 150000::integer,
'relallvisible', 5::integer);
pg_restore_relation_stats
---------------------------
@@ -257,10 +225,9 @@ WHERE oid = 'stats_import.test'::regclass;
16 | 500 | 5 | 3
(1 row)
--- warn: bad relpages type
+-- warn: bad relpages type, rest updated
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
- 'version', 150000::integer,
'relpages', 'nope'::text,
'reltuples', 400.0::real,
'relallvisible', 4::integer,
@@ -279,20 +246,122 @@ WHERE oid = 'stats_import.test'::regclass;
16 | 400 | 4 | 3
(1 row)
+-- unrecognized argument name, rest ok
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'relpages', '171'::integer,
+ 'nope', 10::integer);
+WARNING: unrecognized argument name: "nope"
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+ relpages | reltuples | relallvisible
+----------+-----------+---------------
+ 171 | 400 | 4
+(1 row)
+
+-- ok: clear stats
+SELECT pg_catalog.pg_clear_relation_stats(
+ relation => 'stats_import.test'::regclass);
+ pg_clear_relation_stats
+-------------------------
+
+(1 row)
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+ relpages | reltuples | relallvisible
+----------+-----------+---------------
+ 0 | -1 | 0
+(1 row)
+
-- invalid relkinds for statistics
CREATE SEQUENCE stats_import.testseq;
-CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
-SELECT
- pg_catalog.pg_clear_relation_stats(
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.testseq'::regclass);
+ERROR: cannot modify statistics for relation "testseq"
+DETAIL: This operation is not supported for sequences.
+SELECT pg_catalog.pg_clear_relation_stats(
'stats_import.testseq'::regclass);
ERROR: cannot modify statistics for relation "testseq"
DETAIL: This operation is not supported for sequences.
-SELECT
- pg_catalog.pg_clear_relation_stats(
+CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.testview'::regclass);
+ERROR: cannot modify statistics for relation "testview"
+DETAIL: This operation is not supported for views.
+SELECT pg_catalog.pg_clear_relation_stats(
'stats_import.testview'::regclass);
ERROR: cannot modify statistics for relation "testview"
DETAIL: This operation is not supported for views.
--- ok: no stakinds
+--
+-- attribute stats
+--
+-- error: object does not exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', '0'::oid::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: could not open relation with OID 0
+-- error: relation null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', NULL::oid::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: "relation" cannot be NULL
+-- error: NULL attname
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', NULL::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: must specify either attname or attnum
+-- error: attname doesn't exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'nope'::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+ERROR: column "nope" of relation "test" does not exist
+-- error: both attname and attnum
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'attnum', 1::smallint,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: cannot specify both attname and attnum
+-- error: neither attname nor attnum
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: must specify either attname or attnum
+-- error: attribute is system column
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'xmin'::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: cannot modify statistics on system column "xmin"
+-- error: inherited null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', NULL::boolean,
+ 'null_frac', 0.1::real);
+ERROR: "inherited" cannot be NULL
+-- ok: just the fixed values, with version, no stakinds
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
@@ -317,15 +386,16 @@ AND attname = 'id';
stats_import | test | id | f | 0.2 | 5 | 0.6 | | | | | | | | | |
(1 row)
--- ok: restore by attnum
+--
+-- ok: restore by attnum, we normally reserve this for
+-- indexes, but there is no reason it shouldn't work
+-- for any stat-having relation.
+--
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attnum', 1::smallint,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.4::real,
- 'avg_width', 5::integer,
- 'n_distinct', 0.6::real);
+ 'null_frac', 0.4::real);
pg_restore_attribute_stats
----------------------------
t
@@ -342,14 +412,12 @@ AND attname = 'id';
stats_import | test | id | f | 0.4 | 5 | 0.6 | | | | | | | | | |
(1 row)
--- warn: unrecognized argument name
+-- warn: unrecognized argument name, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
'null_frac', 0.2::real,
- 'avg_width', NULL::integer,
'nope', 0.5::real);
WARNING: unrecognized argument name: "nope"
pg_restore_attribute_stats
@@ -368,15 +436,12 @@ AND attname = 'id';
stats_import | test | id | f | 0.2 | 5 | 0.6 | | | | | | | | | |
(1 row)
--- warn: mcv / mcf null mismatch part 1
+-- warn: mcv / mcf null mismatch part 1, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.6::real,
- 'avg_width', 7::integer,
- 'n_distinct', -0.7::real,
+ 'null_frac', 0.21::real,
'most_common_freqs', '{0.1,0.2,0.3}'::real[]
);
WARNING: "most_common_vals" must be specified when "most_common_freqs" is specified
@@ -393,18 +458,15 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.6 | 7 | -0.7 | | | | | | | | | |
+ stats_import | test | id | f | 0.21 | 5 | 0.6 | | | | | | | | | |
(1 row)
--- warn: mcv / mcf null mismatch part 2
+-- warn: mcv / mcf null mismatch part 2, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.7::real,
- 'avg_width', 8::integer,
- 'n_distinct', -0.8::real,
+ 'null_frac', 0.21::real,
'most_common_vals', '{1,2,3}'::text
);
WARNING: "most_common_freqs" must be specified when "most_common_vals" is specified
@@ -421,18 +483,15 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.7 | 8 | -0.8 | | | | | | | | | |
+ stats_import | test | id | f | 0.21 | 5 | 0.6 | | | | | | | | | |
(1 row)
--- warn: mcv / mcf type mismatch
+-- warn: mcf type mismatch, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.8::real,
- 'avg_width', 9::integer,
- 'n_distinct', -0.9::real,
+ 'null_frac', 0.22::real,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.2,0.1}'::double precision[]
);
@@ -451,18 +510,15 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.8 | 9 | -0.9 | | | | | | | | | |
+ stats_import | test | id | f | 0.22 | 5 | 0.6 | | | | | | | | | |
(1 row)
--- warn: mcv cast failure
+-- warn: mcv cast failure, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.9::real,
- 'avg_width', 10::integer,
- 'n_distinct', -0.4::real,
+ 'null_frac', 0.23::real,
'most_common_vals', '{2,four,3}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
);
@@ -480,7 +536,7 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.9 | 10 | -0.4 | | | | | | | | | |
+ stats_import | test | id | f | 0.23 | 5 | 0.6 | | | | | | | | | |
(1 row)
-- ok: mcv+mcf
@@ -488,10 +544,6 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 1::integer,
- 'n_distinct', -0.1::real,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
);
@@ -508,18 +560,15 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.1 | 1 | -0.1 | {2,1,3} | {0.3,0.25,0.05} | | | | | | | |
+ stats_import | test | id | f | 0.23 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | | | | | | | |
(1 row)
--- warn: NULL in histogram array
+-- warn: NULL in histogram array, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.2::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.2::real,
+ 'null_frac', 0.24::real,
'histogram_bounds', '{1,NULL,3,4}'::text
);
WARNING: "histogram_bounds" array cannot contain NULL values
@@ -536,7 +585,7 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.2 | 2 | -0.2 | {2,1,3} | {0.3,0.25,0.05} | | | | | | | |
+ stats_import | test | id | f | 0.24 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | | | | | | | |
(1 row)
-- ok: histogram_bounds
@@ -544,11 +593,8 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.3::real,
- 'avg_width', 3::integer,
- 'n_distinct', -0.3::real,
- 'histogram_bounds', '{1,2,3,4}'::text );
+ 'histogram_bounds', '{1,2,3,4}'::text
+ );
pg_restore_attribute_stats
----------------------------
t
@@ -562,19 +608,16 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.3 | 3 | -0.3 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
+ stats_import | test | id | f | 0.24 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
(1 row)
--- warn: elem_count_histogram null element
+-- warn: elem_count_histogram null element, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.4::real,
- 'avg_width', 5::integer,
- 'n_distinct', -0.4::real,
- 'elem_count_histogram', '{1,1,NULL,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
+ 'null_frac', 0.25::real,
+ 'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[]
);
WARNING: "elem_count_histogram" array cannot contain NULL values
pg_restore_attribute_stats
@@ -590,7 +633,7 @@ AND inherited = false
AND attname = 'tags';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | tags | f | 0.4 | 5 | -0.4 | | | | | | | | | |
+ stats_import | test | tags | f | 0.25 | 0 | 0 | | | | | | | | | |
(1 row)
-- ok: elem_count_histogram
@@ -598,10 +641,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.5::real,
- 'avg_width', 6::integer,
- 'n_distinct', -0.55::real,
+ 'null_frac', 0.26::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
);
pg_restore_attribute_stats
@@ -617,18 +657,15 @@ AND inherited = false
AND attname = 'tags';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------+------------------+------------------------
- stats_import | test | tags | f | 0.5 | 6 | -0.55 | | | | | | | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
+ stats_import | test | tags | f | 0.26 | 0 | 0 | | | | | | | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
(1 row)
--- range stats on a scalar type
+-- warn: range stats on a scalar type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.6::real,
- 'avg_width', 7::integer,
- 'n_distinct', -0.15::real,
+ 'null_frac', 0.27::real,
'range_empty_frac', 0.5::real,
'range_length_histogram', '{399,499,Infinity}'::text
);
@@ -647,18 +684,15 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.6 | 7 | -0.15 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
+ stats_import | test | id | f | 0.27 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
(1 row)
--- warn: range_empty_frac range_length_hist null mismatch
+-- warn: range_empty_frac range_length_hist null mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.7::real,
- 'avg_width', 8::integer,
- 'n_distinct', -0.25::real,
+ 'null_frac', 0.28::real,
'range_length_histogram', '{399,499,Infinity}'::text
);
WARNING: "range_empty_frac" must be specified when "range_length_histogram" is specified
@@ -675,18 +709,15 @@ AND inherited = false
AND attname = 'arange';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | arange | f | 0.7 | 8 | -0.25 | | | | | | | | | |
+ stats_import | test | arange | f | 0.28 | 0 | 0 | | | | | | | | | |
(1 row)
--- warn: range_empty_frac range_length_hist null mismatch part 2
+-- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.8::real,
- 'avg_width', 9::integer,
- 'n_distinct', -0.35::real,
+ 'null_frac', 0.29::real,
'range_empty_frac', 0.5::real
);
WARNING: "range_length_histogram" must be specified when "range_empty_frac" is specified
@@ -703,7 +734,7 @@ AND inherited = false
AND attname = 'arange';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | arange | f | 0.8 | 9 | -0.35 | | | | | | | | | |
+ stats_import | test | arange | f | 0.29 | 0 | 0 | | | | | | | | | |
(1 row)
-- ok: range_empty_frac + range_length_hist
@@ -711,10 +742,6 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.9::real,
- 'avg_width', 1::integer,
- 'n_distinct', -0.19::real,
'range_empty_frac', 0.5::real,
'range_length_histogram', '{399,499,Infinity}'::text
);
@@ -731,18 +758,15 @@ AND inherited = false
AND attname = 'arange';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | arange | f | 0.9 | 1 | -0.19 | | | | | | | | {399,499,Infinity} | 0.5 |
+ stats_import | test | arange | f | 0.29 | 0 | 0 | | | | | | | | {399,499,Infinity} | 0.5 |
(1 row)
--- warn: range bounds histogram on scalar
+-- warn: range bounds histogram on scalar, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.29::real,
+ 'null_frac', 0.31::real,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
);
WARNING: attribute "id" is not a range type
@@ -760,7 +784,7 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.1 | 2 | -0.29 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
+ stats_import | test | id | f | 0.31 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
(1 row)
-- ok: range_bounds_histogram
@@ -768,10 +792,6 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.2::real,
- 'avg_width', 3::integer,
- 'n_distinct', -0.39::real,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
);
pg_restore_attribute_stats
@@ -787,26 +807,17 @@ AND inherited = false
AND attname = 'arange';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+--------------------------------------
- stats_import | test | arange | f | 0.2 | 3 | -0.39 | | | | | | | | {399,499,Infinity} | 0.5 | {"[-1,1)","[0,4)","[1,4)","[1,100)"}
+ stats_import | test | arange | f | 0.29 | 0 | 0 | | | | | | | | {399,499,Infinity} | 0.5 | {"[-1,1)","[0,4)","[1,4)","[1,100)"}
(1 row)
--- warn: cannot set most_common_elems for range type
+-- warn: cannot set most_common_elems for range type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
- 'most_common_vals', '{"[2,3)","[1,2)","[3,4)"}'::text,
- 'most_common_freqs', '{0.3,0.25,0.05}'::real[],
- 'histogram_bounds', '{"[1,2)","[2,3)","[3,4)","[4,5)"}'::text,
- 'correlation', 1.1::real,
+ 'null_frac', 0.32::real,
'most_common_elems', '{3,1}'::text,
- 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[],
- 'range_empty_frac', -0.5::real,
- 'range_length_histogram', '{399,499,Infinity}'::text,
- 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
+ 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
);
WARNING: unable to determine element type of attribute "arange"
DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.
@@ -821,19 +832,17 @@ WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'arange';
- schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
---------------+-----------+---------+-----------+-----------+-----------+------------+---------------------------+-------------------+-----------------------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+--------------------------------------
- stats_import | test | arange | f | 0.5 | 2 | -0.1 | {"[2,3)","[1,2)","[3,4)"} | {0.3,0.25,0.05} | {"[1,2)","[2,3)","[3,4)","[4,5)"} | 1.1 | | | | {399,499,Infinity} | -0.5 | {"[-1,1)","[0,4)","[1,4)","[1,100)"}
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+--------------------------------------
+ stats_import | test | arange | f | 0.32 | 0 | 0 | | | | | | | | {399,499,Infinity} | 0.5 | {"[-1,1)","[0,4)","[1,4)","[1,100)"}
(1 row)
--- warn: scalars can't have mcelem
+-- warn: scalars can't have mcelem, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
+ 'null_frac', 0.33::real,
'most_common_elems', '{1,3}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
);
@@ -852,17 +861,15 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.5 | 2 | -0.1 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
+ stats_import | test | id | f | 0.33 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
(1 row)
--- warn: mcelem / mcelem mismatch
+-- warn: mcelem / mcelem mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
+ 'null_frac', 0.34::real,
'most_common_elems', '{one,two}'::text
);
WARNING: "most_common_elem_freqs" must be specified when "most_common_elems" is specified
@@ -879,17 +886,15 @@ AND inherited = false
AND attname = 'tags';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------+------------------+------------------------
- stats_import | test | tags | f | 0.5 | 2 | -0.1 | | | | | | | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
+ stats_import | test | tags | f | 0.34 | 0 | 0 | | | | | | | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
(1 row)
--- warn: mcelem / mcelem null mismatch part 2
+-- warn: mcelem / mcelem null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
+ 'null_frac', 0.35::real,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
);
WARNING: "most_common_elems" must be specified when "most_common_elem_freqs" is specified
@@ -898,14 +903,22 @@ WARNING: "most_common_elems" must be specified when "most_common_elem_freqs" is
f
(1 row)
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'tags';
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------+------------------+------------------------
+ stats_import | test | tags | f | 0.35 | 0 | 0 | | | | | | | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
+(1 row)
+
-- ok: mcelem
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
'most_common_elems', '{one,three}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
);
@@ -922,18 +935,16 @@ AND inherited = false
AND attname = 'tags';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------+------------------+------------------------
- stats_import | test | tags | f | 0.5 | 2 | -0.1 | | | | | {one,three} | {0.3,0.2,0.2,0.3,0} | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
+ stats_import | test | tags | f | 0.35 | 0 | 0 | | | | | {one,three} | {0.3,0.2,0.2,0.3,0} | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
(1 row)
--- warn: scalars can't have elem_count_histogram
+-- warn: scalars can't have elem_count_histogram, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
- 'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
+ 'null_frac', 0.36::real,
+ 'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[]
);
WARNING: unable to determine element type of attribute "id"
DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.
@@ -950,43 +961,7 @@ AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
- stats_import | test | id | f | 0.5 | 2 | -0.1 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
-(1 row)
-
--- warn: too many stat kinds
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
- 'most_common_vals', '{"[2,3)","[1,3)","[3,9)"}'::text,
- 'most_common_freqs', '{0.3,0.25,0.05}'::real[],
- 'histogram_bounds', '{"[1,2)","[2,3)","[3,4)","[4,)"}'::text,
- 'correlation', 1.1::real,
- 'most_common_elems', '{3,1}'::text,
- 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[],
- 'range_empty_frac', -0.5::real,
- 'range_length_histogram', '{399,499,Infinity}'::text,
- 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text);
-WARNING: unable to determine element type of attribute "arange"
-DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.
- pg_restore_attribute_stats
-----------------------------
- f
-(1 row)
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'arange';
- schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
---------------+-----------+---------+-----------+-----------+-----------+------------+---------------------------+-------------------+----------------------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+--------------------------------------
- stats_import | test | arange | f | 0.5 | 2 | -0.1 | {"[2,3)","[1,3)","[3,9)"} | {0.3,0.25,0.05} | {"[1,2)","[2,3)","[3,4)","[4,)"} | 1.1 | | | | {399,499,Infinity} | -0.5 | {"[-1,1)","[0,4)","[1,4)","[1,100)"}
+ stats_import | test | id | f | 0.36 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
(1 row)
--
@@ -1006,20 +981,6 @@ SELECT 3, 'tre', (3, 3.3, 'TRE', '2003-03-03', NULL)::stats_import.complex_type,
UNION ALL
SELECT 4, 'four', NULL, int4range(0,100), NULL;
CREATE INDEX is_odd ON stats_import.test(((comp).a % 2 = 1));
--- restoring stats on index
-SELECT * FROM pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.is_odd'::regclass,
- 'version', '180000'::integer,
- 'relpages', '11'::integer,
- 'reltuples', '10000'::real,
- 'relallvisible', '0'::integer,
- 'relallfrozen', '0'::integer
-);
- pg_restore_relation_stats
----------------------------
- t
-(1 row)
-
-- Generate statistics on table with data
ANALYZE stats_import.test;
CREATE TABLE stats_import.test_clone ( LIKE stats_import.test )
@@ -1197,7 +1158,18 @@ WHERE s.starelid = 'stats_import.is_odd'::regclass;
---------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+-----------
(0 rows)
--- ok
+-- attribute stats exist before a clear, but not after
+SELECT COUNT(*)
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'arange';
+ count
+-------
+ 1
+(1 row)
+
SELECT pg_catalog.pg_clear_attribute_stats(
relation => 'stats_import.test'::regclass,
attname => 'arange'::name,
@@ -1207,160 +1179,17 @@ SELECT pg_catalog.pg_clear_attribute_stats(
(1 row)
---
--- Negative tests
---
---- error: relation is wrong type
-SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid,
- 'relpages', 17::integer,
- 'reltuples', 400.0::real,
- 'relallvisible', 4::integer,
- 'relallfrozen', 3::integer);
-WARNING: argument "relation" has type "oid", expected type "regclass"
-ERROR: "relation" cannot be NULL
---- error: relation not found
-SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::regclass,
- 'relpages', 17::integer,
- 'reltuples', 400.0::real,
- 'relallvisible', 4::integer,
- 'relallfrozen', 3::integer);
-ERROR: could not open relation with OID 0
--- warn and error: unrecognized argument name
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 'relpages', '17'::integer,
- 'reltuples', 400::real,
- 'nope', 4::integer);
-WARNING: unrecognized argument name: "nope"
-ERROR: could not open relation with OID 0
--- error: argument name is NULL
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- NULL, '17'::integer,
- 'reltuples', 400::real,
- 'relallvisible', 4::integer,
- 'relallfrozen', 3::integer);
-ERROR: name at variadic position 5 is NULL
--- error: argument name is an integer
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 17, '17'::integer,
- 'reltuples', 400::real,
- 'relallvisible', 4::integer,
- 'relallfrozen', 3::integer);
-ERROR: name at variadic position 5 has type "integer", expected type "text"
--- error: odd number of variadic arguments cannot be pairs
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 'relpages', '17'::integer,
- 'reltuples', 400::real,
- 'relallfrozen', 3::integer,
- 'relallvisible');
-ERROR: variadic arguments must be name/value pairs
-HINT: Provide an even number of variadic arguments that can be divided into pairs.
--- error: object doesn't exist
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 'relpages', '17'::integer,
- 'reltuples', 400::real,
- 'relallvisible', 4::integer,
- 'relallfrozen', 3::integer);
-ERROR: could not open relation with OID 0
--- error: object does not exist
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', '0'::oid::regclass,
- 'attname', 'id'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-ERROR: could not open relation with OID 0
--- error: relation null
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', NULL::oid,
- 'attname', 'id'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-ERROR: "relation" cannot be NULL
--- error: missing attname
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', NULL::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-ERROR: must specify either attname or attnum
--- error: both attname and attnum
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
- 'attnum', 1::smallint,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-ERROR: cannot specify both attname and attnum
--- error: attname doesn't exist
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'nope'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-ERROR: column "nope" of relation "test" does not exist
--- error: attribute is system column
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'xmin'::name,
- 'inherited', false::boolean,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-ERROR: cannot modify statistics on system column "xmin"
--- error: inherited null
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
- 'inherited', NULL::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-ERROR: "inherited" cannot be NULL
--- error: relation not found
-SELECT pg_catalog.pg_clear_relation_stats(
- relation => 'stats_import.nope'::regclass);
-ERROR: relation "stats_import.nope" does not exist
-LINE 2: relation => 'stats_import.nope'::regclass);
- ^
--- error: attribute is system column
-SELECT pg_catalog.pg_clear_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'ctid'::name,
- inherited => false::boolean);
-ERROR: cannot clear statistics on system column "ctid"
--- error: attname doesn't exist
-SELECT pg_catalog.pg_clear_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'nope'::name,
- inherited => false::boolean);
-ERROR: column "nope" of relation "test" does not exist
+SELECT COUNT(*)
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'arange';
+ count
+-------
+ 0
+(1 row)
+
DROP SCHEMA stats_import CASCADE;
NOTICE: drop cascades to 6 other objects
DETAIL: drop cascades to type stats_import.complex_type
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index febda3d18d9..8d04ff4f378 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -17,15 +17,43 @@ CREATE TABLE stats_import.test(
CREATE INDEX test_i ON stats_import.test(id);
+--
+-- relstats tests
+--
+
+--- error: relation is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 0::oid,
+ 'relpages', 17::integer);
+
+-- error: relation not found
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 0::oid::regclass,
+ 'relpages', 17::integer);
+
+-- error: odd number of variadic arguments cannot be pairs
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'relallvisible');
+
+-- error: argument name is NULL
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ NULL, '17'::integer);
+
+-- error: argument name is not a text type
+SELECT pg_restore_relation_stats(
+ 'relation', '0'::oid::regclass,
+ 17, '17'::integer);
+
-- starting stats
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
+WHERE oid = 'stats_import.test_i'::regclass;
-BEGIN;
-- regular indexes have special case locking rules
-SELECT
- pg_catalog.pg_restore_relation_stats(
+BEGIN;
+SELECT pg_catalog.pg_restore_relation_stats(
'relation', 'stats_import.test_i'::regclass,
'relpages', 18::integer);
@@ -39,20 +67,6 @@ WHERE relation = 'stats_import.test_i'::regclass AND
COMMIT;
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.test_i'::regclass,
- 'relpages', 19::integer );
-
--- clear
-SELECT
- pg_catalog.pg_clear_relation_stats(
- 'stats_import.test'::regclass);
-
-SELECT relpages, reltuples, relallvisible, relallfrozen
-FROM pg_class
-WHERE oid = 'stats_import.test'::regclass;
-
-- relpages may be -1 for partitioned tables
CREATE TABLE stats_import.part_parent ( i integer ) PARTITION BY RANGE(i);
CREATE TABLE stats_import.part_child_1
@@ -68,18 +82,6 @@ SELECT relpages
FROM pg_class
WHERE oid = 'stats_import.part_parent'::regclass;
--- although partitioned tables have no storage, setting relpages to a
--- positive value is still allowed
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent_i'::regclass,
- 'relpages', 2::integer);
-
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent'::regclass,
- 'relpages', 2::integer);
-
--
-- Partitioned indexes aren't analyzed but it is possible to set
-- stats. The locking rules are different from normal indexes due to
@@ -88,8 +90,7 @@ SELECT
--
BEGIN;
-SELECT
- pg_catalog.pg_restore_relation_stats(
+SELECT pg_catalog.pg_restore_relation_stats(
'relation', 'stats_import.part_parent_i'::regclass,
'relpages', 2::integer);
@@ -103,22 +104,15 @@ WHERE relation = 'stats_import.part_parent_i'::regclass AND
COMMIT;
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent_i'::regclass,
- 'relpages', 2::integer);
+SELECT relpages
+FROM pg_class
+WHERE oid = 'stats_import.part_parent_i'::regclass;
--- nothing stops us from setting it to -1
-SELECT
- pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent'::regclass,
- 'relpages', -1::integer);
-
--- ok: set all stats
+-- ok: set all relstats, with version, no bounds checking
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
'version', 150000::integer,
- 'relpages', '17'::integer,
+ 'relpages', '-17'::integer,
'reltuples', 400::real,
'relallvisible', 4::integer,
'relallfrozen', 2::integer);
@@ -127,30 +121,27 @@ SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
--- ok: just relpages
+-- ok: set just relpages, rest stay same
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
- 'version', 150000::integer,
'relpages', '16'::integer);
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
--- ok: just reltuples
+-- ok: set just reltuples, rest stay same
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
- 'version', 150000::integer,
'reltuples', '500'::real);
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
--- ok: just relallvisible
+-- ok: set just relallvisible, rest stay same
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
- 'version', 150000::integer,
'relallvisible', 5::integer);
SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -167,10 +158,9 @@ SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
--- warn: bad relpages type
+-- warn: bad relpages type, rest updated
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
- 'version', 150000::integer,
'relpages', 'nope'::text,
'reltuples', 400.0::real,
'relallvisible', 4::integer,
@@ -180,17 +170,104 @@ SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
+-- unrecognized argument name, rest ok
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'relpages', '171'::integer,
+ 'nope', 10::integer);
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+
+-- ok: clear stats
+SELECT pg_catalog.pg_clear_relation_stats(
+ relation => 'stats_import.test'::regclass);
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+
-- invalid relkinds for statistics
CREATE SEQUENCE stats_import.testseq;
-CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
-SELECT
- pg_catalog.pg_clear_relation_stats(
+
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.testseq'::regclass);
+
+SELECT pg_catalog.pg_clear_relation_stats(
'stats_import.testseq'::regclass);
-SELECT
- pg_catalog.pg_clear_relation_stats(
+
+CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
+
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.testview'::regclass);
+
+SELECT pg_catalog.pg_clear_relation_stats(
'stats_import.testview'::regclass);
--- ok: no stakinds
+--
+-- attribute stats
+--
+
+-- error: object does not exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', '0'::oid::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: relation null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', NULL::oid::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: NULL attname
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', NULL::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: attname doesn't exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'nope'::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+
+-- error: both attname and attnum
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'attnum', 1::smallint,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: neither attname nor attnum
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: attribute is system column
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'xmin'::name,
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: inherited null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', NULL::boolean,
+ 'null_frac', 0.1::real);
+
+-- ok: just the fixed values, with version, no stakinds
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
@@ -207,15 +284,16 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- ok: restore by attnum
+--
+-- ok: restore by attnum, we normally reserve this for
+-- indexes, but there is no reason it shouldn't work
+-- for any stat-having relation.
+--
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attnum', 1::smallint,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.4::real,
- 'avg_width', 5::integer,
- 'n_distinct', 0.6::real);
+ 'null_frac', 0.4::real);
SELECT *
FROM pg_stats
@@ -224,14 +302,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: unrecognized argument name
+-- warn: unrecognized argument name, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
'null_frac', 0.2::real,
- 'avg_width', NULL::integer,
'nope', 0.5::real);
SELECT *
@@ -241,15 +317,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: mcv / mcf null mismatch part 1
+-- warn: mcv / mcf null mismatch part 1, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.6::real,
- 'avg_width', 7::integer,
- 'n_distinct', -0.7::real,
+ 'null_frac', 0.21::real,
'most_common_freqs', '{0.1,0.2,0.3}'::real[]
);
@@ -260,15 +333,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: mcv / mcf null mismatch part 2
+-- warn: mcv / mcf null mismatch part 2, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.7::real,
- 'avg_width', 8::integer,
- 'n_distinct', -0.8::real,
+ 'null_frac', 0.21::real,
'most_common_vals', '{1,2,3}'::text
);
@@ -279,15 +349,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: mcv / mcf type mismatch
+-- warn: mcf type mismatch, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.8::real,
- 'avg_width', 9::integer,
- 'n_distinct', -0.9::real,
+ 'null_frac', 0.22::real,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.2,0.1}'::double precision[]
);
@@ -299,15 +366,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: mcv cast failure
+-- warn: mcv cast failure, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.9::real,
- 'avg_width', 10::integer,
- 'n_distinct', -0.4::real,
+ 'null_frac', 0.23::real,
'most_common_vals', '{2,four,3}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
);
@@ -324,10 +388,6 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 1::integer,
- 'n_distinct', -0.1::real,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
);
@@ -339,15 +399,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: NULL in histogram array
+-- warn: NULL in histogram array, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.2::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.2::real,
+ 'null_frac', 0.24::real,
'histogram_bounds', '{1,NULL,3,4}'::text
);
@@ -363,11 +420,8 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.3::real,
- 'avg_width', 3::integer,
- 'n_distinct', -0.3::real,
- 'histogram_bounds', '{1,2,3,4}'::text );
+ 'histogram_bounds', '{1,2,3,4}'::text
+ );
SELECT *
FROM pg_stats
@@ -376,16 +430,13 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: elem_count_histogram null element
+-- warn: elem_count_histogram null element, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.4::real,
- 'avg_width', 5::integer,
- 'n_distinct', -0.4::real,
- 'elem_count_histogram', '{1,1,NULL,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
+ 'null_frac', 0.25::real,
+ 'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[]
);
SELECT *
@@ -400,10 +451,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.5::real,
- 'avg_width', 6::integer,
- 'n_distinct', -0.55::real,
+ 'null_frac', 0.26::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
);
@@ -414,15 +462,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'tags';
--- range stats on a scalar type
+-- warn: range stats on a scalar type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.6::real,
- 'avg_width', 7::integer,
- 'n_distinct', -0.15::real,
+ 'null_frac', 0.27::real,
'range_empty_frac', 0.5::real,
'range_length_histogram', '{399,499,Infinity}'::text
);
@@ -434,15 +479,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: range_empty_frac range_length_hist null mismatch
+-- warn: range_empty_frac range_length_hist null mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.7::real,
- 'avg_width', 8::integer,
- 'n_distinct', -0.25::real,
+ 'null_frac', 0.28::real,
'range_length_histogram', '{399,499,Infinity}'::text
);
@@ -453,15 +495,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'arange';
--- warn: range_empty_frac range_length_hist null mismatch part 2
+-- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.8::real,
- 'avg_width', 9::integer,
- 'n_distinct', -0.35::real,
+ 'null_frac', 0.29::real,
'range_empty_frac', 0.5::real
);
@@ -477,10 +516,6 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.9::real,
- 'avg_width', 1::integer,
- 'n_distinct', -0.19::real,
'range_empty_frac', 0.5::real,
'range_length_histogram', '{399,499,Infinity}'::text
);
@@ -492,15 +527,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'arange';
--- warn: range bounds histogram on scalar
+-- warn: range bounds histogram on scalar, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.29::real,
+ 'null_frac', 0.31::real,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
);
@@ -516,10 +548,6 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.2::real,
- 'avg_width', 3::integer,
- 'n_distinct', -0.39::real,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
);
@@ -530,23 +558,14 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'arange';
--- warn: cannot set most_common_elems for range type
+-- warn: cannot set most_common_elems for range type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'arange'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
- 'most_common_vals', '{"[2,3)","[1,2)","[3,4)"}'::text,
- 'most_common_freqs', '{0.3,0.25,0.05}'::real[],
- 'histogram_bounds', '{"[1,2)","[2,3)","[3,4)","[4,5)"}'::text,
- 'correlation', 1.1::real,
+ 'null_frac', 0.32::real,
'most_common_elems', '{3,1}'::text,
- 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[],
- 'range_empty_frac', -0.5::real,
- 'range_length_histogram', '{399,499,Infinity}'::text,
- 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
+ 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
);
SELECT *
@@ -556,14 +575,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'arange';
--- warn: scalars can't have mcelem
+-- warn: scalars can't have mcelem, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
+ 'null_frac', 0.33::real,
'most_common_elems', '{1,3}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
);
@@ -575,14 +592,12 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: mcelem / mcelem mismatch
+-- warn: mcelem / mcelem mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
+ 'null_frac', 0.34::real,
'most_common_elems', '{one,two}'::text
);
@@ -593,25 +608,27 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'tags';
--- warn: mcelem / mcelem null mismatch part 2
+-- warn: mcelem / mcelem null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
+ 'null_frac', 0.35::real,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
);
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'tags';
+
-- ok: mcelem
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'tags'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
'most_common_elems', '{one,three}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
);
@@ -623,15 +640,13 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'tags';
--- warn: scalars can't have elem_count_histogram
+-- warn: scalars can't have elem_count_histogram, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass,
'attname', 'id'::name,
'inherited', false::boolean,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
- 'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
+ 'null_frac', 0.36::real,
+ 'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[]
);
SELECT *
@@ -641,32 +656,6 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--- warn: too many stat kinds
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.5::real,
- 'avg_width', 2::integer,
- 'n_distinct', -0.1::real,
- 'most_common_vals', '{"[2,3)","[1,3)","[3,9)"}'::text,
- 'most_common_freqs', '{0.3,0.25,0.05}'::real[],
- 'histogram_bounds', '{"[1,2)","[2,3)","[3,4)","[4,)"}'::text,
- 'correlation', 1.1::real,
- 'most_common_elems', '{3,1}'::text,
- 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[],
- 'range_empty_frac', -0.5::real,
- 'range_length_histogram', '{399,499,Infinity}'::text,
- 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text);
-
-SELECT *
-FROM pg_stats
-WHERE schemaname = 'stats_import'
-AND tablename = 'test'
-AND inherited = false
-AND attname = 'arange';
-
--
-- Test the ability to exactly copy data from one table to an identical table,
-- correctly reconstructing the stakind order as well as the staopN and
@@ -686,16 +675,6 @@ SELECT 4, 'four', NULL, int4range(0,100), NULL;
CREATE INDEX is_odd ON stats_import.test(((comp).a % 2 = 1));
--- restoring stats on index
-SELECT * FROM pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.is_odd'::regclass,
- 'version', '180000'::integer,
- 'relpages', '11'::integer,
- 'reltuples', '10000'::real,
- 'relallvisible', '0'::integer,
- 'relallfrozen', '0'::integer
-);
-
-- Generate statistics on table with data
ANALYZE stats_import.test;
@@ -848,160 +827,24 @@ FROM pg_statistic s
JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
WHERE s.starelid = 'stats_import.is_odd'::regclass;
--- ok
+-- attribute stats exist before a clear, but not after
+SELECT COUNT(*)
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'arange';
+
SELECT pg_catalog.pg_clear_attribute_stats(
relation => 'stats_import.test'::regclass,
attname => 'arange'::name,
inherited => false::boolean);
---
--- Negative tests
---
-
---- error: relation is wrong type
-SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid,
- 'relpages', 17::integer,
- 'reltuples', 400.0::real,
- 'relallvisible', 4::integer,
- 'relallfrozen', 3::integer);
-
---- error: relation not found
-SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::regclass,
- 'relpages', 17::integer,
- 'reltuples', 400.0::real,
- 'relallvisible', 4::integer,
- 'relallfrozen', 3::integer);
-
--- warn and error: unrecognized argument name
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 'relpages', '17'::integer,
- 'reltuples', 400::real,
- 'nope', 4::integer);
-
--- error: argument name is NULL
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- NULL, '17'::integer,
- 'reltuples', 400::real,
- 'relallvisible', 4::integer,
- 'relallfrozen', 3::integer);
-
--- error: argument name is an integer
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 17, '17'::integer,
- 'reltuples', 400::real,
- 'relallvisible', 4::integer,
- 'relallfrozen', 3::integer);
-
--- error: odd number of variadic arguments cannot be pairs
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 'relpages', '17'::integer,
- 'reltuples', 400::real,
- 'relallfrozen', 3::integer,
- 'relallvisible');
-
--- error: object doesn't exist
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 'version', 150000::integer,
- 'relpages', '17'::integer,
- 'reltuples', 400::real,
- 'relallvisible', 4::integer,
- 'relallfrozen', 3::integer);
-
--- error: object does not exist
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', '0'::oid::regclass,
- 'attname', 'id'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-
--- error: relation null
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', NULL::oid,
- 'attname', 'id'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-
--- error: missing attname
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', NULL::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-
--- error: both attname and attnum
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
- 'attnum', 1::smallint,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-
--- error: attname doesn't exist
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'nope'::name,
- 'inherited', false::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-
--- error: attribute is system column
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'xmin'::name,
- 'inherited', false::boolean,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-
--- error: inherited null
-SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
- 'inherited', NULL::boolean,
- 'version', 150000::integer,
- 'null_frac', 0.1::real,
- 'avg_width', 2::integer,
- 'n_distinct', 0.3::real);
-
--- error: relation not found
-SELECT pg_catalog.pg_clear_relation_stats(
- relation => 'stats_import.nope'::regclass);
-
--- error: attribute is system column
-SELECT pg_catalog.pg_clear_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'ctid'::name,
- inherited => false::boolean);
-
--- error: attname doesn't exist
-SELECT pg_catalog.pg_clear_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'nope'::name,
- inherited => false::boolean);
+SELECT COUNT(*)
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'arange';
DROP SCHEMA stats_import CASCADE;
--
2.48.1
[text/x-patch] v6-0004-Avoid-getAttributeStats-call-for-indexes-without-.patch (11.8K, ../../CADkLM=f1n2_Vomq0gKab7xdxDHmJGgn=DE48P8fzQOp3Mrs1Qg@mail.gmail.com/5-v6-0004-Avoid-getAttributeStats-call-for-indexes-without-.patch)
download | inline diff:
From 5ea8a35a1d6db14046bb2fb76dfcdddb1bcf9479 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Wed, 5 Mar 2025 22:40:40 -0500
Subject: [PATCH v6 4/4] Avoid getAttributeStats() call for indexes without an
expression column.
---
src/bin/pg_dump/pg_dump.c | 229 ++++++++++++++++++++------------------
1 file changed, 122 insertions(+), 107 deletions(-)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 6cf2c7d1fe4..0cc2a65caa3 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -10487,7 +10487,6 @@ static void
dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
{
const DumpableObject *dobj = &rsinfo->dobj;
- PGresult *res;
PQExpBuffer query;
PQExpBuffer out;
DumpId *deps = NULL;
@@ -10508,11 +10507,22 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
int i_range_length_histogram;
int i_range_empty_frac;
int i_range_bounds_histogram;
+ bool can_have_attrstats = true;
/* nothing to do if we are not dumping statistics */
if (!fout->dopt->dumpStatistics)
return;
+ /*
+ * Indexes can only have statistics for expression columns.
+ */
+ if ((rsinfo->relkind == RELKIND_INDEX) ||
+ (rsinfo->relkind == RELKIND_PARTITIONED_INDEX))
+ {
+ if (rsinfo->nindAttNames == 0)
+ can_have_attrstats = false;
+ }
+
/* dependent on the relation definition, if doing schema */
if (fout->dopt->dumpSchema)
{
@@ -10573,127 +10583,132 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer\n);\n",
rsinfo->relallvisible);
- /* fetch attribute stats */
- appendPQExpBufferStr(query, "EXECUTE getAttributeStats(");
- appendStringLiteralAH(query, dobj->namespace->dobj.name, fout);
- appendPQExpBufferStr(query, ", ");
- appendStringLiteralAH(query, dobj->name, fout);
- appendPQExpBufferStr(query, ");");
-
- res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
-
- i_attname = PQfnumber(res, "attname");
- i_inherited = PQfnumber(res, "inherited");
- i_null_frac = PQfnumber(res, "null_frac");
- i_avg_width = PQfnumber(res, "avg_width");
- i_n_distinct = PQfnumber(res, "n_distinct");
- i_most_common_vals = PQfnumber(res, "most_common_vals");
- i_most_common_freqs = PQfnumber(res, "most_common_freqs");
- i_histogram_bounds = PQfnumber(res, "histogram_bounds");
- i_correlation = PQfnumber(res, "correlation");
- i_most_common_elems = PQfnumber(res, "most_common_elems");
- i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
- i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
- i_range_length_histogram = PQfnumber(res, "range_length_histogram");
- i_range_empty_frac = PQfnumber(res, "range_empty_frac");
- i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
-
- /* restore attribute stats */
- for (int rownum = 0; rownum < PQntuples(res); rownum++)
+ if (can_have_attrstats)
{
- const char *attname;
+ PGresult *res;
- appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
- appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
- fout->remoteVersion);
- appendPQExpBufferStr(out, "\t'schemaname', ");
- appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
- appendPQExpBufferStr(out, ",\n\t'relname', ");
- appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+ /* fetch attribute stats */
+ appendPQExpBufferStr(query, "EXECUTE getAttributeStats(");
+ appendStringLiteralAH(query, dobj->namespace->dobj.name, fout);
+ appendPQExpBufferStr(query, ", ");
+ appendStringLiteralAH(query, dobj->name, fout);
+ appendPQExpBufferStr(query, ");");
- if (PQgetisnull(res, rownum, i_attname))
- pg_fatal("attname cannot be NULL");
- attname = PQgetvalue(res, rownum, i_attname);
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
- /*
- * Indexes look up attname in indAttNames to derive attnum, all others
- * use attname directly. We must specify attnum for indexes, since
- * their attnames are not necessarily stable across dump/reload.
- */
- if (rsinfo->nindAttNames == 0)
+ i_attname = PQfnumber(res, "attname");
+ i_inherited = PQfnumber(res, "inherited");
+ i_null_frac = PQfnumber(res, "null_frac");
+ i_avg_width = PQfnumber(res, "avg_width");
+ i_n_distinct = PQfnumber(res, "n_distinct");
+ i_most_common_vals = PQfnumber(res, "most_common_vals");
+ i_most_common_freqs = PQfnumber(res, "most_common_freqs");
+ i_histogram_bounds = PQfnumber(res, "histogram_bounds");
+ i_correlation = PQfnumber(res, "correlation");
+ i_most_common_elems = PQfnumber(res, "most_common_elems");
+ i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
+ i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
+ i_range_length_histogram = PQfnumber(res, "range_length_histogram");
+ i_range_empty_frac = PQfnumber(res, "range_empty_frac");
+ i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+
+ /* restore attribute stats */
+ for (int rownum = 0; rownum < PQntuples(res); rownum++)
{
- appendPQExpBuffer(out, ",\n\t'attname', ");
- appendStringLiteralAH(out, attname, fout);
- }
- else
- {
- bool found = false;
+ const char *attname;
- for (int i = 0; i < rsinfo->nindAttNames; i++)
+ appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
+ appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+ fout->remoteVersion);
+ appendPQExpBufferStr(out, "\t'schemaname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+ appendPQExpBufferStr(out, ",\n\t'relname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+
+ if (PQgetisnull(res, rownum, i_attname))
+ pg_fatal("attname cannot be NULL");
+ attname = PQgetvalue(res, rownum, i_attname);
+
+ /*
+ * Indexes look up attname in indAttNames to derive attnum, all others
+ * use attname directly. We must specify attnum for indexes, since
+ * their attnames are not necessarily stable across dump/reload.
+ */
+ if (rsinfo->nindAttNames == 0)
{
- if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
+ appendPQExpBuffer(out, ",\n\t'attname', ");
+ appendStringLiteralAH(out, attname, fout);
+ }
+ else
+ {
+ bool found = false;
+
+ for (int i = 0; i < rsinfo->nindAttNames; i++)
{
- appendPQExpBuffer(out, ",\n\t'attnum', '%d'::smallint",
- i + 1);
- found = true;
- break;
+ if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
+ {
+ appendPQExpBuffer(out, ",\n\t'attnum', '%d'::smallint",
+ i + 1);
+ found = true;
+ break;
+ }
}
+
+ if (!found)
+ pg_fatal("could not find index attname \"%s\"", attname);
}
- if (!found)
- pg_fatal("could not find index attname \"%s\"", attname);
+ if (!PQgetisnull(res, rownum, i_inherited))
+ appendNamedArgument(out, fout, "inherited", "boolean",
+ PQgetvalue(res, rownum, i_inherited));
+ if (!PQgetisnull(res, rownum, i_null_frac))
+ appendNamedArgument(out, fout, "null_frac", "real",
+ PQgetvalue(res, rownum, i_null_frac));
+ if (!PQgetisnull(res, rownum, i_avg_width))
+ appendNamedArgument(out, fout, "avg_width", "integer",
+ PQgetvalue(res, rownum, i_avg_width));
+ if (!PQgetisnull(res, rownum, i_n_distinct))
+ appendNamedArgument(out, fout, "n_distinct", "real",
+ PQgetvalue(res, rownum, i_n_distinct));
+ if (!PQgetisnull(res, rownum, i_most_common_vals))
+ appendNamedArgument(out, fout, "most_common_vals", "text",
+ PQgetvalue(res, rownum, i_most_common_vals));
+ if (!PQgetisnull(res, rownum, i_most_common_freqs))
+ appendNamedArgument(out, fout, "most_common_freqs", "real[]",
+ PQgetvalue(res, rownum, i_most_common_freqs));
+ if (!PQgetisnull(res, rownum, i_histogram_bounds))
+ appendNamedArgument(out, fout, "histogram_bounds", "text",
+ PQgetvalue(res, rownum, i_histogram_bounds));
+ if (!PQgetisnull(res, rownum, i_correlation))
+ appendNamedArgument(out, fout, "correlation", "real",
+ PQgetvalue(res, rownum, i_correlation));
+ if (!PQgetisnull(res, rownum, i_most_common_elems))
+ appendNamedArgument(out, fout, "most_common_elems", "text",
+ PQgetvalue(res, rownum, i_most_common_elems));
+ if (!PQgetisnull(res, rownum, i_most_common_elem_freqs))
+ appendNamedArgument(out, fout, "most_common_elem_freqs", "real[]",
+ PQgetvalue(res, rownum, i_most_common_elem_freqs));
+ if (!PQgetisnull(res, rownum, i_elem_count_histogram))
+ appendNamedArgument(out, fout, "elem_count_histogram", "real[]",
+ PQgetvalue(res, rownum, i_elem_count_histogram));
+ if (fout->remoteVersion >= 170000)
+ {
+ if (!PQgetisnull(res, rownum, i_range_length_histogram))
+ appendNamedArgument(out, fout, "range_length_histogram", "text",
+ PQgetvalue(res, rownum, i_range_length_histogram));
+ if (!PQgetisnull(res, rownum, i_range_empty_frac))
+ appendNamedArgument(out, fout, "range_empty_frac", "real",
+ PQgetvalue(res, rownum, i_range_empty_frac));
+ if (!PQgetisnull(res, rownum, i_range_bounds_histogram))
+ appendNamedArgument(out, fout, "range_bounds_histogram", "text",
+ PQgetvalue(res, rownum, i_range_bounds_histogram));
+ }
+ appendPQExpBufferStr(out, "\n);\n");
}
- if (!PQgetisnull(res, rownum, i_inherited))
- appendNamedArgument(out, fout, "inherited", "boolean",
- PQgetvalue(res, rownum, i_inherited));
- if (!PQgetisnull(res, rownum, i_null_frac))
- appendNamedArgument(out, fout, "null_frac", "real",
- PQgetvalue(res, rownum, i_null_frac));
- if (!PQgetisnull(res, rownum, i_avg_width))
- appendNamedArgument(out, fout, "avg_width", "integer",
- PQgetvalue(res, rownum, i_avg_width));
- if (!PQgetisnull(res, rownum, i_n_distinct))
- appendNamedArgument(out, fout, "n_distinct", "real",
- PQgetvalue(res, rownum, i_n_distinct));
- if (!PQgetisnull(res, rownum, i_most_common_vals))
- appendNamedArgument(out, fout, "most_common_vals", "text",
- PQgetvalue(res, rownum, i_most_common_vals));
- if (!PQgetisnull(res, rownum, i_most_common_freqs))
- appendNamedArgument(out, fout, "most_common_freqs", "real[]",
- PQgetvalue(res, rownum, i_most_common_freqs));
- if (!PQgetisnull(res, rownum, i_histogram_bounds))
- appendNamedArgument(out, fout, "histogram_bounds", "text",
- PQgetvalue(res, rownum, i_histogram_bounds));
- if (!PQgetisnull(res, rownum, i_correlation))
- appendNamedArgument(out, fout, "correlation", "real",
- PQgetvalue(res, rownum, i_correlation));
- if (!PQgetisnull(res, rownum, i_most_common_elems))
- appendNamedArgument(out, fout, "most_common_elems", "text",
- PQgetvalue(res, rownum, i_most_common_elems));
- if (!PQgetisnull(res, rownum, i_most_common_elem_freqs))
- appendNamedArgument(out, fout, "most_common_elem_freqs", "real[]",
- PQgetvalue(res, rownum, i_most_common_elem_freqs));
- if (!PQgetisnull(res, rownum, i_elem_count_histogram))
- appendNamedArgument(out, fout, "elem_count_histogram", "real[]",
- PQgetvalue(res, rownum, i_elem_count_histogram));
- if (fout->remoteVersion >= 170000)
- {
- if (!PQgetisnull(res, rownum, i_range_length_histogram))
- appendNamedArgument(out, fout, "range_length_histogram", "text",
- PQgetvalue(res, rownum, i_range_length_histogram));
- if (!PQgetisnull(res, rownum, i_range_empty_frac))
- appendNamedArgument(out, fout, "range_empty_frac", "real",
- PQgetvalue(res, rownum, i_range_empty_frac));
- if (!PQgetisnull(res, rownum, i_range_bounds_histogram))
- appendNamedArgument(out, fout, "range_bounds_histogram", "text",
- PQgetvalue(res, rownum, i_range_bounds_histogram));
- }
- appendPQExpBufferStr(out, "\n);\n");
+ PQclear(res);
}
- PQclear(res);
-
ArchiveEntry(fout, nilCatalogId, createDumpId(),
ARCHIVE_OPTS(.tag = dobj->name,
.namespace = dobj->namespace->dobj.name,
--
2.48.1
[text/x-patch] v6-0003-Split-relation-into-schemaname-and-relname.patch (62.3K, ../../CADkLM=f1n2_Vomq0gKab7xdxDHmJGgn=DE48P8fzQOp3Mrs1Qg@mail.gmail.com/6-v6-0003-Split-relation-into-schemaname-and-relname.patch)
download | inline diff:
From cb22c5782c4652d76885cb8dd3a2753ccac01b24 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Tue, 4 Mar 2025 22:16:52 -0500
Subject: [PATCH v6 3/4] Split relation into schemaname and relname.
In order to further reduce potential error-failures in restores and
upgrades, replace the numerous casts of fully qualified relation names
into their schema+relname text components.
Further remove the ::name casts on attname and change the expected
datatype to text.
---
src/include/catalog/pg_proc.dat | 8 +-
src/backend/statistics/attribute_stats.c | 98 +++++--
src/backend/statistics/relation_stats.c | 70 +++--
src/bin/pg_dump/pg_dump.c | 25 +-
src/bin/pg_dump/t/002_pg_dump.pl | 6 +-
src/test/regress/expected/stats_import.out | 302 +++++++++++++--------
src/test/regress/sql/stats_import.sql | 271 +++++++++++-------
doc/src/sgml/func.sgml | 41 +--
8 files changed, 537 insertions(+), 284 deletions(-)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 134b3dd8689..53aa4bc4df3 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12424,8 +12424,8 @@
descr => 'clear statistics on relation',
proname => 'pg_clear_relation_stats', provolatile => 'v', proisstrict => 'f',
proparallel => 'u', prorettype => 'void',
- proargtypes => 'regclass',
- proargnames => '{relation}',
+ proargtypes => 'text text',
+ proargnames => '{schemaname,relname}',
prosrc => 'pg_clear_relation_stats' },
{ oid => '8461',
descr => 'restore statistics on attribute',
@@ -12440,8 +12440,8 @@
descr => 'clear statistics on attribute',
proname => 'pg_clear_attribute_stats', provolatile => 'v', proisstrict => 'f',
proparallel => 'u', prorettype => 'void',
- proargtypes => 'regclass name bool',
- proargnames => '{relation,attname,inherited}',
+ proargtypes => 'text text text bool',
+ proargnames => '{schemaname,relname,attname,inherited}',
prosrc => 'pg_clear_attribute_stats' },
# GiST stratnum implementations
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index 6bcbee0edba..65456a04ae5 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -19,6 +19,7 @@
#include "access/heapam.h"
#include "catalog/indexing.h"
+#include "catalog/namespace.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_operator.h"
#include "nodes/nodeFuncs.h"
@@ -36,7 +37,8 @@
enum attribute_stats_argnum
{
- ATTRELATION_ARG = 0,
+ ATTRELSCHEMA_ARG = 0,
+ ATTRELNAME_ARG,
ATTNAME_ARG,
ATTNUM_ARG,
INHERITED_ARG,
@@ -58,8 +60,9 @@ enum attribute_stats_argnum
static struct StatsArgInfo attarginfo[] =
{
- [ATTRELATION_ARG] = {"relation", REGCLASSOID},
- [ATTNAME_ARG] = {"attname", NAMEOID},
+ [ATTRELSCHEMA_ARG] = {"schemaname", TEXTOID},
+ [ATTRELNAME_ARG] = {"relname", TEXTOID},
+ [ATTNAME_ARG] = {"attname", TEXTOID},
[ATTNUM_ARG] = {"attnum", INT2OID},
[INHERITED_ARG] = {"inherited", BOOLOID},
[NULL_FRAC_ARG] = {"null_frac", FLOAT4OID},
@@ -80,7 +83,8 @@ static struct StatsArgInfo attarginfo[] =
enum clear_attribute_stats_argnum
{
- C_ATTRELATION_ARG = 0,
+ C_ATTRELSCHEMA_ARG = 0,
+ C_ATTRELNAME_ARG,
C_ATTNAME_ARG,
C_INHERITED_ARG,
C_NUM_ATTRIBUTE_STATS_ARGS
@@ -88,8 +92,9 @@ enum clear_attribute_stats_argnum
static struct StatsArgInfo cleararginfo[] =
{
- [C_ATTRELATION_ARG] = {"relation", REGCLASSOID},
- [C_ATTNAME_ARG] = {"attname", NAMEOID},
+ [C_ATTRELSCHEMA_ARG] = {"relation", TEXTOID},
+ [C_ATTRELNAME_ARG] = {"relation", TEXTOID},
+ [C_ATTNAME_ARG] = {"attname", TEXTOID},
[C_INHERITED_ARG] = {"inherited", BOOLOID},
[C_NUM_ATTRIBUTE_STATS_ARGS] = {0}
};
@@ -133,6 +138,9 @@ static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
static bool
attribute_statistics_update(FunctionCallInfo fcinfo)
{
+ char *nspname;
+ Oid nspoid;
+ char *relname;
Oid reloid;
char *attname;
AttrNumber attnum;
@@ -170,8 +178,28 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
bool result = true;
- stats_check_required_arg(fcinfo, attarginfo, ATTRELATION_ARG);
- reloid = PG_GETARG_OID(ATTRELATION_ARG);
+ stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG);
+ stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG);
+
+ nspname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELSCHEMA_ARG));
+ nspoid = get_namespace_oid(nspname, true);
+ if (nspoid == InvalidOid)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("Namespace \"%s\" not found.", nspname)));
+ return false;
+ }
+
+ relname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELNAME_ARG));
+ reloid = get_relname_relid(relname, nspoid);
+ if (reloid == InvalidOid)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
+ return false;
+ }
if (RecoveryInProgress())
ereport(ERROR,
@@ -185,21 +213,18 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
/* user can specify either attname or attnum, but not both */
if (!PG_ARGISNULL(ATTNAME_ARG))
{
- Name attnamename;
-
if (!PG_ARGISNULL(ATTNUM_ARG))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("cannot specify both attname and attnum")));
- attnamename = PG_GETARG_NAME(ATTNAME_ARG);
- attname = NameStr(*attnamename);
+ attname = TextDatumGetCString(PG_GETARG_DATUM(ATTNAME_ARG));
attnum = get_attnum(reloid, attname);
/* note that this test covers attisdropped cases too: */
if (attnum == InvalidAttrNumber)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
- attname, get_rel_name(reloid))));
+ errmsg("column \"%s\" of relation \"%s\".\"%s\" does not exist",
+ attname, nspname, relname)));
}
else if (!PG_ARGISNULL(ATTNUM_ARG))
{
@@ -210,8 +235,8 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
!SearchSysCacheExistsAttName(reloid, attname))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column %d of relation \"%s\" does not exist",
- attnum, get_rel_name(reloid))));
+ errmsg("column %d of relation \"%s\".\"%s\" does not exist",
+ attnum, nspname, relname)));
}
else
{
@@ -900,13 +925,38 @@ init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
Datum
pg_clear_attribute_stats(PG_FUNCTION_ARGS)
{
+ char *nspname;
+ Oid nspoid;
+ char *relname;
Oid reloid;
- Name attname;
+ char *attname;
AttrNumber attnum;
bool inherited;
- stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELATION_ARG);
- reloid = PG_GETARG_OID(C_ATTRELATION_ARG);
+ stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG);
+ stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG);
+ stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
+ stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
+
+ nspname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELSCHEMA_ARG));
+ nspoid = get_namespace_oid(nspname, true);
+ if (nspoid == InvalidOid)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("Namespace \"%s\" not found.", nspname)));
+ return false;
+ }
+
+ relname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELNAME_ARG));
+ reloid = get_relname_relid(relname, nspoid);
+ if (reloid == InvalidOid)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
+ return false;
+ }
if (RecoveryInProgress())
ereport(ERROR,
@@ -916,23 +966,21 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
stats_lock_check_privileges(reloid);
- stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
- attname = PG_GETARG_NAME(C_ATTNAME_ARG);
- attnum = get_attnum(reloid, NameStr(*attname));
+ attname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTNAME_ARG));
+ attnum = get_attnum(reloid, attname);
if (attnum < 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot clear statistics on system column \"%s\"",
- NameStr(*attname))));
+ attname)));
if (attnum == InvalidAttrNumber)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
- NameStr(*attname), get_rel_name(reloid))));
+ attname, get_rel_name(reloid))));
- stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
inherited = PG_GETARG_BOOL(C_INHERITED_ARG);
delete_pg_statistic(reloid, attnum, inherited);
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index 2c1cea3fc80..4c1e75a3c78 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -19,9 +19,12 @@
#include "access/heapam.h"
#include "catalog/indexing.h"
+#include "catalog/namespace.h"
#include "statistics/stat_utils.h"
+#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/fmgrprotos.h"
+#include "utils/lsyscache.h"
#include "utils/syscache.h"
@@ -32,7 +35,8 @@
enum relation_stats_argnum
{
- RELATION_ARG = 0,
+ RELSCHEMA_ARG = 0,
+ RELNAME_ARG,
RELPAGES_ARG,
RELTUPLES_ARG,
RELALLVISIBLE_ARG,
@@ -42,7 +46,8 @@ enum relation_stats_argnum
static struct StatsArgInfo relarginfo[] =
{
- [RELATION_ARG] = {"relation", REGCLASSOID},
+ [RELSCHEMA_ARG] = {"schemaname", TEXTOID},
+ [RELNAME_ARG] = {"relname", TEXTOID},
[RELPAGES_ARG] = {"relpages", INT4OID},
[RELTUPLES_ARG] = {"reltuples", FLOAT4OID},
[RELALLVISIBLE_ARG] = {"relallvisible", INT4OID},
@@ -59,6 +64,9 @@ static bool
relation_statistics_update(FunctionCallInfo fcinfo)
{
bool result = true;
+ char *nspname;
+ Oid nspoid;
+ char *relname;
Oid reloid;
Relation crel;
BlockNumber relpages = 0;
@@ -76,6 +84,37 @@ relation_statistics_update(FunctionCallInfo fcinfo)
bool nulls[4] = {0};
int nreplaces = 0;
+ stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG);
+ stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG);
+
+ nspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
+ nspoid = get_namespace_oid(nspname, true);
+ if (nspoid == InvalidOid)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("Namespace \"%s\" not found.", nspname)));
+ return false;
+ }
+
+ relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
+ reloid = get_relname_relid(relname, nspoid);
+ if (reloid == InvalidOid)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
+ return false;
+ }
+
+ if (RecoveryInProgress())
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("recovery is in progress"),
+ errhint("Statistics cannot be modified during recovery.")));
+
+ stats_lock_check_privileges(reloid);
+
if (!PG_ARGISNULL(RELPAGES_ARG))
{
relpages = PG_GETARG_UINT32(RELPAGES_ARG);
@@ -108,17 +147,6 @@ relation_statistics_update(FunctionCallInfo fcinfo)
update_relallfrozen = true;
}
- stats_check_required_arg(fcinfo, relarginfo, RELATION_ARG);
- reloid = PG_GETARG_OID(RELATION_ARG);
-
- if (RecoveryInProgress())
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("recovery is in progress"),
- errhint("Statistics cannot be modified during recovery.")));
-
- stats_lock_check_privileges(reloid);
-
/*
* Take RowExclusiveLock on pg_class, consistent with
* vac_update_relstats().
@@ -193,20 +221,22 @@ relation_statistics_update(FunctionCallInfo fcinfo)
Datum
pg_clear_relation_stats(PG_FUNCTION_ARGS)
{
- LOCAL_FCINFO(newfcinfo, 5);
+ LOCAL_FCINFO(newfcinfo, 6);
- InitFunctionCallInfoData(*newfcinfo, NULL, 5, InvalidOid, NULL, NULL);
+ InitFunctionCallInfoData(*newfcinfo, NULL, 6, InvalidOid, NULL, NULL);
- newfcinfo->args[0].value = PG_GETARG_OID(0);
+ newfcinfo->args[0].value = PG_GETARG_DATUM(0);
newfcinfo->args[0].isnull = PG_ARGISNULL(0);
- newfcinfo->args[1].value = UInt32GetDatum(0);
- newfcinfo->args[1].isnull = false;
- newfcinfo->args[2].value = Float4GetDatum(-1.0);
+ newfcinfo->args[1].value = PG_GETARG_DATUM(1);
+ newfcinfo->args[1].isnull = PG_ARGISNULL(1);
+ newfcinfo->args[2].value = UInt32GetDatum(0);
newfcinfo->args[2].isnull = false;
- newfcinfo->args[3].value = UInt32GetDatum(0);
+ newfcinfo->args[3].value = Float4GetDatum(-1.0);
newfcinfo->args[3].isnull = false;
newfcinfo->args[4].value = UInt32GetDatum(0);
newfcinfo->args[4].isnull = false;
+ newfcinfo->args[5].value = UInt32GetDatum(0);
+ newfcinfo->args[5].isnull = false;
relation_statistics_update(newfcinfo);
PG_RETURN_VOID();
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4f4ad2ee150..6cf2c7d1fe4 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -10492,7 +10492,6 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
PQExpBuffer out;
DumpId *deps = NULL;
int ndeps = 0;
- char *qualified_name;
char reltuples_str[FLOAT_SHORTEST_DECIMAL_LEN];
int i_attname;
int i_inherited;
@@ -10558,15 +10557,16 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
out = createPQExpBuffer();
- qualified_name = pg_strdup(fmtQualifiedDumpable(rsinfo));
-
/* restore relation stats */
appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
fout->remoteVersion);
- appendPQExpBufferStr(out, "\t'relation', ");
- appendStringLiteralAH(out, qualified_name, fout);
- appendPQExpBufferStr(out, "::regclass,\n");
+ appendPQExpBufferStr(out, "\t'schemaname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+ appendPQExpBufferStr(out, ",\n");
+ appendPQExpBufferStr(out, "\t'relname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+ appendPQExpBufferStr(out, ",\n");
appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
float_to_shortest_decimal_buf(rsinfo->reltuples, reltuples_str);
appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", reltuples_str);
@@ -10606,9 +10606,10 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
fout->remoteVersion);
- appendPQExpBufferStr(out, "\t'relation', ");
- appendStringLiteralAH(out, qualified_name, fout);
- appendPQExpBufferStr(out, "::regclass");
+ appendPQExpBufferStr(out, "\t'schemaname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+ appendPQExpBufferStr(out, ",\n\t'relname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.name, fout);
if (PQgetisnull(res, rownum, i_attname))
pg_fatal("attname cannot be NULL");
@@ -10620,7 +10621,10 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
* their attnames are not necessarily stable across dump/reload.
*/
if (rsinfo->nindAttNames == 0)
- appendNamedArgument(out, fout, "attname", "name", attname);
+ {
+ appendPQExpBuffer(out, ",\n\t'attname', ");
+ appendStringLiteralAH(out, attname, fout);
+ }
else
{
bool found = false;
@@ -10700,7 +10704,6 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
.deps = deps,
.nDeps = ndeps));
- free(qualified_name);
destroyPQExpBuffer(out);
destroyPQExpBuffer(query);
}
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c7bffc1b045..b037f239136 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -4725,14 +4725,16 @@ my %tests = (
regexp => qr/^
\QSELECT * FROM pg_catalog.pg_restore_relation_stats(\E\s+
'version',\s'\d+'::integer,\s+
- 'relation',\s'dump_test.dup_test_post_data_ix'::regclass,\s+
+ 'schemaname',\s'dump_test',\s+
+ 'relname',\s'dup_test_post_data_ix',\s+
'relpages',\s'\d+'::integer,\s+
'reltuples',\s'\d+'::real,\s+
'relallvisible',\s'\d+'::integer\s+
\);\s+
\QSELECT * FROM pg_catalog.pg_restore_attribute_stats(\E\s+
'version',\s'\d+'::integer,\s+
- 'relation',\s'dump_test.dup_test_post_data_ix'::regclass,\s+
+ 'schemaname',\s'dump_test',\s+
+ 'relname',\s'dup_test_post_data_ix',\s+
'attnum',\s'2'::smallint,\s+
'inherited',\s'f'::boolean,\s+
'null_frac',\s'0'::real,\s+
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index ebba14c6a1d..ca7fe39660e 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -16,33 +16,54 @@ CREATE INDEX test_i ON stats_import.test(id);
--
-- relstats tests
--
---- error: relation is wrong type
+-- error: schemaname missing
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid,
+ 'relname', 'test',
'relpages', 17::integer);
-WARNING: argument "relation" has type "oid", expected type "regclass"
-ERROR: "relation" cannot be NULL
+ERROR: "schemaname" cannot be NULL
+-- error: relname missing
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 'stats_import',
+ 'relpages', 17::integer);
+ERROR: "relname" cannot be NULL
+--- error: schemaname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 3.6::float,
+ 'relname', 'test',
+ 'relpages', 17::integer);
+WARNING: argument "schemaname" has type "double precision", expected type "text"
+ERROR: "schemaname" cannot be NULL
+--- error: relname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 0::oid,
+ 'relpages', 17::integer);
+WARNING: argument "relname" has type "oid", expected type "text"
+ERROR: "relname" cannot be NULL
-- error: relation not found
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'nope',
'relpages', 17::integer);
-ERROR: could not open relation with OID 0
+WARNING: Relation "stats_import"."nope" not found.
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
-- error: odd number of variadic arguments cannot be pairs
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relallvisible');
ERROR: variadic arguments must be name/value pairs
HINT: Provide an even number of variadic arguments that can be divided into pairs.
-- error: argument name is NULL
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
NULL, '17'::integer);
-ERROR: name at variadic position 3 is NULL
--- error: argument name is not a text type
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 17, '17'::integer);
-ERROR: name at variadic position 3 has type "integer", expected type "text"
+ERROR: name at variadic position 5 is NULL
-- starting stats
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
@@ -55,7 +76,8 @@ WHERE oid = 'stats_import.test_i'::regclass;
-- regular indexes have special case locking rules
BEGIN;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.test_i'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test_i',
'relpages', 18::integer);
pg_restore_relation_stats
---------------------------
@@ -103,7 +125,8 @@ WHERE oid = 'stats_import.part_parent'::regclass;
--
BEGIN;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent_i'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'part_parent_i',
'relpages', 2::integer);
pg_restore_relation_stats
---------------------------
@@ -137,7 +160,8 @@ WHERE oid = 'stats_import.part_parent_i'::regclass;
-- ok: set all relstats, with version, no bounds checking
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'version', 150000::integer,
'relpages', '-17'::integer,
'reltuples', 400::real,
@@ -158,7 +182,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just relpages, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', '16'::integer);
pg_restore_relation_stats
---------------------------
@@ -175,7 +200,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just reltuples, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'reltuples', '500'::real);
pg_restore_relation_stats
---------------------------
@@ -192,7 +218,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just relallvisible, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relallvisible', 5::integer);
pg_restore_relation_stats
---------------------------
@@ -209,7 +236,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: just relallfrozen
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'version', 150000::integer,
'relallfrozen', 3::integer);
pg_restore_relation_stats
@@ -227,7 +255,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- warn: bad relpages type, rest updated
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', 'nope'::text,
'reltuples', 400.0::real,
'relallvisible', 4::integer,
@@ -248,7 +277,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- unrecognized argument name, rest ok
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', '171'::integer,
'nope', 10::integer);
WARNING: unrecognized argument name: "nope"
@@ -266,8 +296,7 @@ WHERE oid = 'stats_import.test'::regclass;
(1 row)
-- ok: clear stats
-SELECT pg_catalog.pg_clear_relation_stats(
- relation => 'stats_import.test'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'test');
pg_clear_relation_stats
-------------------------
@@ -284,87 +313,123 @@ WHERE oid = 'stats_import.test'::regclass;
-- invalid relkinds for statistics
CREATE SEQUENCE stats_import.testseq;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.testseq'::regclass);
+ 'schemaname', 'stats_import',
+ 'relname', 'testseq');
ERROR: cannot modify statistics for relation "testseq"
DETAIL: This operation is not supported for sequences.
-SELECT pg_catalog.pg_clear_relation_stats(
- 'stats_import.testseq'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
ERROR: cannot modify statistics for relation "testseq"
DETAIL: This operation is not supported for sequences.
CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
-SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.testview'::regclass);
-ERROR: cannot modify statistics for relation "testview"
-DETAIL: This operation is not supported for views.
-SELECT pg_catalog.pg_clear_relation_stats(
- 'stats_import.testview'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
ERROR: cannot modify statistics for relation "testview"
DETAIL: This operation is not supported for views.
--
-- attribute stats
--
--- error: object does not exist
+-- error: schemaname missing
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', '0'::oid::regclass,
- 'attname', 'id'::name,
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: could not open relation with OID 0
--- error: relation null
+ERROR: "schemaname" cannot be NULL
+-- error: schema does not exist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', NULL::oid::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'nope',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: "relation" cannot be NULL
+WARNING: Namespace "nope" not found.
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- error: relname missing
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: "relname" cannot be NULL
+-- error: relname does not exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'nope',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+WARNING: Relation "stats_import"."nope" not found.
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- error: relname null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'relname', NULL,
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: "relname" cannot be NULL
-- error: NULL attname
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', NULL::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', NULL,
'inherited', false::boolean,
'null_frac', 0.1::real);
ERROR: must specify either attname or attnum
-- error: attname doesn't exist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'nope'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'nope',
'inherited', false::boolean,
'null_frac', 0.1::real,
'avg_width', 2::integer,
'n_distinct', 0.3::real);
-ERROR: column "nope" of relation "test" does not exist
+ERROR: column "nope" of relation "stats_import"."test" does not exist
-- error: both attname and attnum
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.1::real);
ERROR: cannot specify both attname and attnum
-- error: neither attname nor attnum
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'inherited', false::boolean,
'null_frac', 0.1::real);
ERROR: must specify either attname or attnum
-- error: attribute is system column
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'xmin'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'xmin',
'inherited', false::boolean,
'null_frac', 0.1::real);
ERROR: cannot modify statistics on system column "xmin"
-- error: inherited null
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', NULL::boolean,
'null_frac', 0.1::real);
ERROR: "inherited" cannot be NULL
-- ok: just the fixed values, with version, no stakinds
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'version', 150000::integer,
'null_frac', 0.2::real,
@@ -392,7 +457,8 @@ AND attname = 'id';
-- for any stat-having relation.
--
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.4::real);
@@ -414,8 +480,9 @@ AND attname = 'id';
-- warn: unrecognized argument name, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.2::real,
'nope', 0.5::real);
@@ -438,8 +505,9 @@ AND attname = 'id';
-- warn: mcv / mcf null mismatch part 1, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.21::real,
'most_common_freqs', '{0.1,0.2,0.3}'::real[]
@@ -463,8 +531,9 @@ AND attname = 'id';
-- warn: mcv / mcf null mismatch part 2, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.21::real,
'most_common_vals', '{1,2,3}'::text
@@ -488,8 +557,9 @@ AND attname = 'id';
-- warn: mcf type mismatch, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.22::real,
'most_common_vals', '{2,1,3}'::text,
@@ -515,8 +585,9 @@ AND attname = 'id';
-- warn: mcv cast failure, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.23::real,
'most_common_vals', '{2,four,3}'::text,
@@ -541,8 +612,9 @@ AND attname = 'id';
-- ok: mcv+mcf
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
@@ -565,8 +637,9 @@ AND attname = 'id';
-- warn: NULL in histogram array, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.24::real,
'histogram_bounds', '{1,NULL,3,4}'::text
@@ -590,8 +663,9 @@ AND attname = 'id';
-- ok: histogram_bounds
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'histogram_bounds', '{1,2,3,4}'::text
);
@@ -613,8 +687,9 @@ AND attname = 'id';
-- warn: elem_count_histogram null element, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.25::real,
'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[]
@@ -638,8 +713,9 @@ AND attname = 'tags';
-- ok: elem_count_histogram
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.26::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -662,8 +738,9 @@ AND attname = 'tags';
-- warn: range stats on a scalar type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.27::real,
'range_empty_frac', 0.5::real,
@@ -689,8 +766,9 @@ AND attname = 'id';
-- warn: range_empty_frac range_length_hist null mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.28::real,
'range_length_histogram', '{399,499,Infinity}'::text
@@ -714,8 +792,9 @@ AND attname = 'arange';
-- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.29::real,
'range_empty_frac', 0.5::real
@@ -739,8 +818,9 @@ AND attname = 'arange';
-- ok: range_empty_frac + range_length_hist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'range_empty_frac', 0.5::real,
'range_length_histogram', '{399,499,Infinity}'::text
@@ -763,8 +843,9 @@ AND attname = 'arange';
-- warn: range bounds histogram on scalar, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.31::real,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
@@ -789,8 +870,9 @@ AND attname = 'id';
-- ok: range_bounds_histogram
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
);
@@ -812,8 +894,9 @@ AND attname = 'arange';
-- warn: cannot set most_common_elems for range type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.32::real,
'most_common_elems', '{3,1}'::text,
@@ -839,8 +922,9 @@ AND attname = 'arange';
-- warn: scalars can't have mcelem, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.33::real,
'most_common_elems', '{1,3}'::text,
@@ -866,8 +950,9 @@ AND attname = 'id';
-- warn: mcelem / mcelem mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.34::real,
'most_common_elems', '{one,two}'::text
@@ -891,8 +976,9 @@ AND attname = 'tags';
-- warn: mcelem / mcelem null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.35::real,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
@@ -916,8 +1002,9 @@ AND attname = 'tags';
-- ok: mcelem
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'most_common_elems', '{one,three}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
@@ -940,8 +1027,9 @@ AND attname = 'tags';
-- warn: scalars can't have elem_count_histogram, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.36::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -993,8 +1081,9 @@ SELECT s.schemaname, s.tablename, s.attname, s.inherited, r.*
FROM pg_catalog.pg_stats AS s
CROSS JOIN LATERAL
pg_catalog.pg_restore_attribute_stats(
- 'relation', ('stats_import.' || s.tablename || '_clone')::regclass,
- 'attname', s.attname,
+ 'schemaname', 'stats_import',
+ 'relname', s.tablename::text || '_clone',
+ 'attname', s.attname::text,
'inherited', s.inherited,
'version', 150000,
'null_frac', s.null_frac,
@@ -1171,9 +1260,10 @@ AND attname = 'arange';
(1 row)
SELECT pg_catalog.pg_clear_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean);
+ schemaname => 'stats_import',
+ relname => 'test',
+ attname => 'arange',
+ inherited => false);
pg_clear_attribute_stats
--------------------------
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index 8d04ff4f378..3c330387243 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -21,31 +21,46 @@ CREATE INDEX test_i ON stats_import.test(id);
-- relstats tests
--
---- error: relation is wrong type
+-- error: schemaname missing
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid,
+ 'relname', 'test',
+ 'relpages', 17::integer);
+
+-- error: relname missing
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 'stats_import',
+ 'relpages', 17::integer);
+
+--- error: schemaname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 3.6::float,
+ 'relname', 'test',
+ 'relpages', 17::integer);
+
+--- error: relname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 0::oid,
'relpages', 17::integer);
-- error: relation not found
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'nope',
'relpages', 17::integer);
-- error: odd number of variadic arguments cannot be pairs
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relallvisible');
-- error: argument name is NULL
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
NULL, '17'::integer);
--- error: argument name is not a text type
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 17, '17'::integer);
-
-- starting stats
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
@@ -54,7 +69,8 @@ WHERE oid = 'stats_import.test_i'::regclass;
-- regular indexes have special case locking rules
BEGIN;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.test_i'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test_i',
'relpages', 18::integer);
SELECT mode FROM pg_locks
@@ -91,7 +107,8 @@ WHERE oid = 'stats_import.part_parent'::regclass;
BEGIN;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent_i'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'part_parent_i',
'relpages', 2::integer);
SELECT mode FROM pg_locks
@@ -110,7 +127,8 @@ WHERE oid = 'stats_import.part_parent_i'::regclass;
-- ok: set all relstats, with version, no bounds checking
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'version', 150000::integer,
'relpages', '-17'::integer,
'reltuples', 400::real,
@@ -123,7 +141,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just relpages, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', '16'::integer);
SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -132,7 +151,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just reltuples, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'reltuples', '500'::real);
SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -141,7 +161,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just relallvisible, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relallvisible', 5::integer);
SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -150,7 +171,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: just relallfrozen
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'version', 150000::integer,
'relallfrozen', 3::integer);
@@ -160,7 +182,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- warn: bad relpages type, rest updated
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', 'nope'::text,
'reltuples', 400.0::real,
'relallvisible', 4::integer,
@@ -172,7 +195,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- unrecognized argument name, rest ok
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', '171'::integer,
'nope', 10::integer);
@@ -181,8 +205,7 @@ FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
-- ok: clear stats
-SELECT pg_catalog.pg_clear_relation_stats(
- relation => 'stats_import.test'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'test');
SELECT relpages, reltuples, relallvisible
FROM pg_class
@@ -192,48 +215,70 @@ WHERE oid = 'stats_import.test'::regclass;
CREATE SEQUENCE stats_import.testseq;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.testseq'::regclass);
+ 'schemaname', 'stats_import',
+ 'relname', 'testseq');
-SELECT pg_catalog.pg_clear_relation_stats(
- 'stats_import.testseq'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
-SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.testview'::regclass);
-
-SELECT pg_catalog.pg_clear_relation_stats(
- 'stats_import.testview'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
--
-- attribute stats
--
--- error: object does not exist
+-- error: schemaname missing
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', '0'::oid::regclass,
- 'attname', 'id'::name,
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: relation null
+-- error: schema does not exist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', NULL::oid::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'nope',
+ 'relname', 'test',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: relname missing
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: relname does not exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'nope',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: relname null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'relname', NULL,
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: NULL attname
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', NULL::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', NULL,
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: attname doesn't exist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'nope'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'nope',
'inherited', false::boolean,
'null_frac', 0.1::real,
'avg_width', 2::integer,
@@ -241,36 +286,41 @@ SELECT pg_catalog.pg_restore_attribute_stats(
-- error: both attname and attnum
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: neither attname nor attnum
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: attribute is system column
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'xmin'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'xmin',
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: inherited null
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', NULL::boolean,
'null_frac', 0.1::real);
-- ok: just the fixed values, with version, no stakinds
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'version', 150000::integer,
'null_frac', 0.2::real,
@@ -290,7 +340,8 @@ AND attname = 'id';
-- for any stat-having relation.
--
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.4::real);
@@ -304,8 +355,9 @@ AND attname = 'id';
-- warn: unrecognized argument name, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.2::real,
'nope', 0.5::real);
@@ -319,8 +371,9 @@ AND attname = 'id';
-- warn: mcv / mcf null mismatch part 1, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.21::real,
'most_common_freqs', '{0.1,0.2,0.3}'::real[]
@@ -335,8 +388,9 @@ AND attname = 'id';
-- warn: mcv / mcf null mismatch part 2, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.21::real,
'most_common_vals', '{1,2,3}'::text
@@ -351,8 +405,9 @@ AND attname = 'id';
-- warn: mcf type mismatch, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.22::real,
'most_common_vals', '{2,1,3}'::text,
@@ -368,8 +423,9 @@ AND attname = 'id';
-- warn: mcv cast failure, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.23::real,
'most_common_vals', '{2,four,3}'::text,
@@ -385,8 +441,9 @@ AND attname = 'id';
-- ok: mcv+mcf
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
@@ -401,8 +458,9 @@ AND attname = 'id';
-- warn: NULL in histogram array, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.24::real,
'histogram_bounds', '{1,NULL,3,4}'::text
@@ -417,8 +475,9 @@ AND attname = 'id';
-- ok: histogram_bounds
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'histogram_bounds', '{1,2,3,4}'::text
);
@@ -432,8 +491,9 @@ AND attname = 'id';
-- warn: elem_count_histogram null element, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.25::real,
'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[]
@@ -448,8 +508,9 @@ AND attname = 'tags';
-- ok: elem_count_histogram
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.26::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -464,8 +525,9 @@ AND attname = 'tags';
-- warn: range stats on a scalar type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.27::real,
'range_empty_frac', 0.5::real,
@@ -481,8 +543,9 @@ AND attname = 'id';
-- warn: range_empty_frac range_length_hist null mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.28::real,
'range_length_histogram', '{399,499,Infinity}'::text
@@ -497,8 +560,9 @@ AND attname = 'arange';
-- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.29::real,
'range_empty_frac', 0.5::real
@@ -513,8 +577,9 @@ AND attname = 'arange';
-- ok: range_empty_frac + range_length_hist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'range_empty_frac', 0.5::real,
'range_length_histogram', '{399,499,Infinity}'::text
@@ -529,8 +594,9 @@ AND attname = 'arange';
-- warn: range bounds histogram on scalar, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.31::real,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
@@ -545,8 +611,9 @@ AND attname = 'id';
-- ok: range_bounds_histogram
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
);
@@ -560,8 +627,9 @@ AND attname = 'arange';
-- warn: cannot set most_common_elems for range type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.32::real,
'most_common_elems', '{3,1}'::text,
@@ -577,8 +645,9 @@ AND attname = 'arange';
-- warn: scalars can't have mcelem, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.33::real,
'most_common_elems', '{1,3}'::text,
@@ -594,8 +663,9 @@ AND attname = 'id';
-- warn: mcelem / mcelem mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.34::real,
'most_common_elems', '{one,two}'::text
@@ -610,8 +680,9 @@ AND attname = 'tags';
-- warn: mcelem / mcelem null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.35::real,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
@@ -626,8 +697,9 @@ AND attname = 'tags';
-- ok: mcelem
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'most_common_elems', '{one,three}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
@@ -642,8 +714,9 @@ AND attname = 'tags';
-- warn: scalars can't have elem_count_histogram, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.36::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -690,8 +763,9 @@ SELECT s.schemaname, s.tablename, s.attname, s.inherited, r.*
FROM pg_catalog.pg_stats AS s
CROSS JOIN LATERAL
pg_catalog.pg_restore_attribute_stats(
- 'relation', ('stats_import.' || s.tablename || '_clone')::regclass,
- 'attname', s.attname,
+ 'schemaname', 'stats_import',
+ 'relname', s.tablename::text || '_clone',
+ 'attname', s.attname::text,
'inherited', s.inherited,
'version', 150000,
'null_frac', s.null_frac,
@@ -836,9 +910,10 @@ AND inherited = false
AND attname = 'arange';
SELECT pg_catalog.pg_clear_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean);
+ schemaname => 'stats_import',
+ relname => 'test',
+ attname => 'arange',
+ inherited => false);
SELECT COUNT(*)
FROM pg_stats
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index f97f0ce570a..4c5ef0604de 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -30331,22 +30331,24 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<structname>mytable</structname>:
<programlisting>
SELECT pg_restore_relation_stats(
- 'relation', 'mytable'::regclass,
- 'relpages', 173::integer,
- 'reltuples', 10000::real);
+ 'schemaname', 'myschema',
+ 'relname', 'mytable',
+ 'relpages', 173::integer,
+ 'reltuples', 10000::real);
</programlisting>
</para>
<para>
- The argument <literal>relation</literal> with a value of type
- <type>regclass</type> is required, and specifies the table. Other
- arguments are the names and values of statistics corresponding to
- certain columns in <link
+ The arguments <literal>schemaname</literal> with a value of type
+ <type>regclass</type> and <literal>relname</literal> are required,
+ and specifies the table. Other arguments are the names and values
+ of statistics corresponding to certain columns in <link
linkend="catalog-pg-class"><structname>pg_class</structname></link>.
The currently-supported relation statistics are
<literal>relpages</literal> with a value of type
<type>integer</type>, <literal>reltuples</literal> with a value of
- type <type>real</type>, and <literal>relallvisible</literal> with a
- value of type <type>integer</type>.
+ type <type>real</type>, <literal>relallvisible</literal> with a
+ value of type <type>integer</type>, and <literal>relallfrozen</literal>
+ with a value of type <type>integer</type>.
</para>
<para>
Additionally, this function accepts argument name
@@ -30374,7 +30376,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<indexterm>
<primary>pg_clear_relation_stats</primary>
</indexterm>
- <function>pg_clear_relation_stats</function> ( <parameter>relation</parameter> <type>regclass</type> )
+ <function>pg_clear_relation_stats</function> ( <parameter>schemaname</parameter> <type>text</type>, <parameter>relname</parameter> <type>text</type> )
<returnvalue>void</returnvalue>
</para>
<para>
@@ -30423,16 +30425,18 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<structname>mytable</structname>:
<programlisting>
SELECT pg_restore_attribute_stats(
- 'relation', 'mytable'::regclass,
- 'attname', 'col1'::name,
- 'inherited', false,
- 'avg_width', 125::integer,
- 'null_frac', 0.5::real);
+ 'schemaname', 'myschema',
+ 'relname', 'mytable',
+ 'attname', 'col1',
+ 'inherited', false,
+ 'avg_width', 125::integer,
+ 'null_frac', 0.5::real);
</programlisting>
</para>
<para>
- The required arguments are <literal>relation</literal> with a value
- of type <type>regclass</type>, which specifies the table; either
+ The required arguments are <literal>schemaname</literal> with a value
+ of type <type>regclass</type> and <literal>relname</literal> with a value
+ of type <type>text</type> which specify the table; either
<literal>attname</literal> with a value of type <type>name</type> or
<literal>attnum</literal> with a value of type <type>smallint</type>,
which specifies the column; and <literal>inherited</literal>, which
@@ -30468,7 +30472,8 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<primary>pg_clear_attribute_stats</primary>
</indexterm>
<function>pg_clear_attribute_stats</function> (
- <parameter>relation</parameter> <type>regclass</type>,
+ <parameter>schemaname</parameter> <type>text</type>,
+ <parameter>relname</parameter> <type>text</type>,
<parameter>attname</parameter> <type>name</type>,
<parameter>inherited</parameter> <type>boolean</type> )
<returnvalue>void</returnvalue>
--
2.48.1
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export: difference in statistics dumped
@ 2025-03-06 06:52 Jeff Davis <[email protected]>
parent: Ashutosh Bapat <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Jeff Davis @ 2025-03-06 06:52 UTC (permalink / raw)
To: Ashutosh Bapat <[email protected]>; +Cc: Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; Alvaro Herrera <[email protected]>; jian he <[email protected]>
On Wed, 2025-03-05 at 15:22 +0530, Ashutosh Bapat wrote:
> Hmm. Updating the statistics without consuming more CPU is more
> valuable when autovacuum is off it improves query plans with no extra
> efforts. But if adding a new mode is some significant work, riding it
> on top of autovacuum=off might ok. It's not documented either way, so
> we could change that behaviour later if we find it troublesome.
Sounds good. I will commit something like the v2 patch then soon, and
if we need a different condition we can change it later.
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 08:07 Jeff Davis <[email protected]>
parent: Corey Huinker <[email protected]>
2 siblings, 0 replies; 199+ messages in thread
From: Jeff Davis @ 2025-03-06 08:07 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; Andres Freund <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Wed, 2025-03-05 at 23:04 -0500, Corey Huinker wrote:
>
> Anyway, here's a rebased set of the existing up-for-consideration
> patches, plus the optimization of avoiding querying on non-expression
> indexes.
Patch 0001 contains a bug: it returns REQ_STATS early, before doing any
exclusions.
But I agree the previous code was hard to read in one place, and
redundant in another, so I will commit a fixup.
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 08:48 Jeff Davis <[email protected]>
parent: Corey Huinker <[email protected]>
2 siblings, 1 reply; 199+ messages in thread
From: Jeff Davis @ 2025-03-06 08:48 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; Andres Freund <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Wed, 2025-03-05 at 23:04 -0500, Corey Huinker wrote:
>
> Anyway, here's a rebased set of the existing up-for-consideration
> patches, plus the optimization of avoiding querying on non-expression
> indexes.
Comments on 0003:
* All the argument names for pg_restore_attribute_stats match pg_stats,
except relname vs tablename. There doesn't appear to be a great answer
here, because "relname" is the natural name to use for
pg_restore_relation_stats(), so either the two restore functions will
be inconsistent, or the argument name of one of them will be
inconsistent with its respective catalog. I assume that's the
reasoning?
* it decides to only issue a WARNING, rather than an ERROR, if the
table can't be found, which seems fine
* Now that it's doing a namespace lookup, we should also check for the
USAGE privilege on the namespace, right?
Based on the other changes we've made to this feature, I think 0003
makes sense, so I'm inclined to move ahead with it, but I'm open to
opinions.
0004 looks straightforward, though perhaps we should move some of the
code into a static function rather than indenting so many lines.
Did you collect performance results for 0004?
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 13:49 Corey Huinker <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Corey Huinker @ 2025-03-06 13:49 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Thu, Mar 6, 2025 at 3:48 AM Jeff Davis <[email protected]> wrote:
> On Wed, 2025-03-05 at 23:04 -0500, Corey Huinker wrote:
> >
> > Anyway, here's a rebased set of the existing up-for-consideration
> > patches, plus the optimization of avoiding querying on non-expression
> > indexes.
>
> Comments on 0003:
>
> * All the argument names for pg_restore_attribute_stats match pg_stats,
> except relname vs tablename. There doesn't appear to be a great answer
> here, because "relname" is the natural name to use for
> pg_restore_relation_stats(), so either the two restore functions will
> be inconsistent, or the argument name of one of them will be
> inconsistent with its respective catalog. I assume that's the
> reasoning?
>
Correct, either we use 'tablename' to describe indexes as well, or we
diverge from the system view's naming.
> * Now that it's doing a namespace lookup, we should also check for the
> USAGE privilege on the namespace, right?
>
Unless some check was being done by the 'foo.bar'::regclass cast, I
understand why we should add one.
> Based on the other changes we've made to this feature, I think 0003
> makes sense, so I'm inclined to move ahead with it, but I'm open to
> opinions.
>
If we do, we'll want to change downgrade the following errors to
warn+return false:
* stats_check_required_arg()
* stats_lock_check_privileges()
* RecoveryInProgress
* specified both attnum and argnum
* attname/attnum does not exist, or is system column
> 0004 looks straightforward, though perhaps we should move some of the
> code into a static function rather than indenting so many lines.
>
I agree, but the thread conversation had already shifted to doing just one
single call to pg_stats, so this was just a demonstration.
> Did you collect performance results for 0004?
No, as I wasn't sure that I could replicate Andres' setup, and the
conversation was quickly moving to the aforementioned single-query idea.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 14:29 Andres Freund <[email protected]>
parent: Corey Huinker <[email protected]>
2 siblings, 2 replies; 199+ messages in thread
From: Andres Freund @ 2025-03-06 14:29 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Jeff Davis <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Hi,
On 2025-03-05 23:04:29 -0500, Corey Huinker wrote:
> > > I'm uncertain how we'd do that with (schemaname,tablename) pairs. Are you
> > > suggesting we back the joins from pg_stats to pg_namespace and pg_class
> > and
> > > then filter by oids?
> >
> > I was thinking of one query per schema or something like that. But yea, a
> > query to pg_namespace and pg_class wouldn't be a problem if we did it far
> > fewer times than before. Or you could put the list of catalogs / tables
> > to
> > be queried into an unnest() with two arrays or such.
> >
> > Not sure how good the query plan for that would be, but it may be worth
> > looking at.
> >
>
> Ok, so we're willing to take the pg_class/pg_namespace join hit for one or
> a handful of queries, good to know.
It's a tradeoff that needs to be evaluated. But I'd be rather surprised if it
weren't faster to run one query with the additional joins than hundreds of
queries without them.
> > > Each call to getAttributeStats() fetches the pg_stats for one and only
> > one
> > > relation and then writes the SQL call to fout, then discards the result
> > set
> > > once all the attributes of the relation are done.
> >
> > I don't think that's true. For one my example demonstrated that it
> > increases
> > the peak memory usage substantially. That'd not be the case if the data was
> > just written out to stdout or such.
> >
> > Looking at the code confirms that. The ArchiveEntry() in
> > dumpRelationStats()
> > is never freed, afaict. And ArchiveEntry() strdups ->createStmt, which
> > contains the "SELECT pg_restore_attribute_stats(...)".
> >
>
> Pardon my inexperience, but aren't the ArchiveEntry records needed right up
> until the program's run?
s/the/the end of the/?
> If there's value in freeing them, why isn't it being done already? What
> other thing would consume this freed memory?
I'm not saying that they can be freed, they can't right now. My point is just
that we *already* keep all the stats in memory, so the fact that fetching all
stats in a single query would also require keeping them in memory is not an
issue.
But TBH, I do wonder how much the current memory usage of the statistics
dump/restore support is going to bite us. In some cases this will dramatically
increase pg_dump/pg_upgrade's memory usage, my tests were with tiny amounts of
data and very simple scalar datatypes and you already could see a substantial
increase. With something like postgis or even just a lot of jsonb columns
this is going to be way worse.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 16:15 Robert Haas <[email protected]>
parent: Andres Freund <[email protected]>
1 sibling, 2 replies; 199+ messages in thread
From: Robert Haas @ 2025-03-06 16:15 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Corey Huinker <[email protected]>; Nathan Bossart <[email protected]>; Jeff Davis <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Thu, Mar 6, 2025 at 9:29 AM Andres Freund <[email protected]> wrote:
> But TBH, I do wonder how much the current memory usage of the statistics
> dump/restore support is going to bite us. In some cases this will dramatically
> increase pg_dump/pg_upgrade's memory usage, my tests were with tiny amounts of
> data and very simple scalar datatypes and you already could see a substantial
> increase. With something like postgis or even just a lot of jsonb columns
> this is going to be way worse.
To be honest, I am a bit surprised that we decided to enable this by
default. It's not obvious to me that statistics should be regarded as
part of the database in the same way that table definitions or table
data are. That said, I'm not overwhelmingly opposed to that choice.
However, even if it's the right choice in theory, we should maybe
rethink if it's going to be too slow or use too much memory.
--
Robert Haas
EDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 17:04 Corey Huinker <[email protected]>
parent: Andres Freund <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Corey Huinker @ 2025-03-06 17:04 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Jeff Davis <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
>
>
>
> > Pardon my inexperience, but aren't the ArchiveEntry records needed right
> up
> > until the program's run?
>
> s/the/the end of the/?
>
yes
> > If there's value in freeing them, why isn't it being done already? What
> > other thing would consume this freed memory?
>
> I'm not saying that they can be freed, they can't right now. My point is
> just
> that we *already* keep all the stats in memory, so the fact that fetching
> all
> stats in a single query would also require keeping them in memory is not an
> issue.
>
That's true in cases where we're not filtering schemas or tables. We fetch
the pg_class stats as a part of getTables, but those are small, and not a
part of the query in question.
Fetching all the pg_stats for a db when we only want one table could be a
nasty performance regression, and we can't just filter on the oids of the
tables we want, because those tables can have expression indexes, so the
oid filter would get complicated quickly.
> But TBH, I do wonder how much the current memory usage of the statistics
> dump/restore support is going to bite us. In some cases this will
> dramatically
> increase pg_dump/pg_upgrade's memory usage, my tests were with tiny
> amounts of
> data and very simple scalar datatypes and you already could see a
> substantial
> increase. With something like postgis or even just a lot of jsonb columns
> this is going to be way worse.
>
Yes, it will cost us in pg_dump, but it will save customers from some long
ANALYZE operations.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 17:16 Andres Freund <[email protected]>
parent: Corey Huinker <[email protected]>
0 siblings, 2 replies; 199+ messages in thread
From: Andres Freund @ 2025-03-06 17:16 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Jeff Davis <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Hi,
On 2025-03-06 12:04:25 -0500, Corey Huinker wrote:
> > > If there's value in freeing them, why isn't it being done already? What
> > > other thing would consume this freed memory?
> >
> > I'm not saying that they can be freed, they can't right now. My point is
> > just
> > that we *already* keep all the stats in memory, so the fact that fetching
> > all
> > stats in a single query would also require keeping them in memory is not an
> > issue.
> >
>
> That's true in cases where we're not filtering schemas or tables. We fetch
> the pg_class stats as a part of getTables, but those are small, and not a
> part of the query in question.
>
> Fetching all the pg_stats for a db when we only want one table could be a
> nasty performance regression
I don't think anybody argued that we should fetch all stats regardless of
filtering for the to-be-dumped tables.
> and we can't just filter on the oids of the tables we want, because those
> tables can have expression indexes, so the oid filter would get complicated
> quickly.
I don't follow. We already have the tablenames, schemanames and oids of the
to-be-dumped tables/indexes collected in pg_dump, all that's needed is to send
a list of those to the server to filter there?
> > But TBH, I do wonder how much the current memory usage of the statistics
> > dump/restore support is going to bite us. In some cases this will
> > dramatically
> > increase pg_dump/pg_upgrade's memory usage, my tests were with tiny
> > amounts of
> > data and very simple scalar datatypes and you already could see a
> > substantial
> > increase. With something like postgis or even just a lot of jsonb columns
> > this is going to be way worse.
> >
>
> Yes, it will cost us in pg_dump, but it will save customers from some long
> ANALYZE operations.
My concern is that it might prevent some upgrades from *ever* completing,
because of pg_dump running out of memory.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 17:16 Corey Huinker <[email protected]>
parent: Robert Haas <[email protected]>
1 sibling, 2 replies; 199+ messages in thread
From: Corey Huinker @ 2025-03-06 17:16 UTC (permalink / raw)
To: Robert Haas <[email protected]>; +Cc: Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Jeff Davis <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
>
> To be honest, I am a bit surprised that we decided to enable this by
> default. It's not obvious to me that statistics should be regarded as
> part of the database in the same way that table definitions or table
> data are. That said, I'm not overwhelmingly opposed to that choice.
> However, even if it's the right choice in theory, we should maybe
> rethink if it's going to be too slow or use too much memory.
>
I'm strongly in favor of the choice to make it default. This is reducing
the impact of a post-upgrade customer footgun wherein heavy workloads are
applied to a database post-upgrade but before analyze/vacuumdb have had a
chance to do their magic [1].
It seems to me that we're fretting over seconds when the feature is
potentially saving the customer hours of reduced availability if not
outright downtime.
[1] In that situation, the workload queries have no stats, get terrible
plans, everything becomes a sequential scan. Sequential scans swamp the
system, starving the analyze commands of the I/O they need to get the badly
needed statistics. Even after the stats are in place, the system is still
swamped with queries that were in flight before the stats were in place.
Even well intentioned customers [2] can fall prey to this when their
microservices detect that the database is online again, and automatically
resume work.
[2] This exact situation happened at a place where I was consulting. The
microservices all restarted work automatically despite assurances that they
would not. That bad experience was my primary motivator for implementing
theis feature.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 17:59 Jeff Davis <[email protected]>
parent: Corey Huinker <[email protected]>
1 sibling, 0 replies; 199+ messages in thread
From: Jeff Davis @ 2025-03-06 17:59 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; Robert Haas <[email protected]>; +Cc: Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Thu, 2025-03-06 at 12:16 -0500, Corey Huinker wrote:
>
> I'm strongly in favor of the choice to make it default. This is
> reducing the impact of a post-upgrade
There are potentially two different defaults: pg_dump and pg_upgrade.
In any case, let's see what improvements we can make to memory usage
and performance, and take it from there.
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 18:00 Corey Huinker <[email protected]>
parent: Andres Freund <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Corey Huinker @ 2025-03-06 18:00 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Jeff Davis <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
>
> I don't follow. We already have the tablenames, schemanames and oids of the
> to-be-dumped tables/indexes collected in pg_dump, all that's needed is to
> send
> a list of those to the server to filter there?
>
Do we have something that currently does that? All of the collect functions
(collectComments, etc) take an unfiltered approach. Seems like we'd have to
collect the stats sometime after ProcessArchiveRestoreOptions, which is
significantly after the rest of them.
> My concern is that it might prevent some upgrades from *ever* completing,
> because of pg_dump running out of memory.
Obviously a valid concern.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 18:04 Andres Freund <[email protected]>
parent: Corey Huinker <[email protected]>
1 sibling, 3 replies; 199+ messages in thread
From: Andres Freund @ 2025-03-06 18:04 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Robert Haas <[email protected]>; Nathan Bossart <[email protected]>; Jeff Davis <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Hi,
On 2025-03-06 12:16:44 -0500, Corey Huinker wrote:
> >
> > To be honest, I am a bit surprised that we decided to enable this by
> > default. It's not obvious to me that statistics should be regarded as
> > part of the database in the same way that table definitions or table
> > data are. That said, I'm not overwhelmingly opposed to that choice.
> > However, even if it's the right choice in theory, we should maybe
> > rethink if it's going to be too slow or use too much memory.
> >
>
> I'm strongly in favor of the choice to make it default. This is reducing
> the impact of a post-upgrade customer footgun wherein heavy workloads are
> applied to a database post-upgrade but before analyze/vacuumdb have had a
> chance to do their magic [1].
To be clear, I think this is a very important improvement that most people
should use. I just don't think it's quite there yet.
> It seems to me that we're fretting over seconds when the feature is
> potentially saving the customer hours of reduced availability if not
> outright downtime.
FWIW, I care about the performance for two reasons:
1) It's a difference of seconds in the regression database, which has a few
hundred tables, few columns, very little data and thus small stats. In a
database with a lot of tables and columns with complicated datatypes the
difference will be far larger.
And in contrast to analyzing the database in parallel, the pg_dump/restore
work to restore stats afaict happens single-threaded for each database.
2) The changes initially substantially increased the time a test cycle takes
for me locally. I run the tests 10s to 100s time a day, that really adds
up.
002_pg_upgrade is the test that dominates the overall test time for me, so
it getting slower by a good bit means the overall test time increased.
1fd1bd87101^:
total test time: 1m27.010s
003_pg_upgrade alone: 1m6.309s
1fd1bd87101:
total test time: 1m45.945s
003_pg_upgrade alone: 1m24.597s
master at 0f21db36d66:
total test time: 1m34.576s
003_pg_upgrade alone: 1m12.550s
It clearly got a lot better since 1fd1bd87101, but it's still ~9% slower
than before...
I care about the memory usage effects because I've seen plenty systems where
pg_statistics is many gigabytes (after toast compression!), and I am really
worried that pg_dump having all the serialized strings in memory will cause a
lot of previously working pg_dump invocations and pg_upgrades to fail. That'd
also be a really bad experience.
The more I think about it, the less correct it seems to me to have the
statement to restore statistics tracked via ArchiveOpts->createStmt. We use
that for DDL, but this really is data, not DDL. Because we store it in
->createStmt it's stored in-memory for the runtime of pg_dump, which means the
peak memory usage will inherently be quite high.
I think the stats need to be handled much more like we handle the actual table
data, which are obviously *not* stored in memory for the whole run of pg_dump.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 18:07 Jeff Davis <[email protected]>
parent: Andres Freund <[email protected]>
1 sibling, 2 replies; 199+ messages in thread
From: Jeff Davis @ 2025-03-06 18:07 UTC (permalink / raw)
To: Andres Freund <[email protected]>; Corey Huinker <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Thu, 2025-03-06 at 12:16 -0500, Andres Freund wrote:
> I don't follow. We already have the tablenames, schemanames and oids
> of the
> to-be-dumped tables/indexes collected in pg_dump, all that's needed
> is to send
> a list of those to the server to filter there?
Would it be appropriate to create a temp table? I wouldn't normally
expect pg_dump to create temp tables, but I can't think of a major
reason not to.
If not, did you have in mind a CTE with a large VALUES expression, or
just a giant IN() list?
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 18:08 Andres Freund <[email protected]>
parent: Corey Huinker <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Andres Freund @ 2025-03-06 18:08 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Jeff Davis <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Hi,
On 2025-03-06 13:00:07 -0500, Corey Huinker wrote:
> >
> > I don't follow. We already have the tablenames, schemanames and oids of the
> > to-be-dumped tables/indexes collected in pg_dump, all that's needed is to
> > send
> > a list of those to the server to filter there?
> >
>
> Do we have something that currently does that?
Yes. Afaict there's at least:
- getPolicies()
- getIndexes()
- getConstraints()
- getTriggers(),
- getTableAttrs()
They all send an array of oids as part of the query and then join an
unnest()ed version of the array against whatever they're collecting. See
e.g. getPolicies():
"FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n"
"JOIN pg_catalog.pg_policy pol ON (src.tbloid = pol.polrelid)",
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 18:22 Nathan Bossart <[email protected]>
parent: Andres Freund <[email protected]>
2 siblings, 0 replies; 199+ messages in thread
From: Nathan Bossart @ 2025-03-06 18:22 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Corey Huinker <[email protected]>; Robert Haas <[email protected]>; Jeff Davis <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Thu, Mar 06, 2025 at 01:04:55PM -0500, Andres Freund wrote:
> To be clear, I think this is a very important improvement that most people
> should use.
+1
> I just don't think it's quite there yet.
I agree that we should continue working on the performance/memory stuff.
> 1) It's a difference of seconds in the regression database, which has a few
> hundred tables, few columns, very little data and thus small stats. In a
> database with a lot of tables and columns with complicated datatypes the
> difference will be far larger.
>
> And in contrast to analyzing the database in parallel, the pg_dump/restore
> work to restore stats afaict happens single-threaded for each database.
Yeah, I did a lot of work in v18 to rein in pg_dump --binary-upgrade
runtime, and I'm a bit worried that this will undo much of that. Obviously
it's going to increase runtime by some amount, which is acceptable, but it
needs to be within reason. I'm optimistic this is within reach for v18 by
reducing the number of queries.
> I care about the memory usage effects because I've seen plenty systems where
> pg_statistics is many gigabytes (after toast compression!), and I am really
> worried that pg_dump having all the serialized strings in memory will cause a
> lot of previously working pg_dump invocations and pg_upgrades to fail. That'd
> also be a really bad experience.
I think it is entirely warranted to consider these cases. IME cases of "a
million tables" or "a million sequences" are far more common than you might
think.
--
nathan
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 18:23 Corey Huinker <[email protected]>
parent: Jeff Davis <[email protected]>
1 sibling, 0 replies; 199+ messages in thread
From: Corey Huinker @ 2025-03-06 18:23 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
>
> Would it be appropriate to create a temp table? I wouldn't normally
> expect pg_dump to create temp tables, but I can't think of a major
> reason not to.
>
I think we can't - the db might be a replica.
>
> If not, did you have in mind a CTE with a large VALUES expression, or
> just a giant IN() list?
getPolicies - as Andres cited right after your post.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 18:23 Andres Freund <[email protected]>
parent: Jeff Davis <[email protected]>
1 sibling, 0 replies; 199+ messages in thread
From: Andres Freund @ 2025-03-06 18:23 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Corey Huinker <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Hi,
On 2025-03-06 10:07:43 -0800, Jeff Davis wrote:
> On Thu, 2025-03-06 at 12:16 -0500, Andres Freund wrote:
> > I don't follow. We already have the tablenames, schemanames and oids
> > of the
> > to-be-dumped tables/indexes collected in pg_dump, all that's needed
> > is to send
> > a list of those to the server to filter there?
>
> Would it be appropriate to create a temp table? I wouldn't normally
> expect pg_dump to create temp tables, but I can't think of a major
> reason not to.
It doesn't work on a standby.
> If not, did you have in mind a CTE with a large VALUES expression, or
> just a giant IN() list?
An array, with a server-side unnest(), like we do in a bunch of other
places. E.g.
/* need left join to pg_type to not fail on dropped columns ... */
appendPQExpBuffer(q,
"FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n"
"JOIN pg_catalog.pg_attribute a ON (src.tbloid = a.attrelid) "
"LEFT JOIN pg_catalog.pg_type t "
"ON (a.atttypid = t.oid)\n",
tbloids->data);
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 18:47 Tom Lane <[email protected]>
parent: Andres Freund <[email protected]>
2 siblings, 2 replies; 199+ messages in thread
From: Tom Lane @ 2025-03-06 18:47 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Corey Huinker <[email protected]>; Robert Haas <[email protected]>; Nathan Bossart <[email protected]>; Jeff Davis <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Andres Freund <[email protected]> writes:
> And in contrast to analyzing the database in parallel, the pg_dump/restore
> work to restore stats afaict happens single-threaded for each database.
In principle we should be able to do stats dump/restore parallelized
just as we do for data. There are some stumbling blocks in the way
of that:
1. pg_upgrade has made a policy judgement to apply parallelism across
databases not within a database, ie it will launch concurrent dump/
restore tasks in different DBs but not authorize any one of them to
eat multiple CPUs. That needs to be re-thought probably, as I think
that decision dates to before we had useful parallelism in pg_dump and
pg_restore. I wonder if we could just rip out pg_upgrade's support
for DB-level parallelism, which is not terribly pretty anyway, and
simply pass the -j switch straight to pg_dump and pg_restore.
2. pg_restore should already be able to perform stats restores in
parallel (if authorized to use multiple threads), but I'm less clear
on whether that works right now for pg_dump.
3. Also, parallel restore depends critically on the TOC entries'
dependencies being sane, and right now I do not think they are.
I looked at "pg_restore -l -v" output for the regression DB, and it
seems like it's not taking care to ensure that table/MV data is loaded
before the table/MV's stats. (Maybe that accounts for some of the
complaints we've seen about stats getting mangled??)
> I think the stats need to be handled much more like we handle the actual table
> data, which are obviously *not* stored in memory for the whole run of pg_dump.
+1
regards, tom lane
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 18:47 Corey Huinker <[email protected]>
parent: Andres Freund <[email protected]>
2 siblings, 1 reply; 199+ messages in thread
From: Corey Huinker @ 2025-03-06 18:47 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Robert Haas <[email protected]>; Nathan Bossart <[email protected]>; Jeff Davis <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
>
>
> The more I think about it, the less correct it seems to me to have the
> statement to restore statistics tracked via ArchiveOpts->createStmt. We
> use
> that for DDL, but this really is data, not DDL. Because we store it in
> ->createStmt it's stored in-memory for the runtime of pg_dump, which means
> the
> peak memory usage will inherently be quite high.
>
> I think the stats need to be handled much more like we handle the actual
> table
> data, which are obviously *not* stored in memory for the whole run of
> pg_dump.
>
I'm at the same conclusion. This would mean keeping the one
getAttributeStats query perrelation, but at least we'd be able to free up
the result after we write it to disk.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 19:04 Andres Freund <[email protected]>
parent: Corey Huinker <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Andres Freund @ 2025-03-06 19:04 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Robert Haas <[email protected]>; Nathan Bossart <[email protected]>; Jeff Davis <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On 2025-03-06 13:47:51 -0500, Corey Huinker wrote:
> I'm at the same conclusion. This would mean keeping the one
> getAttributeStats query perrelation,
Why does it have to mean that? It surely would be easier with separate
queries, but I don't think there's anything inherently blocking us from doing
something in a more batch-y fashion.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 19:08 Tom Lane <[email protected]>
parent: Andres Freund <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Tom Lane @ 2025-03-06 19:08 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Corey Huinker <[email protected]>; Robert Haas <[email protected]>; Nathan Bossart <[email protected]>; Jeff Davis <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Andres Freund <[email protected]> writes:
> On 2025-03-06 13:47:51 -0500, Corey Huinker wrote:
>> I'm at the same conclusion. This would mean keeping the one
>> getAttributeStats query perrelation,
> Why does it have to mean that? It surely would be easier with separate
> queries, but I don't think there's anything inherently blocking us from doing
> something in a more batch-y fashion.
Complexity? pg_dump doesn't have anything like that at the moment,
and I'm loath to start inventing such facilities at this point in
the release cycle. Let's deal with the blockers for parallelizing
dump and restore of stats, and then see where we are performance-wise.
regards, tom lane
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 19:33 Nathan Bossart <[email protected]>
parent: Tom Lane <[email protected]>
1 sibling, 0 replies; 199+ messages in thread
From: Nathan Bossart @ 2025-03-06 19:33 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Andres Freund <[email protected]>; Corey Huinker <[email protected]>; Robert Haas <[email protected]>; Jeff Davis <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Thu, Mar 06, 2025 at 01:47:34PM -0500, Tom Lane wrote:
> 1. pg_upgrade has made a policy judgement to apply parallelism across
> databases not within a database, ie it will launch concurrent dump/
> restore tasks in different DBs but not authorize any one of them to
> eat multiple CPUs. That needs to be re-thought probably, as I think
> that decision dates to before we had useful parallelism in pg_dump and
> pg_restore. I wonder if we could just rip out pg_upgrade's support
> for DB-level parallelism, which is not terribly pretty anyway, and
> simply pass the -j switch straight to pg_dump and pg_restore.
That would certainly help for clusters with one big database with many LOs
or something, but I worry it would hurt the many database case quite a bit.
Maybe we could add a --jobs-per-db option that indicates how to parallelize
dump/restore. If you set --jobs=8 --jobs-per-db=8, the databases would be
dumped serially, but pg_dump would get -j8. If you set --jobs=8 and
--jobs-per-db=2, we'd process 4 databases at a time, each with -j2.
--
nathan
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 19:34 Andres Freund <[email protected]>
parent: Tom Lane <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Andres Freund @ 2025-03-06 19:34 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Corey Huinker <[email protected]>; Robert Haas <[email protected]>; Nathan Bossart <[email protected]>; Jeff Davis <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Hi,
On 2025-03-06 13:47:34 -0500, Tom Lane wrote:
> Andres Freund <[email protected]> writes:
> > And in contrast to analyzing the database in parallel, the pg_dump/restore
> > work to restore stats afaict happens single-threaded for each database.
>
> In principle we should be able to do stats dump/restore parallelized
> just as we do for data.
Yea.
Whether the gains are worth the cost isn't clear to me though. Issuing
individual queries for each relation needs a fair bit of parallelism to catch
up to doing the dumping in a single statement, if it ever can.
> 1. pg_upgrade has made a policy judgement to apply parallelism across
> databases not within a database, ie it will launch concurrent dump/
> restore tasks in different DBs but not authorize any one of them to
> eat multiple CPUs. That needs to be re-thought probably, as I think
> that decision dates to before we had useful parallelism in pg_dump and
> pg_restore. I wonder if we could just rip out pg_upgrade's support
> for DB-level parallelism, which is not terribly pretty anyway, and
> simply pass the -j switch straight to pg_dump and pg_restore.
I don't think that'd work well, right now pg_dump only handles a single
database (pg_dumpall doesn't yet support -Fc) *and* pg_dump is still serial
for the bulk of the work that pg_upgrade cares about.
I think the only parallelism that'd actually happen for pg_upgrade would be
dumping of large objects?
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-06 19:51 Tom Lane <[email protected]>
parent: Andres Freund <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Tom Lane @ 2025-03-06 19:51 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Corey Huinker <[email protected]>; Robert Haas <[email protected]>; Nathan Bossart <[email protected]>; Jeff Davis <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Andres Freund <[email protected]> writes:
> On 2025-03-06 13:47:34 -0500, Tom Lane wrote:
>> ... I wonder if we could just rip out pg_upgrade's support
>> for DB-level parallelism, which is not terribly pretty anyway, and
>> simply pass the -j switch straight to pg_dump and pg_restore.
> I don't think that'd work well, right now pg_dump only handles a single
> database (pg_dumpall doesn't yet support -Fc) *and* pg_dump is still serial
> for the bulk of the work that pg_upgrade cares about.
> I think the only parallelism that'd actually happen for pg_upgrade would be
> dumping of large objects?
Uh ... the entire point here is that we'd be trying to parallelize its
dumping of stats, no? Most DBs will have enough of those to be
interesting, I should think.
regards, tom lane
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-07 00:58 Jeff Davis <[email protected]>
parent: Corey Huinker <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Jeff Davis @ 2025-03-07 00:58 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Thu, 2025-03-06 at 08:49 -0500, Corey Huinker wrote:
> Unless some check was being done by the 'foo.bar'::regclass cast, I
> understand why we should add one.
"For schemas, allows access to objects contained in the schema
(assuming that the objects' own privilege requirements are also met).
Essentially this allows the grantee to “look up” objects within the
schema. Without this permission, it is still possible to see the object
names, e.g., by querying system catalogs. Also, after revoking this
permission, existing sessions might have statements that have
previously performed this lookup, so this is not a completely secure
way to prevent object access."
https://www.postgresql.org/docs/current/ddl-priv.html
The above text indicates that we should do the check, but also that
it's not terribly important for actual security.
> If we do, we'll want to change downgrade the following errors to
> warn+return false:
Perhaps we should consider the schemaname/relname change as one patch,
which maintains relation lookup failures as hard ERRORs, and a
"downgrade errors to warnings" as a separate patch.
> I agree, but the thread conversation had already shifted to doing
> just one single call to pg_stats, so this was just a demonstration.
It's a simple patch and the discussion seems to be shifting toward
parallelism[1] rather than batching[2]. In that case it still seems
like a good change to me, so I'm inclined to commit it after I verify
that it improves performance.
Regards,
Jeff Davis
[1]
https://www.postgresql.org/message-id/[email protected]
[2] https://www.postgresql.org/message-id/[email protected]
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-07 01:42 Jeff Davis <[email protected]>
parent: Robert Haas <[email protected]>
1 sibling, 3 replies; 199+ messages in thread
From: Jeff Davis @ 2025-03-07 01:42 UTC (permalink / raw)
To: Robert Haas <[email protected]>; Andres Freund <[email protected]>; +Cc: Corey Huinker <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Thu, 2025-03-06 at 11:15 -0500, Robert Haas wrote:
> To be honest, I am a bit surprised that we decided to enable this by
> default. It's not obvious to me that statistics should be regarded as
> part of the database in the same way that table definitions or table
> data are. That said, I'm not overwhelmingly opposed to that choice.
> However, even if it's the right choice in theory, we should maybe
> rethink if it's going to be too slow or use too much memory.
I don't have a strong opinion about whether stats will be opt-out or
opt-in for v18, but if they are opt-in, we would need to adjust the
available options a bit.
At minimum, we would need to at least add the option "--with-
statistics", because right now the only way to explicitly request stats
is to say "--statistics-only".
To generalize this concept: for each of {schema, data, stats} users
might want "yes", "no", or "only".
If we use this options scheme, it would be easy to change the default
for stats independently of the other options, if necessary, without
surprising consequences.
Patch attached. This patch does NOT change the default; stats are still
opt-out. But it makes it easier for users to start specifying what they
want or not explicitly, or to rely on the defaults if they prefer.
Note that the patch would mean we go from 2 options in v17:
--{schema|data}-only
to 9 options in v18:
--{with|no}-{schema|data|stats} and
--{schema|data|stats}-only
I suggest we adjust the options now with something resembling the
attached patch and decide on changing the default sometime during beta.
Regards,
Jeff Davis
Attachments:
[text/x-patch] v1-0001-Add-pg_dump-with-X-options.patch (13.0K, ../../[email protected]/2-v1-0001-Add-pg_dump-with-X-options.patch)
download | inline diff:
From c47fc9e570ddd083097f4bfc708465cf644f48c2 Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Thu, 6 Mar 2025 17:35:41 -0800
Subject: [PATCH v1] Add pg_dump --with-X options.
---
doc/src/sgml/ref/pg_dump.sgml | 27 +++++++++++++++++++
doc/src/sgml/ref/pg_dumpall.sgml | 27 +++++++++++++++++++
doc/src/sgml/ref/pg_restore.sgml | 27 +++++++++++++++++++
src/bin/pg_dump/pg_dump.c | 46 +++++++++++++++++++++++++++++---
src/bin/pg_dump/pg_dumpall.c | 12 +++++++++
src/bin/pg_dump/pg_restore.c | 44 +++++++++++++++++++++++++-----
6 files changed, 173 insertions(+), 10 deletions(-)
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index 1975054d7bf..9eba285687e 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1223,6 +1223,33 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--with-data</option></term>
+ <listitem>
+ <para>
+ Dump data. This is the default.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--with-schema</option></term>
+ <listitem>
+ <para>
+ Dump schema (data definitions). This is the default.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--with-statistics</option></term>
+ <listitem>
+ <para>
+ Dump statistics. This is the default.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--on-conflict-do-nothing</option></term>
<listitem>
diff --git a/doc/src/sgml/ref/pg_dumpall.sgml b/doc/src/sgml/ref/pg_dumpall.sgml
index c2fa5be9519..45f127f0dc9 100644
--- a/doc/src/sgml/ref/pg_dumpall.sgml
+++ b/doc/src/sgml/ref/pg_dumpall.sgml
@@ -551,6 +551,33 @@ exclude database <replaceable class="parameter">PATTERN</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--with-data</option></term>
+ <listitem>
+ <para>
+ Dump data. This is the default.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--with-schema</option></term>
+ <listitem>
+ <para>
+ Dump schema (data definitions). This is the default.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--with-statistics</option></term>
+ <listitem>
+ <para>
+ Dump statistics. This is the default.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--no-unlogged-table-data</option></term>
<listitem>
diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml
index 199ea3345f3..51e6411c8fe 100644
--- a/doc/src/sgml/ref/pg_restore.sgml
+++ b/doc/src/sgml/ref/pg_restore.sgml
@@ -795,6 +795,33 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--with-data</option></term>
+ <listitem>
+ <para>
+ Dump data. This is the default.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--with-schema</option></term>
+ <listitem>
+ <para>
+ Dump schema (data definitions). This is the default.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--with-statistics</option></term>
+ <listitem>
+ <para>
+ Dump statistics. This is the default.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--section=<replaceable class="parameter">sectionname</replaceable></option></term>
<listitem>
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4f4ad2ee150..31c4ac1ee57 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -433,6 +433,9 @@ main(int argc, char **argv)
bool data_only = false;
bool schema_only = false;
bool statistics_only = false;
+ bool with_data = false;
+ bool with_schema = false;
+ bool with_statistics = false;
bool no_data = false;
bool no_schema = false;
bool no_statistics = false;
@@ -508,6 +511,9 @@ main(int argc, char **argv)
{"no-toast-compression", no_argument, &dopt.no_toast_compression, 1},
{"no-unlogged-table-data", no_argument, &dopt.no_unlogged_table_data, 1},
{"no-sync", no_argument, NULL, 7},
+ {"with-data", no_argument, NULL, 22},
+ {"with-schema", no_argument, NULL, 23},
+ {"with-statistics", no_argument, NULL, 24},
{"on-conflict-do-nothing", no_argument, &dopt.do_nothing, 1},
{"rows-per-insert", required_argument, NULL, 10},
{"include-foreign-data", required_argument, NULL, 11},
@@ -776,6 +782,18 @@ main(int argc, char **argv)
no_statistics = true;
break;
+ case 22:
+ with_data = true;
+ break;
+
+ case 23:
+ with_schema = true;
+ break;
+
+ case 24:
+ with_statistics = true;
+ break;
+
default:
/* getopt_long already emitted a complaint */
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -811,6 +829,7 @@ main(int argc, char **argv)
if (dopt.binary_upgrade)
dopt.sequence_data = 1;
+ /* reject conflicting "-only" options */
if (data_only && schema_only)
pg_fatal("options -s/--schema-only and -a/--data-only cannot be used together");
if (schema_only && statistics_only)
@@ -818,6 +837,7 @@ main(int argc, char **argv)
if (data_only && statistics_only)
pg_fatal("options -a/--data-only and --statistics-only cannot be used together");
+ /* reject conflicting "-only" and "no-" options */
if (data_only && no_data)
pg_fatal("options -a/--data-only and --no-data cannot be used together");
if (schema_only && no_schema)
@@ -825,6 +845,14 @@ main(int argc, char **argv)
if (statistics_only && no_statistics)
pg_fatal("options --statistics-only and --no-statistics cannot be used together");
+ /* reject conflicting "with-" and "no-" options */
+ if (with_data && no_data)
+ pg_fatal("options --with-data and --no-data cannot be used together");
+ if (with_schema && no_schema)
+ pg_fatal("options --with-schema and --no-schema cannot be used together");
+ if (with_statistics && no_statistics)
+ pg_fatal("options --with-statistics and --no-statistics cannot be used together");
+
if (schema_only && foreign_servers_include_patterns.head != NULL)
pg_fatal("options -s/--schema-only and --include-foreign-data cannot be used together");
@@ -837,10 +865,20 @@ main(int argc, char **argv)
if (dopt.if_exists && !dopt.outputClean)
pg_fatal("option --if-exists requires option -c/--clean");
- /* set derivative flags */
- dopt.dumpData = data_only || (!schema_only && !statistics_only && !no_data);
- dopt.dumpSchema = schema_only || (!data_only && !statistics_only && !no_schema);
- dopt.dumpStatistics = statistics_only || (!data_only && !schema_only && !no_statistics);
+ /*
+ * Set derivative flags. An "-only" option may be overridden by an
+ * explicit "with-" option; e.g. "--schema-only --with-statistics" will
+ * include schema and statistics. Other ambiguous or nonsensical
+ * combinations, e.g. "--schema-only --no-schema", will have already
+ * caused an error in one of the checks above.
+ */
+ dopt.dumpData = ((dopt.dumpData && !schema_only && !statistics_only) ||
+ (data_only || with_data)) && !no_data;
+ dopt.dumpSchema = ((dopt.dumpSchema && !data_only && !statistics_only) ||
+ (schema_only || with_schema)) && !no_schema;
+ dopt.dumpStatistics = ((dopt.dumpStatistics && !schema_only && !data_only) ||
+ (statistics_only || with_statistics)) && !no_statistics;
+
/*
* --inserts are already implied above if --column-inserts or
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index e0867242526..a7e8c0d2ad5 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -110,6 +110,9 @@ static int no_subscriptions = 0;
static int no_toast_compression = 0;
static int no_unlogged_table_data = 0;
static int no_role_passwords = 0;
+static int with_data = 0;
+static int with_schema = 0;
+static int with_statistics = 0;
static int server_version;
static int load_via_partition_root = 0;
static int on_conflict_do_nothing = 0;
@@ -182,6 +185,9 @@ main(int argc, char *argv[])
{"no-sync", no_argument, NULL, 4},
{"no-toast-compression", no_argument, &no_toast_compression, 1},
{"no-unlogged-table-data", no_argument, &no_unlogged_table_data, 1},
+ {"with-data", no_argument, &with_data, 1},
+ {"with-schema", no_argument, &with_schema, 1},
+ {"with-statistics", no_argument, &with_statistics, 1},
{"on-conflict-do-nothing", no_argument, &on_conflict_do_nothing, 1},
{"rows-per-insert", required_argument, NULL, 7},
{"statistics-only", no_argument, &statistics_only, 1},
@@ -471,6 +477,12 @@ main(int argc, char *argv[])
appendPQExpBufferStr(pgdumpopts, " --no-toast-compression");
if (no_unlogged_table_data)
appendPQExpBufferStr(pgdumpopts, " --no-unlogged-table-data");
+ if (with_data)
+ appendPQExpBufferStr(pgdumpopts, " --with-data");
+ if (with_schema)
+ appendPQExpBufferStr(pgdumpopts, " --with-schema");
+ if (with_statistics)
+ appendPQExpBufferStr(pgdumpopts, " --with-statistics");
if (on_conflict_do_nothing)
appendPQExpBufferStr(pgdumpopts, " --on-conflict-do-nothing");
if (statistics_only)
diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c
index 13e4dc507e0..f22046127b7 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -81,6 +81,9 @@ main(int argc, char **argv)
static int no_subscriptions = 0;
static int strict_names = 0;
static int statistics_only = 0;
+ static int with_data = 0;
+ static int with_schema = 0;
+ static int with_statistics = 0;
struct option cmdopts[] = {
{"clean", 0, NULL, 'c'},
@@ -134,6 +137,9 @@ main(int argc, char **argv)
{"no-security-labels", no_argument, &no_security_labels, 1},
{"no-subscriptions", no_argument, &no_subscriptions, 1},
{"no-statistics", no_argument, &no_statistics, 1},
+ {"with-data", no_argument, &with_data, 1},
+ {"with-schema", no_argument, &with_schema, 1},
+ {"with-statistics", no_argument, &with_statistics, 1},
{"statistics-only", no_argument, &statistics_only, 1},
{"filter", required_argument, NULL, 4},
@@ -349,12 +355,29 @@ main(int argc, char **argv)
opts->useDB = 1;
}
+ /* reject conflicting "-only" options */
if (data_only && schema_only)
pg_fatal("options -s/--schema-only and -a/--data-only cannot be used together");
- if (data_only && statistics_only)
- pg_fatal("options -a/--data-only and --statistics-only cannot be used together");
if (schema_only && statistics_only)
pg_fatal("options -s/--schema-only and --statistics-only cannot be used together");
+ if (data_only && statistics_only)
+ pg_fatal("options -a/--data-only and --statistics-only cannot be used together");
+
+ /* reject conflicting "-only" and "no-" options */
+ if (data_only && no_data)
+ pg_fatal("options -a/--data-only and --no-data cannot be used together");
+ if (schema_only && no_schema)
+ pg_fatal("options -s/--schema-only and --no-schema cannot be used together");
+ if (statistics_only && no_statistics)
+ pg_fatal("options --statistics-only and --no-statistics cannot be used together");
+
+ /* reject conflicting "with-" and "no-" options */
+ if (with_data && no_data)
+ pg_fatal("options --with-data and --no-data cannot be used together");
+ if (with_schema && no_schema)
+ pg_fatal("options --with-schema and --no-schema cannot be used together");
+ if (with_statistics && no_statistics)
+ pg_fatal("options --with-statistics and --no-statistics cannot be used together");
if (data_only && opts->dropSchema)
pg_fatal("options -c/--clean and -a/--data-only cannot be used together");
@@ -373,10 +396,19 @@ main(int argc, char **argv)
if (opts->single_txn && numWorkers > 1)
pg_fatal("cannot specify both --single-transaction and multiple jobs");
- /* set derivative flags */
- opts->dumpData = data_only || (!no_data && !schema_only && !statistics_only);
- opts->dumpSchema = schema_only || (!no_schema && !data_only && !statistics_only);
- opts->dumpStatistics = statistics_only || (!no_statistics && !data_only && !schema_only);
+ /*
+ * Set derivative flags. An "-only" option may be overridden by an
+ * explicit "with-" option; e.g. "--schema-only --with-statistics" will
+ * include schema and statistics. Other ambiguous or nonsensical
+ * combinations, e.g. "--schema-only --no-schema", will have already
+ * caused an error in one of the checks above.
+ */
+ opts->dumpData = ((opts->dumpData && !schema_only && !statistics_only) ||
+ (data_only || with_data)) && !no_data;
+ opts->dumpSchema = ((opts->dumpSchema && !data_only && !statistics_only) ||
+ (schema_only || with_schema)) && !no_schema;
+ opts->dumpStatistics = ((opts->dumpStatistics && !schema_only && !data_only) ||
+ (statistics_only || with_statistics)) && !no_statistics;
opts->disable_triggers = disable_triggers;
opts->enable_row_security = enable_row_security;
--
2.34.1
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-07 01:47 Corey Huinker <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Corey Huinker @ 2025-03-07 01:47 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
>
>
> https://www.postgresql.org/docs/current/ddl-priv.html
>
> The above text indicates that we should do the check, but also that
> it's not terribly important for actual security.
>
Ok, I'm convinced.
>
> > If we do, we'll want to change downgrade the following errors to
> > warn+return false:
>
> Perhaps we should consider the schemaname/relname change as one patch,
> which maintains relation lookup failures as hard ERRORs, and a
> "downgrade errors to warnings" as a separate patch.
>
+1
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-07 02:56 Corey Huinker <[email protected]>
parent: Jeff Davis <[email protected]>
2 siblings, 0 replies; 199+ messages in thread
From: Corey Huinker @ 2025-03-07 02:56 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
>
>
> Patch attached. This patch does NOT change the default; stats are still
> opt-out. But it makes it easier for users to start specifying what they
> want or not explicitly, or to rely on the defaults if they prefer.
>
> Note that the patch would mean we go from 2 options in v17:
> --{schema|data}-only
>
> to 9 options in v18:
> --{with|no}-{schema|data|stats} and
> --{schema|data|stats}-only
>
> I suggest we adjust the options now with something resembling the
> attached patch and decide on changing the default sometime during beta.
>
Patch is straightforward. Comments are very clear as are docs. I can't see
anything that needs to be changed.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-07 16:22 Andres Freund <[email protected]>
parent: Jeff Davis <[email protected]>
2 siblings, 1 reply; 199+ messages in thread
From: Andres Freund @ 2025-03-07 16:22 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Robert Haas <[email protected]>; Corey Huinker <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Hi,
On 2025-03-06 17:42:30 -0800, Jeff Davis wrote:
> At minimum, we would need to at least add the option "--with-
> statistics", because right now the only way to explicitly request stats
> is to say "--statistics-only".
+1, this has been annoying me while testing.
I did get confused for a while because I used --statistics, as the opposite of
--no-statistics, while going back and forth between the two. Kinda appears to
work, but actually means --statistics-only, something rather different...
> To generalize this concept: for each of {schema, data, stats} users
> might want "yes", "no", or "only".
> If we use this options scheme, it would be easy to change the default
> for stats independently of the other options, if necessary, without
> surprising consequences.
>
> Patch attached. This patch does NOT change the default; stats are still
> opt-out. But it makes it easier for users to start specifying what they
> want or not explicitly, or to rely on the defaults if they prefer.
>
> Note that the patch would mean we go from 2 options in v17:
> --{schema|data}-only
>
> to 9 options in v18:
> --{with|no}-{schema|data|stats} and
> --{schema|data|stats}-only
Could we, instead of having --with-$foo, just use --$foo?
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-07 16:53 Jeff Davis <[email protected]>
parent: Andres Freund <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Jeff Davis @ 2025-03-07 16:53 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Robert Haas <[email protected]>; Corey Huinker <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Fri, 2025-03-07 at 11:22 -0500, Andres Freund wrote:
> +1, this has been annoying me while testing.
IIRC, originally someone had questioned the need for options that
expressed what was already the default, but I can't find it right now.
Regardless, now the need is clear enough.
> Could we, instead of having --with-$foo, just use --$foo?
That creates a conflict with the existing --schema option, which is a
namespace filter.
Another idea: we could use --definitions/--data/--statistics.
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-07 17:41 Robert Treat <[email protected]>
parent: Jeff Davis <[email protected]>
2 siblings, 1 reply; 199+ messages in thread
From: Robert Treat @ 2025-03-07 17:41 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Robert Haas <[email protected]>; Andres Freund <[email protected]>; Corey Huinker <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Thu, Mar 6, 2025 at 8:42 PM Jeff Davis <[email protected]> wrote:
> On Thu, 2025-03-06 at 11:15 -0500, Robert Haas wrote:
> Patch attached. This patch does NOT change the default; stats are still
> opt-out. But it makes it easier for users to start specifying what they
> want or not explicitly, or to rely on the defaults if they prefer.
>
> Note that the patch would mean we go from 2 options in v17:
> --{schema|data}-only
>
> to 9 options in v18:
> --{with|no}-{schema|data|stats} and
> --{schema|data|stats}-only
>
Ugh... this feels like a bit of the combinatorial explosion,
especially if we ever need to add another option. I wonder if it would
be possible to do something simple like just providing
"--include={schema|data|stats}" where you specify only what you want,
and leave out what you don't. At the risk of not providing as many
typing shortcuts, if the logic is simpler and more extensible for
future options...
Robert Treat
https://xzilla.net
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-07 18:41 Jeff Davis <[email protected]>
parent: Robert Treat <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Jeff Davis @ 2025-03-07 18:41 UTC (permalink / raw)
To: Robert Treat <[email protected]>; +Cc: Robert Haas <[email protected]>; Andres Freund <[email protected]>; Corey Huinker <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Fri, 2025-03-07 at 12:41 -0500, Robert Treat wrote:
> Ugh... this feels like a bit of the combinatorial explosion,
> especially if we ever need to add another option.
Not quite that bad, because ideally the yes/no/only would not be
expanding as well. But I agree that it feels like a lot of options.
> I wonder if it would
> be possible to do something simple like just providing
> "--include={schema|data|stats}" where you specify only what you want,
> and leave out what you don't.
Can you explain the idea in a bit more detail? Does --
include=statistics mean include statistics also or statistics only? Can
you explicitly request that data be included but rely on the default
for statistics? What options would it override or conflict with?
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export: difference in statistics dumped
@ 2025-03-07 19:14 Tom Lane <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Tom Lane @ 2025-03-07 19:14 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; Alvaro Herrera <[email protected]>; jian he <[email protected]>
Jeff Davis <[email protected]> writes:
> Sounds good. I will commit something like the v2 patch then soon, and
> if we need a different condition we can change it later.
Sadly, this made things worse not better: crake is failing
cross-version-upgrade tests again [1], with dump diffs like
@@ -270836,8 +270836,8 @@
--
SELECT * FROM pg_catalog.pg_restore_relation_stats( 'version', '000000'::integer,
'relation', 'public.hash_f8_index'::regclass,
- 'relpages', '66'::integer,
- 'reltuples', '10000'::real,
+ 'relpages', '0'::integer,
+ 'reltuples', '-1'::real,
'relallvisible', '0'::integer
);
I think what is happening is that the patch shut off CREATE
INDEX's update of not only the table's stats but also the
index's stats. This seems unhelpful: the index's empty
stats can never be what's wanted.
We could band-aid over this by making AdjustUpgrade.pm
lobotomize the comparisons of all three stats fields,
but I think it's just wrong as-is. Perhaps fix by
checking the relation's relkind before applying the
autovacuum heuristic?
regards, tom lane
[1] https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=crake&dt=2025-03-07%2018%3A19%3A14
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-07 20:46 Robert Treat <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 2 replies; 199+ messages in thread
From: Robert Treat @ 2025-03-07 20:46 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Robert Haas <[email protected]>; Andres Freund <[email protected]>; Corey Huinker <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Fri, Mar 7, 2025 at 1:41 PM Jeff Davis <[email protected]> wrote:
>
> On Fri, 2025-03-07 at 12:41 -0500, Robert Treat wrote:
> > Ugh... this feels like a bit of the combinatorial explosion,
> > especially if we ever need to add another option.
>
> Not quite that bad, because ideally the yes/no/only would not be
> expanding as well. But I agree that it feels like a lot of options.
>
> > I wonder if it would
> > be possible to do something simple like just providing
> > "--include={schema|data|stats}" where you specify only what you want,
> > and leave out what you don't.
>
> Can you explain the idea in a bit more detail? Does --
> include=statistics mean include statistics also or statistics only? Can
> you explicitly request that data be included but rely on the default
> for statistics? What options would it override or conflict with?
>
There might be some variability depending on the default behavior, but
if we assume that default means "output everything" (which is the v17
behavior), then use of --include would mean to only include items that
are listed, so:
if you want everything --include=schema,data,statistics (presumably
redundant with the default behavior)
if you want schema only --include=schema
if you want "everything except schema" --include=data,statistics
So it's pretty easy to extrapolate data only or statistics only, and
pretty easy to work up any combo of 2 of the 3.
And if someday, for example, there is ever agreement on including role
information with normal pg_dump, you add "roles" as an option to be
parsed via --include without having to create any new flags.
Robert Treat
https://xzilla.net
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-07 21:43 Jeff Davis <[email protected]>
parent: Robert Treat <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Jeff Davis @ 2025-03-07 21:43 UTC (permalink / raw)
To: Robert Treat <[email protected]>; +Cc: Robert Haas <[email protected]>; Andres Freund <[email protected]>; Corey Huinker <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Fri, 2025-03-07 at 15:46 -0500, Robert Treat wrote:
> There might be some variability depending on the default behavior,
> but
> if we assume that default means "output everything"
The reason I posted this patch is that, depending on performance
characteristics in v18 and a decision to be made during beta, the
default may not output statistics.
So we need whatever set of options we choose to have the freedom to
change statistics to be either opt-in or opt-out, without needing to
reconsider the overall set of options.
I tried to generalize that requirement to all of
{schema|data|statistics} for consistency, but that resulted in 9
options.
We don't need the options to be perfectly consistent at the expense of
usability, though, so if 9 options is too many we can just have three
new options for stats, for a total of 5 options:
--data-only
--schema-only
--statistics-only
--statistics (stats also, regardless of default)
--no-statistics (no stats, regardless of default)
which would allow combinations like "--schema-only --statistics" to
mean "schema and statistics but not data". There would be a bit of
weirdness because --statistics can combine with --data-only and --
schema-only, but nothing can combine with --statistics-only.
> if you want everything --include=schema,data,statistics (presumably
> redundant with the default behavior)
> if you want schema only --include=schema
> if you want "everything except schema" --include=data,statistics
That could work. Comparing to the options above yields:
--include=statistics <=> --statistics-only
--include=schema,data,statistics <=> --statistics
--include=schema,statistics <=> --schema-only --statistics
--include=data,statistics <=> --data-only --statistics
--include=schema,data <=> --no-statistics
Not sure which approach is better.
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-08 03:40 Corey Huinker <[email protected]>
parent: Robert Treat <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Corey Huinker @ 2025-03-08 03:40 UTC (permalink / raw)
To: Robert Treat <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
>
>
> if you want everything --include=schema,data,statistics (presumably
> redundant with the default behavior)
> if you want schema only --include=schema
> if you want "everything except schema" --include=data,statistics
>
Until we add a fourth option, and then it becomes completely ambiguous as
to whether you wanted data+statstics, or you not-wanted schema.
And if someday, for example, there is ever agreement on including role
> information with normal pg_dump, you add "roles" as an option to be
> parsed via --include without having to create any new flags.
>
This is pushing a burden onto our customers for a parsing convenience.
-1.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-08 03:43 Corey Huinker <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Corey Huinker @ 2025-03-08 03:43 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
>
> I tried to generalize that requirement to all of
> {schema|data|statistics} for consistency, but that resulted in 9
> options.
>
9 options that resolve to 3 boolean variables. It's not that hard.
And if we add a fourth option set, then we have 12 options. So it's O(3N),
not O(N^2).
People have scripts now that rely on the existing -only flags, and nearly
every other potentially optional thing has a -no flag. Let's leverage that.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-08 05:51 Hari Krishna Sunder <[email protected]>
parent: Corey Huinker <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Hari Krishna Sunder @ 2025-03-08 05:51 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
To improve the performance of pg_dump can we add a new sql function that
can operate more efficiently than the pg_stats view? It could also take in
an optional list of oids to filter on.
This will help speed up the dump and restore within pg18 and future
upgrades to higher pg versions.
Thanks
Hari Krishna Sunder
On Fri, Mar 7, 2025 at 7:43 PM Corey Huinker <[email protected]>
wrote:
> I tried to generalize that requirement to all of
>> {schema|data|statistics} for consistency, but that resulted in 9
>> options.
>>
>
> 9 options that resolve to 3 boolean variables. It's not that hard.
>
> And if we add a fourth option set, then we have 12 options. So it's O(3N),
> not O(N^2).
>
> People have scripts now that rely on the existing -only flags, and nearly
> every other potentially optional thing has a -no flag. Let's leverage that.
>
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-08 07:51 Corey Huinker <[email protected]>
parent: Hari Krishna Sunder <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Corey Huinker @ 2025-03-08 07:51 UTC (permalink / raw)
To: Hari Krishna Sunder <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Sat, Mar 8, 2025 at 12:52 AM Hari Krishna Sunder <[email protected]>
wrote:
> To improve the performance of pg_dump can we add a new sql function that
> can operate more efficiently than the pg_stats view? It could also take in
> an optional list of oids to filter on.
> This will help speed up the dump and restore within pg18 and future
> upgrades to higher pg versions.
>
>
We can't install functions on the source database - it might be a read
replica.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-08 07:56 Corey Huinker <[email protected]>
parent: Corey Huinker <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Corey Huinker @ 2025-03-08 07:56 UTC (permalink / raw)
To: Hari Krishna Sunder <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Updated and rebase patches.
0001 is the same as v6-0002, but with proper ACL checks on schemas after
cache lookup
0002 attempts to replace all possible ERRORs in the restore/clear functions
with WARNINGs. This is done with an eye towards reducing the set of things
that could potentially cause an upgrade to fail.
Spoke with Nathan about how best to batch the pg_stats fetches. I'll be
working on that now. Given that, the patch that optimized out
getAttributeStats() calls on indexes without expressions has been
withdrawn. It's a clear incremental gain, and we're looking for a couple
orders of magnitude gain.
Attachments:
[text/x-patch] v7-0001-Split-relation-into-schemaname-and-relname.patch (65.0K, ../../CADkLM=fLUHPzvVnM4eC8ZdVH0wewXfZmVoVmn4=8pb12+s1v7Q@mail.gmail.com/3-v7-0001-Split-relation-into-schemaname-and-relname.patch)
download | inline diff:
From 9cd4b4e0e280d0fd8cb120ac105d6e65a491cd7e Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Tue, 4 Mar 2025 22:16:52 -0500
Subject: [PATCH v7 1/2] Split relation into schemaname and relname.
In order to further reduce potential error-failures in restores and
upgrades, replace the numerous casts of fully qualified relation names
into their schema+relname text components.
Further remove the ::name casts on attname and change the expected
datatype to text.
Add an ACL_USAGE check on the namespace oid after it is looked up.
---
src/include/catalog/pg_proc.dat | 8 +-
src/include/statistics/stat_utils.h | 2 +
src/backend/statistics/attribute_stats.c | 87 ++++--
src/backend/statistics/relation_stats.c | 65 +++--
src/backend/statistics/stat_utils.c | 37 +++
src/bin/pg_dump/pg_dump.c | 25 +-
src/bin/pg_dump/t/002_pg_dump.pl | 6 +-
src/test/regress/expected/stats_import.out | 307 +++++++++++++--------
src/test/regress/sql/stats_import.sql | 276 +++++++++++-------
doc/src/sgml/func.sgml | 41 +--
10 files changed, 566 insertions(+), 288 deletions(-)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index cede992b6e2..fdd4b8d7dba 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12443,8 +12443,8 @@
descr => 'clear statistics on relation',
proname => 'pg_clear_relation_stats', provolatile => 'v', proisstrict => 'f',
proparallel => 'u', prorettype => 'void',
- proargtypes => 'regclass',
- proargnames => '{relation}',
+ proargtypes => 'text text',
+ proargnames => '{schemaname,relname}',
prosrc => 'pg_clear_relation_stats' },
{ oid => '8461',
descr => 'restore statistics on attribute',
@@ -12459,8 +12459,8 @@
descr => 'clear statistics on attribute',
proname => 'pg_clear_attribute_stats', provolatile => 'v', proisstrict => 'f',
proparallel => 'u', prorettype => 'void',
- proargtypes => 'regclass name bool',
- proargnames => '{relation,attname,inherited}',
+ proargtypes => 'text text text bool',
+ proargnames => '{schemaname,relname,attname,inherited}',
prosrc => 'pg_clear_attribute_stats' },
# GiST stratnum implementations
diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h
index 0eb4decfcac..cad042c8e4a 100644
--- a/src/include/statistics/stat_utils.h
+++ b/src/include/statistics/stat_utils.h
@@ -32,6 +32,8 @@ extern bool stats_check_arg_pair(FunctionCallInfo fcinfo,
extern void stats_lock_check_privileges(Oid reloid);
+extern Oid stats_schema_check_privileges(const char *nspname);
+
extern bool stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
FunctionCallInfo positional_fcinfo,
struct StatsArgInfo *arginfo);
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index 6bcbee0edba..f87db2d6102 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -36,7 +36,8 @@
enum attribute_stats_argnum
{
- ATTRELATION_ARG = 0,
+ ATTRELSCHEMA_ARG = 0,
+ ATTRELNAME_ARG,
ATTNAME_ARG,
ATTNUM_ARG,
INHERITED_ARG,
@@ -58,8 +59,9 @@ enum attribute_stats_argnum
static struct StatsArgInfo attarginfo[] =
{
- [ATTRELATION_ARG] = {"relation", REGCLASSOID},
- [ATTNAME_ARG] = {"attname", NAMEOID},
+ [ATTRELSCHEMA_ARG] = {"schemaname", TEXTOID},
+ [ATTRELNAME_ARG] = {"relname", TEXTOID},
+ [ATTNAME_ARG] = {"attname", TEXTOID},
[ATTNUM_ARG] = {"attnum", INT2OID},
[INHERITED_ARG] = {"inherited", BOOLOID},
[NULL_FRAC_ARG] = {"null_frac", FLOAT4OID},
@@ -80,7 +82,8 @@ static struct StatsArgInfo attarginfo[] =
enum clear_attribute_stats_argnum
{
- C_ATTRELATION_ARG = 0,
+ C_ATTRELSCHEMA_ARG = 0,
+ C_ATTRELNAME_ARG,
C_ATTNAME_ARG,
C_INHERITED_ARG,
C_NUM_ATTRIBUTE_STATS_ARGS
@@ -88,8 +91,9 @@ enum clear_attribute_stats_argnum
static struct StatsArgInfo cleararginfo[] =
{
- [C_ATTRELATION_ARG] = {"relation", REGCLASSOID},
- [C_ATTNAME_ARG] = {"attname", NAMEOID},
+ [C_ATTRELSCHEMA_ARG] = {"relation", TEXTOID},
+ [C_ATTRELNAME_ARG] = {"relation", TEXTOID},
+ [C_ATTNAME_ARG] = {"attname", TEXTOID},
[C_INHERITED_ARG] = {"inherited", BOOLOID},
[C_NUM_ATTRIBUTE_STATS_ARGS] = {0}
};
@@ -133,6 +137,9 @@ static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
static bool
attribute_statistics_update(FunctionCallInfo fcinfo)
{
+ char *nspname;
+ Oid nspoid;
+ char *relname;
Oid reloid;
char *attname;
AttrNumber attnum;
@@ -170,8 +177,23 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
bool result = true;
- stats_check_required_arg(fcinfo, attarginfo, ATTRELATION_ARG);
- reloid = PG_GETARG_OID(ATTRELATION_ARG);
+ stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG);
+ stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG);
+
+ nspname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELSCHEMA_ARG));
+ nspoid = stats_schema_check_privileges(nspname);
+ if (nspoid == InvalidOid)
+ return false;
+
+ relname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELNAME_ARG));
+ reloid = get_relname_relid(relname, nspoid);
+ if (reloid == InvalidOid)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
+ return false;
+ }
if (RecoveryInProgress())
ereport(ERROR,
@@ -185,21 +207,18 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
/* user can specify either attname or attnum, but not both */
if (!PG_ARGISNULL(ATTNAME_ARG))
{
- Name attnamename;
-
if (!PG_ARGISNULL(ATTNUM_ARG))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("cannot specify both attname and attnum")));
- attnamename = PG_GETARG_NAME(ATTNAME_ARG);
- attname = NameStr(*attnamename);
+ attname = TextDatumGetCString(PG_GETARG_DATUM(ATTNAME_ARG));
attnum = get_attnum(reloid, attname);
/* note that this test covers attisdropped cases too: */
if (attnum == InvalidAttrNumber)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
- attname, get_rel_name(reloid))));
+ errmsg("column \"%s\" of relation \"%s\".\"%s\" does not exist",
+ attname, nspname, relname)));
}
else if (!PG_ARGISNULL(ATTNUM_ARG))
{
@@ -210,8 +229,8 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
!SearchSysCacheExistsAttName(reloid, attname))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column %d of relation \"%s\" does not exist",
- attnum, get_rel_name(reloid))));
+ errmsg("column %d of relation \"%s\".\"%s\" does not exist",
+ attnum, nspname, relname)));
}
else
{
@@ -900,13 +919,33 @@ init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
Datum
pg_clear_attribute_stats(PG_FUNCTION_ARGS)
{
+ char *nspname;
+ Oid nspoid;
+ char *relname;
Oid reloid;
- Name attname;
+ char *attname;
AttrNumber attnum;
bool inherited;
- stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELATION_ARG);
- reloid = PG_GETARG_OID(C_ATTRELATION_ARG);
+ stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG);
+ stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG);
+ stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
+ stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
+
+ nspname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELSCHEMA_ARG));
+ nspoid = stats_schema_check_privileges(nspname);
+ if (!OidIsValid(nspoid))
+ return false;
+
+ relname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELNAME_ARG));
+ reloid = get_relname_relid(relname, nspoid);
+ if (!OidIsValid(reloid))
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
+ return false;
+ }
if (RecoveryInProgress())
ereport(ERROR,
@@ -916,23 +955,21 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
stats_lock_check_privileges(reloid);
- stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
- attname = PG_GETARG_NAME(C_ATTNAME_ARG);
- attnum = get_attnum(reloid, NameStr(*attname));
+ attname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTNAME_ARG));
+ attnum = get_attnum(reloid, attname);
if (attnum < 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot clear statistics on system column \"%s\"",
- NameStr(*attname))));
+ attname)));
if (attnum == InvalidAttrNumber)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
- NameStr(*attname), get_rel_name(reloid))));
+ attname, get_rel_name(reloid))));
- stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
inherited = PG_GETARG_BOOL(C_INHERITED_ARG);
delete_pg_statistic(reloid, attnum, inherited);
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index 52dfa477187..fdc69bc93e2 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -19,9 +19,12 @@
#include "access/heapam.h"
#include "catalog/indexing.h"
+#include "catalog/namespace.h"
#include "statistics/stat_utils.h"
+#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/fmgrprotos.h"
+#include "utils/lsyscache.h"
#include "utils/syscache.h"
@@ -32,7 +35,8 @@
enum relation_stats_argnum
{
- RELATION_ARG = 0,
+ RELSCHEMA_ARG = 0,
+ RELNAME_ARG,
RELPAGES_ARG,
RELTUPLES_ARG,
RELALLVISIBLE_ARG,
@@ -42,7 +46,8 @@ enum relation_stats_argnum
static struct StatsArgInfo relarginfo[] =
{
- [RELATION_ARG] = {"relation", REGCLASSOID},
+ [RELSCHEMA_ARG] = {"schemaname", TEXTOID},
+ [RELNAME_ARG] = {"relname", TEXTOID},
[RELPAGES_ARG] = {"relpages", INT4OID},
[RELTUPLES_ARG] = {"reltuples", FLOAT4OID},
[RELALLVISIBLE_ARG] = {"relallvisible", INT4OID},
@@ -59,6 +64,9 @@ static bool
relation_statistics_update(FunctionCallInfo fcinfo)
{
bool result = true;
+ char *nspname;
+ Oid nspoid;
+ char *relname;
Oid reloid;
Relation crel;
BlockNumber relpages = 0;
@@ -76,6 +84,32 @@ relation_statistics_update(FunctionCallInfo fcinfo)
bool nulls[4] = {0};
int nreplaces = 0;
+ stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG);
+ stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG);
+
+ nspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
+ nspoid = stats_schema_check_privileges(nspname);
+ if (!OidIsValid(nspoid))
+ return false;
+
+ relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
+ reloid = get_relname_relid(relname, nspoid);
+ if (!OidIsValid(reloid))
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
+ return false;
+ }
+
+ if (RecoveryInProgress())
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("recovery is in progress"),
+ errhint("Statistics cannot be modified during recovery.")));
+
+ stats_lock_check_privileges(reloid);
+
if (!PG_ARGISNULL(RELPAGES_ARG))
{
relpages = PG_GETARG_UINT32(RELPAGES_ARG);
@@ -108,17 +142,6 @@ relation_statistics_update(FunctionCallInfo fcinfo)
update_relallfrozen = true;
}
- stats_check_required_arg(fcinfo, relarginfo, RELATION_ARG);
- reloid = PG_GETARG_OID(RELATION_ARG);
-
- if (RecoveryInProgress())
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("recovery is in progress"),
- errhint("Statistics cannot be modified during recovery.")));
-
- stats_lock_check_privileges(reloid);
-
/*
* Take RowExclusiveLock on pg_class, consistent with
* vac_update_relstats().
@@ -187,20 +210,22 @@ relation_statistics_update(FunctionCallInfo fcinfo)
Datum
pg_clear_relation_stats(PG_FUNCTION_ARGS)
{
- LOCAL_FCINFO(newfcinfo, 5);
+ LOCAL_FCINFO(newfcinfo, 6);
- InitFunctionCallInfoData(*newfcinfo, NULL, 5, InvalidOid, NULL, NULL);
+ InitFunctionCallInfoData(*newfcinfo, NULL, 6, InvalidOid, NULL, NULL);
- newfcinfo->args[0].value = PG_GETARG_OID(0);
+ newfcinfo->args[0].value = PG_GETARG_DATUM(0);
newfcinfo->args[0].isnull = PG_ARGISNULL(0);
- newfcinfo->args[1].value = UInt32GetDatum(0);
- newfcinfo->args[1].isnull = false;
- newfcinfo->args[2].value = Float4GetDatum(-1.0);
+ newfcinfo->args[1].value = PG_GETARG_DATUM(1);
+ newfcinfo->args[1].isnull = PG_ARGISNULL(1);
+ newfcinfo->args[2].value = UInt32GetDatum(0);
newfcinfo->args[2].isnull = false;
- newfcinfo->args[3].value = UInt32GetDatum(0);
+ newfcinfo->args[3].value = Float4GetDatum(-1.0);
newfcinfo->args[3].isnull = false;
newfcinfo->args[4].value = UInt32GetDatum(0);
newfcinfo->args[4].isnull = false;
+ newfcinfo->args[5].value = UInt32GetDatum(0);
+ newfcinfo->args[5].isnull = false;
relation_statistics_update(newfcinfo);
PG_RETURN_VOID();
diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c
index 9647f5108b3..e037d4994e8 100644
--- a/src/backend/statistics/stat_utils.c
+++ b/src/backend/statistics/stat_utils.c
@@ -18,7 +18,9 @@
#include "access/relation.h"
#include "catalog/index.h"
+#include "catalog/namespace.h"
#include "catalog/pg_database.h"
+#include "catalog/pg_namespace.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "statistics/stat_utils.h"
@@ -213,6 +215,41 @@ stats_lock_check_privileges(Oid reloid)
relation_close(table, NoLock);
}
+
+/*
+ * Resolve a schema name into an Oid, ensure that the user has usage privs on
+ * that schema.
+ */
+Oid
+stats_schema_check_privileges(const char *nspname)
+{
+ Oid nspoid;
+ AclResult aclresult;
+
+ nspoid = get_namespace_oid(nspname, true);
+
+ if (nspoid == InvalidOid)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_SCHEMA_NAME),
+ errmsg("schema %s does not exist", nspname)));
+ return InvalidOid;
+ }
+
+ aclresult = object_aclcheck(NamespaceRelationId, nspoid, GetUserId(), ACL_USAGE);
+
+ if (aclresult != ACLCHECK_OK)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied for schema %s", nspname)));
+ return InvalidOid;
+ }
+
+ return nspoid;
+}
+
+
/*
* Find the argument number for the given argument name, returning -1 if not
* found.
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4f4ad2ee150..6cf2c7d1fe4 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -10492,7 +10492,6 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
PQExpBuffer out;
DumpId *deps = NULL;
int ndeps = 0;
- char *qualified_name;
char reltuples_str[FLOAT_SHORTEST_DECIMAL_LEN];
int i_attname;
int i_inherited;
@@ -10558,15 +10557,16 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
out = createPQExpBuffer();
- qualified_name = pg_strdup(fmtQualifiedDumpable(rsinfo));
-
/* restore relation stats */
appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
fout->remoteVersion);
- appendPQExpBufferStr(out, "\t'relation', ");
- appendStringLiteralAH(out, qualified_name, fout);
- appendPQExpBufferStr(out, "::regclass,\n");
+ appendPQExpBufferStr(out, "\t'schemaname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+ appendPQExpBufferStr(out, ",\n");
+ appendPQExpBufferStr(out, "\t'relname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+ appendPQExpBufferStr(out, ",\n");
appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
float_to_shortest_decimal_buf(rsinfo->reltuples, reltuples_str);
appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", reltuples_str);
@@ -10606,9 +10606,10 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
fout->remoteVersion);
- appendPQExpBufferStr(out, "\t'relation', ");
- appendStringLiteralAH(out, qualified_name, fout);
- appendPQExpBufferStr(out, "::regclass");
+ appendPQExpBufferStr(out, "\t'schemaname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+ appendPQExpBufferStr(out, ",\n\t'relname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.name, fout);
if (PQgetisnull(res, rownum, i_attname))
pg_fatal("attname cannot be NULL");
@@ -10620,7 +10621,10 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
* their attnames are not necessarily stable across dump/reload.
*/
if (rsinfo->nindAttNames == 0)
- appendNamedArgument(out, fout, "attname", "name", attname);
+ {
+ appendPQExpBuffer(out, ",\n\t'attname', ");
+ appendStringLiteralAH(out, attname, fout);
+ }
else
{
bool found = false;
@@ -10700,7 +10704,6 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
.deps = deps,
.nDeps = ndeps));
- free(qualified_name);
destroyPQExpBuffer(out);
destroyPQExpBuffer(query);
}
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c7bffc1b045..b037f239136 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -4725,14 +4725,16 @@ my %tests = (
regexp => qr/^
\QSELECT * FROM pg_catalog.pg_restore_relation_stats(\E\s+
'version',\s'\d+'::integer,\s+
- 'relation',\s'dump_test.dup_test_post_data_ix'::regclass,\s+
+ 'schemaname',\s'dump_test',\s+
+ 'relname',\s'dup_test_post_data_ix',\s+
'relpages',\s'\d+'::integer,\s+
'reltuples',\s'\d+'::real,\s+
'relallvisible',\s'\d+'::integer\s+
\);\s+
\QSELECT * FROM pg_catalog.pg_restore_attribute_stats(\E\s+
'version',\s'\d+'::integer,\s+
- 'relation',\s'dump_test.dup_test_post_data_ix'::regclass,\s+
+ 'schemaname',\s'dump_test',\s+
+ 'relname',\s'dup_test_post_data_ix',\s+
'attnum',\s'2'::smallint,\s+
'inherited',\s'f'::boolean,\s+
'null_frac',\s'0'::real,\s+
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 1f46d5e7854..2f1295f2149 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -14,7 +14,8 @@ CREATE TABLE stats_import.test(
) WITH (autovacuum_enabled = false);
SELECT
pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', 18::integer,
'reltuples', 21::real,
'relallvisible', 24::integer,
@@ -36,7 +37,7 @@ ORDER BY relname;
test | 18 | 21 | 24 | 27
(1 row)
-SELECT pg_clear_relation_stats('stats_import.test'::regclass);
+SELECT pg_clear_relation_stats('stats_import', 'test');
pg_clear_relation_stats
-------------------------
@@ -45,33 +46,54 @@ SELECT pg_clear_relation_stats('stats_import.test'::regclass);
--
-- relstats tests
--
---- error: relation is wrong type
+-- error: schemaname missing
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid,
+ 'relname', 'test',
'relpages', 17::integer);
-WARNING: argument "relation" has type "oid", expected type "regclass"
-ERROR: "relation" cannot be NULL
+ERROR: "schemaname" cannot be NULL
+-- error: relname missing
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 'stats_import',
+ 'relpages', 17::integer);
+ERROR: "relname" cannot be NULL
+--- error: schemaname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 3.6::float,
+ 'relname', 'test',
+ 'relpages', 17::integer);
+WARNING: argument "schemaname" has type "double precision", expected type "text"
+ERROR: "schemaname" cannot be NULL
+--- error: relname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 0::oid,
+ 'relpages', 17::integer);
+WARNING: argument "relname" has type "oid", expected type "text"
+ERROR: "relname" cannot be NULL
-- error: relation not found
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'nope',
'relpages', 17::integer);
-ERROR: could not open relation with OID 0
+WARNING: Relation "stats_import"."nope" not found.
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
-- error: odd number of variadic arguments cannot be pairs
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relallvisible');
ERROR: variadic arguments must be name/value pairs
HINT: Provide an even number of variadic arguments that can be divided into pairs.
-- error: argument name is NULL
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
NULL, '17'::integer);
-ERROR: name at variadic position 3 is NULL
--- error: argument name is not a text type
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 17, '17'::integer);
-ERROR: name at variadic position 3 has type "integer", expected type "text"
+ERROR: name at variadic position 5 is NULL
-- starting stats
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
@@ -84,7 +106,8 @@ WHERE oid = 'stats_import.test_i'::regclass;
-- regular indexes have special case locking rules
BEGIN;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.test_i'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test_i',
'relpages', 18::integer);
pg_restore_relation_stats
---------------------------
@@ -132,7 +155,8 @@ WHERE oid = 'stats_import.part_parent'::regclass;
--
BEGIN;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent_i'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'part_parent_i',
'relpages', 2::integer);
pg_restore_relation_stats
---------------------------
@@ -166,7 +190,8 @@ WHERE oid = 'stats_import.part_parent_i'::regclass;
-- ok: set all relstats, with version, no bounds checking
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'version', 150000::integer,
'relpages', '-17'::integer,
'reltuples', 400::real,
@@ -187,7 +212,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just relpages, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', '16'::integer);
pg_restore_relation_stats
---------------------------
@@ -204,7 +230,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just reltuples, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'reltuples', '500'::real);
pg_restore_relation_stats
---------------------------
@@ -221,7 +248,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just relallvisible, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relallvisible', 5::integer);
pg_restore_relation_stats
---------------------------
@@ -238,7 +266,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: just relallfrozen
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'version', 150000::integer,
'relallfrozen', 3::integer);
pg_restore_relation_stats
@@ -256,7 +285,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- warn: bad relpages type, rest updated
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', 'nope'::text,
'reltuples', 400.0::real,
'relallvisible', 4::integer,
@@ -277,7 +307,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- unrecognized argument name, rest ok
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', '171'::integer,
'nope', 10::integer);
WARNING: unrecognized argument name: "nope"
@@ -295,8 +326,7 @@ WHERE oid = 'stats_import.test'::regclass;
(1 row)
-- ok: clear stats
-SELECT pg_catalog.pg_clear_relation_stats(
- relation => 'stats_import.test'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'test');
pg_clear_relation_stats
-------------------------
@@ -313,87 +343,123 @@ WHERE oid = 'stats_import.test'::regclass;
-- invalid relkinds for statistics
CREATE SEQUENCE stats_import.testseq;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.testseq'::regclass);
+ 'schemaname', 'stats_import',
+ 'relname', 'testseq');
ERROR: cannot modify statistics for relation "testseq"
DETAIL: This operation is not supported for sequences.
-SELECT pg_catalog.pg_clear_relation_stats(
- 'stats_import.testseq'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
ERROR: cannot modify statistics for relation "testseq"
DETAIL: This operation is not supported for sequences.
CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
-SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.testview'::regclass);
-ERROR: cannot modify statistics for relation "testview"
-DETAIL: This operation is not supported for views.
-SELECT pg_catalog.pg_clear_relation_stats(
- 'stats_import.testview'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
ERROR: cannot modify statistics for relation "testview"
DETAIL: This operation is not supported for views.
--
-- attribute stats
--
--- error: object does not exist
+-- error: schemaname missing
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', '0'::oid::regclass,
- 'attname', 'id'::name,
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: could not open relation with OID 0
--- error: relation null
+ERROR: "schemaname" cannot be NULL
+-- error: schema does not exist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', NULL::oid::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'nope',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: "relation" cannot be NULL
+WARNING: schema nope does not exist
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- error: relname missing
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: "relname" cannot be NULL
+-- error: relname does not exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'nope',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+WARNING: Relation "stats_import"."nope" not found.
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- error: relname null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'relname', NULL,
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: "relname" cannot be NULL
-- error: NULL attname
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', NULL::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', NULL,
'inherited', false::boolean,
'null_frac', 0.1::real);
ERROR: must specify either attname or attnum
-- error: attname doesn't exist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'nope'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'nope',
'inherited', false::boolean,
'null_frac', 0.1::real,
'avg_width', 2::integer,
'n_distinct', 0.3::real);
-ERROR: column "nope" of relation "test" does not exist
+ERROR: column "nope" of relation "stats_import"."test" does not exist
-- error: both attname and attnum
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.1::real);
ERROR: cannot specify both attname and attnum
-- error: neither attname nor attnum
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'inherited', false::boolean,
'null_frac', 0.1::real);
ERROR: must specify either attname or attnum
-- error: attribute is system column
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'xmin'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'xmin',
'inherited', false::boolean,
'null_frac', 0.1::real);
ERROR: cannot modify statistics on system column "xmin"
-- error: inherited null
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', NULL::boolean,
'null_frac', 0.1::real);
ERROR: "inherited" cannot be NULL
-- ok: just the fixed values, with version, no stakinds
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'version', 150000::integer,
'null_frac', 0.2::real,
@@ -421,7 +487,8 @@ AND attname = 'id';
-- for any stat-having relation.
--
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.4::real);
@@ -443,8 +510,9 @@ AND attname = 'id';
-- warn: unrecognized argument name, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.2::real,
'nope', 0.5::real);
@@ -467,8 +535,9 @@ AND attname = 'id';
-- warn: mcv / mcf null mismatch part 1, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.21::real,
'most_common_freqs', '{0.1,0.2,0.3}'::real[]
@@ -492,8 +561,9 @@ AND attname = 'id';
-- warn: mcv / mcf null mismatch part 2, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.21::real,
'most_common_vals', '{1,2,3}'::text
@@ -517,8 +587,9 @@ AND attname = 'id';
-- warn: mcf type mismatch, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.22::real,
'most_common_vals', '{2,1,3}'::text,
@@ -544,8 +615,9 @@ AND attname = 'id';
-- warn: mcv cast failure, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.23::real,
'most_common_vals', '{2,four,3}'::text,
@@ -570,8 +642,9 @@ AND attname = 'id';
-- ok: mcv+mcf
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
@@ -594,8 +667,9 @@ AND attname = 'id';
-- warn: NULL in histogram array, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.24::real,
'histogram_bounds', '{1,NULL,3,4}'::text
@@ -619,8 +693,9 @@ AND attname = 'id';
-- ok: histogram_bounds
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'histogram_bounds', '{1,2,3,4}'::text
);
@@ -642,8 +717,9 @@ AND attname = 'id';
-- warn: elem_count_histogram null element, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.25::real,
'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[]
@@ -667,8 +743,9 @@ AND attname = 'tags';
-- ok: elem_count_histogram
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.26::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -691,8 +768,9 @@ AND attname = 'tags';
-- warn: range stats on a scalar type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.27::real,
'range_empty_frac', 0.5::real,
@@ -718,8 +796,9 @@ AND attname = 'id';
-- warn: range_empty_frac range_length_hist null mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.28::real,
'range_length_histogram', '{399,499,Infinity}'::text
@@ -743,8 +822,9 @@ AND attname = 'arange';
-- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.29::real,
'range_empty_frac', 0.5::real
@@ -768,8 +848,9 @@ AND attname = 'arange';
-- ok: range_empty_frac + range_length_hist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'range_empty_frac', 0.5::real,
'range_length_histogram', '{399,499,Infinity}'::text
@@ -792,8 +873,9 @@ AND attname = 'arange';
-- warn: range bounds histogram on scalar, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.31::real,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
@@ -818,8 +900,9 @@ AND attname = 'id';
-- ok: range_bounds_histogram
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
);
@@ -841,8 +924,9 @@ AND attname = 'arange';
-- warn: cannot set most_common_elems for range type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.32::real,
'most_common_elems', '{3,1}'::text,
@@ -868,8 +952,9 @@ AND attname = 'arange';
-- warn: scalars can't have mcelem, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.33::real,
'most_common_elems', '{1,3}'::text,
@@ -895,8 +980,9 @@ AND attname = 'id';
-- warn: mcelem / mcelem mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.34::real,
'most_common_elems', '{one,two}'::text
@@ -920,8 +1006,9 @@ AND attname = 'tags';
-- warn: mcelem / mcelem null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.35::real,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
@@ -945,8 +1032,9 @@ AND attname = 'tags';
-- ok: mcelem
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'most_common_elems', '{one,three}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
@@ -969,8 +1057,9 @@ AND attname = 'tags';
-- warn: scalars can't have elem_count_histogram, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.36::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -1022,8 +1111,9 @@ SELECT s.schemaname, s.tablename, s.attname, s.inherited, r.*
FROM pg_catalog.pg_stats AS s
CROSS JOIN LATERAL
pg_catalog.pg_restore_attribute_stats(
- 'relation', ('stats_import.' || s.tablename || '_clone')::regclass,
- 'attname', s.attname,
+ 'schemaname', 'stats_import',
+ 'relname', s.tablename::text || '_clone',
+ 'attname', s.attname::text,
'inherited', s.inherited,
'version', 150000,
'null_frac', s.null_frac,
@@ -1200,9 +1290,10 @@ AND attname = 'arange';
(1 row)
SELECT pg_catalog.pg_clear_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean);
+ schemaname => 'stats_import',
+ relname => 'test',
+ attname => 'arange',
+ inherited => false);
pg_clear_attribute_stats
--------------------------
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index 0ec590688c2..ccdc44e9236 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -17,7 +17,8 @@ CREATE TABLE stats_import.test(
SELECT
pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', 18::integer,
'reltuples', 21::real,
'relallvisible', 24::integer,
@@ -32,37 +33,52 @@ FROM pg_class
WHERE oid = 'stats_import.test'::regclass
ORDER BY relname;
-SELECT pg_clear_relation_stats('stats_import.test'::regclass);
+SELECT pg_clear_relation_stats('stats_import', 'test');
--
-- relstats tests
--
---- error: relation is wrong type
+-- error: schemaname missing
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid,
+ 'relname', 'test',
+ 'relpages', 17::integer);
+
+-- error: relname missing
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 'stats_import',
+ 'relpages', 17::integer);
+
+--- error: schemaname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 3.6::float,
+ 'relname', 'test',
+ 'relpages', 17::integer);
+
+--- error: relname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 0::oid,
'relpages', 17::integer);
-- error: relation not found
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'nope',
'relpages', 17::integer);
-- error: odd number of variadic arguments cannot be pairs
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relallvisible');
-- error: argument name is NULL
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
NULL, '17'::integer);
--- error: argument name is not a text type
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 17, '17'::integer);
-
-- starting stats
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
@@ -71,7 +87,8 @@ WHERE oid = 'stats_import.test_i'::regclass;
-- regular indexes have special case locking rules
BEGIN;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.test_i'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test_i',
'relpages', 18::integer);
SELECT mode FROM pg_locks
@@ -108,7 +125,8 @@ WHERE oid = 'stats_import.part_parent'::regclass;
BEGIN;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent_i'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'part_parent_i',
'relpages', 2::integer);
SELECT mode FROM pg_locks
@@ -127,7 +145,8 @@ WHERE oid = 'stats_import.part_parent_i'::regclass;
-- ok: set all relstats, with version, no bounds checking
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'version', 150000::integer,
'relpages', '-17'::integer,
'reltuples', 400::real,
@@ -140,7 +159,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just relpages, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', '16'::integer);
SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -149,7 +169,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just reltuples, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'reltuples', '500'::real);
SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -158,7 +179,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just relallvisible, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relallvisible', 5::integer);
SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -167,7 +189,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: just relallfrozen
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'version', 150000::integer,
'relallfrozen', 3::integer);
@@ -177,7 +200,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- warn: bad relpages type, rest updated
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', 'nope'::text,
'reltuples', 400.0::real,
'relallvisible', 4::integer,
@@ -189,7 +213,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- unrecognized argument name, rest ok
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', '171'::integer,
'nope', 10::integer);
@@ -198,8 +223,7 @@ FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
-- ok: clear stats
-SELECT pg_catalog.pg_clear_relation_stats(
- relation => 'stats_import.test'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'test');
SELECT relpages, reltuples, relallvisible
FROM pg_class
@@ -209,48 +233,70 @@ WHERE oid = 'stats_import.test'::regclass;
CREATE SEQUENCE stats_import.testseq;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.testseq'::regclass);
+ 'schemaname', 'stats_import',
+ 'relname', 'testseq');
-SELECT pg_catalog.pg_clear_relation_stats(
- 'stats_import.testseq'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
-SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.testview'::regclass);
-
-SELECT pg_catalog.pg_clear_relation_stats(
- 'stats_import.testview'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
--
-- attribute stats
--
--- error: object does not exist
+-- error: schemaname missing
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', '0'::oid::regclass,
- 'attname', 'id'::name,
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: relation null
+-- error: schema does not exist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', NULL::oid::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'nope',
+ 'relname', 'test',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: relname missing
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: relname does not exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'nope',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: relname null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'relname', NULL,
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: NULL attname
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', NULL::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', NULL,
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: attname doesn't exist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'nope'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'nope',
'inherited', false::boolean,
'null_frac', 0.1::real,
'avg_width', 2::integer,
@@ -258,36 +304,41 @@ SELECT pg_catalog.pg_restore_attribute_stats(
-- error: both attname and attnum
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: neither attname nor attnum
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: attribute is system column
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'xmin'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'xmin',
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: inherited null
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', NULL::boolean,
'null_frac', 0.1::real);
-- ok: just the fixed values, with version, no stakinds
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'version', 150000::integer,
'null_frac', 0.2::real,
@@ -307,7 +358,8 @@ AND attname = 'id';
-- for any stat-having relation.
--
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.4::real);
@@ -321,8 +373,9 @@ AND attname = 'id';
-- warn: unrecognized argument name, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.2::real,
'nope', 0.5::real);
@@ -336,8 +389,9 @@ AND attname = 'id';
-- warn: mcv / mcf null mismatch part 1, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.21::real,
'most_common_freqs', '{0.1,0.2,0.3}'::real[]
@@ -352,8 +406,9 @@ AND attname = 'id';
-- warn: mcv / mcf null mismatch part 2, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.21::real,
'most_common_vals', '{1,2,3}'::text
@@ -368,8 +423,9 @@ AND attname = 'id';
-- warn: mcf type mismatch, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.22::real,
'most_common_vals', '{2,1,3}'::text,
@@ -385,8 +441,9 @@ AND attname = 'id';
-- warn: mcv cast failure, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.23::real,
'most_common_vals', '{2,four,3}'::text,
@@ -402,8 +459,9 @@ AND attname = 'id';
-- ok: mcv+mcf
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
@@ -418,8 +476,9 @@ AND attname = 'id';
-- warn: NULL in histogram array, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.24::real,
'histogram_bounds', '{1,NULL,3,4}'::text
@@ -434,8 +493,9 @@ AND attname = 'id';
-- ok: histogram_bounds
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'histogram_bounds', '{1,2,3,4}'::text
);
@@ -449,8 +509,9 @@ AND attname = 'id';
-- warn: elem_count_histogram null element, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.25::real,
'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[]
@@ -465,8 +526,9 @@ AND attname = 'tags';
-- ok: elem_count_histogram
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.26::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -481,8 +543,9 @@ AND attname = 'tags';
-- warn: range stats on a scalar type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.27::real,
'range_empty_frac', 0.5::real,
@@ -498,8 +561,9 @@ AND attname = 'id';
-- warn: range_empty_frac range_length_hist null mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.28::real,
'range_length_histogram', '{399,499,Infinity}'::text
@@ -514,8 +578,9 @@ AND attname = 'arange';
-- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.29::real,
'range_empty_frac', 0.5::real
@@ -530,8 +595,9 @@ AND attname = 'arange';
-- ok: range_empty_frac + range_length_hist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'range_empty_frac', 0.5::real,
'range_length_histogram', '{399,499,Infinity}'::text
@@ -546,8 +612,9 @@ AND attname = 'arange';
-- warn: range bounds histogram on scalar, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.31::real,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
@@ -562,8 +629,9 @@ AND attname = 'id';
-- ok: range_bounds_histogram
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
);
@@ -577,8 +645,9 @@ AND attname = 'arange';
-- warn: cannot set most_common_elems for range type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.32::real,
'most_common_elems', '{3,1}'::text,
@@ -594,8 +663,9 @@ AND attname = 'arange';
-- warn: scalars can't have mcelem, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.33::real,
'most_common_elems', '{1,3}'::text,
@@ -611,8 +681,9 @@ AND attname = 'id';
-- warn: mcelem / mcelem mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.34::real,
'most_common_elems', '{one,two}'::text
@@ -627,8 +698,9 @@ AND attname = 'tags';
-- warn: mcelem / mcelem null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.35::real,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
@@ -643,8 +715,9 @@ AND attname = 'tags';
-- ok: mcelem
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'most_common_elems', '{one,three}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
@@ -659,8 +732,9 @@ AND attname = 'tags';
-- warn: scalars can't have elem_count_histogram, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.36::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -707,8 +781,9 @@ SELECT s.schemaname, s.tablename, s.attname, s.inherited, r.*
FROM pg_catalog.pg_stats AS s
CROSS JOIN LATERAL
pg_catalog.pg_restore_attribute_stats(
- 'relation', ('stats_import.' || s.tablename || '_clone')::regclass,
- 'attname', s.attname,
+ 'schemaname', 'stats_import',
+ 'relname', s.tablename::text || '_clone',
+ 'attname', s.attname::text,
'inherited', s.inherited,
'version', 150000,
'null_frac', s.null_frac,
@@ -853,9 +928,10 @@ AND inherited = false
AND attname = 'arange';
SELECT pg_catalog.pg_clear_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean);
+ schemaname => 'stats_import',
+ relname => 'test',
+ attname => 'arange',
+ inherited => false);
SELECT COUNT(*)
FROM pg_stats
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 51dd8ad6571..63a260a8ff8 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -30348,22 +30348,24 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<structname>mytable</structname>:
<programlisting>
SELECT pg_restore_relation_stats(
- 'relation', 'mytable'::regclass,
- 'relpages', 173::integer,
- 'reltuples', 10000::real);
+ 'schemaname', 'myschema',
+ 'relname', 'mytable',
+ 'relpages', 173::integer,
+ 'reltuples', 10000::real);
</programlisting>
</para>
<para>
- The argument <literal>relation</literal> with a value of type
- <type>regclass</type> is required, and specifies the table. Other
- arguments are the names and values of statistics corresponding to
- certain columns in <link
+ The arguments <literal>schemaname</literal> with a value of type
+ <type>regclass</type> and <literal>relname</literal> are required,
+ and specifies the table. Other arguments are the names and values
+ of statistics corresponding to certain columns in <link
linkend="catalog-pg-class"><structname>pg_class</structname></link>.
The currently-supported relation statistics are
<literal>relpages</literal> with a value of type
<type>integer</type>, <literal>reltuples</literal> with a value of
- type <type>real</type>, and <literal>relallvisible</literal> with a
- value of type <type>integer</type>.
+ type <type>real</type>, <literal>relallvisible</literal> with a
+ value of type <type>integer</type>, and <literal>relallfrozen</literal>
+ with a value of type <type>integer</type>.
</para>
<para>
Additionally, this function accepts argument name
@@ -30391,7 +30393,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<indexterm>
<primary>pg_clear_relation_stats</primary>
</indexterm>
- <function>pg_clear_relation_stats</function> ( <parameter>relation</parameter> <type>regclass</type> )
+ <function>pg_clear_relation_stats</function> ( <parameter>schemaname</parameter> <type>text</type>, <parameter>relname</parameter> <type>text</type> )
<returnvalue>void</returnvalue>
</para>
<para>
@@ -30440,16 +30442,18 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<structname>mytable</structname>:
<programlisting>
SELECT pg_restore_attribute_stats(
- 'relation', 'mytable'::regclass,
- 'attname', 'col1'::name,
- 'inherited', false,
- 'avg_width', 125::integer,
- 'null_frac', 0.5::real);
+ 'schemaname', 'myschema',
+ 'relname', 'mytable',
+ 'attname', 'col1',
+ 'inherited', false,
+ 'avg_width', 125::integer,
+ 'null_frac', 0.5::real);
</programlisting>
</para>
<para>
- The required arguments are <literal>relation</literal> with a value
- of type <type>regclass</type>, which specifies the table; either
+ The required arguments are <literal>schemaname</literal> with a value
+ of type <type>regclass</type> and <literal>relname</literal> with a value
+ of type <type>text</type> which specify the table; either
<literal>attname</literal> with a value of type <type>name</type> or
<literal>attnum</literal> with a value of type <type>smallint</type>,
which specifies the column; and <literal>inherited</literal>, which
@@ -30485,7 +30489,8 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<primary>pg_clear_attribute_stats</primary>
</indexterm>
<function>pg_clear_attribute_stats</function> (
- <parameter>relation</parameter> <type>regclass</type>,
+ <parameter>schemaname</parameter> <type>text</type>,
+ <parameter>relname</parameter> <type>text</type>,
<parameter>attname</parameter> <type>name</type>,
<parameter>inherited</parameter> <type>boolean</type> )
<returnvalue>void</returnvalue>
base-commit: 21f653cc0024100f8ecc279162631f2b1ba8c46c
--
2.48.1
[text/x-patch] v7-0002-Downgrade-as-man-pg_restore_-_stats-errors-to-war.patch (29.8K, ../../CADkLM=fLUHPzvVnM4eC8ZdVH0wewXfZmVoVmn4=8pb12+s1v7Q@mail.gmail.com/4-v7-0002-Downgrade-as-man-pg_restore_-_stats-errors-to-war.patch)
download | inline diff:
From 4d8d76b78b87f53d0adbd6781a2a66beac5bc264 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sat, 8 Mar 2025 00:52:41 -0500
Subject: [PATCH v7 2/2] Downgrade as man pg_restore_*_stats errors to
warnings.
We want to avoid errors that can potentially stop an otherwise
successful pg_upgrade or pg_restore operation. With that in mind, change
as many ERROR reports to WARNING + early termination with no data
updated.
---
src/include/statistics/stat_utils.h | 4 +-
src/backend/statistics/attribute_stats.c | 124 +++++++++++-----
src/backend/statistics/relation_stats.c | 10 +-
src/backend/statistics/stat_utils.c | 51 +++++--
src/test/regress/expected/stats_import.out | 163 ++++++++++++++++-----
src/test/regress/sql/stats_import.sql | 36 ++---
6 files changed, 277 insertions(+), 111 deletions(-)
diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h
index cad042c8e4a..298cbae3436 100644
--- a/src/include/statistics/stat_utils.h
+++ b/src/include/statistics/stat_utils.h
@@ -21,7 +21,7 @@ struct StatsArgInfo
Oid argtype;
};
-extern void stats_check_required_arg(FunctionCallInfo fcinfo,
+extern bool stats_check_required_arg(FunctionCallInfo fcinfo,
struct StatsArgInfo *arginfo,
int argnum);
extern bool stats_check_arg_array(FunctionCallInfo fcinfo,
@@ -30,7 +30,7 @@ extern bool stats_check_arg_pair(FunctionCallInfo fcinfo,
struct StatsArgInfo *arginfo,
int argnum1, int argnum2);
-extern void stats_lock_check_privileges(Oid reloid);
+extern bool stats_lock_check_privileges(Oid reloid);
extern Oid stats_schema_check_privileges(const char *nspname);
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index f87db2d6102..4f9bc18f8c6 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -100,7 +100,7 @@ static struct StatsArgInfo cleararginfo[] =
static bool attribute_statistics_update(FunctionCallInfo fcinfo);
static Node *get_attr_expr(Relation rel, int attnum);
-static void get_attr_stat_type(Oid reloid, AttrNumber attnum,
+static bool get_attr_stat_type(Oid reloid, AttrNumber attnum,
Oid *atttypid, int32 *atttypmod,
char *atttyptype, Oid *atttypcoll,
Oid *eq_opr, Oid *lt_opr);
@@ -129,10 +129,12 @@ static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
* stored as an anyarray, and the representation of the array needs to store
* the correct element type, which must be derived from the attribute.
*
- * Major errors, such as the table not existing, the attribute not existing,
- * or a permissions failure are always reported at ERROR. Other errors, such
- * as a conversion failure on one statistic kind, are reported as a WARNING
- * and other statistic kinds may still be updated.
+ * This function is called during database upgrades and restorations, therefore
+ * it is imperative to avoid ERRORs that could potentially end the upgrade or
+ * restore unless. Major errors, such as the table not existing, the attribute
+ * not existing, or permissions failure are reported as WARNINGs with an end to
+ * the function, thus allowing the upgrade/restore to continue, but without the
+ * stats that can be regenereated once the database is online again.
*/
static bool
attribute_statistics_update(FunctionCallInfo fcinfo)
@@ -149,8 +151,8 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
HeapTuple statup;
Oid atttypid = InvalidOid;
- int32 atttypmod;
- char atttyptype;
+ int32 atttypmod = -1;
+ char atttyptype = TYPTYPE_PSEUDO; /* Not a great default, but there is no TYPTYPE_INVALID */
Oid atttypcoll = InvalidOid;
Oid eq_opr = InvalidOid;
Oid lt_opr = InvalidOid;
@@ -177,17 +179,19 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
bool result = true;
- stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG);
- stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG);
+ if (!stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG))
+ return false;
+ if (!stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG))
+ return false;
nspname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELSCHEMA_ARG));
nspoid = stats_schema_check_privileges(nspname);
- if (nspoid == InvalidOid)
+ if (!OidIsValid(nspoid))
return false;
relname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELNAME_ARG));
reloid = get_relname_relid(relname, nspoid);
- if (reloid == InvalidOid)
+ if (!OidIsValid(reloid))
{
ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_OBJECT),
@@ -196,29 +200,39 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
}
if (RecoveryInProgress())
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("recovery is in progress"),
errhint("Statistics cannot be modified during recovery.")));
+ return false;
+ }
/* lock before looking up attribute */
- stats_lock_check_privileges(reloid);
+ if (!stats_lock_check_privileges(reloid))
+ return false;
/* user can specify either attname or attnum, but not both */
if (!PG_ARGISNULL(ATTNAME_ARG))
{
if (!PG_ARGISNULL(ATTNUM_ARG))
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("cannot specify both attname and attnum")));
+ return false;
+ }
attname = TextDatumGetCString(PG_GETARG_DATUM(ATTNAME_ARG));
attnum = get_attnum(reloid, attname);
/* note that this test covers attisdropped cases too: */
if (attnum == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\".\"%s\" does not exist",
attname, nspname, relname)));
+ return false;
+ }
}
else if (!PG_ARGISNULL(ATTNUM_ARG))
{
@@ -227,27 +241,33 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
/* annoyingly, get_attname doesn't check attisdropped */
if (attname == NULL ||
!SearchSysCacheExistsAttName(reloid, attname))
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column %d of relation \"%s\".\"%s\" does not exist",
attnum, nspname, relname)));
+ return false;
+ }
}
else
{
- ereport(ERROR,
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("must specify either attname or attnum")));
- attname = NULL; /* keep compiler quiet */
- attnum = 0;
+ return false;
}
if (attnum < 0)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot modify statistics on system column \"%s\"",
attname)));
+ return false;
+ }
- stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG);
+ if (!stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG))
+ return false;
inherited = PG_GETARG_BOOL(INHERITED_ARG);
/*
@@ -296,10 +316,11 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
}
/* derive information from attribute */
- get_attr_stat_type(reloid, attnum,
- &atttypid, &atttypmod,
- &atttyptype, &atttypcoll,
- &eq_opr, <_opr);
+ if (!get_attr_stat_type(reloid, attnum,
+ &atttypid, &atttypmod,
+ &atttyptype, &atttypcoll,
+ &eq_opr, <_opr))
+ result = false;
/* if needed, derive element type */
if (do_mcelem || do_dechist)
@@ -579,7 +600,7 @@ get_attr_expr(Relation rel, int attnum)
/*
* Derive type information from the attribute.
*/
-static void
+static bool
get_attr_stat_type(Oid reloid, AttrNumber attnum,
Oid *atttypid, int32 *atttypmod,
char *atttyptype, Oid *atttypcoll,
@@ -596,18 +617,26 @@ get_attr_stat_type(Oid reloid, AttrNumber attnum,
/* Attribute not found */
if (!HeapTupleIsValid(atup))
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("attribute %d of relation \"%s\" does not exist",
attnum, RelationGetRelationName(rel))));
+ relation_close(rel, NoLock);
+ return false;
+ }
attr = (Form_pg_attribute) GETSTRUCT(atup);
if (attr->attisdropped)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("attribute %d of relation \"%s\" does not exist",
attnum, RelationGetRelationName(rel))));
+ relation_close(rel, NoLock);
+ return false;
+ }
expr = get_attr_expr(rel, attr->attnum);
@@ -656,6 +685,7 @@ get_attr_stat_type(Oid reloid, AttrNumber attnum,
*atttypcoll = DEFAULT_COLLATION_OID;
relation_close(rel, NoLock);
+ return true;
}
/*
@@ -781,6 +811,10 @@ set_stats_slot(Datum *values, bool *nulls, bool *replaces,
if (slotidx >= STATISTIC_NUM_SLOTS && first_empty >= 0)
slotidx = first_empty;
+ /*
+ * Currently there is no datatype that can have more than STATISTIC_NUM_SLOTS
+ * statistic kinds, so this can safely remain an ERROR for now.
+ */
if (slotidx >= STATISTIC_NUM_SLOTS)
ereport(ERROR,
(errmsg("maximum number of statistics slots exceeded: %d",
@@ -927,15 +961,19 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
AttrNumber attnum;
bool inherited;
- stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG);
- stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG);
- stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
- stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
+ if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG))
+ PG_RETURN_VOID();
+ if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG))
+ PG_RETURN_VOID();
+ if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG))
+ PG_RETURN_VOID();
+ if (!stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG))
+ PG_RETURN_VOID();
nspname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELSCHEMA_ARG));
nspoid = stats_schema_check_privileges(nspname);
if (!OidIsValid(nspoid))
- return false;
+ PG_RETURN_VOID();
relname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELNAME_ARG));
reloid = get_relname_relid(relname, nspoid);
@@ -944,31 +982,41 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
- return false;
+ PG_RETURN_VOID();
}
if (RecoveryInProgress())
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("recovery is in progress"),
errhint("Statistics cannot be modified during recovery.")));
+ PG_RETURN_VOID();
+ }
- stats_lock_check_privileges(reloid);
+ if (!stats_lock_check_privileges(reloid))
+ PG_RETURN_VOID();
attname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTNAME_ARG));
attnum = get_attnum(reloid, attname);
if (attnum < 0)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot clear statistics on system column \"%s\"",
attname)));
+ PG_RETURN_VOID();
+ }
if (attnum == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
attname, get_rel_name(reloid))));
+ PG_RETURN_VOID();
+ }
inherited = PG_GETARG_BOOL(C_INHERITED_ARG);
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index fdc69bc93e2..49109cf721d 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -84,8 +84,11 @@ relation_statistics_update(FunctionCallInfo fcinfo)
bool nulls[4] = {0};
int nreplaces = 0;
- stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG);
- stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG);
+ if (!stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG))
+ return false;
+
+ if (!stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG))
+ return false;
nspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
nspoid = stats_schema_check_privileges(nspname);
@@ -108,7 +111,8 @@ relation_statistics_update(FunctionCallInfo fcinfo)
errmsg("recovery is in progress"),
errhint("Statistics cannot be modified during recovery.")));
- stats_lock_check_privileges(reloid);
+ if (!stats_lock_check_privileges(reloid))
+ return false;
if (!PG_ARGISNULL(RELPAGES_ARG))
{
diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c
index e037d4994e8..dd9d88ac1c5 100644
--- a/src/backend/statistics/stat_utils.c
+++ b/src/backend/statistics/stat_utils.c
@@ -34,16 +34,20 @@
/*
* Ensure that a given argument is not null.
*/
-void
+bool
stats_check_required_arg(FunctionCallInfo fcinfo,
struct StatsArgInfo *arginfo,
int argnum)
{
if (PG_ARGISNULL(argnum))
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("\"%s\" cannot be NULL",
arginfo[argnum].argname)));
+ return false;
+ }
+ return true;
}
/*
@@ -128,13 +132,14 @@ stats_check_arg_pair(FunctionCallInfo fcinfo,
* - the role owns the current database and the relation is not shared
* - the role has the MAINTAIN privilege on the relation
*/
-void
+bool
stats_lock_check_privileges(Oid reloid)
{
Relation table;
Oid table_oid = reloid;
Oid index_oid = InvalidOid;
LOCKMODE index_lockmode = NoLock;
+ bool ok = true;
/*
* For indexes, we follow the locking behavior in do_analyze_rel() and
@@ -174,14 +179,15 @@ stats_lock_check_privileges(Oid reloid)
case RELKIND_PARTITIONED_TABLE:
break;
default:
- ereport(ERROR,
+ ereport(WARNING,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot modify statistics for relation \"%s\"",
RelationGetRelationName(table)),
errdetail_relkind_not_supported(table->rd_rel->relkind)));
+ ok = false;
}
- if (OidIsValid(index_oid))
+ if (ok && (OidIsValid(index_oid)))
{
Relation index;
@@ -194,25 +200,33 @@ stats_lock_check_privileges(Oid reloid)
relation_close(index, NoLock);
}
- if (table->rd_rel->relisshared)
- ereport(ERROR,
+ if (ok && (table->rd_rel->relisshared))
+ {
+ ereport(WARNING,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot modify statistics for shared relation")));
+ ok = false;
+ }
- if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()))
+ if (ok && (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId())))
{
AclResult aclresult = pg_class_aclcheck(RelationGetRelid(table),
GetUserId(),
ACL_MAINTAIN);
if (aclresult != ACLCHECK_OK)
- aclcheck_error(aclresult,
- get_relkind_objtype(table->rd_rel->relkind),
- NameStr(table->rd_rel->relname));
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied for relation %s",
+ NameStr(table->rd_rel->relname))));
+ ok = false;
+ }
}
/* retain lock on table */
relation_close(table, NoLock);
+ return ok;
}
@@ -318,9 +332,12 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
&args, &types, &argnulls);
if (nargs % 2 != 0)
- ereport(ERROR,
+ {
+ ereport(WARNING,
errmsg("variadic arguments must be name/value pairs"),
errhint("Provide an even number of variadic arguments that can be divided into pairs."));
+ return false;
+ }
/*
* For each argument name/value pair, find corresponding positional
@@ -333,14 +350,20 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
char *argname;
if (argnulls[i])
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errmsg("name at variadic position %d is NULL", i + 1)));
+ return false;
+ }
if (types[i] != TEXTOID)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errmsg("name at variadic position %d has type \"%s\", expected type \"%s\"",
i + 1, format_type_be(types[i]),
format_type_be(TEXTOID))));
+ return false;
+ }
if (argnulls[i + 1])
continue;
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 2f1295f2149..6551d6bf099 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -46,31 +46,51 @@ SELECT pg_clear_relation_stats('stats_import', 'test');
--
-- relstats tests
--
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'relname', 'test',
'relpages', 17::integer);
-ERROR: "schemaname" cannot be NULL
--- error: relname missing
+WARNING: "schemaname" cannot be NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+-- warning: relname missing, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relpages', 17::integer);
-ERROR: "relname" cannot be NULL
---- error: schemaname is wrong type
+WARNING: "relname" cannot be NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+--- warning: schemaname is wrong type, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 3.6::float,
'relname', 'test',
'relpages', 17::integer);
WARNING: argument "schemaname" has type "double precision", expected type "text"
-ERROR: "schemaname" cannot be NULL
---- error: relname is wrong type
+WARNING: "schemaname" cannot be NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+--- warning: relname is wrong type, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 0::oid,
'relpages', 17::integer);
WARNING: argument "relname" has type "oid", expected type "text"
-ERROR: "relname" cannot be NULL
--- error: relation not found
+WARNING: "relname" cannot be NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+-- warning: relation not found, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'nope',
@@ -81,19 +101,30 @@ WARNING: Relation "stats_import"."nope" not found.
f
(1 row)
--- error: odd number of variadic arguments cannot be pairs
+-- warning: odd number of variadic arguments cannot be pairs, nothing updated
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
'relallvisible');
-ERROR: variadic arguments must be name/value pairs
+WARNING: variadic arguments must be name/value pairs
HINT: Provide an even number of variadic arguments that can be divided into pairs.
--- error: argument name is NULL
+WARNING: "schemaname" cannot be NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+-- warning: argument name is NULL, nothing updated
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
NULL, '17'::integer);
-ERROR: name at variadic position 5 is NULL
+WARNING: name at variadic position 5 is NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
-- starting stats
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
@@ -345,26 +376,46 @@ CREATE SEQUENCE stats_import.testseq;
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'testseq');
-ERROR: cannot modify statistics for relation "testseq"
+WARNING: cannot modify statistics for relation "testseq"
DETAIL: This operation is not supported for sequences.
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
-ERROR: cannot modify statistics for relation "testseq"
+WARNING: cannot modify statistics for relation "testseq"
DETAIL: This operation is not supported for sequences.
+ pg_clear_relation_stats
+-------------------------
+
+(1 row)
+
CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
-ERROR: cannot modify statistics for relation "testview"
+WARNING: cannot modify statistics for relation "testview"
DETAIL: This operation is not supported for views.
+ pg_clear_relation_stats
+-------------------------
+
+(1 row)
+
--
-- attribute stats
--
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: "schemaname" cannot be NULL
--- error: schema does not exist
+WARNING: "schemaname" cannot be NULL
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: schema does not exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'nope',
'relname', 'test',
@@ -377,14 +428,19 @@ WARNING: schema nope does not exist
f
(1 row)
--- error: relname missing
+-- warning: relname missing, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: "relname" cannot be NULL
--- error: relname does not exist
+WARNING: "relname" cannot be NULL
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: relname does not exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'nope',
@@ -397,23 +453,33 @@ WARNING: Relation "stats_import"."nope" not found.
f
(1 row)
--- error: relname null
+-- warning: relname null, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', NULL,
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: "relname" cannot be NULL
--- error: NULL attname
+WARNING: "relname" cannot be NULL
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: NULL attname, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', NULL,
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: must specify either attname or attnum
--- error: attname doesn't exist
+WARNING: must specify either attname or attnum
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: attname doesn't exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -422,8 +488,13 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'null_frac', 0.1::real,
'avg_width', 2::integer,
'n_distinct', 0.3::real);
-ERROR: column "nope" of relation "stats_import"."test" does not exist
--- error: both attname and attnum
+WARNING: column "nope" of relation "stats_import"."test" does not exist
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: both attname and attnum, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -431,30 +502,50 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: cannot specify both attname and attnum
--- error: neither attname nor attnum
+WARNING: cannot specify both attname and attnum
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: neither attname nor attnum, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: must specify either attname or attnum
--- error: attribute is system column
+WARNING: must specify either attname or attnum
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: attribute is system column, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'xmin',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: cannot modify statistics on system column "xmin"
--- error: inherited null
+WARNING: cannot modify statistics on system column "xmin"
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: inherited null, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'id',
'inherited', NULL::boolean,
'null_frac', 0.1::real);
-ERROR: "inherited" cannot be NULL
+WARNING: "inherited" cannot be NULL
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
-- ok: just the fixed values, with version, no stakinds
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index ccdc44e9236..dbbebce1673 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -39,41 +39,41 @@ SELECT pg_clear_relation_stats('stats_import', 'test');
-- relstats tests
--
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'relname', 'test',
'relpages', 17::integer);
--- error: relname missing
+-- warning: relname missing, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relpages', 17::integer);
---- error: schemaname is wrong type
+--- warning: schemaname is wrong type, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 3.6::float,
'relname', 'test',
'relpages', 17::integer);
---- error: relname is wrong type
+--- warning: relname is wrong type, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 0::oid,
'relpages', 17::integer);
--- error: relation not found
+-- warning: relation not found, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'nope',
'relpages', 17::integer);
--- error: odd number of variadic arguments cannot be pairs
+-- warning: odd number of variadic arguments cannot be pairs, nothing updated
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
'relallvisible');
--- error: argument name is NULL
+-- warning: argument name is NULL, nothing updated
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -246,14 +246,14 @@ SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname
-- attribute stats
--
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: schema does not exist
+-- warning: schema does not exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'nope',
'relname', 'test',
@@ -261,14 +261,14 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: relname missing
+-- warning: relname missing, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: relname does not exist
+-- warning: relname does not exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'nope',
@@ -276,7 +276,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: relname null
+-- warning: relname null, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', NULL,
@@ -284,7 +284,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: NULL attname
+-- warning: NULL attname, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -292,7 +292,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: attname doesn't exist
+-- warning: attname doesn't exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -302,7 +302,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'avg_width', 2::integer,
'n_distinct', 0.3::real);
--- error: both attname and attnum
+-- warning: both attname and attnum, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -311,14 +311,14 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: neither attname nor attnum
+-- warning: neither attname nor attnum, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: attribute is system column
+-- warning: attribute is system column, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -326,7 +326,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: inherited null
+-- warning: inherited null, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
--
2.48.1
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-08 15:56 Robert Treat <[email protected]>
parent: Corey Huinker <[email protected]>
0 siblings, 2 replies; 199+ messages in thread
From: Robert Treat @ 2025-03-08 15:56 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Fri, Mar 7, 2025 at 10:40 PM Corey Huinker <[email protected]>
wrote:
>
>> if you want everything --include=schema,data,statistics (presumably
>> redundant with the default behavior)
>> if you want schema only --include=schema
>> if you want "everything except schema" --include=data,statistics
>>
>
> Until we add a fourth option, and then it becomes completely ambiguous as
> to whether you wanted data+statstics, or you not-wanted schema.
>
>
except it is perfectly clear that you *asked for* data and statistics, so
you get what you asked for. however the user conjures in their heads what
they are looking for, the logic is simple, you get what you asked for.
>
>
> And if someday, for example, there is ever agreement on including role
>> information with normal pg_dump, you add "roles" as an option to be
>> parsed via --include without having to create any new flags.
>>
>
> This is pushing a burden onto our customers for a parsing convenience.
>
>
In the UX world, the general pattern is people start to get overwhelmed
once you get over a 1/2 dozen options (I think that's based on Miller's
law, but might be mis-remembering); we are already at 9 for this use case.
So really it is quite the opposite, we'd be reducing the burden on
customers by simplifying the interface rather than just throwing out every
possible combination and saying "you figure it out".
Robert Treat
https://xzilla.net
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-08 19:09 Corey Huinker <[email protected]>
parent: Robert Treat <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Corey Huinker @ 2025-03-08 19:09 UTC (permalink / raw)
To: Robert Treat <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
>
> Until we add a fourth option, and then it becomes completely ambiguous as
>> to whether you wanted data+statstics, or you not-wanted schema.
>>
>>
> except it is perfectly clear that you *asked for* data and statistics, so
> you get what you asked for. however the user conjures in their heads what
> they are looking for, the logic is simple, you get what you asked for.
>
They *asked for* that because they didn't have the mechanism to say "hold
the mayo" or "everything except pickles". That's reducing their choice, and
then blaming them for their choice.
In the UX world, the general pattern is people start to get overwhelmed
> once you get over a 1/2 dozen options (I think that's based on Miller's
> law, but might be mis-remembering); we are already at 9 for this use case.
> So really it is quite the opposite, we'd be reducing the burden on
> customers by simplifying the interface rather than just throwing out every
> possible combination and saying "you figure it out".
>
Except that those options are easily grouped into families. I see that
there's a --no-comments flag, so why wouldn't there be a --no-statistics
flag? Lots of $thing have a --no-$thing. That's the established UX pattern
_working_. The user learned that pattern and we shouldn't punish them by
changing it for our own parsing convenience.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-09 17:00 Jeff Davis <[email protected]>
parent: Robert Treat <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Jeff Davis @ 2025-03-09 17:00 UTC (permalink / raw)
To: Robert Treat <[email protected]>; Corey Huinker <[email protected]>; +Cc: Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Sat, 2025-03-08 at 10:56 -0500, Robert Treat wrote:
> In the UX world, the general pattern is people start to get
> overwhelmed once you get over a 1/2 dozen options (I think that's
> based on Miller's law, but might be mis-remembering); we are already
> at 9 for this use case. So really it is quite the opposite, we'd be
> reducing the burden on customers by simplifying the interface rather
> than just throwing out every possible combination and saying "you
> figure it out".
To be clear about your proposal:
* --include conflicts with --schema-only and --data-only
* --include overrides any default
is that right?
Thoughts on how we should document when/how to use --section vs --
include? Granted, that might be a point of confusion regardless of the
options we offer.
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export: difference in statistics dumped
@ 2025-03-10 21:53 Tom Lane <[email protected]>
parent: Tom Lane <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Tom Lane @ 2025-03-10 21:53 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; Alvaro Herrera <[email protected]>; jian he <[email protected]>
I wrote:
> I think what is happening is that the patch shut off CREATE
> INDEX's update of not only the table's stats but also the
> index's stats. This seems unhelpful: the index's empty
> stats can never be what's wanted.
I looked at this more closely and realized that it's a simple matter
of having made the tests in the wrong order. The whole stanza
should only apply when dealing with the table, not the index.
I verified that this change fixes the cross-version-upgrade
failure in local testing, and pushed it.
regards, tom lane
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export: difference in statistics dumped
@ 2025-03-10 23:53 Jeff Davis <[email protected]>
parent: Tom Lane <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Jeff Davis @ 2025-03-10 23:53 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; Alvaro Herrera <[email protected]>; jian he <[email protected]>
On Mon, 2025-03-10 at 17:53 -0400, Tom Lane wrote:
> I wrote:
> > I think what is happening is that the patch shut off CREATE
> > INDEX's update of not only the table's stats but also the
> > index's stats. This seems unhelpful: the index's empty
> > stats can never be what's wanted.
>
> I looked at this more closely and realized that it's a simple matter
> of having made the tests in the wrong order. The whole stanza
> should only apply when dealing with the table, not the index.
>
> I verified that this change fixes the cross-version-upgrade
> failure in local testing, and pushed it.
Ah, thank you.
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export: difference in statistics dumped
@ 2025-03-11 10:20 Ashutosh Bapat <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Ashutosh Bapat @ 2025-03-11 10:20 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Tom Lane <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; Alvaro Herrera <[email protected]>; jian he <[email protected]>
On Tue, Mar 11, 2025 at 5:23 AM Jeff Davis <[email protected]> wrote:
>
> On Mon, 2025-03-10 at 17:53 -0400, Tom Lane wrote:
> > I wrote:
> > > I think what is happening is that the patch shut off CREATE
> > > INDEX's update of not only the table's stats but also the
> > > index's stats. This seems unhelpful: the index's empty
> > > stats can never be what's wanted.
> >
> > I looked at this more closely and realized that it's a simple matter
> > of having made the tests in the wrong order. The whole stanza
> > should only apply when dealing with the table, not the index.
> >
> > I verified that this change fixes the cross-version-upgrade
> > failure in local testing, and pushed it.
>
> Ah, thank you.
>
> Regards,
> Jeff Davis
>
Thanks. I verified that it has been fixed now. But there's something
wrong with materialized view statistics. I am starting a new thread
for the same.
--
Best Wishes,
Ashutosh Bapat
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-14 20:03 Corey Huinker <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 2 replies; 199+ messages in thread
From: Corey Huinker @ 2025-03-14 20:03 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
New patches and a rebase.
0001 - no changes, but the longer I go the more I'm certain this is
something we want to do.
0002- same as 0001
0003 -
Storing the restore function calls in the archive entry hogged a lot of
memory and made people nervous. This introduces a new function pointer that
generates those restore SQL calls right before they're written to disk,
thus reducing the memory load from "stats for every object to be dumped" to
just one object. Thanks to Nathan for diagnosing some weird quirks with
various formats.
0004 -
This replaces the query in the prepared statement with one that batches
them 100 relations at a time, and then maintains that result set until it
is consumed. It seems to have obvious speedups.
database pg14, 100k tables x 2 columns each:
0004: 34.5s with statistics, 25.04s without
0003: 42.23s with statistics, 24.29s without
0002: 42.25s with statistics, 23.17s without
Gory details:
PGSERVICE=benchmark14 time /usr/local/pgsql/bin/pg_dump --file=tip.run1.dump
5.45user 2.38system 0:34.50elapsed 22%CPU (0avgtext+0avgdata
912680maxresident)k
0inputs+2105736outputs (0major+245090minor)pagefaults 0swaps
PGSERVICE=benchmark14 time /usr/local/pgsql/bin/pg_dump --no-statistics
--file=tip.nostats.run1.dump
4.36user 2.05system 0:25.04elapsed 25%CPU (0avgtext+0avgdata
702488maxresident)k
0inputs+1643048outputs (0major+192512minor)pagefaults 0swaps
PGSERVICE=benchmark14 time /usr/local/pgsql/bin/pg_dump
--file=nobatch.run1.dump
5.60user 3.95system 0:42.23elapsed 22%CPU (0avgtext+0avgdata
902424maxresident)k
0inputs+2105672outputs (0major+242536minor)pagefaults 0swaps
PGSERVICE=benchmark14 time /usr/local/pgsql/bin/pg_dump --no-statistics
--file=nobatch-nostats.run1.dump
4.38user 2.13system 0:24.29elapsed 26%CPU (0avgtext+0avgdata
702292maxresident)k
48inputs+1642952outputs (0major+192515minor)pagefaults 0swaps
PGSERVICE=benchmark14 time /usr/local/pgsql/bin/pg_dump
--file=nostmtfn.run1.dump
6.01user 4.47system 0:42.25elapsed 24%CPU (0avgtext+0avgdata
1089784maxresident)k
0inputs+2106840outputs (0major+289407minor)pagefaults 0swaps
PGSERVICE=benchmark14 time /usr/local/pgsql/bin/pg_dump --no-statistics
--file=nostmtfn-nostats.run1.dump
4.35user 2.13system 0:23.17elapsed 27%CPU (0avgtext+0avgdata
690000maxresident)k
0inputs+1642952outputs (0major+189383minor)pagefaults 0swaps
Attachments:
[text/x-patch] v8-0001-Split-relation-into-schemaname-and-relname.patch (65.0K, ../../CADkLM=c+r05srPy9w+-+nbmLEo15dKXYQ03Q_xyK+riJerigLQ@mail.gmail.com/3-v8-0001-Split-relation-into-schemaname-and-relname.patch)
download | inline diff:
From a2c68b8390cf137323f449a4bc826ad66bada0bb Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Tue, 4 Mar 2025 22:16:52 -0500
Subject: [PATCH v8 1/4] Split relation into schemaname and relname.
In order to further reduce potential error-failures in restores and
upgrades, replace the numerous casts of fully qualified relation names
into their schema+relname text components.
Further remove the ::name casts on attname and change the expected
datatype to text.
Add an ACL_USAGE check on the namespace oid after it is looked up.
---
src/include/catalog/pg_proc.dat | 8 +-
src/include/statistics/stat_utils.h | 2 +
src/backend/statistics/attribute_stats.c | 87 ++++--
src/backend/statistics/relation_stats.c | 65 +++--
src/backend/statistics/stat_utils.c | 37 +++
src/bin/pg_dump/pg_dump.c | 25 +-
src/bin/pg_dump/t/002_pg_dump.pl | 6 +-
src/test/regress/expected/stats_import.out | 307 +++++++++++++--------
src/test/regress/sql/stats_import.sql | 276 +++++++++++-------
doc/src/sgml/func.sgml | 41 +--
10 files changed, 566 insertions(+), 288 deletions(-)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 890822eaf79..8dee321d248 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12453,8 +12453,8 @@
descr => 'clear statistics on relation',
proname => 'pg_clear_relation_stats', provolatile => 'v', proisstrict => 'f',
proparallel => 'u', prorettype => 'void',
- proargtypes => 'regclass',
- proargnames => '{relation}',
+ proargtypes => 'text text',
+ proargnames => '{schemaname,relname}',
prosrc => 'pg_clear_relation_stats' },
{ oid => '8461',
descr => 'restore statistics on attribute',
@@ -12469,8 +12469,8 @@
descr => 'clear statistics on attribute',
proname => 'pg_clear_attribute_stats', provolatile => 'v', proisstrict => 'f',
proparallel => 'u', prorettype => 'void',
- proargtypes => 'regclass name bool',
- proargnames => '{relation,attname,inherited}',
+ proargtypes => 'text text text bool',
+ proargnames => '{schemaname,relname,attname,inherited}',
prosrc => 'pg_clear_attribute_stats' },
# GiST stratnum implementations
diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h
index 0eb4decfcac..cad042c8e4a 100644
--- a/src/include/statistics/stat_utils.h
+++ b/src/include/statistics/stat_utils.h
@@ -32,6 +32,8 @@ extern bool stats_check_arg_pair(FunctionCallInfo fcinfo,
extern void stats_lock_check_privileges(Oid reloid);
+extern Oid stats_schema_check_privileges(const char *nspname);
+
extern bool stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
FunctionCallInfo positional_fcinfo,
struct StatsArgInfo *arginfo);
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index 6bcbee0edba..f87db2d6102 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -36,7 +36,8 @@
enum attribute_stats_argnum
{
- ATTRELATION_ARG = 0,
+ ATTRELSCHEMA_ARG = 0,
+ ATTRELNAME_ARG,
ATTNAME_ARG,
ATTNUM_ARG,
INHERITED_ARG,
@@ -58,8 +59,9 @@ enum attribute_stats_argnum
static struct StatsArgInfo attarginfo[] =
{
- [ATTRELATION_ARG] = {"relation", REGCLASSOID},
- [ATTNAME_ARG] = {"attname", NAMEOID},
+ [ATTRELSCHEMA_ARG] = {"schemaname", TEXTOID},
+ [ATTRELNAME_ARG] = {"relname", TEXTOID},
+ [ATTNAME_ARG] = {"attname", TEXTOID},
[ATTNUM_ARG] = {"attnum", INT2OID},
[INHERITED_ARG] = {"inherited", BOOLOID},
[NULL_FRAC_ARG] = {"null_frac", FLOAT4OID},
@@ -80,7 +82,8 @@ static struct StatsArgInfo attarginfo[] =
enum clear_attribute_stats_argnum
{
- C_ATTRELATION_ARG = 0,
+ C_ATTRELSCHEMA_ARG = 0,
+ C_ATTRELNAME_ARG,
C_ATTNAME_ARG,
C_INHERITED_ARG,
C_NUM_ATTRIBUTE_STATS_ARGS
@@ -88,8 +91,9 @@ enum clear_attribute_stats_argnum
static struct StatsArgInfo cleararginfo[] =
{
- [C_ATTRELATION_ARG] = {"relation", REGCLASSOID},
- [C_ATTNAME_ARG] = {"attname", NAMEOID},
+ [C_ATTRELSCHEMA_ARG] = {"relation", TEXTOID},
+ [C_ATTRELNAME_ARG] = {"relation", TEXTOID},
+ [C_ATTNAME_ARG] = {"attname", TEXTOID},
[C_INHERITED_ARG] = {"inherited", BOOLOID},
[C_NUM_ATTRIBUTE_STATS_ARGS] = {0}
};
@@ -133,6 +137,9 @@ static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
static bool
attribute_statistics_update(FunctionCallInfo fcinfo)
{
+ char *nspname;
+ Oid nspoid;
+ char *relname;
Oid reloid;
char *attname;
AttrNumber attnum;
@@ -170,8 +177,23 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
bool result = true;
- stats_check_required_arg(fcinfo, attarginfo, ATTRELATION_ARG);
- reloid = PG_GETARG_OID(ATTRELATION_ARG);
+ stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG);
+ stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG);
+
+ nspname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELSCHEMA_ARG));
+ nspoid = stats_schema_check_privileges(nspname);
+ if (nspoid == InvalidOid)
+ return false;
+
+ relname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELNAME_ARG));
+ reloid = get_relname_relid(relname, nspoid);
+ if (reloid == InvalidOid)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
+ return false;
+ }
if (RecoveryInProgress())
ereport(ERROR,
@@ -185,21 +207,18 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
/* user can specify either attname or attnum, but not both */
if (!PG_ARGISNULL(ATTNAME_ARG))
{
- Name attnamename;
-
if (!PG_ARGISNULL(ATTNUM_ARG))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("cannot specify both attname and attnum")));
- attnamename = PG_GETARG_NAME(ATTNAME_ARG);
- attname = NameStr(*attnamename);
+ attname = TextDatumGetCString(PG_GETARG_DATUM(ATTNAME_ARG));
attnum = get_attnum(reloid, attname);
/* note that this test covers attisdropped cases too: */
if (attnum == InvalidAttrNumber)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
- attname, get_rel_name(reloid))));
+ errmsg("column \"%s\" of relation \"%s\".\"%s\" does not exist",
+ attname, nspname, relname)));
}
else if (!PG_ARGISNULL(ATTNUM_ARG))
{
@@ -210,8 +229,8 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
!SearchSysCacheExistsAttName(reloid, attname))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column %d of relation \"%s\" does not exist",
- attnum, get_rel_name(reloid))));
+ errmsg("column %d of relation \"%s\".\"%s\" does not exist",
+ attnum, nspname, relname)));
}
else
{
@@ -900,13 +919,33 @@ init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
Datum
pg_clear_attribute_stats(PG_FUNCTION_ARGS)
{
+ char *nspname;
+ Oid nspoid;
+ char *relname;
Oid reloid;
- Name attname;
+ char *attname;
AttrNumber attnum;
bool inherited;
- stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELATION_ARG);
- reloid = PG_GETARG_OID(C_ATTRELATION_ARG);
+ stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG);
+ stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG);
+ stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
+ stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
+
+ nspname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELSCHEMA_ARG));
+ nspoid = stats_schema_check_privileges(nspname);
+ if (!OidIsValid(nspoid))
+ return false;
+
+ relname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELNAME_ARG));
+ reloid = get_relname_relid(relname, nspoid);
+ if (!OidIsValid(reloid))
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
+ return false;
+ }
if (RecoveryInProgress())
ereport(ERROR,
@@ -916,23 +955,21 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
stats_lock_check_privileges(reloid);
- stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
- attname = PG_GETARG_NAME(C_ATTNAME_ARG);
- attnum = get_attnum(reloid, NameStr(*attname));
+ attname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTNAME_ARG));
+ attnum = get_attnum(reloid, attname);
if (attnum < 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot clear statistics on system column \"%s\"",
- NameStr(*attname))));
+ attname)));
if (attnum == InvalidAttrNumber)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
- NameStr(*attname), get_rel_name(reloid))));
+ attname, get_rel_name(reloid))));
- stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
inherited = PG_GETARG_BOOL(C_INHERITED_ARG);
delete_pg_statistic(reloid, attnum, inherited);
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index 52dfa477187..fdc69bc93e2 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -19,9 +19,12 @@
#include "access/heapam.h"
#include "catalog/indexing.h"
+#include "catalog/namespace.h"
#include "statistics/stat_utils.h"
+#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/fmgrprotos.h"
+#include "utils/lsyscache.h"
#include "utils/syscache.h"
@@ -32,7 +35,8 @@
enum relation_stats_argnum
{
- RELATION_ARG = 0,
+ RELSCHEMA_ARG = 0,
+ RELNAME_ARG,
RELPAGES_ARG,
RELTUPLES_ARG,
RELALLVISIBLE_ARG,
@@ -42,7 +46,8 @@ enum relation_stats_argnum
static struct StatsArgInfo relarginfo[] =
{
- [RELATION_ARG] = {"relation", REGCLASSOID},
+ [RELSCHEMA_ARG] = {"schemaname", TEXTOID},
+ [RELNAME_ARG] = {"relname", TEXTOID},
[RELPAGES_ARG] = {"relpages", INT4OID},
[RELTUPLES_ARG] = {"reltuples", FLOAT4OID},
[RELALLVISIBLE_ARG] = {"relallvisible", INT4OID},
@@ -59,6 +64,9 @@ static bool
relation_statistics_update(FunctionCallInfo fcinfo)
{
bool result = true;
+ char *nspname;
+ Oid nspoid;
+ char *relname;
Oid reloid;
Relation crel;
BlockNumber relpages = 0;
@@ -76,6 +84,32 @@ relation_statistics_update(FunctionCallInfo fcinfo)
bool nulls[4] = {0};
int nreplaces = 0;
+ stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG);
+ stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG);
+
+ nspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
+ nspoid = stats_schema_check_privileges(nspname);
+ if (!OidIsValid(nspoid))
+ return false;
+
+ relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
+ reloid = get_relname_relid(relname, nspoid);
+ if (!OidIsValid(reloid))
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
+ return false;
+ }
+
+ if (RecoveryInProgress())
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("recovery is in progress"),
+ errhint("Statistics cannot be modified during recovery.")));
+
+ stats_lock_check_privileges(reloid);
+
if (!PG_ARGISNULL(RELPAGES_ARG))
{
relpages = PG_GETARG_UINT32(RELPAGES_ARG);
@@ -108,17 +142,6 @@ relation_statistics_update(FunctionCallInfo fcinfo)
update_relallfrozen = true;
}
- stats_check_required_arg(fcinfo, relarginfo, RELATION_ARG);
- reloid = PG_GETARG_OID(RELATION_ARG);
-
- if (RecoveryInProgress())
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("recovery is in progress"),
- errhint("Statistics cannot be modified during recovery.")));
-
- stats_lock_check_privileges(reloid);
-
/*
* Take RowExclusiveLock on pg_class, consistent with
* vac_update_relstats().
@@ -187,20 +210,22 @@ relation_statistics_update(FunctionCallInfo fcinfo)
Datum
pg_clear_relation_stats(PG_FUNCTION_ARGS)
{
- LOCAL_FCINFO(newfcinfo, 5);
+ LOCAL_FCINFO(newfcinfo, 6);
- InitFunctionCallInfoData(*newfcinfo, NULL, 5, InvalidOid, NULL, NULL);
+ InitFunctionCallInfoData(*newfcinfo, NULL, 6, InvalidOid, NULL, NULL);
- newfcinfo->args[0].value = PG_GETARG_OID(0);
+ newfcinfo->args[0].value = PG_GETARG_DATUM(0);
newfcinfo->args[0].isnull = PG_ARGISNULL(0);
- newfcinfo->args[1].value = UInt32GetDatum(0);
- newfcinfo->args[1].isnull = false;
- newfcinfo->args[2].value = Float4GetDatum(-1.0);
+ newfcinfo->args[1].value = PG_GETARG_DATUM(1);
+ newfcinfo->args[1].isnull = PG_ARGISNULL(1);
+ newfcinfo->args[2].value = UInt32GetDatum(0);
newfcinfo->args[2].isnull = false;
- newfcinfo->args[3].value = UInt32GetDatum(0);
+ newfcinfo->args[3].value = Float4GetDatum(-1.0);
newfcinfo->args[3].isnull = false;
newfcinfo->args[4].value = UInt32GetDatum(0);
newfcinfo->args[4].isnull = false;
+ newfcinfo->args[5].value = UInt32GetDatum(0);
+ newfcinfo->args[5].isnull = false;
relation_statistics_update(newfcinfo);
PG_RETURN_VOID();
diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c
index 9647f5108b3..e037d4994e8 100644
--- a/src/backend/statistics/stat_utils.c
+++ b/src/backend/statistics/stat_utils.c
@@ -18,7 +18,9 @@
#include "access/relation.h"
#include "catalog/index.h"
+#include "catalog/namespace.h"
#include "catalog/pg_database.h"
+#include "catalog/pg_namespace.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "statistics/stat_utils.h"
@@ -213,6 +215,41 @@ stats_lock_check_privileges(Oid reloid)
relation_close(table, NoLock);
}
+
+/*
+ * Resolve a schema name into an Oid, ensure that the user has usage privs on
+ * that schema.
+ */
+Oid
+stats_schema_check_privileges(const char *nspname)
+{
+ Oid nspoid;
+ AclResult aclresult;
+
+ nspoid = get_namespace_oid(nspname, true);
+
+ if (nspoid == InvalidOid)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_SCHEMA_NAME),
+ errmsg("schema %s does not exist", nspname)));
+ return InvalidOid;
+ }
+
+ aclresult = object_aclcheck(NamespaceRelationId, nspoid, GetUserId(), ACL_USAGE);
+
+ if (aclresult != ACLCHECK_OK)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied for schema %s", nspname)));
+ return InvalidOid;
+ }
+
+ return nspoid;
+}
+
+
/*
* Find the argument number for the given argument name, returning -1 if not
* found.
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c371570501a..bd857bb076c 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -10490,7 +10490,6 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
PQExpBuffer out;
DumpId *deps = NULL;
int ndeps = 0;
- char *qualified_name;
int i_attname;
int i_inherited;
int i_null_frac;
@@ -10555,15 +10554,16 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
out = createPQExpBuffer();
- qualified_name = pg_strdup(fmtQualifiedDumpable(rsinfo));
-
/* restore relation stats */
appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
fout->remoteVersion);
- appendPQExpBufferStr(out, "\t'relation', ");
- appendStringLiteralAH(out, qualified_name, fout);
- appendPQExpBufferStr(out, "::regclass,\n");
+ appendPQExpBufferStr(out, "\t'schemaname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+ appendPQExpBufferStr(out, ",\n");
+ appendPQExpBufferStr(out, "\t'relname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+ appendPQExpBufferStr(out, ",\n");
appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer\n);\n",
@@ -10602,9 +10602,10 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
fout->remoteVersion);
- appendPQExpBufferStr(out, "\t'relation', ");
- appendStringLiteralAH(out, qualified_name, fout);
- appendPQExpBufferStr(out, "::regclass");
+ appendPQExpBufferStr(out, "\t'schemaname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+ appendPQExpBufferStr(out, ",\n\t'relname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.name, fout);
if (PQgetisnull(res, rownum, i_attname))
pg_fatal("attname cannot be NULL");
@@ -10616,7 +10617,10 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
* their attnames are not necessarily stable across dump/reload.
*/
if (rsinfo->nindAttNames == 0)
- appendNamedArgument(out, fout, "attname", "name", attname);
+ {
+ appendPQExpBuffer(out, ",\n\t'attname', ");
+ appendStringLiteralAH(out, attname, fout);
+ }
else
{
bool found = false;
@@ -10696,7 +10700,6 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
.deps = deps,
.nDeps = ndeps));
- free(qualified_name);
destroyPQExpBuffer(out);
destroyPQExpBuffer(query);
}
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c7bffc1b045..b037f239136 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -4725,14 +4725,16 @@ my %tests = (
regexp => qr/^
\QSELECT * FROM pg_catalog.pg_restore_relation_stats(\E\s+
'version',\s'\d+'::integer,\s+
- 'relation',\s'dump_test.dup_test_post_data_ix'::regclass,\s+
+ 'schemaname',\s'dump_test',\s+
+ 'relname',\s'dup_test_post_data_ix',\s+
'relpages',\s'\d+'::integer,\s+
'reltuples',\s'\d+'::real,\s+
'relallvisible',\s'\d+'::integer\s+
\);\s+
\QSELECT * FROM pg_catalog.pg_restore_attribute_stats(\E\s+
'version',\s'\d+'::integer,\s+
- 'relation',\s'dump_test.dup_test_post_data_ix'::regclass,\s+
+ 'schemaname',\s'dump_test',\s+
+ 'relname',\s'dup_test_post_data_ix',\s+
'attnum',\s'2'::smallint,\s+
'inherited',\s'f'::boolean,\s+
'null_frac',\s'0'::real,\s+
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 1f46d5e7854..2f1295f2149 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -14,7 +14,8 @@ CREATE TABLE stats_import.test(
) WITH (autovacuum_enabled = false);
SELECT
pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', 18::integer,
'reltuples', 21::real,
'relallvisible', 24::integer,
@@ -36,7 +37,7 @@ ORDER BY relname;
test | 18 | 21 | 24 | 27
(1 row)
-SELECT pg_clear_relation_stats('stats_import.test'::regclass);
+SELECT pg_clear_relation_stats('stats_import', 'test');
pg_clear_relation_stats
-------------------------
@@ -45,33 +46,54 @@ SELECT pg_clear_relation_stats('stats_import.test'::regclass);
--
-- relstats tests
--
---- error: relation is wrong type
+-- error: schemaname missing
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid,
+ 'relname', 'test',
'relpages', 17::integer);
-WARNING: argument "relation" has type "oid", expected type "regclass"
-ERROR: "relation" cannot be NULL
+ERROR: "schemaname" cannot be NULL
+-- error: relname missing
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 'stats_import',
+ 'relpages', 17::integer);
+ERROR: "relname" cannot be NULL
+--- error: schemaname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 3.6::float,
+ 'relname', 'test',
+ 'relpages', 17::integer);
+WARNING: argument "schemaname" has type "double precision", expected type "text"
+ERROR: "schemaname" cannot be NULL
+--- error: relname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 0::oid,
+ 'relpages', 17::integer);
+WARNING: argument "relname" has type "oid", expected type "text"
+ERROR: "relname" cannot be NULL
-- error: relation not found
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'nope',
'relpages', 17::integer);
-ERROR: could not open relation with OID 0
+WARNING: Relation "stats_import"."nope" not found.
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
-- error: odd number of variadic arguments cannot be pairs
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relallvisible');
ERROR: variadic arguments must be name/value pairs
HINT: Provide an even number of variadic arguments that can be divided into pairs.
-- error: argument name is NULL
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
NULL, '17'::integer);
-ERROR: name at variadic position 3 is NULL
--- error: argument name is not a text type
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 17, '17'::integer);
-ERROR: name at variadic position 3 has type "integer", expected type "text"
+ERROR: name at variadic position 5 is NULL
-- starting stats
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
@@ -84,7 +106,8 @@ WHERE oid = 'stats_import.test_i'::regclass;
-- regular indexes have special case locking rules
BEGIN;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.test_i'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test_i',
'relpages', 18::integer);
pg_restore_relation_stats
---------------------------
@@ -132,7 +155,8 @@ WHERE oid = 'stats_import.part_parent'::regclass;
--
BEGIN;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent_i'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'part_parent_i',
'relpages', 2::integer);
pg_restore_relation_stats
---------------------------
@@ -166,7 +190,8 @@ WHERE oid = 'stats_import.part_parent_i'::regclass;
-- ok: set all relstats, with version, no bounds checking
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'version', 150000::integer,
'relpages', '-17'::integer,
'reltuples', 400::real,
@@ -187,7 +212,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just relpages, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', '16'::integer);
pg_restore_relation_stats
---------------------------
@@ -204,7 +230,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just reltuples, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'reltuples', '500'::real);
pg_restore_relation_stats
---------------------------
@@ -221,7 +248,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just relallvisible, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relallvisible', 5::integer);
pg_restore_relation_stats
---------------------------
@@ -238,7 +266,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: just relallfrozen
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'version', 150000::integer,
'relallfrozen', 3::integer);
pg_restore_relation_stats
@@ -256,7 +285,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- warn: bad relpages type, rest updated
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', 'nope'::text,
'reltuples', 400.0::real,
'relallvisible', 4::integer,
@@ -277,7 +307,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- unrecognized argument name, rest ok
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', '171'::integer,
'nope', 10::integer);
WARNING: unrecognized argument name: "nope"
@@ -295,8 +326,7 @@ WHERE oid = 'stats_import.test'::regclass;
(1 row)
-- ok: clear stats
-SELECT pg_catalog.pg_clear_relation_stats(
- relation => 'stats_import.test'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'test');
pg_clear_relation_stats
-------------------------
@@ -313,87 +343,123 @@ WHERE oid = 'stats_import.test'::regclass;
-- invalid relkinds for statistics
CREATE SEQUENCE stats_import.testseq;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.testseq'::regclass);
+ 'schemaname', 'stats_import',
+ 'relname', 'testseq');
ERROR: cannot modify statistics for relation "testseq"
DETAIL: This operation is not supported for sequences.
-SELECT pg_catalog.pg_clear_relation_stats(
- 'stats_import.testseq'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
ERROR: cannot modify statistics for relation "testseq"
DETAIL: This operation is not supported for sequences.
CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
-SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.testview'::regclass);
-ERROR: cannot modify statistics for relation "testview"
-DETAIL: This operation is not supported for views.
-SELECT pg_catalog.pg_clear_relation_stats(
- 'stats_import.testview'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
ERROR: cannot modify statistics for relation "testview"
DETAIL: This operation is not supported for views.
--
-- attribute stats
--
--- error: object does not exist
+-- error: schemaname missing
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', '0'::oid::regclass,
- 'attname', 'id'::name,
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: could not open relation with OID 0
--- error: relation null
+ERROR: "schemaname" cannot be NULL
+-- error: schema does not exist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', NULL::oid::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'nope',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: "relation" cannot be NULL
+WARNING: schema nope does not exist
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- error: relname missing
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: "relname" cannot be NULL
+-- error: relname does not exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'nope',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+WARNING: Relation "stats_import"."nope" not found.
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- error: relname null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'relname', NULL,
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: "relname" cannot be NULL
-- error: NULL attname
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', NULL::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', NULL,
'inherited', false::boolean,
'null_frac', 0.1::real);
ERROR: must specify either attname or attnum
-- error: attname doesn't exist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'nope'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'nope',
'inherited', false::boolean,
'null_frac', 0.1::real,
'avg_width', 2::integer,
'n_distinct', 0.3::real);
-ERROR: column "nope" of relation "test" does not exist
+ERROR: column "nope" of relation "stats_import"."test" does not exist
-- error: both attname and attnum
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.1::real);
ERROR: cannot specify both attname and attnum
-- error: neither attname nor attnum
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'inherited', false::boolean,
'null_frac', 0.1::real);
ERROR: must specify either attname or attnum
-- error: attribute is system column
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'xmin'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'xmin',
'inherited', false::boolean,
'null_frac', 0.1::real);
ERROR: cannot modify statistics on system column "xmin"
-- error: inherited null
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', NULL::boolean,
'null_frac', 0.1::real);
ERROR: "inherited" cannot be NULL
-- ok: just the fixed values, with version, no stakinds
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'version', 150000::integer,
'null_frac', 0.2::real,
@@ -421,7 +487,8 @@ AND attname = 'id';
-- for any stat-having relation.
--
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.4::real);
@@ -443,8 +510,9 @@ AND attname = 'id';
-- warn: unrecognized argument name, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.2::real,
'nope', 0.5::real);
@@ -467,8 +535,9 @@ AND attname = 'id';
-- warn: mcv / mcf null mismatch part 1, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.21::real,
'most_common_freqs', '{0.1,0.2,0.3}'::real[]
@@ -492,8 +561,9 @@ AND attname = 'id';
-- warn: mcv / mcf null mismatch part 2, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.21::real,
'most_common_vals', '{1,2,3}'::text
@@ -517,8 +587,9 @@ AND attname = 'id';
-- warn: mcf type mismatch, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.22::real,
'most_common_vals', '{2,1,3}'::text,
@@ -544,8 +615,9 @@ AND attname = 'id';
-- warn: mcv cast failure, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.23::real,
'most_common_vals', '{2,four,3}'::text,
@@ -570,8 +642,9 @@ AND attname = 'id';
-- ok: mcv+mcf
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
@@ -594,8 +667,9 @@ AND attname = 'id';
-- warn: NULL in histogram array, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.24::real,
'histogram_bounds', '{1,NULL,3,4}'::text
@@ -619,8 +693,9 @@ AND attname = 'id';
-- ok: histogram_bounds
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'histogram_bounds', '{1,2,3,4}'::text
);
@@ -642,8 +717,9 @@ AND attname = 'id';
-- warn: elem_count_histogram null element, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.25::real,
'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[]
@@ -667,8 +743,9 @@ AND attname = 'tags';
-- ok: elem_count_histogram
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.26::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -691,8 +768,9 @@ AND attname = 'tags';
-- warn: range stats on a scalar type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.27::real,
'range_empty_frac', 0.5::real,
@@ -718,8 +796,9 @@ AND attname = 'id';
-- warn: range_empty_frac range_length_hist null mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.28::real,
'range_length_histogram', '{399,499,Infinity}'::text
@@ -743,8 +822,9 @@ AND attname = 'arange';
-- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.29::real,
'range_empty_frac', 0.5::real
@@ -768,8 +848,9 @@ AND attname = 'arange';
-- ok: range_empty_frac + range_length_hist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'range_empty_frac', 0.5::real,
'range_length_histogram', '{399,499,Infinity}'::text
@@ -792,8 +873,9 @@ AND attname = 'arange';
-- warn: range bounds histogram on scalar, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.31::real,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
@@ -818,8 +900,9 @@ AND attname = 'id';
-- ok: range_bounds_histogram
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
);
@@ -841,8 +924,9 @@ AND attname = 'arange';
-- warn: cannot set most_common_elems for range type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.32::real,
'most_common_elems', '{3,1}'::text,
@@ -868,8 +952,9 @@ AND attname = 'arange';
-- warn: scalars can't have mcelem, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.33::real,
'most_common_elems', '{1,3}'::text,
@@ -895,8 +980,9 @@ AND attname = 'id';
-- warn: mcelem / mcelem mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.34::real,
'most_common_elems', '{one,two}'::text
@@ -920,8 +1006,9 @@ AND attname = 'tags';
-- warn: mcelem / mcelem null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.35::real,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
@@ -945,8 +1032,9 @@ AND attname = 'tags';
-- ok: mcelem
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'most_common_elems', '{one,three}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
@@ -969,8 +1057,9 @@ AND attname = 'tags';
-- warn: scalars can't have elem_count_histogram, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.36::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -1022,8 +1111,9 @@ SELECT s.schemaname, s.tablename, s.attname, s.inherited, r.*
FROM pg_catalog.pg_stats AS s
CROSS JOIN LATERAL
pg_catalog.pg_restore_attribute_stats(
- 'relation', ('stats_import.' || s.tablename || '_clone')::regclass,
- 'attname', s.attname,
+ 'schemaname', 'stats_import',
+ 'relname', s.tablename::text || '_clone',
+ 'attname', s.attname::text,
'inherited', s.inherited,
'version', 150000,
'null_frac', s.null_frac,
@@ -1200,9 +1290,10 @@ AND attname = 'arange';
(1 row)
SELECT pg_catalog.pg_clear_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean);
+ schemaname => 'stats_import',
+ relname => 'test',
+ attname => 'arange',
+ inherited => false);
pg_clear_attribute_stats
--------------------------
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index 0ec590688c2..ccdc44e9236 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -17,7 +17,8 @@ CREATE TABLE stats_import.test(
SELECT
pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', 18::integer,
'reltuples', 21::real,
'relallvisible', 24::integer,
@@ -32,37 +33,52 @@ FROM pg_class
WHERE oid = 'stats_import.test'::regclass
ORDER BY relname;
-SELECT pg_clear_relation_stats('stats_import.test'::regclass);
+SELECT pg_clear_relation_stats('stats_import', 'test');
--
-- relstats tests
--
---- error: relation is wrong type
+-- error: schemaname missing
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid,
+ 'relname', 'test',
+ 'relpages', 17::integer);
+
+-- error: relname missing
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 'stats_import',
+ 'relpages', 17::integer);
+
+--- error: schemaname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 3.6::float,
+ 'relname', 'test',
+ 'relpages', 17::integer);
+
+--- error: relname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 0::oid,
'relpages', 17::integer);
-- error: relation not found
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'nope',
'relpages', 17::integer);
-- error: odd number of variadic arguments cannot be pairs
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relallvisible');
-- error: argument name is NULL
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
NULL, '17'::integer);
--- error: argument name is not a text type
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 17, '17'::integer);
-
-- starting stats
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
@@ -71,7 +87,8 @@ WHERE oid = 'stats_import.test_i'::regclass;
-- regular indexes have special case locking rules
BEGIN;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.test_i'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test_i',
'relpages', 18::integer);
SELECT mode FROM pg_locks
@@ -108,7 +125,8 @@ WHERE oid = 'stats_import.part_parent'::regclass;
BEGIN;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent_i'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'part_parent_i',
'relpages', 2::integer);
SELECT mode FROM pg_locks
@@ -127,7 +145,8 @@ WHERE oid = 'stats_import.part_parent_i'::regclass;
-- ok: set all relstats, with version, no bounds checking
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'version', 150000::integer,
'relpages', '-17'::integer,
'reltuples', 400::real,
@@ -140,7 +159,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just relpages, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', '16'::integer);
SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -149,7 +169,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just reltuples, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'reltuples', '500'::real);
SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -158,7 +179,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just relallvisible, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relallvisible', 5::integer);
SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -167,7 +189,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: just relallfrozen
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'version', 150000::integer,
'relallfrozen', 3::integer);
@@ -177,7 +200,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- warn: bad relpages type, rest updated
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', 'nope'::text,
'reltuples', 400.0::real,
'relallvisible', 4::integer,
@@ -189,7 +213,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- unrecognized argument name, rest ok
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', '171'::integer,
'nope', 10::integer);
@@ -198,8 +223,7 @@ FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
-- ok: clear stats
-SELECT pg_catalog.pg_clear_relation_stats(
- relation => 'stats_import.test'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'test');
SELECT relpages, reltuples, relallvisible
FROM pg_class
@@ -209,48 +233,70 @@ WHERE oid = 'stats_import.test'::regclass;
CREATE SEQUENCE stats_import.testseq;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.testseq'::regclass);
+ 'schemaname', 'stats_import',
+ 'relname', 'testseq');
-SELECT pg_catalog.pg_clear_relation_stats(
- 'stats_import.testseq'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
-SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.testview'::regclass);
-
-SELECT pg_catalog.pg_clear_relation_stats(
- 'stats_import.testview'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
--
-- attribute stats
--
--- error: object does not exist
+-- error: schemaname missing
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', '0'::oid::regclass,
- 'attname', 'id'::name,
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: relation null
+-- error: schema does not exist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', NULL::oid::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'nope',
+ 'relname', 'test',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: relname missing
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: relname does not exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'nope',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: relname null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'relname', NULL,
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: NULL attname
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', NULL::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', NULL,
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: attname doesn't exist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'nope'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'nope',
'inherited', false::boolean,
'null_frac', 0.1::real,
'avg_width', 2::integer,
@@ -258,36 +304,41 @@ SELECT pg_catalog.pg_restore_attribute_stats(
-- error: both attname and attnum
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: neither attname nor attnum
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: attribute is system column
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'xmin'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'xmin',
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: inherited null
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', NULL::boolean,
'null_frac', 0.1::real);
-- ok: just the fixed values, with version, no stakinds
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'version', 150000::integer,
'null_frac', 0.2::real,
@@ -307,7 +358,8 @@ AND attname = 'id';
-- for any stat-having relation.
--
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.4::real);
@@ -321,8 +373,9 @@ AND attname = 'id';
-- warn: unrecognized argument name, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.2::real,
'nope', 0.5::real);
@@ -336,8 +389,9 @@ AND attname = 'id';
-- warn: mcv / mcf null mismatch part 1, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.21::real,
'most_common_freqs', '{0.1,0.2,0.3}'::real[]
@@ -352,8 +406,9 @@ AND attname = 'id';
-- warn: mcv / mcf null mismatch part 2, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.21::real,
'most_common_vals', '{1,2,3}'::text
@@ -368,8 +423,9 @@ AND attname = 'id';
-- warn: mcf type mismatch, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.22::real,
'most_common_vals', '{2,1,3}'::text,
@@ -385,8 +441,9 @@ AND attname = 'id';
-- warn: mcv cast failure, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.23::real,
'most_common_vals', '{2,four,3}'::text,
@@ -402,8 +459,9 @@ AND attname = 'id';
-- ok: mcv+mcf
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
@@ -418,8 +476,9 @@ AND attname = 'id';
-- warn: NULL in histogram array, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.24::real,
'histogram_bounds', '{1,NULL,3,4}'::text
@@ -434,8 +493,9 @@ AND attname = 'id';
-- ok: histogram_bounds
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'histogram_bounds', '{1,2,3,4}'::text
);
@@ -449,8 +509,9 @@ AND attname = 'id';
-- warn: elem_count_histogram null element, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.25::real,
'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[]
@@ -465,8 +526,9 @@ AND attname = 'tags';
-- ok: elem_count_histogram
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.26::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -481,8 +543,9 @@ AND attname = 'tags';
-- warn: range stats on a scalar type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.27::real,
'range_empty_frac', 0.5::real,
@@ -498,8 +561,9 @@ AND attname = 'id';
-- warn: range_empty_frac range_length_hist null mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.28::real,
'range_length_histogram', '{399,499,Infinity}'::text
@@ -514,8 +578,9 @@ AND attname = 'arange';
-- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.29::real,
'range_empty_frac', 0.5::real
@@ -530,8 +595,9 @@ AND attname = 'arange';
-- ok: range_empty_frac + range_length_hist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'range_empty_frac', 0.5::real,
'range_length_histogram', '{399,499,Infinity}'::text
@@ -546,8 +612,9 @@ AND attname = 'arange';
-- warn: range bounds histogram on scalar, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.31::real,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
@@ -562,8 +629,9 @@ AND attname = 'id';
-- ok: range_bounds_histogram
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
);
@@ -577,8 +645,9 @@ AND attname = 'arange';
-- warn: cannot set most_common_elems for range type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.32::real,
'most_common_elems', '{3,1}'::text,
@@ -594,8 +663,9 @@ AND attname = 'arange';
-- warn: scalars can't have mcelem, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.33::real,
'most_common_elems', '{1,3}'::text,
@@ -611,8 +681,9 @@ AND attname = 'id';
-- warn: mcelem / mcelem mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.34::real,
'most_common_elems', '{one,two}'::text
@@ -627,8 +698,9 @@ AND attname = 'tags';
-- warn: mcelem / mcelem null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.35::real,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
@@ -643,8 +715,9 @@ AND attname = 'tags';
-- ok: mcelem
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'most_common_elems', '{one,three}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
@@ -659,8 +732,9 @@ AND attname = 'tags';
-- warn: scalars can't have elem_count_histogram, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.36::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -707,8 +781,9 @@ SELECT s.schemaname, s.tablename, s.attname, s.inherited, r.*
FROM pg_catalog.pg_stats AS s
CROSS JOIN LATERAL
pg_catalog.pg_restore_attribute_stats(
- 'relation', ('stats_import.' || s.tablename || '_clone')::regclass,
- 'attname', s.attname,
+ 'schemaname', 'stats_import',
+ 'relname', s.tablename::text || '_clone',
+ 'attname', s.attname::text,
'inherited', s.inherited,
'version', 150000,
'null_frac', s.null_frac,
@@ -853,9 +928,10 @@ AND inherited = false
AND attname = 'arange';
SELECT pg_catalog.pg_clear_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean);
+ schemaname => 'stats_import',
+ relname => 'test',
+ attname => 'arange',
+ inherited => false);
SELECT COUNT(*)
FROM pg_stats
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 1c3810e1a04..a75e95bc5fd 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -30365,22 +30365,24 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<structname>mytable</structname>:
<programlisting>
SELECT pg_restore_relation_stats(
- 'relation', 'mytable'::regclass,
- 'relpages', 173::integer,
- 'reltuples', 10000::real);
+ 'schemaname', 'myschema',
+ 'relname', 'mytable',
+ 'relpages', 173::integer,
+ 'reltuples', 10000::real);
</programlisting>
</para>
<para>
- The argument <literal>relation</literal> with a value of type
- <type>regclass</type> is required, and specifies the table. Other
- arguments are the names and values of statistics corresponding to
- certain columns in <link
+ The arguments <literal>schemaname</literal> with a value of type
+ <type>regclass</type> and <literal>relname</literal> are required,
+ and specifies the table. Other arguments are the names and values
+ of statistics corresponding to certain columns in <link
linkend="catalog-pg-class"><structname>pg_class</structname></link>.
The currently-supported relation statistics are
<literal>relpages</literal> with a value of type
<type>integer</type>, <literal>reltuples</literal> with a value of
- type <type>real</type>, and <literal>relallvisible</literal> with a
- value of type <type>integer</type>.
+ type <type>real</type>, <literal>relallvisible</literal> with a
+ value of type <type>integer</type>, and <literal>relallfrozen</literal>
+ with a value of type <type>integer</type>.
</para>
<para>
Additionally, this function accepts argument name
@@ -30408,7 +30410,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<indexterm>
<primary>pg_clear_relation_stats</primary>
</indexterm>
- <function>pg_clear_relation_stats</function> ( <parameter>relation</parameter> <type>regclass</type> )
+ <function>pg_clear_relation_stats</function> ( <parameter>schemaname</parameter> <type>text</type>, <parameter>relname</parameter> <type>text</type> )
<returnvalue>void</returnvalue>
</para>
<para>
@@ -30457,16 +30459,18 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<structname>mytable</structname>:
<programlisting>
SELECT pg_restore_attribute_stats(
- 'relation', 'mytable'::regclass,
- 'attname', 'col1'::name,
- 'inherited', false,
- 'avg_width', 125::integer,
- 'null_frac', 0.5::real);
+ 'schemaname', 'myschema',
+ 'relname', 'mytable',
+ 'attname', 'col1',
+ 'inherited', false,
+ 'avg_width', 125::integer,
+ 'null_frac', 0.5::real);
</programlisting>
</para>
<para>
- The required arguments are <literal>relation</literal> with a value
- of type <type>regclass</type>, which specifies the table; either
+ The required arguments are <literal>schemaname</literal> with a value
+ of type <type>regclass</type> and <literal>relname</literal> with a value
+ of type <type>text</type> which specify the table; either
<literal>attname</literal> with a value of type <type>name</type> or
<literal>attnum</literal> with a value of type <type>smallint</type>,
which specifies the column; and <literal>inherited</literal>, which
@@ -30502,7 +30506,8 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<primary>pg_clear_attribute_stats</primary>
</indexterm>
<function>pg_clear_attribute_stats</function> (
- <parameter>relation</parameter> <type>regclass</type>,
+ <parameter>schemaname</parameter> <type>text</type>,
+ <parameter>relname</parameter> <type>text</type>,
<parameter>attname</parameter> <type>name</type>,
<parameter>inherited</parameter> <type>boolean</type> )
<returnvalue>void</returnvalue>
base-commit: 6d376c3b0d1e79c318d2a1c04097025784e28377
--
2.48.1
[text/x-patch] v8-0002-Downgrade-as-man-pg_restore_-_stats-errors-to-war.patch (29.8K, ../../CADkLM=c+r05srPy9w+-+nbmLEo15dKXYQ03Q_xyK+riJerigLQ@mail.gmail.com/4-v8-0002-Downgrade-as-man-pg_restore_-_stats-errors-to-war.patch)
download | inline diff:
From 98f1eea90be0804ecdd43a89306aa83a6b9784c5 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sat, 8 Mar 2025 00:52:41 -0500
Subject: [PATCH v8 2/4] Downgrade as man pg_restore_*_stats errors to
warnings.
We want to avoid errors that can potentially stop an otherwise
successful pg_upgrade or pg_restore operation. With that in mind, change
as many ERROR reports to WARNING + early termination with no data
updated.
---
src/include/statistics/stat_utils.h | 4 +-
src/backend/statistics/attribute_stats.c | 124 +++++++++++-----
src/backend/statistics/relation_stats.c | 10 +-
src/backend/statistics/stat_utils.c | 51 +++++--
src/test/regress/expected/stats_import.out | 163 ++++++++++++++++-----
src/test/regress/sql/stats_import.sql | 36 ++---
6 files changed, 277 insertions(+), 111 deletions(-)
diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h
index cad042c8e4a..298cbae3436 100644
--- a/src/include/statistics/stat_utils.h
+++ b/src/include/statistics/stat_utils.h
@@ -21,7 +21,7 @@ struct StatsArgInfo
Oid argtype;
};
-extern void stats_check_required_arg(FunctionCallInfo fcinfo,
+extern bool stats_check_required_arg(FunctionCallInfo fcinfo,
struct StatsArgInfo *arginfo,
int argnum);
extern bool stats_check_arg_array(FunctionCallInfo fcinfo,
@@ -30,7 +30,7 @@ extern bool stats_check_arg_pair(FunctionCallInfo fcinfo,
struct StatsArgInfo *arginfo,
int argnum1, int argnum2);
-extern void stats_lock_check_privileges(Oid reloid);
+extern bool stats_lock_check_privileges(Oid reloid);
extern Oid stats_schema_check_privileges(const char *nspname);
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index f87db2d6102..4f9bc18f8c6 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -100,7 +100,7 @@ static struct StatsArgInfo cleararginfo[] =
static bool attribute_statistics_update(FunctionCallInfo fcinfo);
static Node *get_attr_expr(Relation rel, int attnum);
-static void get_attr_stat_type(Oid reloid, AttrNumber attnum,
+static bool get_attr_stat_type(Oid reloid, AttrNumber attnum,
Oid *atttypid, int32 *atttypmod,
char *atttyptype, Oid *atttypcoll,
Oid *eq_opr, Oid *lt_opr);
@@ -129,10 +129,12 @@ static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
* stored as an anyarray, and the representation of the array needs to store
* the correct element type, which must be derived from the attribute.
*
- * Major errors, such as the table not existing, the attribute not existing,
- * or a permissions failure are always reported at ERROR. Other errors, such
- * as a conversion failure on one statistic kind, are reported as a WARNING
- * and other statistic kinds may still be updated.
+ * This function is called during database upgrades and restorations, therefore
+ * it is imperative to avoid ERRORs that could potentially end the upgrade or
+ * restore unless. Major errors, such as the table not existing, the attribute
+ * not existing, or permissions failure are reported as WARNINGs with an end to
+ * the function, thus allowing the upgrade/restore to continue, but without the
+ * stats that can be regenereated once the database is online again.
*/
static bool
attribute_statistics_update(FunctionCallInfo fcinfo)
@@ -149,8 +151,8 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
HeapTuple statup;
Oid atttypid = InvalidOid;
- int32 atttypmod;
- char atttyptype;
+ int32 atttypmod = -1;
+ char atttyptype = TYPTYPE_PSEUDO; /* Not a great default, but there is no TYPTYPE_INVALID */
Oid atttypcoll = InvalidOid;
Oid eq_opr = InvalidOid;
Oid lt_opr = InvalidOid;
@@ -177,17 +179,19 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
bool result = true;
- stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG);
- stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG);
+ if (!stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG))
+ return false;
+ if (!stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG))
+ return false;
nspname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELSCHEMA_ARG));
nspoid = stats_schema_check_privileges(nspname);
- if (nspoid == InvalidOid)
+ if (!OidIsValid(nspoid))
return false;
relname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELNAME_ARG));
reloid = get_relname_relid(relname, nspoid);
- if (reloid == InvalidOid)
+ if (!OidIsValid(reloid))
{
ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_OBJECT),
@@ -196,29 +200,39 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
}
if (RecoveryInProgress())
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("recovery is in progress"),
errhint("Statistics cannot be modified during recovery.")));
+ return false;
+ }
/* lock before looking up attribute */
- stats_lock_check_privileges(reloid);
+ if (!stats_lock_check_privileges(reloid))
+ return false;
/* user can specify either attname or attnum, but not both */
if (!PG_ARGISNULL(ATTNAME_ARG))
{
if (!PG_ARGISNULL(ATTNUM_ARG))
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("cannot specify both attname and attnum")));
+ return false;
+ }
attname = TextDatumGetCString(PG_GETARG_DATUM(ATTNAME_ARG));
attnum = get_attnum(reloid, attname);
/* note that this test covers attisdropped cases too: */
if (attnum == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\".\"%s\" does not exist",
attname, nspname, relname)));
+ return false;
+ }
}
else if (!PG_ARGISNULL(ATTNUM_ARG))
{
@@ -227,27 +241,33 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
/* annoyingly, get_attname doesn't check attisdropped */
if (attname == NULL ||
!SearchSysCacheExistsAttName(reloid, attname))
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column %d of relation \"%s\".\"%s\" does not exist",
attnum, nspname, relname)));
+ return false;
+ }
}
else
{
- ereport(ERROR,
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("must specify either attname or attnum")));
- attname = NULL; /* keep compiler quiet */
- attnum = 0;
+ return false;
}
if (attnum < 0)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot modify statistics on system column \"%s\"",
attname)));
+ return false;
+ }
- stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG);
+ if (!stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG))
+ return false;
inherited = PG_GETARG_BOOL(INHERITED_ARG);
/*
@@ -296,10 +316,11 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
}
/* derive information from attribute */
- get_attr_stat_type(reloid, attnum,
- &atttypid, &atttypmod,
- &atttyptype, &atttypcoll,
- &eq_opr, <_opr);
+ if (!get_attr_stat_type(reloid, attnum,
+ &atttypid, &atttypmod,
+ &atttyptype, &atttypcoll,
+ &eq_opr, <_opr))
+ result = false;
/* if needed, derive element type */
if (do_mcelem || do_dechist)
@@ -579,7 +600,7 @@ get_attr_expr(Relation rel, int attnum)
/*
* Derive type information from the attribute.
*/
-static void
+static bool
get_attr_stat_type(Oid reloid, AttrNumber attnum,
Oid *atttypid, int32 *atttypmod,
char *atttyptype, Oid *atttypcoll,
@@ -596,18 +617,26 @@ get_attr_stat_type(Oid reloid, AttrNumber attnum,
/* Attribute not found */
if (!HeapTupleIsValid(atup))
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("attribute %d of relation \"%s\" does not exist",
attnum, RelationGetRelationName(rel))));
+ relation_close(rel, NoLock);
+ return false;
+ }
attr = (Form_pg_attribute) GETSTRUCT(atup);
if (attr->attisdropped)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("attribute %d of relation \"%s\" does not exist",
attnum, RelationGetRelationName(rel))));
+ relation_close(rel, NoLock);
+ return false;
+ }
expr = get_attr_expr(rel, attr->attnum);
@@ -656,6 +685,7 @@ get_attr_stat_type(Oid reloid, AttrNumber attnum,
*atttypcoll = DEFAULT_COLLATION_OID;
relation_close(rel, NoLock);
+ return true;
}
/*
@@ -781,6 +811,10 @@ set_stats_slot(Datum *values, bool *nulls, bool *replaces,
if (slotidx >= STATISTIC_NUM_SLOTS && first_empty >= 0)
slotidx = first_empty;
+ /*
+ * Currently there is no datatype that can have more than STATISTIC_NUM_SLOTS
+ * statistic kinds, so this can safely remain an ERROR for now.
+ */
if (slotidx >= STATISTIC_NUM_SLOTS)
ereport(ERROR,
(errmsg("maximum number of statistics slots exceeded: %d",
@@ -927,15 +961,19 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
AttrNumber attnum;
bool inherited;
- stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG);
- stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG);
- stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
- stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
+ if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG))
+ PG_RETURN_VOID();
+ if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG))
+ PG_RETURN_VOID();
+ if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG))
+ PG_RETURN_VOID();
+ if (!stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG))
+ PG_RETURN_VOID();
nspname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELSCHEMA_ARG));
nspoid = stats_schema_check_privileges(nspname);
if (!OidIsValid(nspoid))
- return false;
+ PG_RETURN_VOID();
relname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELNAME_ARG));
reloid = get_relname_relid(relname, nspoid);
@@ -944,31 +982,41 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
- return false;
+ PG_RETURN_VOID();
}
if (RecoveryInProgress())
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("recovery is in progress"),
errhint("Statistics cannot be modified during recovery.")));
+ PG_RETURN_VOID();
+ }
- stats_lock_check_privileges(reloid);
+ if (!stats_lock_check_privileges(reloid))
+ PG_RETURN_VOID();
attname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTNAME_ARG));
attnum = get_attnum(reloid, attname);
if (attnum < 0)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot clear statistics on system column \"%s\"",
attname)));
+ PG_RETURN_VOID();
+ }
if (attnum == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
attname, get_rel_name(reloid))));
+ PG_RETURN_VOID();
+ }
inherited = PG_GETARG_BOOL(C_INHERITED_ARG);
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index fdc69bc93e2..49109cf721d 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -84,8 +84,11 @@ relation_statistics_update(FunctionCallInfo fcinfo)
bool nulls[4] = {0};
int nreplaces = 0;
- stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG);
- stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG);
+ if (!stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG))
+ return false;
+
+ if (!stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG))
+ return false;
nspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
nspoid = stats_schema_check_privileges(nspname);
@@ -108,7 +111,8 @@ relation_statistics_update(FunctionCallInfo fcinfo)
errmsg("recovery is in progress"),
errhint("Statistics cannot be modified during recovery.")));
- stats_lock_check_privileges(reloid);
+ if (!stats_lock_check_privileges(reloid))
+ return false;
if (!PG_ARGISNULL(RELPAGES_ARG))
{
diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c
index e037d4994e8..dd9d88ac1c5 100644
--- a/src/backend/statistics/stat_utils.c
+++ b/src/backend/statistics/stat_utils.c
@@ -34,16 +34,20 @@
/*
* Ensure that a given argument is not null.
*/
-void
+bool
stats_check_required_arg(FunctionCallInfo fcinfo,
struct StatsArgInfo *arginfo,
int argnum)
{
if (PG_ARGISNULL(argnum))
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("\"%s\" cannot be NULL",
arginfo[argnum].argname)));
+ return false;
+ }
+ return true;
}
/*
@@ -128,13 +132,14 @@ stats_check_arg_pair(FunctionCallInfo fcinfo,
* - the role owns the current database and the relation is not shared
* - the role has the MAINTAIN privilege on the relation
*/
-void
+bool
stats_lock_check_privileges(Oid reloid)
{
Relation table;
Oid table_oid = reloid;
Oid index_oid = InvalidOid;
LOCKMODE index_lockmode = NoLock;
+ bool ok = true;
/*
* For indexes, we follow the locking behavior in do_analyze_rel() and
@@ -174,14 +179,15 @@ stats_lock_check_privileges(Oid reloid)
case RELKIND_PARTITIONED_TABLE:
break;
default:
- ereport(ERROR,
+ ereport(WARNING,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot modify statistics for relation \"%s\"",
RelationGetRelationName(table)),
errdetail_relkind_not_supported(table->rd_rel->relkind)));
+ ok = false;
}
- if (OidIsValid(index_oid))
+ if (ok && (OidIsValid(index_oid)))
{
Relation index;
@@ -194,25 +200,33 @@ stats_lock_check_privileges(Oid reloid)
relation_close(index, NoLock);
}
- if (table->rd_rel->relisshared)
- ereport(ERROR,
+ if (ok && (table->rd_rel->relisshared))
+ {
+ ereport(WARNING,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot modify statistics for shared relation")));
+ ok = false;
+ }
- if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()))
+ if (ok && (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId())))
{
AclResult aclresult = pg_class_aclcheck(RelationGetRelid(table),
GetUserId(),
ACL_MAINTAIN);
if (aclresult != ACLCHECK_OK)
- aclcheck_error(aclresult,
- get_relkind_objtype(table->rd_rel->relkind),
- NameStr(table->rd_rel->relname));
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied for relation %s",
+ NameStr(table->rd_rel->relname))));
+ ok = false;
+ }
}
/* retain lock on table */
relation_close(table, NoLock);
+ return ok;
}
@@ -318,9 +332,12 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
&args, &types, &argnulls);
if (nargs % 2 != 0)
- ereport(ERROR,
+ {
+ ereport(WARNING,
errmsg("variadic arguments must be name/value pairs"),
errhint("Provide an even number of variadic arguments that can be divided into pairs."));
+ return false;
+ }
/*
* For each argument name/value pair, find corresponding positional
@@ -333,14 +350,20 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
char *argname;
if (argnulls[i])
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errmsg("name at variadic position %d is NULL", i + 1)));
+ return false;
+ }
if (types[i] != TEXTOID)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errmsg("name at variadic position %d has type \"%s\", expected type \"%s\"",
i + 1, format_type_be(types[i]),
format_type_be(TEXTOID))));
+ return false;
+ }
if (argnulls[i + 1])
continue;
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 2f1295f2149..6551d6bf099 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -46,31 +46,51 @@ SELECT pg_clear_relation_stats('stats_import', 'test');
--
-- relstats tests
--
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'relname', 'test',
'relpages', 17::integer);
-ERROR: "schemaname" cannot be NULL
--- error: relname missing
+WARNING: "schemaname" cannot be NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+-- warning: relname missing, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relpages', 17::integer);
-ERROR: "relname" cannot be NULL
---- error: schemaname is wrong type
+WARNING: "relname" cannot be NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+--- warning: schemaname is wrong type, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 3.6::float,
'relname', 'test',
'relpages', 17::integer);
WARNING: argument "schemaname" has type "double precision", expected type "text"
-ERROR: "schemaname" cannot be NULL
---- error: relname is wrong type
+WARNING: "schemaname" cannot be NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+--- warning: relname is wrong type, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 0::oid,
'relpages', 17::integer);
WARNING: argument "relname" has type "oid", expected type "text"
-ERROR: "relname" cannot be NULL
--- error: relation not found
+WARNING: "relname" cannot be NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+-- warning: relation not found, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'nope',
@@ -81,19 +101,30 @@ WARNING: Relation "stats_import"."nope" not found.
f
(1 row)
--- error: odd number of variadic arguments cannot be pairs
+-- warning: odd number of variadic arguments cannot be pairs, nothing updated
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
'relallvisible');
-ERROR: variadic arguments must be name/value pairs
+WARNING: variadic arguments must be name/value pairs
HINT: Provide an even number of variadic arguments that can be divided into pairs.
--- error: argument name is NULL
+WARNING: "schemaname" cannot be NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+-- warning: argument name is NULL, nothing updated
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
NULL, '17'::integer);
-ERROR: name at variadic position 5 is NULL
+WARNING: name at variadic position 5 is NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
-- starting stats
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
@@ -345,26 +376,46 @@ CREATE SEQUENCE stats_import.testseq;
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'testseq');
-ERROR: cannot modify statistics for relation "testseq"
+WARNING: cannot modify statistics for relation "testseq"
DETAIL: This operation is not supported for sequences.
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
-ERROR: cannot modify statistics for relation "testseq"
+WARNING: cannot modify statistics for relation "testseq"
DETAIL: This operation is not supported for sequences.
+ pg_clear_relation_stats
+-------------------------
+
+(1 row)
+
CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
-ERROR: cannot modify statistics for relation "testview"
+WARNING: cannot modify statistics for relation "testview"
DETAIL: This operation is not supported for views.
+ pg_clear_relation_stats
+-------------------------
+
+(1 row)
+
--
-- attribute stats
--
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: "schemaname" cannot be NULL
--- error: schema does not exist
+WARNING: "schemaname" cannot be NULL
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: schema does not exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'nope',
'relname', 'test',
@@ -377,14 +428,19 @@ WARNING: schema nope does not exist
f
(1 row)
--- error: relname missing
+-- warning: relname missing, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: "relname" cannot be NULL
--- error: relname does not exist
+WARNING: "relname" cannot be NULL
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: relname does not exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'nope',
@@ -397,23 +453,33 @@ WARNING: Relation "stats_import"."nope" not found.
f
(1 row)
--- error: relname null
+-- warning: relname null, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', NULL,
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: "relname" cannot be NULL
--- error: NULL attname
+WARNING: "relname" cannot be NULL
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: NULL attname, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', NULL,
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: must specify either attname or attnum
--- error: attname doesn't exist
+WARNING: must specify either attname or attnum
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: attname doesn't exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -422,8 +488,13 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'null_frac', 0.1::real,
'avg_width', 2::integer,
'n_distinct', 0.3::real);
-ERROR: column "nope" of relation "stats_import"."test" does not exist
--- error: both attname and attnum
+WARNING: column "nope" of relation "stats_import"."test" does not exist
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: both attname and attnum, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -431,30 +502,50 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: cannot specify both attname and attnum
--- error: neither attname nor attnum
+WARNING: cannot specify both attname and attnum
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: neither attname nor attnum, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: must specify either attname or attnum
--- error: attribute is system column
+WARNING: must specify either attname or attnum
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: attribute is system column, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'xmin',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: cannot modify statistics on system column "xmin"
--- error: inherited null
+WARNING: cannot modify statistics on system column "xmin"
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: inherited null, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'id',
'inherited', NULL::boolean,
'null_frac', 0.1::real);
-ERROR: "inherited" cannot be NULL
+WARNING: "inherited" cannot be NULL
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
-- ok: just the fixed values, with version, no stakinds
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index ccdc44e9236..dbbebce1673 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -39,41 +39,41 @@ SELECT pg_clear_relation_stats('stats_import', 'test');
-- relstats tests
--
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'relname', 'test',
'relpages', 17::integer);
--- error: relname missing
+-- warning: relname missing, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relpages', 17::integer);
---- error: schemaname is wrong type
+--- warning: schemaname is wrong type, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 3.6::float,
'relname', 'test',
'relpages', 17::integer);
---- error: relname is wrong type
+--- warning: relname is wrong type, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 0::oid,
'relpages', 17::integer);
--- error: relation not found
+-- warning: relation not found, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'nope',
'relpages', 17::integer);
--- error: odd number of variadic arguments cannot be pairs
+-- warning: odd number of variadic arguments cannot be pairs, nothing updated
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
'relallvisible');
--- error: argument name is NULL
+-- warning: argument name is NULL, nothing updated
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -246,14 +246,14 @@ SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname
-- attribute stats
--
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: schema does not exist
+-- warning: schema does not exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'nope',
'relname', 'test',
@@ -261,14 +261,14 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: relname missing
+-- warning: relname missing, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: relname does not exist
+-- warning: relname does not exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'nope',
@@ -276,7 +276,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: relname null
+-- warning: relname null, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', NULL,
@@ -284,7 +284,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: NULL attname
+-- warning: NULL attname, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -292,7 +292,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: attname doesn't exist
+-- warning: attname doesn't exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -302,7 +302,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'avg_width', 2::integer,
'n_distinct', 0.3::real);
--- error: both attname and attnum
+-- warning: both attname and attnum, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -311,14 +311,14 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: neither attname nor attnum
+-- warning: neither attname nor attnum, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: attribute is system column
+-- warning: attribute is system column, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -326,7 +326,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: inherited null
+-- warning: inherited null, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
--
2.48.1
[text/x-patch] v8-0003-Introduce-CreateStmtPtr.patch (17.2K, ../../CADkLM=c+r05srPy9w+-+nbmLEo15dKXYQ03Q_xyK+riJerigLQ@mail.gmail.com/5-v8-0003-Introduce-CreateStmtPtr.patch)
download | inline diff:
From e2b00e46afdfa45f263b4666831160bf4d21dd09 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Fri, 14 Mar 2025 01:06:19 -0400
Subject: [PATCH v8 3/4] Introduce CreateStmtPtr.
CreateStmtPtr is a function pointer that can replace the createStmt/defn
parameter. This is useful in situations where the amount of text
generated for a definition is so large that it is undesirable to hold
many such objects in memory at the same time.
Using functions of this type, the text created is then immediately
written out to the appropriate file for the given dump format.
---
src/bin/pg_dump/pg_backup.h | 2 +
src/bin/pg_dump/pg_backup_archiver.c | 22 ++-
src/bin/pg_dump/pg_backup_archiver.h | 7 +
src/bin/pg_dump/pg_dump.c | 230 +++++++++++++++------------
4 files changed, 156 insertions(+), 105 deletions(-)
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index e783cc68d89..bb175874a5a 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -287,6 +287,8 @@ typedef int (*DataDumperPtr) (Archive *AH, const void *userArg);
typedef void (*SetupWorkerPtrType) (Archive *AH);
+typedef char *(*CreateStmtPtr) (Archive *AH, const void *userArg);
+
/*
* Main archiver interface.
*/
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 7480e122b61..3fcfecf6719 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -1263,6 +1263,9 @@ ArchiveEntry(Archive *AHX, CatalogId catalogId, DumpId dumpId,
newToc->dataDumper = opts->dumpFn;
newToc->dataDumperArg = opts->dumpArg;
newToc->hadDumper = opts->dumpFn ? true : false;
+ newToc->createDumper = opts->createFn;
+ newToc->createDumperArg = opts->createArg;
+ newToc->hadCreateDumper = opts->createFn ? true : false;
newToc->formatData = NULL;
newToc->dataLength = 0;
@@ -2619,7 +2622,17 @@ WriteToc(ArchiveHandle *AH)
WriteStr(AH, te->tag);
WriteStr(AH, te->desc);
WriteInt(AH, te->section);
- WriteStr(AH, te->defn);
+
+ if (te->hadCreateDumper)
+ {
+ char *defn = te->createDumper((Archive *) AH, te->createDumperArg);
+
+ WriteStr(AH, defn);
+ pg_free(defn);
+ }
+ else
+ WriteStr(AH, te->defn);
+
WriteStr(AH, te->dropStmt);
WriteStr(AH, te->copyStmt);
WriteStr(AH, te->namespace);
@@ -3849,6 +3862,13 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, const char *pfx)
{
IssueACLPerBlob(AH, te);
}
+ else if (te->hadCreateDumper)
+ {
+ char *ptr = te->createDumper((Archive *) AH, te->createDumperArg);
+
+ ahwrite(ptr, 1, strlen(ptr), AH);
+ pg_free(ptr);
+ }
else if (te->defn && strlen(te->defn) > 0)
{
ahprintf(AH, "%s\n\n", te->defn);
diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h
index a2064f471ed..e68db633995 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -368,6 +368,11 @@ struct _tocEntry
const void *dataDumperArg; /* Arg for above routine */
void *formatData; /* TOC Entry data specific to file format */
+ CreateStmtPtr createDumper; /* Routine for create statement creation */
+ const void *createDumperArg; /* arg for the above routine */
+ bool hadCreateDumper; /* Archiver was passed a create statement
+ * routine */
+
/* working state while dumping/restoring */
pgoff_t dataLength; /* item's data size; 0 if none or unknown */
int reqs; /* do we need schema and/or data of object
@@ -407,6 +412,8 @@ typedef struct _archiveOpts
int nDeps;
DataDumperPtr dumpFn;
const void *dumpArg;
+ CreateStmtPtr createFn;
+ const void *createArg;
} ArchiveOpts;
#define ARCHIVE_OPTS(...) &(ArchiveOpts){__VA_ARGS__}
/* Called to add a TOC entry */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index bd857bb076c..38ba6a90106 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -10477,51 +10477,44 @@ statisticsDumpSection(const RelStatsInfo *rsinfo)
}
/*
- * dumpRelationStats --
+ * printDumpRelationStats --
*
- * Dump command to import stats into the relation on the new database.
+ * Generate the SQL statements needed to restore a relation's statistics.
*/
-static void
-dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
+static char *
+printRelationStats(Archive *fout, const void *userArg)
{
+ const RelStatsInfo *rsinfo = (RelStatsInfo *) userArg;
const DumpableObject *dobj = &rsinfo->dobj;
+
+ PQExpBufferData query;
+ PQExpBufferData out;
+
PGresult *res;
- PQExpBuffer query;
- PQExpBuffer out;
- DumpId *deps = NULL;
- int ndeps = 0;
- int i_attname;
- int i_inherited;
- int i_null_frac;
- int i_avg_width;
- int i_n_distinct;
- int i_most_common_vals;
- int i_most_common_freqs;
- int i_histogram_bounds;
- int i_correlation;
- int i_most_common_elems;
- int i_most_common_elem_freqs;
- int i_elem_count_histogram;
- int i_range_length_histogram;
- int i_range_empty_frac;
- int i_range_bounds_histogram;
- /* nothing to do if we are not dumping statistics */
- if (!fout->dopt->dumpStatistics)
- return;
+ static bool first_query = true;
+ static int i_attname;
+ static int i_inherited;
+ static int i_null_frac;
+ static int i_avg_width;
+ static int i_n_distinct;
+ static int i_most_common_vals;
+ static int i_most_common_freqs;
+ static int i_histogram_bounds;
+ static int i_correlation;
+ static int i_most_common_elems;
+ static int i_most_common_elem_freqs;
+ static int i_elem_count_histogram;
+ static int i_range_length_histogram;
+ static int i_range_empty_frac;
+ static int i_range_bounds_histogram;
- /* dependent on the relation definition, if doing schema */
- if (fout->dopt->dumpSchema)
+ initPQExpBuffer(&query);
+
+ if (first_query)
{
- deps = dobj->dependencies;
- ndeps = dobj->nDeps;
- }
-
- query = createPQExpBuffer();
- if (!fout->is_prepared[PREPQUERY_GETATTRIBUTESTATS])
- {
- appendPQExpBufferStr(query,
- "PREPARE getAttributeStats(pg_catalog.name, pg_catalog.name) AS\n"
+ appendPQExpBufferStr(&query,
+ "PREPARE getAttributeStats(pg_catalog.text, pg_catalog.text) AS\n"
"SELECT s.attname, s.inherited, "
"s.null_frac, s.avg_width, s.n_distinct, "
"s.most_common_vals, s.most_common_freqs, "
@@ -10530,82 +10523,85 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
"s.elem_count_histogram, ");
if (fout->remoteVersion >= 170000)
- appendPQExpBufferStr(query,
+ appendPQExpBufferStr(&query,
"s.range_length_histogram, "
"s.range_empty_frac, "
"s.range_bounds_histogram ");
else
- appendPQExpBufferStr(query,
+ appendPQExpBufferStr(&query,
"NULL AS range_length_histogram,"
"NULL AS range_empty_frac,"
"NULL AS range_bounds_histogram ");
- appendPQExpBufferStr(query,
+ appendPQExpBufferStr(&query,
"FROM pg_catalog.pg_stats s "
"WHERE s.schemaname = $1 "
"AND s.tablename = $2 "
"ORDER BY s.attname, s.inherited");
- ExecuteSqlStatement(fout, query->data);
+ ExecuteSqlStatement(fout, query.data);
- fout->is_prepared[PREPQUERY_GETATTRIBUTESTATS] = true;
- resetPQExpBuffer(query);
+ resetPQExpBuffer(&query);
}
- out = createPQExpBuffer();
+ initPQExpBuffer(&out);
/* restore relation stats */
- appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
- appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+ appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
+ appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n",
fout->remoteVersion);
- appendPQExpBufferStr(out, "\t'schemaname', ");
- appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
- appendPQExpBufferStr(out, ",\n");
- appendPQExpBufferStr(out, "\t'relname', ");
- appendStringLiteralAH(out, rsinfo->dobj.name, fout);
- appendPQExpBufferStr(out, ",\n");
- appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
- appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
- appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer\n);\n",
+ appendPQExpBufferStr(&out, "\t'schemaname', ");
+ appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
+ appendPQExpBufferStr(&out, ",\n");
+ appendPQExpBufferStr(&out, "\t'relname', ");
+ appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
+ appendPQExpBufferStr(&out, ",\n");
+ appendPQExpBuffer(&out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
+ appendPQExpBuffer(&out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
+ appendPQExpBuffer(&out, "\t'relallvisible', '%d'::integer\n);\n",
rsinfo->relallvisible);
/* fetch attribute stats */
- appendPQExpBufferStr(query, "EXECUTE getAttributeStats(");
- appendStringLiteralAH(query, dobj->namespace->dobj.name, fout);
- appendPQExpBufferStr(query, ", ");
- appendStringLiteralAH(query, dobj->name, fout);
- appendPQExpBufferStr(query, ");");
+ appendPQExpBufferStr(&query, "EXECUTE getAttributeStats(");
+ appendStringLiteralAH(&query, dobj->namespace->dobj.name, fout);
+ appendPQExpBufferStr(&query, ", ");
+ appendStringLiteralAH(&query, dobj->name, fout);
+ appendPQExpBufferStr(&query, ")");
- res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+ res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK);
- i_attname = PQfnumber(res, "attname");
- i_inherited = PQfnumber(res, "inherited");
- i_null_frac = PQfnumber(res, "null_frac");
- i_avg_width = PQfnumber(res, "avg_width");
- i_n_distinct = PQfnumber(res, "n_distinct");
- i_most_common_vals = PQfnumber(res, "most_common_vals");
- i_most_common_freqs = PQfnumber(res, "most_common_freqs");
- i_histogram_bounds = PQfnumber(res, "histogram_bounds");
- i_correlation = PQfnumber(res, "correlation");
- i_most_common_elems = PQfnumber(res, "most_common_elems");
- i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
- i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
- i_range_length_histogram = PQfnumber(res, "range_length_histogram");
- i_range_empty_frac = PQfnumber(res, "range_empty_frac");
- i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+ if (first_query)
+ {
+ i_attname = PQfnumber(res, "attname");
+ i_inherited = PQfnumber(res, "inherited");
+ i_null_frac = PQfnumber(res, "null_frac");
+ i_avg_width = PQfnumber(res, "avg_width");
+ i_n_distinct = PQfnumber(res, "n_distinct");
+ i_most_common_vals = PQfnumber(res, "most_common_vals");
+ i_most_common_freqs = PQfnumber(res, "most_common_freqs");
+ i_histogram_bounds = PQfnumber(res, "histogram_bounds");
+ i_correlation = PQfnumber(res, "correlation");
+ i_most_common_elems = PQfnumber(res, "most_common_elems");
+ i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
+ i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
+ i_range_length_histogram = PQfnumber(res, "range_length_histogram");
+ i_range_empty_frac = PQfnumber(res, "range_empty_frac");
+ i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+ first_query = false;
+ }
/* restore attribute stats */
for (int rownum = 0; rownum < PQntuples(res); rownum++)
{
const char *attname;
- appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
- appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+ appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
+ appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n",
fout->remoteVersion);
- appendPQExpBufferStr(out, "\t'schemaname', ");
- appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
- appendPQExpBufferStr(out, ",\n\t'relname', ");
- appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+ appendPQExpBufferStr(&out, "\t'schemaname', ");
+ appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
+ appendPQExpBufferStr(&out, ",\n\t'relname', ");
+ appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
if (PQgetisnull(res, rownum, i_attname))
pg_fatal("attname cannot be NULL");
@@ -10618,8 +10614,8 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
*/
if (rsinfo->nindAttNames == 0)
{
- appendPQExpBuffer(out, ",\n\t'attname', ");
- appendStringLiteralAH(out, attname, fout);
+ appendPQExpBuffer(&out, ",\n\t'attname', ");
+ appendStringLiteralAH(&out, attname, fout);
}
else
{
@@ -10629,7 +10625,7 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
{
if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
{
- appendPQExpBuffer(out, ",\n\t'attnum', '%d'::smallint",
+ appendPQExpBuffer(&out, ",\n\t'attnum', '%d'::smallint",
i + 1);
found = true;
break;
@@ -10641,67 +10637,93 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
}
if (!PQgetisnull(res, rownum, i_inherited))
- appendNamedArgument(out, fout, "inherited", "boolean",
+ appendNamedArgument(&out, fout, "inherited", "boolean",
PQgetvalue(res, rownum, i_inherited));
if (!PQgetisnull(res, rownum, i_null_frac))
- appendNamedArgument(out, fout, "null_frac", "real",
+ appendNamedArgument(&out, fout, "null_frac", "real",
PQgetvalue(res, rownum, i_null_frac));
if (!PQgetisnull(res, rownum, i_avg_width))
- appendNamedArgument(out, fout, "avg_width", "integer",
+ appendNamedArgument(&out, fout, "avg_width", "integer",
PQgetvalue(res, rownum, i_avg_width));
if (!PQgetisnull(res, rownum, i_n_distinct))
- appendNamedArgument(out, fout, "n_distinct", "real",
+ appendNamedArgument(&out, fout, "n_distinct", "real",
PQgetvalue(res, rownum, i_n_distinct));
if (!PQgetisnull(res, rownum, i_most_common_vals))
- appendNamedArgument(out, fout, "most_common_vals", "text",
+ appendNamedArgument(&out, fout, "most_common_vals", "text",
PQgetvalue(res, rownum, i_most_common_vals));
if (!PQgetisnull(res, rownum, i_most_common_freqs))
- appendNamedArgument(out, fout, "most_common_freqs", "real[]",
+ appendNamedArgument(&out, fout, "most_common_freqs", "real[]",
PQgetvalue(res, rownum, i_most_common_freqs));
if (!PQgetisnull(res, rownum, i_histogram_bounds))
- appendNamedArgument(out, fout, "histogram_bounds", "text",
+ appendNamedArgument(&out, fout, "histogram_bounds", "text",
PQgetvalue(res, rownum, i_histogram_bounds));
if (!PQgetisnull(res, rownum, i_correlation))
- appendNamedArgument(out, fout, "correlation", "real",
+ appendNamedArgument(&out, fout, "correlation", "real",
PQgetvalue(res, rownum, i_correlation));
if (!PQgetisnull(res, rownum, i_most_common_elems))
- appendNamedArgument(out, fout, "most_common_elems", "text",
+ appendNamedArgument(&out, fout, "most_common_elems", "text",
PQgetvalue(res, rownum, i_most_common_elems));
if (!PQgetisnull(res, rownum, i_most_common_elem_freqs))
- appendNamedArgument(out, fout, "most_common_elem_freqs", "real[]",
+ appendNamedArgument(&out, fout, "most_common_elem_freqs", "real[]",
PQgetvalue(res, rownum, i_most_common_elem_freqs));
if (!PQgetisnull(res, rownum, i_elem_count_histogram))
- appendNamedArgument(out, fout, "elem_count_histogram", "real[]",
+ appendNamedArgument(&out, fout, "elem_count_histogram", "real[]",
PQgetvalue(res, rownum, i_elem_count_histogram));
if (fout->remoteVersion >= 170000)
{
if (!PQgetisnull(res, rownum, i_range_length_histogram))
- appendNamedArgument(out, fout, "range_length_histogram", "text",
+ appendNamedArgument(&out, fout, "range_length_histogram", "text",
PQgetvalue(res, rownum, i_range_length_histogram));
if (!PQgetisnull(res, rownum, i_range_empty_frac))
- appendNamedArgument(out, fout, "range_empty_frac", "real",
+ appendNamedArgument(&out, fout, "range_empty_frac", "real",
PQgetvalue(res, rownum, i_range_empty_frac));
if (!PQgetisnull(res, rownum, i_range_bounds_histogram))
- appendNamedArgument(out, fout, "range_bounds_histogram", "text",
+ appendNamedArgument(&out, fout, "range_bounds_histogram", "text",
PQgetvalue(res, rownum, i_range_bounds_histogram));
}
- appendPQExpBufferStr(out, "\n);\n");
+ appendPQExpBufferStr(&out, "\n);\n");
}
PQclear(res);
+ termPQExpBuffer(&query);
+ return out.data;
+}
+
+/*
+ * dumpRelationStats --
+ *
+ * Dump command to import stats into the relation on the new database.
+ */
+static void
+dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
+{
+ const DumpableObject *dobj = &rsinfo->dobj;
+
+ DumpId *deps = NULL;
+ int ndeps = 0;
+
+ /* nothing to do if we are not dumping statistics */
+ if (!fout->dopt->dumpStatistics)
+ return;
+
+ /* dependent on the relation definition, if doing schema */
+ if (fout->dopt->dumpSchema)
+ {
+ deps = dobj->dependencies;
+ ndeps = dobj->nDeps;
+ }
+
ArchiveEntry(fout, nilCatalogId, createDumpId(),
ARCHIVE_OPTS(.tag = dobj->name,
.namespace = dobj->namespace->dobj.name,
.description = "STATISTICS DATA",
.section = rsinfo->postponed_def ?
SECTION_POST_DATA : statisticsDumpSection(rsinfo),
- .createStmt = out->data,
+ .createFn = printRelationStats,
+ .createArg = rsinfo,
.deps = deps,
.nDeps = ndeps));
-
- destroyPQExpBuffer(out);
- destroyPQExpBuffer(query);
}
/*
--
2.48.1
[text/x-patch] v8-0004-Batching-getAttributeStats.patch (21.6K, ../../CADkLM=c+r05srPy9w+-+nbmLEo15dKXYQ03Q_xyK+riJerigLQ@mail.gmail.com/6-v8-0004-Batching-getAttributeStats.patch)
download | inline diff:
From 737a29c9b8f146eb037630ab183eb8601a76409a Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Fri, 14 Mar 2025 03:54:26 -0400
Subject: [PATCH v8 4/4] Batching getAttributeStats().
The prepared statement getAttributeStats() is fairly heavyweight and
could greatly increase pg_dump/pg_upgrade runtime. To alleviate this,
create a result set buffer of all of the attribute stats fetched for a
batch of 100 relations that could potentially have stats.
The query ensures that the order of results exactly matches the needs of
the code walking the TOC to print the stats calls.
---
src/bin/pg_dump/pg_dump.c | 556 ++++++++++++++++++++++++++------------
1 file changed, 385 insertions(+), 171 deletions(-)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 38ba6a90106..0c26dc7a1b4 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -143,6 +143,25 @@ typedef enum OidOptions
zeroAsNone = 4,
} OidOptions;
+typedef enum StatsBufferState
+{
+ STATSBUF_UNINITIALIZED = 0,
+ STATSBUF_ACTIVE,
+ STATSBUF_EXHAUSTED
+} StatsBufferState;
+
+typedef struct
+{
+ PGresult *res; /* results from most recent
+ * getAttributeStats() */
+ int idx; /* first un-consumed row of results */
+ TocEntry *te; /* next TOC entry to search for statsitics
+ * data */
+
+ StatsBufferState state; /* current state of the buffer */
+} AttributeStatsBuffer;
+
+
/* global decls */
static bool dosync = true; /* Issue fsync() to make dump durable on disk. */
@@ -209,6 +228,18 @@ static int nbinaryUpgradeClassOids = 0;
static SequenceItem *sequences = NULL;
static int nsequences = 0;
+static AttributeStatsBuffer attrstats =
+{
+ NULL, 0, NULL, STATSBUF_UNINITIALIZED
+};
+
+/*
+ * The maximum number of relations that should be fetched in any one
+ * getAttributeStats() call.
+ */
+
+#define MAX_ATTR_STATS_RELS 100
+
/*
* The default number of rows per INSERT when
* --inserts is specified without --rows-per-insert
@@ -222,6 +253,10 @@ static int nsequences = 0;
*/
#define MAX_BLOBS_PER_ARCHIVE_ENTRY 1000
+
+
+/* TODO: fmtId(const char *rawid) */
+
/*
* Macro for producing quoted, schema-qualified name of a dumpable object.
*/
@@ -399,6 +434,9 @@ static void setupDumpWorker(Archive *AH);
static TableInfo *getRootTableInfo(const TableInfo *tbinfo);
static bool forcePartitionRootLoad(const TableInfo *tbinfo);
static void read_dump_filters(const char *filename, DumpOptions *dopt);
+static void appendNamedArgument(PQExpBuffer out, Archive *fout,
+ const char *argname, const char *argtype,
+ const char *argval);
int
@@ -10477,7 +10515,286 @@ statisticsDumpSection(const RelStatsInfo *rsinfo)
}
/*
- * printDumpRelationStats --
+ * Fetch next batch of rows from getAttributeStats()
+ */
+static void
+fetchNextAttributeStats(Archive *fout)
+{
+ ArchiveHandle *AH = (ArchiveHandle *) fout;
+ PQExpBufferData schemas;
+ PQExpBufferData relations;
+ int numoids = 0;
+
+ Assert(AH != NULL);
+
+ /* free last result set, if any */
+ if (attrstats.state == STATSBUF_ACTIVE)
+ PQclear(attrstats.res);
+
+ /* If we have looped around to the start of the TOC, restart */
+ if (attrstats.te == AH->toc)
+ attrstats.te = AH->toc->next;
+
+ initPQExpBuffer(&schemas);
+ initPQExpBuffer(&relations);
+
+ /*
+ * Walk ahead looking for relstats entries that are active in this
+ * section, adding the names to the schemas and relations lists.
+ */
+ while ((attrstats.te != AH->toc) && (numoids < MAX_ATTR_STATS_RELS))
+ {
+ if (attrstats.te->reqs != 0 &&
+ strcmp(attrstats.te->desc, "STATISTICS DATA") == 0)
+ {
+ RelStatsInfo *rsinfo = (RelStatsInfo *) attrstats.te->createDumperArg;
+
+ Assert(rsinfo != NULL);
+
+ if (numoids > 0)
+ {
+ appendPQExpBufferStr(&schemas, ",");
+ appendPQExpBufferStr(&relations, ",");
+ }
+ appendPQExpBufferStr(&schemas, fmtId(rsinfo->dobj.namespace->dobj.name));
+ appendPQExpBufferStr(&relations, fmtId(rsinfo->dobj.name));
+ numoids++;
+ }
+
+ attrstats.te = attrstats.te->next;
+ }
+
+ if (numoids > 0)
+ {
+ PQExpBufferData query;
+
+ initPQExpBuffer(&query);
+ appendPQExpBuffer(&query,
+ "EXECUTE getAttributeStats('{%s}'::pg_catalog.text[],'{%s}'::pg_catalog.text[])",
+ schemas.data, relations.data);
+ attrstats.res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK);
+ attrstats.idx = 0;
+ }
+ else
+ {
+ attrstats.state = STATSBUF_EXHAUSTED;
+ attrstats.res = NULL;
+ attrstats.idx = -1;
+ }
+
+ termPQExpBuffer(&schemas);
+ termPQExpBuffer(&relations);
+}
+
+/*
+ * Prepare the getAttributeStats() statement
+ *
+ * This is done automatically if the user specified dumpStatistics.
+ */
+static void
+initAttributeStats(Archive *fout)
+{
+ ArchiveHandle *AH = (ArchiveHandle *) fout;
+ PQExpBufferData query;
+
+ Assert(AH != NULL);
+ initPQExpBuffer(&query);
+
+ appendPQExpBufferStr(&query,
+ "PREPARE getAttributeStats(pg_catalog.text[], pg_catalog.text[]) AS\n"
+ "SELECT s.schemaname, s.tablename, s.attname, s.inherited, "
+ "s.null_frac, s.avg_width, s.n_distinct, s.most_common_vals, "
+ "s.most_common_freqs, s.histogram_bounds, s.correlation, "
+ "s.most_common_elems, s.most_common_elem_freqs, "
+ "s.elem_count_histogram, ");
+
+ if (fout->remoteVersion >= 170000)
+ appendPQExpBufferStr(&query,
+ "s.range_length_histogram, "
+ "s.range_empty_frac, "
+ "s.range_bounds_histogram ");
+ else
+ appendPQExpBufferStr(&query,
+ "NULL AS range_length_histogram, "
+ "NULL AS range_empty_frac, "
+ " NULL AS range_bounds_histogram ");
+
+ /*
+ * The results must be in the order of relations supplied in the
+ * parameters to ensure that they are in sync with a walk of the TOC.
+ *
+ * The redundant (and incomplete) filter clause on s.tablename = ANY(...)
+ * is a way to lead the query into using the index
+ * pg_class_relname_nsp_index which in turn allows the planner to avoid an
+ * expensive full scan of pg_stats.
+ *
+ * We may need to adjust this query for versions that are not so easily
+ * led.
+ */
+ appendPQExpBufferStr(&query,
+ "FROM pg_catalog.pg_stats AS s "
+ "JOIN unnest($1, $2) WITH ORDINALITY AS u(schemaname, tablename, ord) "
+ "ON s.schemaname = u.schemaname "
+ "AND s.tablename = u.tablename "
+ "WHERE s.tablename = ANY($2) "
+ "ORDER BY u.ord, s.attname, s.inherited");
+
+ ExecuteSqlStatement(fout, query.data);
+
+ termPQExpBuffer(&query);
+
+ attrstats.te = AH->toc->next;
+
+ fetchNextAttributeStats(fout);
+
+ attrstats.state = STATSBUF_ACTIVE;
+}
+
+
+/*
+ * append a single attribute stat to the buffer for this relation.
+ */
+static void
+appendAttributeStats(Archive *fout, PQExpBuffer out,
+ const RelStatsInfo *rsinfo)
+{
+ PGresult *res = attrstats.res;
+ int tup_num = attrstats.idx;
+
+ const char *attname;
+
+ static bool indexes_set = false;
+ static int i_attname,
+ i_inherited,
+ i_null_frac,
+ i_avg_width,
+ i_n_distinct,
+ i_most_common_vals,
+ i_most_common_freqs,
+ i_histogram_bounds,
+ i_correlation,
+ i_most_common_elems,
+ i_most_common_elem_freqs,
+ i_elem_count_histogram,
+ i_range_length_histogram,
+ i_range_empty_frac,
+ i_range_bounds_histogram;
+
+ if (!indexes_set)
+ {
+ /*
+ * It's a prepared statement, so the indexes will be the same for all
+ * result sets, so we only need to set them once.
+ */
+ i_attname = PQfnumber(res, "attname");
+ i_inherited = PQfnumber(res, "inherited");
+ i_null_frac = PQfnumber(res, "null_frac");
+ i_avg_width = PQfnumber(res, "avg_width");
+ i_n_distinct = PQfnumber(res, "n_distinct");
+ i_most_common_vals = PQfnumber(res, "most_common_vals");
+ i_most_common_freqs = PQfnumber(res, "most_common_freqs");
+ i_histogram_bounds = PQfnumber(res, "histogram_bounds");
+ i_correlation = PQfnumber(res, "correlation");
+ i_most_common_elems = PQfnumber(res, "most_common_elems");
+ i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
+ i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
+ i_range_length_histogram = PQfnumber(res, "range_length_histogram");
+ i_range_empty_frac = PQfnumber(res, "range_empty_frac");
+ i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+ indexes_set = true;
+ }
+
+ appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
+ appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+ fout->remoteVersion);
+ appendPQExpBufferStr(out, "\t'schemaname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+ appendPQExpBufferStr(out, ",\n\t'relname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+
+ if (PQgetisnull(res, tup_num, i_attname))
+ pg_fatal("attname cannot be NULL");
+ attname = PQgetvalue(res, tup_num, i_attname);
+
+ /*
+ * Indexes look up attname in indAttNames to derive attnum, all others use
+ * attname directly. We must specify attnum for indexes, since their
+ * attnames are not necessarily stable across dump/reload.
+ */
+ if (rsinfo->nindAttNames == 0)
+ {
+ appendPQExpBuffer(out, ",\n\t'attname', ");
+ appendStringLiteralAH(out, attname, fout);
+ }
+ else
+ {
+ bool found = false;
+
+ for (int i = 0; i < rsinfo->nindAttNames; i++)
+ if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
+ {
+ appendPQExpBuffer(out, ",\n\t'attnum', '%d'::smallint",
+ i + 1);
+ found = true;
+ break;
+ }
+
+ if (!found)
+ pg_fatal("could not find index attname \"%s\"", attname);
+ }
+
+ if (!PQgetisnull(res, tup_num, i_inherited))
+ appendNamedArgument(out, fout, "inherited", "boolean",
+ PQgetvalue(res, tup_num, i_inherited));
+ if (!PQgetisnull(res, tup_num, i_null_frac))
+ appendNamedArgument(out, fout, "null_frac", "real",
+ PQgetvalue(res, tup_num, i_null_frac));
+ if (!PQgetisnull(res, tup_num, i_avg_width))
+ appendNamedArgument(out, fout, "avg_width", "integer",
+ PQgetvalue(res, tup_num, i_avg_width));
+ if (!PQgetisnull(res, tup_num, i_n_distinct))
+ appendNamedArgument(out, fout, "n_distinct", "real",
+ PQgetvalue(res, tup_num, i_n_distinct));
+ if (!PQgetisnull(res, tup_num, i_most_common_vals))
+ appendNamedArgument(out, fout, "most_common_vals", "text",
+ PQgetvalue(res, tup_num, i_most_common_vals));
+ if (!PQgetisnull(res, tup_num, i_most_common_freqs))
+ appendNamedArgument(out, fout, "most_common_freqs", "real[]",
+ PQgetvalue(res, tup_num, i_most_common_freqs));
+ if (!PQgetisnull(res, tup_num, i_histogram_bounds))
+ appendNamedArgument(out, fout, "histogram_bounds", "text",
+ PQgetvalue(res, tup_num, i_histogram_bounds));
+ if (!PQgetisnull(res, tup_num, i_correlation))
+ appendNamedArgument(out, fout, "correlation", "real",
+ PQgetvalue(res, tup_num, i_correlation));
+ if (!PQgetisnull(res, tup_num, i_most_common_elems))
+ appendNamedArgument(out, fout, "most_common_elems", "text",
+ PQgetvalue(res, tup_num, i_most_common_elems));
+ if (!PQgetisnull(res, tup_num, i_most_common_elem_freqs))
+ appendNamedArgument(out, fout, "most_common_elem_freqs", "real[]",
+ PQgetvalue(res, tup_num, i_most_common_elem_freqs));
+ if (!PQgetisnull(res, tup_num, i_elem_count_histogram))
+ appendNamedArgument(out, fout, "elem_count_histogram", "real[]",
+ PQgetvalue(res, tup_num, i_elem_count_histogram));
+ if (fout->remoteVersion >= 170000)
+ {
+ if (!PQgetisnull(res, tup_num, i_range_length_histogram))
+ appendNamedArgument(out, fout, "range_length_histogram", "text",
+ PQgetvalue(res, tup_num, i_range_length_histogram));
+ if (!PQgetisnull(res, tup_num, i_range_empty_frac))
+ appendNamedArgument(out, fout, "range_empty_frac", "real",
+ PQgetvalue(res, tup_num, i_range_empty_frac));
+ if (!PQgetisnull(res, tup_num, i_range_bounds_histogram))
+ appendNamedArgument(out, fout, "range_bounds_histogram", "text",
+ PQgetvalue(res, tup_num, i_range_bounds_histogram));
+ }
+ appendPQExpBufferStr(out, "\n);\n");
+}
+
+
+
+/*
+ * printRelationStats --
*
* Generate the SQL statements needed to restore a relation's statistics.
*/
@@ -10485,64 +10802,21 @@ static char *
printRelationStats(Archive *fout, const void *userArg)
{
const RelStatsInfo *rsinfo = (RelStatsInfo *) userArg;
- const DumpableObject *dobj = &rsinfo->dobj;
+ const DumpableObject *dobj;
+ const char *relschema;
+ const char *relname;
+
+ ArchiveHandle *AH = (ArchiveHandle *) fout;
- PQExpBufferData query;
PQExpBufferData out;
- PGresult *res;
-
- static bool first_query = true;
- static int i_attname;
- static int i_inherited;
- static int i_null_frac;
- static int i_avg_width;
- static int i_n_distinct;
- static int i_most_common_vals;
- static int i_most_common_freqs;
- static int i_histogram_bounds;
- static int i_correlation;
- static int i_most_common_elems;
- static int i_most_common_elem_freqs;
- static int i_elem_count_histogram;
- static int i_range_length_histogram;
- static int i_range_empty_frac;
- static int i_range_bounds_histogram;
-
- initPQExpBuffer(&query);
-
- if (first_query)
- {
- appendPQExpBufferStr(&query,
- "PREPARE getAttributeStats(pg_catalog.text, pg_catalog.text) AS\n"
- "SELECT s.attname, s.inherited, "
- "s.null_frac, s.avg_width, s.n_distinct, "
- "s.most_common_vals, s.most_common_freqs, "
- "s.histogram_bounds, s.correlation, "
- "s.most_common_elems, s.most_common_elem_freqs, "
- "s.elem_count_histogram, ");
-
- if (fout->remoteVersion >= 170000)
- appendPQExpBufferStr(&query,
- "s.range_length_histogram, "
- "s.range_empty_frac, "
- "s.range_bounds_histogram ");
- else
- appendPQExpBufferStr(&query,
- "NULL AS range_length_histogram,"
- "NULL AS range_empty_frac,"
- "NULL AS range_bounds_histogram ");
-
- appendPQExpBufferStr(&query,
- "FROM pg_catalog.pg_stats s "
- "WHERE s.schemaname = $1 "
- "AND s.tablename = $2 "
- "ORDER BY s.attname, s.inherited");
-
- ExecuteSqlStatement(fout, query.data);
-
- resetPQExpBuffer(&query);
- }
+ Assert(rsinfo != NULL);
+ dobj = &rsinfo->dobj;
+ Assert(dobj != NULL);
+ relschema = dobj->namespace->dobj.name;
+ Assert(relschema != NULL);
+ relname = dobj->name;
+ Assert(relname != NULL);
initPQExpBuffer(&out);
@@ -10561,132 +10835,72 @@ printRelationStats(Archive *fout, const void *userArg)
appendPQExpBuffer(&out, "\t'relallvisible', '%d'::integer\n);\n",
rsinfo->relallvisible);
- /* fetch attribute stats */
- appendPQExpBufferStr(&query, "EXECUTE getAttributeStats(");
- appendStringLiteralAH(&query, dobj->namespace->dobj.name, fout);
- appendPQExpBufferStr(&query, ", ");
- appendStringLiteralAH(&query, dobj->name, fout);
- appendPQExpBufferStr(&query, ")");
+ AH->txnCount++;
- res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK);
+ if (attrstats.state == STATSBUF_UNINITIALIZED)
+ initAttributeStats(fout);
- if (first_query)
+ /*
+ * Because the query returns rows in the same order as the relations
+ * requested, and because every relation gets at least one row in the
+ * result set, the first row for this relation must correspond either to
+ * the current row of this result set (if one exists) or the first row of
+ * the next result set (if this one is already consumed).
+ */
+ if (attrstats.state != STATSBUF_ACTIVE)
+ pg_fatal("Exhausted getAttributeStats() before processing %s.%s",
+ rsinfo->dobj.namespace->dobj.name,
+ rsinfo->dobj.name);
+
+ /*
+ * If the current result set has been fully consumed, then the row(s) we
+ * need (if any) would be found in the next one. This will update
+ * attrstats.res and attrstats.idx.
+ */
+ if (PQntuples(attrstats.res) <= attrstats.idx)
+ fetchNextAttributeStats(fout);
+
+ while (true)
{
- i_attname = PQfnumber(res, "attname");
- i_inherited = PQfnumber(res, "inherited");
- i_null_frac = PQfnumber(res, "null_frac");
- i_avg_width = PQfnumber(res, "avg_width");
- i_n_distinct = PQfnumber(res, "n_distinct");
- i_most_common_vals = PQfnumber(res, "most_common_vals");
- i_most_common_freqs = PQfnumber(res, "most_common_freqs");
- i_histogram_bounds = PQfnumber(res, "histogram_bounds");
- i_correlation = PQfnumber(res, "correlation");
- i_most_common_elems = PQfnumber(res, "most_common_elems");
- i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
- i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
- i_range_length_histogram = PQfnumber(res, "range_length_histogram");
- i_range_empty_frac = PQfnumber(res, "range_empty_frac");
- i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
- first_query = false;
- }
-
- /* restore attribute stats */
- for (int rownum = 0; rownum < PQntuples(res); rownum++)
- {
- const char *attname;
-
- appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
- appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n",
- fout->remoteVersion);
- appendPQExpBufferStr(&out, "\t'schemaname', ");
- appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
- appendPQExpBufferStr(&out, ",\n\t'relname', ");
- appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
-
- if (PQgetisnull(res, rownum, i_attname))
- pg_fatal("attname cannot be NULL");
- attname = PQgetvalue(res, rownum, i_attname);
+ int i_schemaname;
+ int i_tablename;
+ char *schemaname;
+ char *tablename; /* misnomer, following pg_stats naming */
/*
- * Indexes look up attname in indAttNames to derive attnum, all others
- * use attname directly. We must specify attnum for indexes, since
- * their attnames are not necessarily stable across dump/reload.
+ * If we hit the end of the result set, then there are no more records
+ * for this relation, so we should stop, but first get the next result
+ * set for the next batch of relations.
*/
- if (rsinfo->nindAttNames == 0)
+ if (PQntuples(attrstats.res) <= attrstats.idx)
{
- appendPQExpBuffer(&out, ",\n\t'attname', ");
- appendStringLiteralAH(&out, attname, fout);
- }
- else
- {
- bool found = false;
-
- for (int i = 0; i < rsinfo->nindAttNames; i++)
- {
- if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
- {
- appendPQExpBuffer(&out, ",\n\t'attnum', '%d'::smallint",
- i + 1);
- found = true;
- break;
- }
- }
-
- if (!found)
- pg_fatal("could not find index attname \"%s\"", attname);
+ fetchNextAttributeStats(fout);
+ break;
}
- if (!PQgetisnull(res, rownum, i_inherited))
- appendNamedArgument(&out, fout, "inherited", "boolean",
- PQgetvalue(res, rownum, i_inherited));
- if (!PQgetisnull(res, rownum, i_null_frac))
- appendNamedArgument(&out, fout, "null_frac", "real",
- PQgetvalue(res, rownum, i_null_frac));
- if (!PQgetisnull(res, rownum, i_avg_width))
- appendNamedArgument(&out, fout, "avg_width", "integer",
- PQgetvalue(res, rownum, i_avg_width));
- if (!PQgetisnull(res, rownum, i_n_distinct))
- appendNamedArgument(&out, fout, "n_distinct", "real",
- PQgetvalue(res, rownum, i_n_distinct));
- if (!PQgetisnull(res, rownum, i_most_common_vals))
- appendNamedArgument(&out, fout, "most_common_vals", "text",
- PQgetvalue(res, rownum, i_most_common_vals));
- if (!PQgetisnull(res, rownum, i_most_common_freqs))
- appendNamedArgument(&out, fout, "most_common_freqs", "real[]",
- PQgetvalue(res, rownum, i_most_common_freqs));
- if (!PQgetisnull(res, rownum, i_histogram_bounds))
- appendNamedArgument(&out, fout, "histogram_bounds", "text",
- PQgetvalue(res, rownum, i_histogram_bounds));
- if (!PQgetisnull(res, rownum, i_correlation))
- appendNamedArgument(&out, fout, "correlation", "real",
- PQgetvalue(res, rownum, i_correlation));
- if (!PQgetisnull(res, rownum, i_most_common_elems))
- appendNamedArgument(&out, fout, "most_common_elems", "text",
- PQgetvalue(res, rownum, i_most_common_elems));
- if (!PQgetisnull(res, rownum, i_most_common_elem_freqs))
- appendNamedArgument(&out, fout, "most_common_elem_freqs", "real[]",
- PQgetvalue(res, rownum, i_most_common_elem_freqs));
- if (!PQgetisnull(res, rownum, i_elem_count_histogram))
- appendNamedArgument(&out, fout, "elem_count_histogram", "real[]",
- PQgetvalue(res, rownum, i_elem_count_histogram));
- if (fout->remoteVersion >= 170000)
- {
- if (!PQgetisnull(res, rownum, i_range_length_histogram))
- appendNamedArgument(&out, fout, "range_length_histogram", "text",
- PQgetvalue(res, rownum, i_range_length_histogram));
- if (!PQgetisnull(res, rownum, i_range_empty_frac))
- appendNamedArgument(&out, fout, "range_empty_frac", "real",
- PQgetvalue(res, rownum, i_range_empty_frac));
- if (!PQgetisnull(res, rownum, i_range_bounds_histogram))
- appendNamedArgument(&out, fout, "range_bounds_histogram", "text",
- PQgetvalue(res, rownum, i_range_bounds_histogram));
- }
- appendPQExpBufferStr(&out, "\n);\n");
+ i_schemaname = PQfnumber(attrstats.res, "schemaname");
+ Assert(i_schemaname >= 0);
+ i_tablename = PQfnumber(attrstats.res, "tablename");
+ Assert(i_tablename >= 0);
+
+ if (PQgetisnull(attrstats.res, attrstats.idx, i_schemaname))
+ pg_fatal("getAttributeStats() schemaname cannot be NULL");
+
+ if (PQgetisnull(attrstats.res, attrstats.idx, i_tablename))
+ pg_fatal("getAttributeStats() tablename cannot be NULL");
+
+ schemaname = PQgetvalue(attrstats.res, attrstats.idx, i_schemaname);
+ tablename = PQgetvalue(attrstats.res, attrstats.idx, i_tablename);
+
+ /* stop if current stat row isn't for this relation */
+ if (strcmp(relname, tablename) != 0 || strcmp(relschema, schemaname) != 0)
+ break;
+
+ appendAttributeStats(fout, &out, rsinfo);
+ AH->txnCount++;
+ attrstats.idx++;
}
- PQclear(res);
-
- termPQExpBuffer(&query);
return out.data;
}
--
2.48.1
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-16 01:37 Corey Huinker <[email protected]>
parent: Corey Huinker <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Corey Huinker @ 2025-03-16 01:37 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Fri, Mar 14, 2025 at 4:03 PM Corey Huinker <[email protected]>
wrote:
> New patches and a rebase.
>
> 0001 - no changes, but the longer I go the more I'm certain this is
> something we want to do.
> 0002- same as 0001
>
> 0003 -
>
> Storing the restore function calls in the archive entry hogged a lot of
> memory and made people nervous. This introduces a new function pointer that
> generates those restore SQL calls right before they're written to disk,
> thus reducing the memory load from "stats for every object to be dumped" to
> just one object. Thanks to Nathan for diagnosing some weird quirks with
> various formats.
>
> 0004 -
>
> This replaces the query in the prepared statement with one that batches
> them 100 relations at a time, and then maintains that result set until it
> is consumed. It seems to have obvious speedups.
>
Another rebase, and a new patch 0005 to have pg_dump fetch and restore
relallfrozen for dbs of version 18 and higher. With older versions we omit
relallfrozen and let the import function assign the default.
Attachments:
[text/x-patch] v9-0005-Add-relallfrozen-to-pg_dump-statistics.patch (7.9K, ../../CADkLM=ceOSsx_=oe73QQ-BxUFR2Cwqum7-UP_fPe22DBY0NerA@mail.gmail.com/3-v9-0005-Add-relallfrozen-to-pg_dump-statistics.patch)
download | inline diff:
From 79b459706b09458b4c27d3f80a8fab9ec9600ce7 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sat, 15 Mar 2025 17:34:30 -0400
Subject: [PATCH v9 5/5] Add relallfrozen to pg_dump statistics.
The column relallfrozen was recently added to pg_class and it also
represent statistics, so we should add it to the dump/restore/upgrade
operations.
Dumps of databases prior to v18 will not attempt to restore any value to
relallfrozen, allowing pg_restore_relation_stats() to set the default it
deems appropriate.
---
src/bin/pg_dump/pg_dump.c | 52 ++++++++++++++++++++++----------
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/pg_dump/t/002_pg_dump.pl | 3 +-
3 files changed, 39 insertions(+), 17 deletions(-)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 0c26dc7a1b4..249bcfb80a1 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6856,7 +6856,8 @@ getFuncs(Archive *fout)
*/
static RelStatsInfo *
getRelationStatistics(Archive *fout, DumpableObject *rel, int32 relpages,
- char *reltuples, int32 relallvisible, char relkind,
+ char *reltuples, int32 relallvisible,
+ int32 relallfrozen, char relkind,
char **indAttNames, int nindAttNames)
{
if (!fout->dopt->dumpStatistics)
@@ -6885,6 +6886,7 @@ getRelationStatistics(Archive *fout, DumpableObject *rel, int32 relpages,
info->relpages = relpages;
info->reltuples = pstrdup(reltuples);
info->relallvisible = relallvisible;
+ info->relallfrozen = relallfrozen;
info->relkind = relkind;
info->indAttNames = indAttNames;
info->nindAttNames = nindAttNames;
@@ -6924,6 +6926,7 @@ getTables(Archive *fout, int *numTables)
int i_relpages;
int i_reltuples;
int i_relallvisible;
+ int i_relallfrozen;
int i_toastpages;
int i_owning_tab;
int i_owning_col;
@@ -6974,8 +6977,13 @@ getTables(Archive *fout, int *numTables)
"c.relowner, "
"c.relchecks, "
"c.relhasindex, c.relhasrules, c.relpages, "
- "c.reltuples, c.relallvisible, c.relhastriggers, "
- "c.relpersistence, "
+ "c.reltuples, c.relallvisible, ");
+
+ if (fout->remoteVersion >= 180000)
+ appendPQExpBufferStr(query, "c.relallfrozen, ");
+
+ appendPQExpBufferStr(query,
+ "c.relhastriggers, c.relpersistence, "
"c.reloftype, "
"c.relacl, "
"acldefault(CASE WHEN c.relkind = " CppAsString2(RELKIND_SEQUENCE)
@@ -7140,6 +7148,7 @@ getTables(Archive *fout, int *numTables)
i_relpages = PQfnumber(res, "relpages");
i_reltuples = PQfnumber(res, "reltuples");
i_relallvisible = PQfnumber(res, "relallvisible");
+ i_relallfrozen = PQfnumber(res, "relallfrozen");
i_toastpages = PQfnumber(res, "toastpages");
i_owning_tab = PQfnumber(res, "owning_tab");
i_owning_col = PQfnumber(res, "owning_col");
@@ -7187,6 +7196,7 @@ getTables(Archive *fout, int *numTables)
for (i = 0; i < ntups; i++)
{
int32 relallvisible = atoi(PQgetvalue(res, i, i_relallvisible));
+ int32 relallfrozen = atoi(PQgetvalue(res, i, i_relallfrozen));
tblinfo[i].dobj.objType = DO_TABLE;
tblinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_reltableoid));
@@ -7289,7 +7299,7 @@ getTables(Archive *fout, int *numTables)
if (tblinfo[i].interesting)
getRelationStatistics(fout, &tblinfo[i].dobj, tblinfo[i].relpages,
PQgetvalue(res, i, i_reltuples),
- relallvisible, tblinfo[i].relkind, NULL, 0);
+ relallvisible, relallfrozen, tblinfo[i].relkind, NULL, 0);
/*
* Read-lock target tables to make sure they aren't DROPPED or altered
@@ -7558,6 +7568,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
i_relpages,
i_reltuples,
i_relallvisible,
+ i_relallfrozen,
i_parentidx,
i_indexdef,
i_indnkeyatts,
@@ -7612,7 +7623,12 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
appendPQExpBufferStr(query,
"SELECT t.tableoid, t.oid, i.indrelid, "
"t.relname AS indexname, "
- "t.relpages, t.reltuples, t.relallvisible, "
+ "t.relpages, t.reltuples, t.relallvisible, ");
+
+ if (fout->remoteVersion >= 180000)
+ appendPQExpBufferStr(query, "t.relallfrozen, ");
+
+ appendPQExpBufferStr(query,
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
"i.indkey, i.indisclustered, "
"c.contype, c.conname, "
@@ -7728,6 +7744,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
i_relpages = PQfnumber(res, "relpages");
i_reltuples = PQfnumber(res, "reltuples");
i_relallvisible = PQfnumber(res, "relallvisible");
+ i_relallfrozen = PQfnumber(res, "relallfrozen");
i_parentidx = PQfnumber(res, "parentidx");
i_indexdef = PQfnumber(res, "indexdef");
i_indnkeyatts = PQfnumber(res, "indnkeyatts");
@@ -7799,6 +7816,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
RelStatsInfo *relstats;
int32 relpages = atoi(PQgetvalue(res, j, i_relpages));
int32 relallvisible = atoi(PQgetvalue(res, j, i_relallvisible));
+ int32 relallfrozen = atoi(PQgetvalue(res, j, i_relallfrozen));
indxinfo[j].dobj.objType = DO_INDEX;
indxinfo[j].dobj.catId.tableoid = atooid(PQgetvalue(res, j, i_tableoid));
@@ -7841,7 +7859,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
relstats = getRelationStatistics(fout, &indxinfo[j].dobj, relpages,
PQgetvalue(res, j, i_reltuples),
- relallvisible, indexkind,
+ relallvisible, relallfrozen, indexkind,
indAttNames, nindAttNames);
contype = *(PQgetvalue(res, j, i_contype));
@@ -10821,19 +10839,21 @@ printRelationStats(Archive *fout, const void *userArg)
initPQExpBuffer(&out);
/* restore relation stats */
- appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
- appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n",
+ appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(");
+ appendPQExpBuffer(&out, "\n\t'version', '%u'::integer",
fout->remoteVersion);
- appendPQExpBufferStr(&out, "\t'schemaname', ");
+ appendPQExpBufferStr(&out, ",\n\t'schemaname', ");
appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
- appendPQExpBufferStr(&out, ",\n");
- appendPQExpBufferStr(&out, "\t'relname', ");
+ appendPQExpBufferStr(&out, ",\n\t'relname', ");
appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
- appendPQExpBufferStr(&out, ",\n");
- appendPQExpBuffer(&out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
- appendPQExpBuffer(&out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
- appendPQExpBuffer(&out, "\t'relallvisible', '%d'::integer\n);\n",
- rsinfo->relallvisible);
+ appendPQExpBuffer(&out, ",\n\t'relpages', '%d'::integer", rsinfo->relpages);
+ appendPQExpBuffer(&out, ",\n\t'reltuples', '%s'::real", rsinfo->reltuples);
+ appendPQExpBuffer(&out, ",\n\t'relallvisible', '%d'::integer", rsinfo->relallvisible);
+
+ if (fout->remoteVersion >= 180000)
+ appendPQExpBuffer(&out, ",\n\t'relallfrozen', '%d'::integer", rsinfo->relallfrozen);
+
+ appendPQExpBufferStr(&out, "\n);\n");
AH->txnCount++;
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index bbdb30b5f54..82f1eb3c4b7 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -441,6 +441,7 @@ typedef struct _relStatsInfo
int32 relpages;
char *reltuples;
int32 relallvisible;
+ int32 relallfrozen;
char relkind; /* 'r', 'm', 'i', etc */
/*
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index b037f239136..1d69e55a861 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -4729,7 +4729,8 @@ my %tests = (
'relname',\s'dup_test_post_data_ix',\s+
'relpages',\s'\d+'::integer,\s+
'reltuples',\s'\d+'::real,\s+
- 'relallvisible',\s'\d+'::integer\s+
+ 'relallvisible',\s'\d+'::integer,\s+
+ 'relallfrozen',\s'\d+'::integer\s+
\);\s+
\QSELECT * FROM pg_catalog.pg_restore_attribute_stats(\E\s+
'version',\s'\d+'::integer,\s+
--
2.48.1
[text/x-patch] v9-0003-Introduce-CreateStmtPtr.patch (17.2K, ../../CADkLM=ceOSsx_=oe73QQ-BxUFR2Cwqum7-UP_fPe22DBY0NerA@mail.gmail.com/4-v9-0003-Introduce-CreateStmtPtr.patch)
download | inline diff:
From 72d0fe4b5de382a2f36151de2b26960d3a85267f Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Fri, 14 Mar 2025 01:06:19 -0400
Subject: [PATCH v9 3/5] Introduce CreateStmtPtr.
CreateStmtPtr is a function pointer that can replace the createStmt/defn
parameter. This is useful in situations where the amount of text
generated for a definition is so large that it is undesirable to hold
many such objects in memory at the same time.
Using functions of this type, the text created is then immediately
written out to the appropriate file for the given dump format.
---
src/bin/pg_dump/pg_backup.h | 2 +
src/bin/pg_dump/pg_backup_archiver.c | 22 ++-
src/bin/pg_dump/pg_backup_archiver.h | 7 +
src/bin/pg_dump/pg_dump.c | 230 +++++++++++++++------------
4 files changed, 156 insertions(+), 105 deletions(-)
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index e783cc68d89..bb175874a5a 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -287,6 +287,8 @@ typedef int (*DataDumperPtr) (Archive *AH, const void *userArg);
typedef void (*SetupWorkerPtrType) (Archive *AH);
+typedef char *(*CreateStmtPtr) (Archive *AH, const void *userArg);
+
/*
* Main archiver interface.
*/
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 7480e122b61..3fcfecf6719 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -1263,6 +1263,9 @@ ArchiveEntry(Archive *AHX, CatalogId catalogId, DumpId dumpId,
newToc->dataDumper = opts->dumpFn;
newToc->dataDumperArg = opts->dumpArg;
newToc->hadDumper = opts->dumpFn ? true : false;
+ newToc->createDumper = opts->createFn;
+ newToc->createDumperArg = opts->createArg;
+ newToc->hadCreateDumper = opts->createFn ? true : false;
newToc->formatData = NULL;
newToc->dataLength = 0;
@@ -2619,7 +2622,17 @@ WriteToc(ArchiveHandle *AH)
WriteStr(AH, te->tag);
WriteStr(AH, te->desc);
WriteInt(AH, te->section);
- WriteStr(AH, te->defn);
+
+ if (te->hadCreateDumper)
+ {
+ char *defn = te->createDumper((Archive *) AH, te->createDumperArg);
+
+ WriteStr(AH, defn);
+ pg_free(defn);
+ }
+ else
+ WriteStr(AH, te->defn);
+
WriteStr(AH, te->dropStmt);
WriteStr(AH, te->copyStmt);
WriteStr(AH, te->namespace);
@@ -3849,6 +3862,13 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, const char *pfx)
{
IssueACLPerBlob(AH, te);
}
+ else if (te->hadCreateDumper)
+ {
+ char *ptr = te->createDumper((Archive *) AH, te->createDumperArg);
+
+ ahwrite(ptr, 1, strlen(ptr), AH);
+ pg_free(ptr);
+ }
else if (te->defn && strlen(te->defn) > 0)
{
ahprintf(AH, "%s\n\n", te->defn);
diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h
index a2064f471ed..e68db633995 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -368,6 +368,11 @@ struct _tocEntry
const void *dataDumperArg; /* Arg for above routine */
void *formatData; /* TOC Entry data specific to file format */
+ CreateStmtPtr createDumper; /* Routine for create statement creation */
+ const void *createDumperArg; /* arg for the above routine */
+ bool hadCreateDumper; /* Archiver was passed a create statement
+ * routine */
+
/* working state while dumping/restoring */
pgoff_t dataLength; /* item's data size; 0 if none or unknown */
int reqs; /* do we need schema and/or data of object
@@ -407,6 +412,8 @@ typedef struct _archiveOpts
int nDeps;
DataDumperPtr dumpFn;
const void *dumpArg;
+ CreateStmtPtr createFn;
+ const void *createArg;
} ArchiveOpts;
#define ARCHIVE_OPTS(...) &(ArchiveOpts){__VA_ARGS__}
/* Called to add a TOC entry */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index bd857bb076c..38ba6a90106 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -10477,51 +10477,44 @@ statisticsDumpSection(const RelStatsInfo *rsinfo)
}
/*
- * dumpRelationStats --
+ * printDumpRelationStats --
*
- * Dump command to import stats into the relation on the new database.
+ * Generate the SQL statements needed to restore a relation's statistics.
*/
-static void
-dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
+static char *
+printRelationStats(Archive *fout, const void *userArg)
{
+ const RelStatsInfo *rsinfo = (RelStatsInfo *) userArg;
const DumpableObject *dobj = &rsinfo->dobj;
+
+ PQExpBufferData query;
+ PQExpBufferData out;
+
PGresult *res;
- PQExpBuffer query;
- PQExpBuffer out;
- DumpId *deps = NULL;
- int ndeps = 0;
- int i_attname;
- int i_inherited;
- int i_null_frac;
- int i_avg_width;
- int i_n_distinct;
- int i_most_common_vals;
- int i_most_common_freqs;
- int i_histogram_bounds;
- int i_correlation;
- int i_most_common_elems;
- int i_most_common_elem_freqs;
- int i_elem_count_histogram;
- int i_range_length_histogram;
- int i_range_empty_frac;
- int i_range_bounds_histogram;
- /* nothing to do if we are not dumping statistics */
- if (!fout->dopt->dumpStatistics)
- return;
+ static bool first_query = true;
+ static int i_attname;
+ static int i_inherited;
+ static int i_null_frac;
+ static int i_avg_width;
+ static int i_n_distinct;
+ static int i_most_common_vals;
+ static int i_most_common_freqs;
+ static int i_histogram_bounds;
+ static int i_correlation;
+ static int i_most_common_elems;
+ static int i_most_common_elem_freqs;
+ static int i_elem_count_histogram;
+ static int i_range_length_histogram;
+ static int i_range_empty_frac;
+ static int i_range_bounds_histogram;
- /* dependent on the relation definition, if doing schema */
- if (fout->dopt->dumpSchema)
+ initPQExpBuffer(&query);
+
+ if (first_query)
{
- deps = dobj->dependencies;
- ndeps = dobj->nDeps;
- }
-
- query = createPQExpBuffer();
- if (!fout->is_prepared[PREPQUERY_GETATTRIBUTESTATS])
- {
- appendPQExpBufferStr(query,
- "PREPARE getAttributeStats(pg_catalog.name, pg_catalog.name) AS\n"
+ appendPQExpBufferStr(&query,
+ "PREPARE getAttributeStats(pg_catalog.text, pg_catalog.text) AS\n"
"SELECT s.attname, s.inherited, "
"s.null_frac, s.avg_width, s.n_distinct, "
"s.most_common_vals, s.most_common_freqs, "
@@ -10530,82 +10523,85 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
"s.elem_count_histogram, ");
if (fout->remoteVersion >= 170000)
- appendPQExpBufferStr(query,
+ appendPQExpBufferStr(&query,
"s.range_length_histogram, "
"s.range_empty_frac, "
"s.range_bounds_histogram ");
else
- appendPQExpBufferStr(query,
+ appendPQExpBufferStr(&query,
"NULL AS range_length_histogram,"
"NULL AS range_empty_frac,"
"NULL AS range_bounds_histogram ");
- appendPQExpBufferStr(query,
+ appendPQExpBufferStr(&query,
"FROM pg_catalog.pg_stats s "
"WHERE s.schemaname = $1 "
"AND s.tablename = $2 "
"ORDER BY s.attname, s.inherited");
- ExecuteSqlStatement(fout, query->data);
+ ExecuteSqlStatement(fout, query.data);
- fout->is_prepared[PREPQUERY_GETATTRIBUTESTATS] = true;
- resetPQExpBuffer(query);
+ resetPQExpBuffer(&query);
}
- out = createPQExpBuffer();
+ initPQExpBuffer(&out);
/* restore relation stats */
- appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
- appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+ appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
+ appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n",
fout->remoteVersion);
- appendPQExpBufferStr(out, "\t'schemaname', ");
- appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
- appendPQExpBufferStr(out, ",\n");
- appendPQExpBufferStr(out, "\t'relname', ");
- appendStringLiteralAH(out, rsinfo->dobj.name, fout);
- appendPQExpBufferStr(out, ",\n");
- appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
- appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
- appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer\n);\n",
+ appendPQExpBufferStr(&out, "\t'schemaname', ");
+ appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
+ appendPQExpBufferStr(&out, ",\n");
+ appendPQExpBufferStr(&out, "\t'relname', ");
+ appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
+ appendPQExpBufferStr(&out, ",\n");
+ appendPQExpBuffer(&out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
+ appendPQExpBuffer(&out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
+ appendPQExpBuffer(&out, "\t'relallvisible', '%d'::integer\n);\n",
rsinfo->relallvisible);
/* fetch attribute stats */
- appendPQExpBufferStr(query, "EXECUTE getAttributeStats(");
- appendStringLiteralAH(query, dobj->namespace->dobj.name, fout);
- appendPQExpBufferStr(query, ", ");
- appendStringLiteralAH(query, dobj->name, fout);
- appendPQExpBufferStr(query, ");");
+ appendPQExpBufferStr(&query, "EXECUTE getAttributeStats(");
+ appendStringLiteralAH(&query, dobj->namespace->dobj.name, fout);
+ appendPQExpBufferStr(&query, ", ");
+ appendStringLiteralAH(&query, dobj->name, fout);
+ appendPQExpBufferStr(&query, ")");
- res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+ res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK);
- i_attname = PQfnumber(res, "attname");
- i_inherited = PQfnumber(res, "inherited");
- i_null_frac = PQfnumber(res, "null_frac");
- i_avg_width = PQfnumber(res, "avg_width");
- i_n_distinct = PQfnumber(res, "n_distinct");
- i_most_common_vals = PQfnumber(res, "most_common_vals");
- i_most_common_freqs = PQfnumber(res, "most_common_freqs");
- i_histogram_bounds = PQfnumber(res, "histogram_bounds");
- i_correlation = PQfnumber(res, "correlation");
- i_most_common_elems = PQfnumber(res, "most_common_elems");
- i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
- i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
- i_range_length_histogram = PQfnumber(res, "range_length_histogram");
- i_range_empty_frac = PQfnumber(res, "range_empty_frac");
- i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+ if (first_query)
+ {
+ i_attname = PQfnumber(res, "attname");
+ i_inherited = PQfnumber(res, "inherited");
+ i_null_frac = PQfnumber(res, "null_frac");
+ i_avg_width = PQfnumber(res, "avg_width");
+ i_n_distinct = PQfnumber(res, "n_distinct");
+ i_most_common_vals = PQfnumber(res, "most_common_vals");
+ i_most_common_freqs = PQfnumber(res, "most_common_freqs");
+ i_histogram_bounds = PQfnumber(res, "histogram_bounds");
+ i_correlation = PQfnumber(res, "correlation");
+ i_most_common_elems = PQfnumber(res, "most_common_elems");
+ i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
+ i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
+ i_range_length_histogram = PQfnumber(res, "range_length_histogram");
+ i_range_empty_frac = PQfnumber(res, "range_empty_frac");
+ i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+ first_query = false;
+ }
/* restore attribute stats */
for (int rownum = 0; rownum < PQntuples(res); rownum++)
{
const char *attname;
- appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
- appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+ appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
+ appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n",
fout->remoteVersion);
- appendPQExpBufferStr(out, "\t'schemaname', ");
- appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
- appendPQExpBufferStr(out, ",\n\t'relname', ");
- appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+ appendPQExpBufferStr(&out, "\t'schemaname', ");
+ appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
+ appendPQExpBufferStr(&out, ",\n\t'relname', ");
+ appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
if (PQgetisnull(res, rownum, i_attname))
pg_fatal("attname cannot be NULL");
@@ -10618,8 +10614,8 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
*/
if (rsinfo->nindAttNames == 0)
{
- appendPQExpBuffer(out, ",\n\t'attname', ");
- appendStringLiteralAH(out, attname, fout);
+ appendPQExpBuffer(&out, ",\n\t'attname', ");
+ appendStringLiteralAH(&out, attname, fout);
}
else
{
@@ -10629,7 +10625,7 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
{
if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
{
- appendPQExpBuffer(out, ",\n\t'attnum', '%d'::smallint",
+ appendPQExpBuffer(&out, ",\n\t'attnum', '%d'::smallint",
i + 1);
found = true;
break;
@@ -10641,67 +10637,93 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
}
if (!PQgetisnull(res, rownum, i_inherited))
- appendNamedArgument(out, fout, "inherited", "boolean",
+ appendNamedArgument(&out, fout, "inherited", "boolean",
PQgetvalue(res, rownum, i_inherited));
if (!PQgetisnull(res, rownum, i_null_frac))
- appendNamedArgument(out, fout, "null_frac", "real",
+ appendNamedArgument(&out, fout, "null_frac", "real",
PQgetvalue(res, rownum, i_null_frac));
if (!PQgetisnull(res, rownum, i_avg_width))
- appendNamedArgument(out, fout, "avg_width", "integer",
+ appendNamedArgument(&out, fout, "avg_width", "integer",
PQgetvalue(res, rownum, i_avg_width));
if (!PQgetisnull(res, rownum, i_n_distinct))
- appendNamedArgument(out, fout, "n_distinct", "real",
+ appendNamedArgument(&out, fout, "n_distinct", "real",
PQgetvalue(res, rownum, i_n_distinct));
if (!PQgetisnull(res, rownum, i_most_common_vals))
- appendNamedArgument(out, fout, "most_common_vals", "text",
+ appendNamedArgument(&out, fout, "most_common_vals", "text",
PQgetvalue(res, rownum, i_most_common_vals));
if (!PQgetisnull(res, rownum, i_most_common_freqs))
- appendNamedArgument(out, fout, "most_common_freqs", "real[]",
+ appendNamedArgument(&out, fout, "most_common_freqs", "real[]",
PQgetvalue(res, rownum, i_most_common_freqs));
if (!PQgetisnull(res, rownum, i_histogram_bounds))
- appendNamedArgument(out, fout, "histogram_bounds", "text",
+ appendNamedArgument(&out, fout, "histogram_bounds", "text",
PQgetvalue(res, rownum, i_histogram_bounds));
if (!PQgetisnull(res, rownum, i_correlation))
- appendNamedArgument(out, fout, "correlation", "real",
+ appendNamedArgument(&out, fout, "correlation", "real",
PQgetvalue(res, rownum, i_correlation));
if (!PQgetisnull(res, rownum, i_most_common_elems))
- appendNamedArgument(out, fout, "most_common_elems", "text",
+ appendNamedArgument(&out, fout, "most_common_elems", "text",
PQgetvalue(res, rownum, i_most_common_elems));
if (!PQgetisnull(res, rownum, i_most_common_elem_freqs))
- appendNamedArgument(out, fout, "most_common_elem_freqs", "real[]",
+ appendNamedArgument(&out, fout, "most_common_elem_freqs", "real[]",
PQgetvalue(res, rownum, i_most_common_elem_freqs));
if (!PQgetisnull(res, rownum, i_elem_count_histogram))
- appendNamedArgument(out, fout, "elem_count_histogram", "real[]",
+ appendNamedArgument(&out, fout, "elem_count_histogram", "real[]",
PQgetvalue(res, rownum, i_elem_count_histogram));
if (fout->remoteVersion >= 170000)
{
if (!PQgetisnull(res, rownum, i_range_length_histogram))
- appendNamedArgument(out, fout, "range_length_histogram", "text",
+ appendNamedArgument(&out, fout, "range_length_histogram", "text",
PQgetvalue(res, rownum, i_range_length_histogram));
if (!PQgetisnull(res, rownum, i_range_empty_frac))
- appendNamedArgument(out, fout, "range_empty_frac", "real",
+ appendNamedArgument(&out, fout, "range_empty_frac", "real",
PQgetvalue(res, rownum, i_range_empty_frac));
if (!PQgetisnull(res, rownum, i_range_bounds_histogram))
- appendNamedArgument(out, fout, "range_bounds_histogram", "text",
+ appendNamedArgument(&out, fout, "range_bounds_histogram", "text",
PQgetvalue(res, rownum, i_range_bounds_histogram));
}
- appendPQExpBufferStr(out, "\n);\n");
+ appendPQExpBufferStr(&out, "\n);\n");
}
PQclear(res);
+ termPQExpBuffer(&query);
+ return out.data;
+}
+
+/*
+ * dumpRelationStats --
+ *
+ * Dump command to import stats into the relation on the new database.
+ */
+static void
+dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
+{
+ const DumpableObject *dobj = &rsinfo->dobj;
+
+ DumpId *deps = NULL;
+ int ndeps = 0;
+
+ /* nothing to do if we are not dumping statistics */
+ if (!fout->dopt->dumpStatistics)
+ return;
+
+ /* dependent on the relation definition, if doing schema */
+ if (fout->dopt->dumpSchema)
+ {
+ deps = dobj->dependencies;
+ ndeps = dobj->nDeps;
+ }
+
ArchiveEntry(fout, nilCatalogId, createDumpId(),
ARCHIVE_OPTS(.tag = dobj->name,
.namespace = dobj->namespace->dobj.name,
.description = "STATISTICS DATA",
.section = rsinfo->postponed_def ?
SECTION_POST_DATA : statisticsDumpSection(rsinfo),
- .createStmt = out->data,
+ .createFn = printRelationStats,
+ .createArg = rsinfo,
.deps = deps,
.nDeps = ndeps));
-
- destroyPQExpBuffer(out);
- destroyPQExpBuffer(query);
}
/*
--
2.48.1
[text/x-patch] v9-0001-Split-relation-into-schemaname-and-relname.patch (65.0K, ../../CADkLM=ceOSsx_=oe73QQ-BxUFR2Cwqum7-UP_fPe22DBY0NerA@mail.gmail.com/5-v9-0001-Split-relation-into-schemaname-and-relname.patch)
download | inline diff:
From fd2cced89c11353e909e6ef9bb824a3a7536e6cb Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Tue, 4 Mar 2025 22:16:52 -0500
Subject: [PATCH v9 1/5] Split relation into schemaname and relname.
In order to further reduce potential error-failures in restores and
upgrades, replace the numerous casts of fully qualified relation names
into their schema+relname text components.
Further remove the ::name casts on attname and change the expected
datatype to text.
Add an ACL_USAGE check on the namespace oid after it is looked up.
---
src/include/catalog/pg_proc.dat | 8 +-
src/include/statistics/stat_utils.h | 2 +
src/backend/statistics/attribute_stats.c | 87 ++++--
src/backend/statistics/relation_stats.c | 65 +++--
src/backend/statistics/stat_utils.c | 37 +++
src/bin/pg_dump/pg_dump.c | 25 +-
src/bin/pg_dump/t/002_pg_dump.pl | 6 +-
src/test/regress/expected/stats_import.out | 307 +++++++++++++--------
src/test/regress/sql/stats_import.sql | 276 +++++++++++-------
doc/src/sgml/func.sgml | 41 +--
10 files changed, 566 insertions(+), 288 deletions(-)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 890822eaf79..8dee321d248 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12453,8 +12453,8 @@
descr => 'clear statistics on relation',
proname => 'pg_clear_relation_stats', provolatile => 'v', proisstrict => 'f',
proparallel => 'u', prorettype => 'void',
- proargtypes => 'regclass',
- proargnames => '{relation}',
+ proargtypes => 'text text',
+ proargnames => '{schemaname,relname}',
prosrc => 'pg_clear_relation_stats' },
{ oid => '8461',
descr => 'restore statistics on attribute',
@@ -12469,8 +12469,8 @@
descr => 'clear statistics on attribute',
proname => 'pg_clear_attribute_stats', provolatile => 'v', proisstrict => 'f',
proparallel => 'u', prorettype => 'void',
- proargtypes => 'regclass name bool',
- proargnames => '{relation,attname,inherited}',
+ proargtypes => 'text text text bool',
+ proargnames => '{schemaname,relname,attname,inherited}',
prosrc => 'pg_clear_attribute_stats' },
# GiST stratnum implementations
diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h
index 0eb4decfcac..cad042c8e4a 100644
--- a/src/include/statistics/stat_utils.h
+++ b/src/include/statistics/stat_utils.h
@@ -32,6 +32,8 @@ extern bool stats_check_arg_pair(FunctionCallInfo fcinfo,
extern void stats_lock_check_privileges(Oid reloid);
+extern Oid stats_schema_check_privileges(const char *nspname);
+
extern bool stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
FunctionCallInfo positional_fcinfo,
struct StatsArgInfo *arginfo);
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index 6bcbee0edba..f87db2d6102 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -36,7 +36,8 @@
enum attribute_stats_argnum
{
- ATTRELATION_ARG = 0,
+ ATTRELSCHEMA_ARG = 0,
+ ATTRELNAME_ARG,
ATTNAME_ARG,
ATTNUM_ARG,
INHERITED_ARG,
@@ -58,8 +59,9 @@ enum attribute_stats_argnum
static struct StatsArgInfo attarginfo[] =
{
- [ATTRELATION_ARG] = {"relation", REGCLASSOID},
- [ATTNAME_ARG] = {"attname", NAMEOID},
+ [ATTRELSCHEMA_ARG] = {"schemaname", TEXTOID},
+ [ATTRELNAME_ARG] = {"relname", TEXTOID},
+ [ATTNAME_ARG] = {"attname", TEXTOID},
[ATTNUM_ARG] = {"attnum", INT2OID},
[INHERITED_ARG] = {"inherited", BOOLOID},
[NULL_FRAC_ARG] = {"null_frac", FLOAT4OID},
@@ -80,7 +82,8 @@ static struct StatsArgInfo attarginfo[] =
enum clear_attribute_stats_argnum
{
- C_ATTRELATION_ARG = 0,
+ C_ATTRELSCHEMA_ARG = 0,
+ C_ATTRELNAME_ARG,
C_ATTNAME_ARG,
C_INHERITED_ARG,
C_NUM_ATTRIBUTE_STATS_ARGS
@@ -88,8 +91,9 @@ enum clear_attribute_stats_argnum
static struct StatsArgInfo cleararginfo[] =
{
- [C_ATTRELATION_ARG] = {"relation", REGCLASSOID},
- [C_ATTNAME_ARG] = {"attname", NAMEOID},
+ [C_ATTRELSCHEMA_ARG] = {"relation", TEXTOID},
+ [C_ATTRELNAME_ARG] = {"relation", TEXTOID},
+ [C_ATTNAME_ARG] = {"attname", TEXTOID},
[C_INHERITED_ARG] = {"inherited", BOOLOID},
[C_NUM_ATTRIBUTE_STATS_ARGS] = {0}
};
@@ -133,6 +137,9 @@ static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
static bool
attribute_statistics_update(FunctionCallInfo fcinfo)
{
+ char *nspname;
+ Oid nspoid;
+ char *relname;
Oid reloid;
char *attname;
AttrNumber attnum;
@@ -170,8 +177,23 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
bool result = true;
- stats_check_required_arg(fcinfo, attarginfo, ATTRELATION_ARG);
- reloid = PG_GETARG_OID(ATTRELATION_ARG);
+ stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG);
+ stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG);
+
+ nspname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELSCHEMA_ARG));
+ nspoid = stats_schema_check_privileges(nspname);
+ if (nspoid == InvalidOid)
+ return false;
+
+ relname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELNAME_ARG));
+ reloid = get_relname_relid(relname, nspoid);
+ if (reloid == InvalidOid)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
+ return false;
+ }
if (RecoveryInProgress())
ereport(ERROR,
@@ -185,21 +207,18 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
/* user can specify either attname or attnum, but not both */
if (!PG_ARGISNULL(ATTNAME_ARG))
{
- Name attnamename;
-
if (!PG_ARGISNULL(ATTNUM_ARG))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("cannot specify both attname and attnum")));
- attnamename = PG_GETARG_NAME(ATTNAME_ARG);
- attname = NameStr(*attnamename);
+ attname = TextDatumGetCString(PG_GETARG_DATUM(ATTNAME_ARG));
attnum = get_attnum(reloid, attname);
/* note that this test covers attisdropped cases too: */
if (attnum == InvalidAttrNumber)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
- attname, get_rel_name(reloid))));
+ errmsg("column \"%s\" of relation \"%s\".\"%s\" does not exist",
+ attname, nspname, relname)));
}
else if (!PG_ARGISNULL(ATTNUM_ARG))
{
@@ -210,8 +229,8 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
!SearchSysCacheExistsAttName(reloid, attname))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column %d of relation \"%s\" does not exist",
- attnum, get_rel_name(reloid))));
+ errmsg("column %d of relation \"%s\".\"%s\" does not exist",
+ attnum, nspname, relname)));
}
else
{
@@ -900,13 +919,33 @@ init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
Datum
pg_clear_attribute_stats(PG_FUNCTION_ARGS)
{
+ char *nspname;
+ Oid nspoid;
+ char *relname;
Oid reloid;
- Name attname;
+ char *attname;
AttrNumber attnum;
bool inherited;
- stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELATION_ARG);
- reloid = PG_GETARG_OID(C_ATTRELATION_ARG);
+ stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG);
+ stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG);
+ stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
+ stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
+
+ nspname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELSCHEMA_ARG));
+ nspoid = stats_schema_check_privileges(nspname);
+ if (!OidIsValid(nspoid))
+ return false;
+
+ relname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELNAME_ARG));
+ reloid = get_relname_relid(relname, nspoid);
+ if (!OidIsValid(reloid))
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
+ return false;
+ }
if (RecoveryInProgress())
ereport(ERROR,
@@ -916,23 +955,21 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
stats_lock_check_privileges(reloid);
- stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
- attname = PG_GETARG_NAME(C_ATTNAME_ARG);
- attnum = get_attnum(reloid, NameStr(*attname));
+ attname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTNAME_ARG));
+ attnum = get_attnum(reloid, attname);
if (attnum < 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot clear statistics on system column \"%s\"",
- NameStr(*attname))));
+ attname)));
if (attnum == InvalidAttrNumber)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
- NameStr(*attname), get_rel_name(reloid))));
+ attname, get_rel_name(reloid))));
- stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
inherited = PG_GETARG_BOOL(C_INHERITED_ARG);
delete_pg_statistic(reloid, attnum, inherited);
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index 52dfa477187..fdc69bc93e2 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -19,9 +19,12 @@
#include "access/heapam.h"
#include "catalog/indexing.h"
+#include "catalog/namespace.h"
#include "statistics/stat_utils.h"
+#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/fmgrprotos.h"
+#include "utils/lsyscache.h"
#include "utils/syscache.h"
@@ -32,7 +35,8 @@
enum relation_stats_argnum
{
- RELATION_ARG = 0,
+ RELSCHEMA_ARG = 0,
+ RELNAME_ARG,
RELPAGES_ARG,
RELTUPLES_ARG,
RELALLVISIBLE_ARG,
@@ -42,7 +46,8 @@ enum relation_stats_argnum
static struct StatsArgInfo relarginfo[] =
{
- [RELATION_ARG] = {"relation", REGCLASSOID},
+ [RELSCHEMA_ARG] = {"schemaname", TEXTOID},
+ [RELNAME_ARG] = {"relname", TEXTOID},
[RELPAGES_ARG] = {"relpages", INT4OID},
[RELTUPLES_ARG] = {"reltuples", FLOAT4OID},
[RELALLVISIBLE_ARG] = {"relallvisible", INT4OID},
@@ -59,6 +64,9 @@ static bool
relation_statistics_update(FunctionCallInfo fcinfo)
{
bool result = true;
+ char *nspname;
+ Oid nspoid;
+ char *relname;
Oid reloid;
Relation crel;
BlockNumber relpages = 0;
@@ -76,6 +84,32 @@ relation_statistics_update(FunctionCallInfo fcinfo)
bool nulls[4] = {0};
int nreplaces = 0;
+ stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG);
+ stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG);
+
+ nspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
+ nspoid = stats_schema_check_privileges(nspname);
+ if (!OidIsValid(nspoid))
+ return false;
+
+ relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
+ reloid = get_relname_relid(relname, nspoid);
+ if (!OidIsValid(reloid))
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
+ return false;
+ }
+
+ if (RecoveryInProgress())
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("recovery is in progress"),
+ errhint("Statistics cannot be modified during recovery.")));
+
+ stats_lock_check_privileges(reloid);
+
if (!PG_ARGISNULL(RELPAGES_ARG))
{
relpages = PG_GETARG_UINT32(RELPAGES_ARG);
@@ -108,17 +142,6 @@ relation_statistics_update(FunctionCallInfo fcinfo)
update_relallfrozen = true;
}
- stats_check_required_arg(fcinfo, relarginfo, RELATION_ARG);
- reloid = PG_GETARG_OID(RELATION_ARG);
-
- if (RecoveryInProgress())
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("recovery is in progress"),
- errhint("Statistics cannot be modified during recovery.")));
-
- stats_lock_check_privileges(reloid);
-
/*
* Take RowExclusiveLock on pg_class, consistent with
* vac_update_relstats().
@@ -187,20 +210,22 @@ relation_statistics_update(FunctionCallInfo fcinfo)
Datum
pg_clear_relation_stats(PG_FUNCTION_ARGS)
{
- LOCAL_FCINFO(newfcinfo, 5);
+ LOCAL_FCINFO(newfcinfo, 6);
- InitFunctionCallInfoData(*newfcinfo, NULL, 5, InvalidOid, NULL, NULL);
+ InitFunctionCallInfoData(*newfcinfo, NULL, 6, InvalidOid, NULL, NULL);
- newfcinfo->args[0].value = PG_GETARG_OID(0);
+ newfcinfo->args[0].value = PG_GETARG_DATUM(0);
newfcinfo->args[0].isnull = PG_ARGISNULL(0);
- newfcinfo->args[1].value = UInt32GetDatum(0);
- newfcinfo->args[1].isnull = false;
- newfcinfo->args[2].value = Float4GetDatum(-1.0);
+ newfcinfo->args[1].value = PG_GETARG_DATUM(1);
+ newfcinfo->args[1].isnull = PG_ARGISNULL(1);
+ newfcinfo->args[2].value = UInt32GetDatum(0);
newfcinfo->args[2].isnull = false;
- newfcinfo->args[3].value = UInt32GetDatum(0);
+ newfcinfo->args[3].value = Float4GetDatum(-1.0);
newfcinfo->args[3].isnull = false;
newfcinfo->args[4].value = UInt32GetDatum(0);
newfcinfo->args[4].isnull = false;
+ newfcinfo->args[5].value = UInt32GetDatum(0);
+ newfcinfo->args[5].isnull = false;
relation_statistics_update(newfcinfo);
PG_RETURN_VOID();
diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c
index 9647f5108b3..e037d4994e8 100644
--- a/src/backend/statistics/stat_utils.c
+++ b/src/backend/statistics/stat_utils.c
@@ -18,7 +18,9 @@
#include "access/relation.h"
#include "catalog/index.h"
+#include "catalog/namespace.h"
#include "catalog/pg_database.h"
+#include "catalog/pg_namespace.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "statistics/stat_utils.h"
@@ -213,6 +215,41 @@ stats_lock_check_privileges(Oid reloid)
relation_close(table, NoLock);
}
+
+/*
+ * Resolve a schema name into an Oid, ensure that the user has usage privs on
+ * that schema.
+ */
+Oid
+stats_schema_check_privileges(const char *nspname)
+{
+ Oid nspoid;
+ AclResult aclresult;
+
+ nspoid = get_namespace_oid(nspname, true);
+
+ if (nspoid == InvalidOid)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_SCHEMA_NAME),
+ errmsg("schema %s does not exist", nspname)));
+ return InvalidOid;
+ }
+
+ aclresult = object_aclcheck(NamespaceRelationId, nspoid, GetUserId(), ACL_USAGE);
+
+ if (aclresult != ACLCHECK_OK)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied for schema %s", nspname)));
+ return InvalidOid;
+ }
+
+ return nspoid;
+}
+
+
/*
* Find the argument number for the given argument name, returning -1 if not
* found.
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c371570501a..bd857bb076c 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -10490,7 +10490,6 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
PQExpBuffer out;
DumpId *deps = NULL;
int ndeps = 0;
- char *qualified_name;
int i_attname;
int i_inherited;
int i_null_frac;
@@ -10555,15 +10554,16 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
out = createPQExpBuffer();
- qualified_name = pg_strdup(fmtQualifiedDumpable(rsinfo));
-
/* restore relation stats */
appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
fout->remoteVersion);
- appendPQExpBufferStr(out, "\t'relation', ");
- appendStringLiteralAH(out, qualified_name, fout);
- appendPQExpBufferStr(out, "::regclass,\n");
+ appendPQExpBufferStr(out, "\t'schemaname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+ appendPQExpBufferStr(out, ",\n");
+ appendPQExpBufferStr(out, "\t'relname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+ appendPQExpBufferStr(out, ",\n");
appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer\n);\n",
@@ -10602,9 +10602,10 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
fout->remoteVersion);
- appendPQExpBufferStr(out, "\t'relation', ");
- appendStringLiteralAH(out, qualified_name, fout);
- appendPQExpBufferStr(out, "::regclass");
+ appendPQExpBufferStr(out, "\t'schemaname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+ appendPQExpBufferStr(out, ",\n\t'relname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.name, fout);
if (PQgetisnull(res, rownum, i_attname))
pg_fatal("attname cannot be NULL");
@@ -10616,7 +10617,10 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
* their attnames are not necessarily stable across dump/reload.
*/
if (rsinfo->nindAttNames == 0)
- appendNamedArgument(out, fout, "attname", "name", attname);
+ {
+ appendPQExpBuffer(out, ",\n\t'attname', ");
+ appendStringLiteralAH(out, attname, fout);
+ }
else
{
bool found = false;
@@ -10696,7 +10700,6 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
.deps = deps,
.nDeps = ndeps));
- free(qualified_name);
destroyPQExpBuffer(out);
destroyPQExpBuffer(query);
}
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c7bffc1b045..b037f239136 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -4725,14 +4725,16 @@ my %tests = (
regexp => qr/^
\QSELECT * FROM pg_catalog.pg_restore_relation_stats(\E\s+
'version',\s'\d+'::integer,\s+
- 'relation',\s'dump_test.dup_test_post_data_ix'::regclass,\s+
+ 'schemaname',\s'dump_test',\s+
+ 'relname',\s'dup_test_post_data_ix',\s+
'relpages',\s'\d+'::integer,\s+
'reltuples',\s'\d+'::real,\s+
'relallvisible',\s'\d+'::integer\s+
\);\s+
\QSELECT * FROM pg_catalog.pg_restore_attribute_stats(\E\s+
'version',\s'\d+'::integer,\s+
- 'relation',\s'dump_test.dup_test_post_data_ix'::regclass,\s+
+ 'schemaname',\s'dump_test',\s+
+ 'relname',\s'dup_test_post_data_ix',\s+
'attnum',\s'2'::smallint,\s+
'inherited',\s'f'::boolean,\s+
'null_frac',\s'0'::real,\s+
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 1f46d5e7854..2f1295f2149 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -14,7 +14,8 @@ CREATE TABLE stats_import.test(
) WITH (autovacuum_enabled = false);
SELECT
pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', 18::integer,
'reltuples', 21::real,
'relallvisible', 24::integer,
@@ -36,7 +37,7 @@ ORDER BY relname;
test | 18 | 21 | 24 | 27
(1 row)
-SELECT pg_clear_relation_stats('stats_import.test'::regclass);
+SELECT pg_clear_relation_stats('stats_import', 'test');
pg_clear_relation_stats
-------------------------
@@ -45,33 +46,54 @@ SELECT pg_clear_relation_stats('stats_import.test'::regclass);
--
-- relstats tests
--
---- error: relation is wrong type
+-- error: schemaname missing
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid,
+ 'relname', 'test',
'relpages', 17::integer);
-WARNING: argument "relation" has type "oid", expected type "regclass"
-ERROR: "relation" cannot be NULL
+ERROR: "schemaname" cannot be NULL
+-- error: relname missing
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 'stats_import',
+ 'relpages', 17::integer);
+ERROR: "relname" cannot be NULL
+--- error: schemaname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 3.6::float,
+ 'relname', 'test',
+ 'relpages', 17::integer);
+WARNING: argument "schemaname" has type "double precision", expected type "text"
+ERROR: "schemaname" cannot be NULL
+--- error: relname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 0::oid,
+ 'relpages', 17::integer);
+WARNING: argument "relname" has type "oid", expected type "text"
+ERROR: "relname" cannot be NULL
-- error: relation not found
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'nope',
'relpages', 17::integer);
-ERROR: could not open relation with OID 0
+WARNING: Relation "stats_import"."nope" not found.
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
-- error: odd number of variadic arguments cannot be pairs
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relallvisible');
ERROR: variadic arguments must be name/value pairs
HINT: Provide an even number of variadic arguments that can be divided into pairs.
-- error: argument name is NULL
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
NULL, '17'::integer);
-ERROR: name at variadic position 3 is NULL
--- error: argument name is not a text type
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 17, '17'::integer);
-ERROR: name at variadic position 3 has type "integer", expected type "text"
+ERROR: name at variadic position 5 is NULL
-- starting stats
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
@@ -84,7 +106,8 @@ WHERE oid = 'stats_import.test_i'::regclass;
-- regular indexes have special case locking rules
BEGIN;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.test_i'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test_i',
'relpages', 18::integer);
pg_restore_relation_stats
---------------------------
@@ -132,7 +155,8 @@ WHERE oid = 'stats_import.part_parent'::regclass;
--
BEGIN;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent_i'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'part_parent_i',
'relpages', 2::integer);
pg_restore_relation_stats
---------------------------
@@ -166,7 +190,8 @@ WHERE oid = 'stats_import.part_parent_i'::regclass;
-- ok: set all relstats, with version, no bounds checking
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'version', 150000::integer,
'relpages', '-17'::integer,
'reltuples', 400::real,
@@ -187,7 +212,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just relpages, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', '16'::integer);
pg_restore_relation_stats
---------------------------
@@ -204,7 +230,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just reltuples, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'reltuples', '500'::real);
pg_restore_relation_stats
---------------------------
@@ -221,7 +248,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just relallvisible, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relallvisible', 5::integer);
pg_restore_relation_stats
---------------------------
@@ -238,7 +266,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: just relallfrozen
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'version', 150000::integer,
'relallfrozen', 3::integer);
pg_restore_relation_stats
@@ -256,7 +285,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- warn: bad relpages type, rest updated
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', 'nope'::text,
'reltuples', 400.0::real,
'relallvisible', 4::integer,
@@ -277,7 +307,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- unrecognized argument name, rest ok
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', '171'::integer,
'nope', 10::integer);
WARNING: unrecognized argument name: "nope"
@@ -295,8 +326,7 @@ WHERE oid = 'stats_import.test'::regclass;
(1 row)
-- ok: clear stats
-SELECT pg_catalog.pg_clear_relation_stats(
- relation => 'stats_import.test'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'test');
pg_clear_relation_stats
-------------------------
@@ -313,87 +343,123 @@ WHERE oid = 'stats_import.test'::regclass;
-- invalid relkinds for statistics
CREATE SEQUENCE stats_import.testseq;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.testseq'::regclass);
+ 'schemaname', 'stats_import',
+ 'relname', 'testseq');
ERROR: cannot modify statistics for relation "testseq"
DETAIL: This operation is not supported for sequences.
-SELECT pg_catalog.pg_clear_relation_stats(
- 'stats_import.testseq'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
ERROR: cannot modify statistics for relation "testseq"
DETAIL: This operation is not supported for sequences.
CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
-SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.testview'::regclass);
-ERROR: cannot modify statistics for relation "testview"
-DETAIL: This operation is not supported for views.
-SELECT pg_catalog.pg_clear_relation_stats(
- 'stats_import.testview'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
ERROR: cannot modify statistics for relation "testview"
DETAIL: This operation is not supported for views.
--
-- attribute stats
--
--- error: object does not exist
+-- error: schemaname missing
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', '0'::oid::regclass,
- 'attname', 'id'::name,
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: could not open relation with OID 0
--- error: relation null
+ERROR: "schemaname" cannot be NULL
+-- error: schema does not exist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', NULL::oid::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'nope',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: "relation" cannot be NULL
+WARNING: schema nope does not exist
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- error: relname missing
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: "relname" cannot be NULL
+-- error: relname does not exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'nope',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+WARNING: Relation "stats_import"."nope" not found.
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- error: relname null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'relname', NULL,
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: "relname" cannot be NULL
-- error: NULL attname
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', NULL::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', NULL,
'inherited', false::boolean,
'null_frac', 0.1::real);
ERROR: must specify either attname or attnum
-- error: attname doesn't exist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'nope'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'nope',
'inherited', false::boolean,
'null_frac', 0.1::real,
'avg_width', 2::integer,
'n_distinct', 0.3::real);
-ERROR: column "nope" of relation "test" does not exist
+ERROR: column "nope" of relation "stats_import"."test" does not exist
-- error: both attname and attnum
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.1::real);
ERROR: cannot specify both attname and attnum
-- error: neither attname nor attnum
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'inherited', false::boolean,
'null_frac', 0.1::real);
ERROR: must specify either attname or attnum
-- error: attribute is system column
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'xmin'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'xmin',
'inherited', false::boolean,
'null_frac', 0.1::real);
ERROR: cannot modify statistics on system column "xmin"
-- error: inherited null
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', NULL::boolean,
'null_frac', 0.1::real);
ERROR: "inherited" cannot be NULL
-- ok: just the fixed values, with version, no stakinds
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'version', 150000::integer,
'null_frac', 0.2::real,
@@ -421,7 +487,8 @@ AND attname = 'id';
-- for any stat-having relation.
--
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.4::real);
@@ -443,8 +510,9 @@ AND attname = 'id';
-- warn: unrecognized argument name, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.2::real,
'nope', 0.5::real);
@@ -467,8 +535,9 @@ AND attname = 'id';
-- warn: mcv / mcf null mismatch part 1, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.21::real,
'most_common_freqs', '{0.1,0.2,0.3}'::real[]
@@ -492,8 +561,9 @@ AND attname = 'id';
-- warn: mcv / mcf null mismatch part 2, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.21::real,
'most_common_vals', '{1,2,3}'::text
@@ -517,8 +587,9 @@ AND attname = 'id';
-- warn: mcf type mismatch, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.22::real,
'most_common_vals', '{2,1,3}'::text,
@@ -544,8 +615,9 @@ AND attname = 'id';
-- warn: mcv cast failure, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.23::real,
'most_common_vals', '{2,four,3}'::text,
@@ -570,8 +642,9 @@ AND attname = 'id';
-- ok: mcv+mcf
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
@@ -594,8 +667,9 @@ AND attname = 'id';
-- warn: NULL in histogram array, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.24::real,
'histogram_bounds', '{1,NULL,3,4}'::text
@@ -619,8 +693,9 @@ AND attname = 'id';
-- ok: histogram_bounds
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'histogram_bounds', '{1,2,3,4}'::text
);
@@ -642,8 +717,9 @@ AND attname = 'id';
-- warn: elem_count_histogram null element, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.25::real,
'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[]
@@ -667,8 +743,9 @@ AND attname = 'tags';
-- ok: elem_count_histogram
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.26::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -691,8 +768,9 @@ AND attname = 'tags';
-- warn: range stats on a scalar type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.27::real,
'range_empty_frac', 0.5::real,
@@ -718,8 +796,9 @@ AND attname = 'id';
-- warn: range_empty_frac range_length_hist null mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.28::real,
'range_length_histogram', '{399,499,Infinity}'::text
@@ -743,8 +822,9 @@ AND attname = 'arange';
-- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.29::real,
'range_empty_frac', 0.5::real
@@ -768,8 +848,9 @@ AND attname = 'arange';
-- ok: range_empty_frac + range_length_hist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'range_empty_frac', 0.5::real,
'range_length_histogram', '{399,499,Infinity}'::text
@@ -792,8 +873,9 @@ AND attname = 'arange';
-- warn: range bounds histogram on scalar, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.31::real,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
@@ -818,8 +900,9 @@ AND attname = 'id';
-- ok: range_bounds_histogram
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
);
@@ -841,8 +924,9 @@ AND attname = 'arange';
-- warn: cannot set most_common_elems for range type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.32::real,
'most_common_elems', '{3,1}'::text,
@@ -868,8 +952,9 @@ AND attname = 'arange';
-- warn: scalars can't have mcelem, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.33::real,
'most_common_elems', '{1,3}'::text,
@@ -895,8 +980,9 @@ AND attname = 'id';
-- warn: mcelem / mcelem mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.34::real,
'most_common_elems', '{one,two}'::text
@@ -920,8 +1006,9 @@ AND attname = 'tags';
-- warn: mcelem / mcelem null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.35::real,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
@@ -945,8 +1032,9 @@ AND attname = 'tags';
-- ok: mcelem
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'most_common_elems', '{one,three}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
@@ -969,8 +1057,9 @@ AND attname = 'tags';
-- warn: scalars can't have elem_count_histogram, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.36::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -1022,8 +1111,9 @@ SELECT s.schemaname, s.tablename, s.attname, s.inherited, r.*
FROM pg_catalog.pg_stats AS s
CROSS JOIN LATERAL
pg_catalog.pg_restore_attribute_stats(
- 'relation', ('stats_import.' || s.tablename || '_clone')::regclass,
- 'attname', s.attname,
+ 'schemaname', 'stats_import',
+ 'relname', s.tablename::text || '_clone',
+ 'attname', s.attname::text,
'inherited', s.inherited,
'version', 150000,
'null_frac', s.null_frac,
@@ -1200,9 +1290,10 @@ AND attname = 'arange';
(1 row)
SELECT pg_catalog.pg_clear_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean);
+ schemaname => 'stats_import',
+ relname => 'test',
+ attname => 'arange',
+ inherited => false);
pg_clear_attribute_stats
--------------------------
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index 0ec590688c2..ccdc44e9236 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -17,7 +17,8 @@ CREATE TABLE stats_import.test(
SELECT
pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', 18::integer,
'reltuples', 21::real,
'relallvisible', 24::integer,
@@ -32,37 +33,52 @@ FROM pg_class
WHERE oid = 'stats_import.test'::regclass
ORDER BY relname;
-SELECT pg_clear_relation_stats('stats_import.test'::regclass);
+SELECT pg_clear_relation_stats('stats_import', 'test');
--
-- relstats tests
--
---- error: relation is wrong type
+-- error: schemaname missing
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid,
+ 'relname', 'test',
+ 'relpages', 17::integer);
+
+-- error: relname missing
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 'stats_import',
+ 'relpages', 17::integer);
+
+--- error: schemaname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 3.6::float,
+ 'relname', 'test',
+ 'relpages', 17::integer);
+
+--- error: relname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 0::oid,
'relpages', 17::integer);
-- error: relation not found
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'nope',
'relpages', 17::integer);
-- error: odd number of variadic arguments cannot be pairs
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relallvisible');
-- error: argument name is NULL
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
NULL, '17'::integer);
--- error: argument name is not a text type
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 17, '17'::integer);
-
-- starting stats
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
@@ -71,7 +87,8 @@ WHERE oid = 'stats_import.test_i'::regclass;
-- regular indexes have special case locking rules
BEGIN;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.test_i'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test_i',
'relpages', 18::integer);
SELECT mode FROM pg_locks
@@ -108,7 +125,8 @@ WHERE oid = 'stats_import.part_parent'::regclass;
BEGIN;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent_i'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'part_parent_i',
'relpages', 2::integer);
SELECT mode FROM pg_locks
@@ -127,7 +145,8 @@ WHERE oid = 'stats_import.part_parent_i'::regclass;
-- ok: set all relstats, with version, no bounds checking
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'version', 150000::integer,
'relpages', '-17'::integer,
'reltuples', 400::real,
@@ -140,7 +159,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just relpages, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', '16'::integer);
SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -149,7 +169,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just reltuples, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'reltuples', '500'::real);
SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -158,7 +179,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just relallvisible, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relallvisible', 5::integer);
SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -167,7 +189,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: just relallfrozen
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'version', 150000::integer,
'relallfrozen', 3::integer);
@@ -177,7 +200,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- warn: bad relpages type, rest updated
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', 'nope'::text,
'reltuples', 400.0::real,
'relallvisible', 4::integer,
@@ -189,7 +213,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- unrecognized argument name, rest ok
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', '171'::integer,
'nope', 10::integer);
@@ -198,8 +223,7 @@ FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
-- ok: clear stats
-SELECT pg_catalog.pg_clear_relation_stats(
- relation => 'stats_import.test'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'test');
SELECT relpages, reltuples, relallvisible
FROM pg_class
@@ -209,48 +233,70 @@ WHERE oid = 'stats_import.test'::regclass;
CREATE SEQUENCE stats_import.testseq;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.testseq'::regclass);
+ 'schemaname', 'stats_import',
+ 'relname', 'testseq');
-SELECT pg_catalog.pg_clear_relation_stats(
- 'stats_import.testseq'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
-SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.testview'::regclass);
-
-SELECT pg_catalog.pg_clear_relation_stats(
- 'stats_import.testview'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
--
-- attribute stats
--
--- error: object does not exist
+-- error: schemaname missing
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', '0'::oid::regclass,
- 'attname', 'id'::name,
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: relation null
+-- error: schema does not exist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', NULL::oid::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'nope',
+ 'relname', 'test',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: relname missing
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: relname does not exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'nope',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: relname null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'relname', NULL,
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: NULL attname
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', NULL::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', NULL,
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: attname doesn't exist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'nope'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'nope',
'inherited', false::boolean,
'null_frac', 0.1::real,
'avg_width', 2::integer,
@@ -258,36 +304,41 @@ SELECT pg_catalog.pg_restore_attribute_stats(
-- error: both attname and attnum
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: neither attname nor attnum
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: attribute is system column
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'xmin'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'xmin',
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: inherited null
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', NULL::boolean,
'null_frac', 0.1::real);
-- ok: just the fixed values, with version, no stakinds
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'version', 150000::integer,
'null_frac', 0.2::real,
@@ -307,7 +358,8 @@ AND attname = 'id';
-- for any stat-having relation.
--
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.4::real);
@@ -321,8 +373,9 @@ AND attname = 'id';
-- warn: unrecognized argument name, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.2::real,
'nope', 0.5::real);
@@ -336,8 +389,9 @@ AND attname = 'id';
-- warn: mcv / mcf null mismatch part 1, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.21::real,
'most_common_freqs', '{0.1,0.2,0.3}'::real[]
@@ -352,8 +406,9 @@ AND attname = 'id';
-- warn: mcv / mcf null mismatch part 2, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.21::real,
'most_common_vals', '{1,2,3}'::text
@@ -368,8 +423,9 @@ AND attname = 'id';
-- warn: mcf type mismatch, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.22::real,
'most_common_vals', '{2,1,3}'::text,
@@ -385,8 +441,9 @@ AND attname = 'id';
-- warn: mcv cast failure, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.23::real,
'most_common_vals', '{2,four,3}'::text,
@@ -402,8 +459,9 @@ AND attname = 'id';
-- ok: mcv+mcf
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
@@ -418,8 +476,9 @@ AND attname = 'id';
-- warn: NULL in histogram array, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.24::real,
'histogram_bounds', '{1,NULL,3,4}'::text
@@ -434,8 +493,9 @@ AND attname = 'id';
-- ok: histogram_bounds
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'histogram_bounds', '{1,2,3,4}'::text
);
@@ -449,8 +509,9 @@ AND attname = 'id';
-- warn: elem_count_histogram null element, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.25::real,
'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[]
@@ -465,8 +526,9 @@ AND attname = 'tags';
-- ok: elem_count_histogram
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.26::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -481,8 +543,9 @@ AND attname = 'tags';
-- warn: range stats on a scalar type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.27::real,
'range_empty_frac', 0.5::real,
@@ -498,8 +561,9 @@ AND attname = 'id';
-- warn: range_empty_frac range_length_hist null mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.28::real,
'range_length_histogram', '{399,499,Infinity}'::text
@@ -514,8 +578,9 @@ AND attname = 'arange';
-- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.29::real,
'range_empty_frac', 0.5::real
@@ -530,8 +595,9 @@ AND attname = 'arange';
-- ok: range_empty_frac + range_length_hist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'range_empty_frac', 0.5::real,
'range_length_histogram', '{399,499,Infinity}'::text
@@ -546,8 +612,9 @@ AND attname = 'arange';
-- warn: range bounds histogram on scalar, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.31::real,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
@@ -562,8 +629,9 @@ AND attname = 'id';
-- ok: range_bounds_histogram
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
);
@@ -577,8 +645,9 @@ AND attname = 'arange';
-- warn: cannot set most_common_elems for range type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.32::real,
'most_common_elems', '{3,1}'::text,
@@ -594,8 +663,9 @@ AND attname = 'arange';
-- warn: scalars can't have mcelem, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.33::real,
'most_common_elems', '{1,3}'::text,
@@ -611,8 +681,9 @@ AND attname = 'id';
-- warn: mcelem / mcelem mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.34::real,
'most_common_elems', '{one,two}'::text
@@ -627,8 +698,9 @@ AND attname = 'tags';
-- warn: mcelem / mcelem null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.35::real,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
@@ -643,8 +715,9 @@ AND attname = 'tags';
-- ok: mcelem
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'most_common_elems', '{one,three}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
@@ -659,8 +732,9 @@ AND attname = 'tags';
-- warn: scalars can't have elem_count_histogram, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.36::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -707,8 +781,9 @@ SELECT s.schemaname, s.tablename, s.attname, s.inherited, r.*
FROM pg_catalog.pg_stats AS s
CROSS JOIN LATERAL
pg_catalog.pg_restore_attribute_stats(
- 'relation', ('stats_import.' || s.tablename || '_clone')::regclass,
- 'attname', s.attname,
+ 'schemaname', 'stats_import',
+ 'relname', s.tablename::text || '_clone',
+ 'attname', s.attname::text,
'inherited', s.inherited,
'version', 150000,
'null_frac', s.null_frac,
@@ -853,9 +928,10 @@ AND inherited = false
AND attname = 'arange';
SELECT pg_catalog.pg_clear_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean);
+ schemaname => 'stats_import',
+ relname => 'test',
+ attname => 'arange',
+ inherited => false);
SELECT COUNT(*)
FROM pg_stats
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 1c3810e1a04..a75e95bc5fd 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -30365,22 +30365,24 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<structname>mytable</structname>:
<programlisting>
SELECT pg_restore_relation_stats(
- 'relation', 'mytable'::regclass,
- 'relpages', 173::integer,
- 'reltuples', 10000::real);
+ 'schemaname', 'myschema',
+ 'relname', 'mytable',
+ 'relpages', 173::integer,
+ 'reltuples', 10000::real);
</programlisting>
</para>
<para>
- The argument <literal>relation</literal> with a value of type
- <type>regclass</type> is required, and specifies the table. Other
- arguments are the names and values of statistics corresponding to
- certain columns in <link
+ The arguments <literal>schemaname</literal> with a value of type
+ <type>regclass</type> and <literal>relname</literal> are required,
+ and specifies the table. Other arguments are the names and values
+ of statistics corresponding to certain columns in <link
linkend="catalog-pg-class"><structname>pg_class</structname></link>.
The currently-supported relation statistics are
<literal>relpages</literal> with a value of type
<type>integer</type>, <literal>reltuples</literal> with a value of
- type <type>real</type>, and <literal>relallvisible</literal> with a
- value of type <type>integer</type>.
+ type <type>real</type>, <literal>relallvisible</literal> with a
+ value of type <type>integer</type>, and <literal>relallfrozen</literal>
+ with a value of type <type>integer</type>.
</para>
<para>
Additionally, this function accepts argument name
@@ -30408,7 +30410,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<indexterm>
<primary>pg_clear_relation_stats</primary>
</indexterm>
- <function>pg_clear_relation_stats</function> ( <parameter>relation</parameter> <type>regclass</type> )
+ <function>pg_clear_relation_stats</function> ( <parameter>schemaname</parameter> <type>text</type>, <parameter>relname</parameter> <type>text</type> )
<returnvalue>void</returnvalue>
</para>
<para>
@@ -30457,16 +30459,18 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<structname>mytable</structname>:
<programlisting>
SELECT pg_restore_attribute_stats(
- 'relation', 'mytable'::regclass,
- 'attname', 'col1'::name,
- 'inherited', false,
- 'avg_width', 125::integer,
- 'null_frac', 0.5::real);
+ 'schemaname', 'myschema',
+ 'relname', 'mytable',
+ 'attname', 'col1',
+ 'inherited', false,
+ 'avg_width', 125::integer,
+ 'null_frac', 0.5::real);
</programlisting>
</para>
<para>
- The required arguments are <literal>relation</literal> with a value
- of type <type>regclass</type>, which specifies the table; either
+ The required arguments are <literal>schemaname</literal> with a value
+ of type <type>regclass</type> and <literal>relname</literal> with a value
+ of type <type>text</type> which specify the table; either
<literal>attname</literal> with a value of type <type>name</type> or
<literal>attnum</literal> with a value of type <type>smallint</type>,
which specifies the column; and <literal>inherited</literal>, which
@@ -30502,7 +30506,8 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<primary>pg_clear_attribute_stats</primary>
</indexterm>
<function>pg_clear_attribute_stats</function> (
- <parameter>relation</parameter> <type>regclass</type>,
+ <parameter>schemaname</parameter> <type>text</type>,
+ <parameter>relname</parameter> <type>text</type>,
<parameter>attname</parameter> <type>name</type>,
<parameter>inherited</parameter> <type>boolean</type> )
<returnvalue>void</returnvalue>
base-commit: 5eabd91a83adae75f53b61857343660919fef4c7
--
2.48.1
[text/x-patch] v9-0002-Downgrade-as-man-pg_restore_-_stats-errors-to-war.patch (29.8K, ../../CADkLM=ceOSsx_=oe73QQ-BxUFR2Cwqum7-UP_fPe22DBY0NerA@mail.gmail.com/6-v9-0002-Downgrade-as-man-pg_restore_-_stats-errors-to-war.patch)
download | inline diff:
From b5b96d4a2fe4119ef26689cc8f3e1a5b8d24bdda Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sat, 8 Mar 2025 00:52:41 -0500
Subject: [PATCH v9 2/5] Downgrade as man pg_restore_*_stats errors to
warnings.
We want to avoid errors that can potentially stop an otherwise
successful pg_upgrade or pg_restore operation. With that in mind, change
as many ERROR reports to WARNING + early termination with no data
updated.
---
src/include/statistics/stat_utils.h | 4 +-
src/backend/statistics/attribute_stats.c | 124 +++++++++++-----
src/backend/statistics/relation_stats.c | 10 +-
src/backend/statistics/stat_utils.c | 51 +++++--
src/test/regress/expected/stats_import.out | 163 ++++++++++++++++-----
src/test/regress/sql/stats_import.sql | 36 ++---
6 files changed, 277 insertions(+), 111 deletions(-)
diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h
index cad042c8e4a..298cbae3436 100644
--- a/src/include/statistics/stat_utils.h
+++ b/src/include/statistics/stat_utils.h
@@ -21,7 +21,7 @@ struct StatsArgInfo
Oid argtype;
};
-extern void stats_check_required_arg(FunctionCallInfo fcinfo,
+extern bool stats_check_required_arg(FunctionCallInfo fcinfo,
struct StatsArgInfo *arginfo,
int argnum);
extern bool stats_check_arg_array(FunctionCallInfo fcinfo,
@@ -30,7 +30,7 @@ extern bool stats_check_arg_pair(FunctionCallInfo fcinfo,
struct StatsArgInfo *arginfo,
int argnum1, int argnum2);
-extern void stats_lock_check_privileges(Oid reloid);
+extern bool stats_lock_check_privileges(Oid reloid);
extern Oid stats_schema_check_privileges(const char *nspname);
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index f87db2d6102..4f9bc18f8c6 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -100,7 +100,7 @@ static struct StatsArgInfo cleararginfo[] =
static bool attribute_statistics_update(FunctionCallInfo fcinfo);
static Node *get_attr_expr(Relation rel, int attnum);
-static void get_attr_stat_type(Oid reloid, AttrNumber attnum,
+static bool get_attr_stat_type(Oid reloid, AttrNumber attnum,
Oid *atttypid, int32 *atttypmod,
char *atttyptype, Oid *atttypcoll,
Oid *eq_opr, Oid *lt_opr);
@@ -129,10 +129,12 @@ static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
* stored as an anyarray, and the representation of the array needs to store
* the correct element type, which must be derived from the attribute.
*
- * Major errors, such as the table not existing, the attribute not existing,
- * or a permissions failure are always reported at ERROR. Other errors, such
- * as a conversion failure on one statistic kind, are reported as a WARNING
- * and other statistic kinds may still be updated.
+ * This function is called during database upgrades and restorations, therefore
+ * it is imperative to avoid ERRORs that could potentially end the upgrade or
+ * restore unless. Major errors, such as the table not existing, the attribute
+ * not existing, or permissions failure are reported as WARNINGs with an end to
+ * the function, thus allowing the upgrade/restore to continue, but without the
+ * stats that can be regenereated once the database is online again.
*/
static bool
attribute_statistics_update(FunctionCallInfo fcinfo)
@@ -149,8 +151,8 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
HeapTuple statup;
Oid atttypid = InvalidOid;
- int32 atttypmod;
- char atttyptype;
+ int32 atttypmod = -1;
+ char atttyptype = TYPTYPE_PSEUDO; /* Not a great default, but there is no TYPTYPE_INVALID */
Oid atttypcoll = InvalidOid;
Oid eq_opr = InvalidOid;
Oid lt_opr = InvalidOid;
@@ -177,17 +179,19 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
bool result = true;
- stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG);
- stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG);
+ if (!stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG))
+ return false;
+ if (!stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG))
+ return false;
nspname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELSCHEMA_ARG));
nspoid = stats_schema_check_privileges(nspname);
- if (nspoid == InvalidOid)
+ if (!OidIsValid(nspoid))
return false;
relname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELNAME_ARG));
reloid = get_relname_relid(relname, nspoid);
- if (reloid == InvalidOid)
+ if (!OidIsValid(reloid))
{
ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_OBJECT),
@@ -196,29 +200,39 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
}
if (RecoveryInProgress())
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("recovery is in progress"),
errhint("Statistics cannot be modified during recovery.")));
+ return false;
+ }
/* lock before looking up attribute */
- stats_lock_check_privileges(reloid);
+ if (!stats_lock_check_privileges(reloid))
+ return false;
/* user can specify either attname or attnum, but not both */
if (!PG_ARGISNULL(ATTNAME_ARG))
{
if (!PG_ARGISNULL(ATTNUM_ARG))
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("cannot specify both attname and attnum")));
+ return false;
+ }
attname = TextDatumGetCString(PG_GETARG_DATUM(ATTNAME_ARG));
attnum = get_attnum(reloid, attname);
/* note that this test covers attisdropped cases too: */
if (attnum == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\".\"%s\" does not exist",
attname, nspname, relname)));
+ return false;
+ }
}
else if (!PG_ARGISNULL(ATTNUM_ARG))
{
@@ -227,27 +241,33 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
/* annoyingly, get_attname doesn't check attisdropped */
if (attname == NULL ||
!SearchSysCacheExistsAttName(reloid, attname))
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column %d of relation \"%s\".\"%s\" does not exist",
attnum, nspname, relname)));
+ return false;
+ }
}
else
{
- ereport(ERROR,
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("must specify either attname or attnum")));
- attname = NULL; /* keep compiler quiet */
- attnum = 0;
+ return false;
}
if (attnum < 0)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot modify statistics on system column \"%s\"",
attname)));
+ return false;
+ }
- stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG);
+ if (!stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG))
+ return false;
inherited = PG_GETARG_BOOL(INHERITED_ARG);
/*
@@ -296,10 +316,11 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
}
/* derive information from attribute */
- get_attr_stat_type(reloid, attnum,
- &atttypid, &atttypmod,
- &atttyptype, &atttypcoll,
- &eq_opr, <_opr);
+ if (!get_attr_stat_type(reloid, attnum,
+ &atttypid, &atttypmod,
+ &atttyptype, &atttypcoll,
+ &eq_opr, <_opr))
+ result = false;
/* if needed, derive element type */
if (do_mcelem || do_dechist)
@@ -579,7 +600,7 @@ get_attr_expr(Relation rel, int attnum)
/*
* Derive type information from the attribute.
*/
-static void
+static bool
get_attr_stat_type(Oid reloid, AttrNumber attnum,
Oid *atttypid, int32 *atttypmod,
char *atttyptype, Oid *atttypcoll,
@@ -596,18 +617,26 @@ get_attr_stat_type(Oid reloid, AttrNumber attnum,
/* Attribute not found */
if (!HeapTupleIsValid(atup))
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("attribute %d of relation \"%s\" does not exist",
attnum, RelationGetRelationName(rel))));
+ relation_close(rel, NoLock);
+ return false;
+ }
attr = (Form_pg_attribute) GETSTRUCT(atup);
if (attr->attisdropped)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("attribute %d of relation \"%s\" does not exist",
attnum, RelationGetRelationName(rel))));
+ relation_close(rel, NoLock);
+ return false;
+ }
expr = get_attr_expr(rel, attr->attnum);
@@ -656,6 +685,7 @@ get_attr_stat_type(Oid reloid, AttrNumber attnum,
*atttypcoll = DEFAULT_COLLATION_OID;
relation_close(rel, NoLock);
+ return true;
}
/*
@@ -781,6 +811,10 @@ set_stats_slot(Datum *values, bool *nulls, bool *replaces,
if (slotidx >= STATISTIC_NUM_SLOTS && first_empty >= 0)
slotidx = first_empty;
+ /*
+ * Currently there is no datatype that can have more than STATISTIC_NUM_SLOTS
+ * statistic kinds, so this can safely remain an ERROR for now.
+ */
if (slotidx >= STATISTIC_NUM_SLOTS)
ereport(ERROR,
(errmsg("maximum number of statistics slots exceeded: %d",
@@ -927,15 +961,19 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
AttrNumber attnum;
bool inherited;
- stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG);
- stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG);
- stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
- stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
+ if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG))
+ PG_RETURN_VOID();
+ if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG))
+ PG_RETURN_VOID();
+ if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG))
+ PG_RETURN_VOID();
+ if (!stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG))
+ PG_RETURN_VOID();
nspname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELSCHEMA_ARG));
nspoid = stats_schema_check_privileges(nspname);
if (!OidIsValid(nspoid))
- return false;
+ PG_RETURN_VOID();
relname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELNAME_ARG));
reloid = get_relname_relid(relname, nspoid);
@@ -944,31 +982,41 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
- return false;
+ PG_RETURN_VOID();
}
if (RecoveryInProgress())
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("recovery is in progress"),
errhint("Statistics cannot be modified during recovery.")));
+ PG_RETURN_VOID();
+ }
- stats_lock_check_privileges(reloid);
+ if (!stats_lock_check_privileges(reloid))
+ PG_RETURN_VOID();
attname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTNAME_ARG));
attnum = get_attnum(reloid, attname);
if (attnum < 0)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot clear statistics on system column \"%s\"",
attname)));
+ PG_RETURN_VOID();
+ }
if (attnum == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
attname, get_rel_name(reloid))));
+ PG_RETURN_VOID();
+ }
inherited = PG_GETARG_BOOL(C_INHERITED_ARG);
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index fdc69bc93e2..49109cf721d 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -84,8 +84,11 @@ relation_statistics_update(FunctionCallInfo fcinfo)
bool nulls[4] = {0};
int nreplaces = 0;
- stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG);
- stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG);
+ if (!stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG))
+ return false;
+
+ if (!stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG))
+ return false;
nspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
nspoid = stats_schema_check_privileges(nspname);
@@ -108,7 +111,8 @@ relation_statistics_update(FunctionCallInfo fcinfo)
errmsg("recovery is in progress"),
errhint("Statistics cannot be modified during recovery.")));
- stats_lock_check_privileges(reloid);
+ if (!stats_lock_check_privileges(reloid))
+ return false;
if (!PG_ARGISNULL(RELPAGES_ARG))
{
diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c
index e037d4994e8..dd9d88ac1c5 100644
--- a/src/backend/statistics/stat_utils.c
+++ b/src/backend/statistics/stat_utils.c
@@ -34,16 +34,20 @@
/*
* Ensure that a given argument is not null.
*/
-void
+bool
stats_check_required_arg(FunctionCallInfo fcinfo,
struct StatsArgInfo *arginfo,
int argnum)
{
if (PG_ARGISNULL(argnum))
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("\"%s\" cannot be NULL",
arginfo[argnum].argname)));
+ return false;
+ }
+ return true;
}
/*
@@ -128,13 +132,14 @@ stats_check_arg_pair(FunctionCallInfo fcinfo,
* - the role owns the current database and the relation is not shared
* - the role has the MAINTAIN privilege on the relation
*/
-void
+bool
stats_lock_check_privileges(Oid reloid)
{
Relation table;
Oid table_oid = reloid;
Oid index_oid = InvalidOid;
LOCKMODE index_lockmode = NoLock;
+ bool ok = true;
/*
* For indexes, we follow the locking behavior in do_analyze_rel() and
@@ -174,14 +179,15 @@ stats_lock_check_privileges(Oid reloid)
case RELKIND_PARTITIONED_TABLE:
break;
default:
- ereport(ERROR,
+ ereport(WARNING,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot modify statistics for relation \"%s\"",
RelationGetRelationName(table)),
errdetail_relkind_not_supported(table->rd_rel->relkind)));
+ ok = false;
}
- if (OidIsValid(index_oid))
+ if (ok && (OidIsValid(index_oid)))
{
Relation index;
@@ -194,25 +200,33 @@ stats_lock_check_privileges(Oid reloid)
relation_close(index, NoLock);
}
- if (table->rd_rel->relisshared)
- ereport(ERROR,
+ if (ok && (table->rd_rel->relisshared))
+ {
+ ereport(WARNING,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot modify statistics for shared relation")));
+ ok = false;
+ }
- if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()))
+ if (ok && (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId())))
{
AclResult aclresult = pg_class_aclcheck(RelationGetRelid(table),
GetUserId(),
ACL_MAINTAIN);
if (aclresult != ACLCHECK_OK)
- aclcheck_error(aclresult,
- get_relkind_objtype(table->rd_rel->relkind),
- NameStr(table->rd_rel->relname));
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied for relation %s",
+ NameStr(table->rd_rel->relname))));
+ ok = false;
+ }
}
/* retain lock on table */
relation_close(table, NoLock);
+ return ok;
}
@@ -318,9 +332,12 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
&args, &types, &argnulls);
if (nargs % 2 != 0)
- ereport(ERROR,
+ {
+ ereport(WARNING,
errmsg("variadic arguments must be name/value pairs"),
errhint("Provide an even number of variadic arguments that can be divided into pairs."));
+ return false;
+ }
/*
* For each argument name/value pair, find corresponding positional
@@ -333,14 +350,20 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
char *argname;
if (argnulls[i])
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errmsg("name at variadic position %d is NULL", i + 1)));
+ return false;
+ }
if (types[i] != TEXTOID)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errmsg("name at variadic position %d has type \"%s\", expected type \"%s\"",
i + 1, format_type_be(types[i]),
format_type_be(TEXTOID))));
+ return false;
+ }
if (argnulls[i + 1])
continue;
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 2f1295f2149..6551d6bf099 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -46,31 +46,51 @@ SELECT pg_clear_relation_stats('stats_import', 'test');
--
-- relstats tests
--
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'relname', 'test',
'relpages', 17::integer);
-ERROR: "schemaname" cannot be NULL
--- error: relname missing
+WARNING: "schemaname" cannot be NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+-- warning: relname missing, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relpages', 17::integer);
-ERROR: "relname" cannot be NULL
---- error: schemaname is wrong type
+WARNING: "relname" cannot be NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+--- warning: schemaname is wrong type, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 3.6::float,
'relname', 'test',
'relpages', 17::integer);
WARNING: argument "schemaname" has type "double precision", expected type "text"
-ERROR: "schemaname" cannot be NULL
---- error: relname is wrong type
+WARNING: "schemaname" cannot be NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+--- warning: relname is wrong type, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 0::oid,
'relpages', 17::integer);
WARNING: argument "relname" has type "oid", expected type "text"
-ERROR: "relname" cannot be NULL
--- error: relation not found
+WARNING: "relname" cannot be NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+-- warning: relation not found, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'nope',
@@ -81,19 +101,30 @@ WARNING: Relation "stats_import"."nope" not found.
f
(1 row)
--- error: odd number of variadic arguments cannot be pairs
+-- warning: odd number of variadic arguments cannot be pairs, nothing updated
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
'relallvisible');
-ERROR: variadic arguments must be name/value pairs
+WARNING: variadic arguments must be name/value pairs
HINT: Provide an even number of variadic arguments that can be divided into pairs.
--- error: argument name is NULL
+WARNING: "schemaname" cannot be NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+-- warning: argument name is NULL, nothing updated
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
NULL, '17'::integer);
-ERROR: name at variadic position 5 is NULL
+WARNING: name at variadic position 5 is NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
-- starting stats
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
@@ -345,26 +376,46 @@ CREATE SEQUENCE stats_import.testseq;
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'testseq');
-ERROR: cannot modify statistics for relation "testseq"
+WARNING: cannot modify statistics for relation "testseq"
DETAIL: This operation is not supported for sequences.
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
-ERROR: cannot modify statistics for relation "testseq"
+WARNING: cannot modify statistics for relation "testseq"
DETAIL: This operation is not supported for sequences.
+ pg_clear_relation_stats
+-------------------------
+
+(1 row)
+
CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
-ERROR: cannot modify statistics for relation "testview"
+WARNING: cannot modify statistics for relation "testview"
DETAIL: This operation is not supported for views.
+ pg_clear_relation_stats
+-------------------------
+
+(1 row)
+
--
-- attribute stats
--
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: "schemaname" cannot be NULL
--- error: schema does not exist
+WARNING: "schemaname" cannot be NULL
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: schema does not exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'nope',
'relname', 'test',
@@ -377,14 +428,19 @@ WARNING: schema nope does not exist
f
(1 row)
--- error: relname missing
+-- warning: relname missing, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: "relname" cannot be NULL
--- error: relname does not exist
+WARNING: "relname" cannot be NULL
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: relname does not exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'nope',
@@ -397,23 +453,33 @@ WARNING: Relation "stats_import"."nope" not found.
f
(1 row)
--- error: relname null
+-- warning: relname null, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', NULL,
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: "relname" cannot be NULL
--- error: NULL attname
+WARNING: "relname" cannot be NULL
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: NULL attname, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', NULL,
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: must specify either attname or attnum
--- error: attname doesn't exist
+WARNING: must specify either attname or attnum
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: attname doesn't exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -422,8 +488,13 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'null_frac', 0.1::real,
'avg_width', 2::integer,
'n_distinct', 0.3::real);
-ERROR: column "nope" of relation "stats_import"."test" does not exist
--- error: both attname and attnum
+WARNING: column "nope" of relation "stats_import"."test" does not exist
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: both attname and attnum, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -431,30 +502,50 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: cannot specify both attname and attnum
--- error: neither attname nor attnum
+WARNING: cannot specify both attname and attnum
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: neither attname nor attnum, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: must specify either attname or attnum
--- error: attribute is system column
+WARNING: must specify either attname or attnum
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: attribute is system column, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'xmin',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: cannot modify statistics on system column "xmin"
--- error: inherited null
+WARNING: cannot modify statistics on system column "xmin"
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: inherited null, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'id',
'inherited', NULL::boolean,
'null_frac', 0.1::real);
-ERROR: "inherited" cannot be NULL
+WARNING: "inherited" cannot be NULL
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
-- ok: just the fixed values, with version, no stakinds
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index ccdc44e9236..dbbebce1673 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -39,41 +39,41 @@ SELECT pg_clear_relation_stats('stats_import', 'test');
-- relstats tests
--
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'relname', 'test',
'relpages', 17::integer);
--- error: relname missing
+-- warning: relname missing, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relpages', 17::integer);
---- error: schemaname is wrong type
+--- warning: schemaname is wrong type, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 3.6::float,
'relname', 'test',
'relpages', 17::integer);
---- error: relname is wrong type
+--- warning: relname is wrong type, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 0::oid,
'relpages', 17::integer);
--- error: relation not found
+-- warning: relation not found, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'nope',
'relpages', 17::integer);
--- error: odd number of variadic arguments cannot be pairs
+-- warning: odd number of variadic arguments cannot be pairs, nothing updated
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
'relallvisible');
--- error: argument name is NULL
+-- warning: argument name is NULL, nothing updated
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -246,14 +246,14 @@ SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname
-- attribute stats
--
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: schema does not exist
+-- warning: schema does not exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'nope',
'relname', 'test',
@@ -261,14 +261,14 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: relname missing
+-- warning: relname missing, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: relname does not exist
+-- warning: relname does not exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'nope',
@@ -276,7 +276,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: relname null
+-- warning: relname null, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', NULL,
@@ -284,7 +284,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: NULL attname
+-- warning: NULL attname, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -292,7 +292,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: attname doesn't exist
+-- warning: attname doesn't exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -302,7 +302,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'avg_width', 2::integer,
'n_distinct', 0.3::real);
--- error: both attname and attnum
+-- warning: both attname and attnum, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -311,14 +311,14 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: neither attname nor attnum
+-- warning: neither attname nor attnum, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: attribute is system column
+-- warning: attribute is system column, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -326,7 +326,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: inherited null
+-- warning: inherited null, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
--
2.48.1
[text/x-patch] v9-0004-Batching-getAttributeStats.patch (21.6K, ../../CADkLM=ceOSsx_=oe73QQ-BxUFR2Cwqum7-UP_fPe22DBY0NerA@mail.gmail.com/7-v9-0004-Batching-getAttributeStats.patch)
download | inline diff:
From d9ab97b8ca00d03ab370de5b99546d01e0480bd5 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Fri, 14 Mar 2025 03:54:26 -0400
Subject: [PATCH v9 4/5] Batching getAttributeStats().
The prepared statement getAttributeStats() is fairly heavyweight and
could greatly increase pg_dump/pg_upgrade runtime. To alleviate this,
create a result set buffer of all of the attribute stats fetched for a
batch of 100 relations that could potentially have stats.
The query ensures that the order of results exactly matches the needs of
the code walking the TOC to print the stats calls.
---
src/bin/pg_dump/pg_dump.c | 556 ++++++++++++++++++++++++++------------
1 file changed, 385 insertions(+), 171 deletions(-)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 38ba6a90106..0c26dc7a1b4 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -143,6 +143,25 @@ typedef enum OidOptions
zeroAsNone = 4,
} OidOptions;
+typedef enum StatsBufferState
+{
+ STATSBUF_UNINITIALIZED = 0,
+ STATSBUF_ACTIVE,
+ STATSBUF_EXHAUSTED
+} StatsBufferState;
+
+typedef struct
+{
+ PGresult *res; /* results from most recent
+ * getAttributeStats() */
+ int idx; /* first un-consumed row of results */
+ TocEntry *te; /* next TOC entry to search for statsitics
+ * data */
+
+ StatsBufferState state; /* current state of the buffer */
+} AttributeStatsBuffer;
+
+
/* global decls */
static bool dosync = true; /* Issue fsync() to make dump durable on disk. */
@@ -209,6 +228,18 @@ static int nbinaryUpgradeClassOids = 0;
static SequenceItem *sequences = NULL;
static int nsequences = 0;
+static AttributeStatsBuffer attrstats =
+{
+ NULL, 0, NULL, STATSBUF_UNINITIALIZED
+};
+
+/*
+ * The maximum number of relations that should be fetched in any one
+ * getAttributeStats() call.
+ */
+
+#define MAX_ATTR_STATS_RELS 100
+
/*
* The default number of rows per INSERT when
* --inserts is specified without --rows-per-insert
@@ -222,6 +253,10 @@ static int nsequences = 0;
*/
#define MAX_BLOBS_PER_ARCHIVE_ENTRY 1000
+
+
+/* TODO: fmtId(const char *rawid) */
+
/*
* Macro for producing quoted, schema-qualified name of a dumpable object.
*/
@@ -399,6 +434,9 @@ static void setupDumpWorker(Archive *AH);
static TableInfo *getRootTableInfo(const TableInfo *tbinfo);
static bool forcePartitionRootLoad(const TableInfo *tbinfo);
static void read_dump_filters(const char *filename, DumpOptions *dopt);
+static void appendNamedArgument(PQExpBuffer out, Archive *fout,
+ const char *argname, const char *argtype,
+ const char *argval);
int
@@ -10477,7 +10515,286 @@ statisticsDumpSection(const RelStatsInfo *rsinfo)
}
/*
- * printDumpRelationStats --
+ * Fetch next batch of rows from getAttributeStats()
+ */
+static void
+fetchNextAttributeStats(Archive *fout)
+{
+ ArchiveHandle *AH = (ArchiveHandle *) fout;
+ PQExpBufferData schemas;
+ PQExpBufferData relations;
+ int numoids = 0;
+
+ Assert(AH != NULL);
+
+ /* free last result set, if any */
+ if (attrstats.state == STATSBUF_ACTIVE)
+ PQclear(attrstats.res);
+
+ /* If we have looped around to the start of the TOC, restart */
+ if (attrstats.te == AH->toc)
+ attrstats.te = AH->toc->next;
+
+ initPQExpBuffer(&schemas);
+ initPQExpBuffer(&relations);
+
+ /*
+ * Walk ahead looking for relstats entries that are active in this
+ * section, adding the names to the schemas and relations lists.
+ */
+ while ((attrstats.te != AH->toc) && (numoids < MAX_ATTR_STATS_RELS))
+ {
+ if (attrstats.te->reqs != 0 &&
+ strcmp(attrstats.te->desc, "STATISTICS DATA") == 0)
+ {
+ RelStatsInfo *rsinfo = (RelStatsInfo *) attrstats.te->createDumperArg;
+
+ Assert(rsinfo != NULL);
+
+ if (numoids > 0)
+ {
+ appendPQExpBufferStr(&schemas, ",");
+ appendPQExpBufferStr(&relations, ",");
+ }
+ appendPQExpBufferStr(&schemas, fmtId(rsinfo->dobj.namespace->dobj.name));
+ appendPQExpBufferStr(&relations, fmtId(rsinfo->dobj.name));
+ numoids++;
+ }
+
+ attrstats.te = attrstats.te->next;
+ }
+
+ if (numoids > 0)
+ {
+ PQExpBufferData query;
+
+ initPQExpBuffer(&query);
+ appendPQExpBuffer(&query,
+ "EXECUTE getAttributeStats('{%s}'::pg_catalog.text[],'{%s}'::pg_catalog.text[])",
+ schemas.data, relations.data);
+ attrstats.res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK);
+ attrstats.idx = 0;
+ }
+ else
+ {
+ attrstats.state = STATSBUF_EXHAUSTED;
+ attrstats.res = NULL;
+ attrstats.idx = -1;
+ }
+
+ termPQExpBuffer(&schemas);
+ termPQExpBuffer(&relations);
+}
+
+/*
+ * Prepare the getAttributeStats() statement
+ *
+ * This is done automatically if the user specified dumpStatistics.
+ */
+static void
+initAttributeStats(Archive *fout)
+{
+ ArchiveHandle *AH = (ArchiveHandle *) fout;
+ PQExpBufferData query;
+
+ Assert(AH != NULL);
+ initPQExpBuffer(&query);
+
+ appendPQExpBufferStr(&query,
+ "PREPARE getAttributeStats(pg_catalog.text[], pg_catalog.text[]) AS\n"
+ "SELECT s.schemaname, s.tablename, s.attname, s.inherited, "
+ "s.null_frac, s.avg_width, s.n_distinct, s.most_common_vals, "
+ "s.most_common_freqs, s.histogram_bounds, s.correlation, "
+ "s.most_common_elems, s.most_common_elem_freqs, "
+ "s.elem_count_histogram, ");
+
+ if (fout->remoteVersion >= 170000)
+ appendPQExpBufferStr(&query,
+ "s.range_length_histogram, "
+ "s.range_empty_frac, "
+ "s.range_bounds_histogram ");
+ else
+ appendPQExpBufferStr(&query,
+ "NULL AS range_length_histogram, "
+ "NULL AS range_empty_frac, "
+ " NULL AS range_bounds_histogram ");
+
+ /*
+ * The results must be in the order of relations supplied in the
+ * parameters to ensure that they are in sync with a walk of the TOC.
+ *
+ * The redundant (and incomplete) filter clause on s.tablename = ANY(...)
+ * is a way to lead the query into using the index
+ * pg_class_relname_nsp_index which in turn allows the planner to avoid an
+ * expensive full scan of pg_stats.
+ *
+ * We may need to adjust this query for versions that are not so easily
+ * led.
+ */
+ appendPQExpBufferStr(&query,
+ "FROM pg_catalog.pg_stats AS s "
+ "JOIN unnest($1, $2) WITH ORDINALITY AS u(schemaname, tablename, ord) "
+ "ON s.schemaname = u.schemaname "
+ "AND s.tablename = u.tablename "
+ "WHERE s.tablename = ANY($2) "
+ "ORDER BY u.ord, s.attname, s.inherited");
+
+ ExecuteSqlStatement(fout, query.data);
+
+ termPQExpBuffer(&query);
+
+ attrstats.te = AH->toc->next;
+
+ fetchNextAttributeStats(fout);
+
+ attrstats.state = STATSBUF_ACTIVE;
+}
+
+
+/*
+ * append a single attribute stat to the buffer for this relation.
+ */
+static void
+appendAttributeStats(Archive *fout, PQExpBuffer out,
+ const RelStatsInfo *rsinfo)
+{
+ PGresult *res = attrstats.res;
+ int tup_num = attrstats.idx;
+
+ const char *attname;
+
+ static bool indexes_set = false;
+ static int i_attname,
+ i_inherited,
+ i_null_frac,
+ i_avg_width,
+ i_n_distinct,
+ i_most_common_vals,
+ i_most_common_freqs,
+ i_histogram_bounds,
+ i_correlation,
+ i_most_common_elems,
+ i_most_common_elem_freqs,
+ i_elem_count_histogram,
+ i_range_length_histogram,
+ i_range_empty_frac,
+ i_range_bounds_histogram;
+
+ if (!indexes_set)
+ {
+ /*
+ * It's a prepared statement, so the indexes will be the same for all
+ * result sets, so we only need to set them once.
+ */
+ i_attname = PQfnumber(res, "attname");
+ i_inherited = PQfnumber(res, "inherited");
+ i_null_frac = PQfnumber(res, "null_frac");
+ i_avg_width = PQfnumber(res, "avg_width");
+ i_n_distinct = PQfnumber(res, "n_distinct");
+ i_most_common_vals = PQfnumber(res, "most_common_vals");
+ i_most_common_freqs = PQfnumber(res, "most_common_freqs");
+ i_histogram_bounds = PQfnumber(res, "histogram_bounds");
+ i_correlation = PQfnumber(res, "correlation");
+ i_most_common_elems = PQfnumber(res, "most_common_elems");
+ i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
+ i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
+ i_range_length_histogram = PQfnumber(res, "range_length_histogram");
+ i_range_empty_frac = PQfnumber(res, "range_empty_frac");
+ i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+ indexes_set = true;
+ }
+
+ appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
+ appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+ fout->remoteVersion);
+ appendPQExpBufferStr(out, "\t'schemaname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+ appendPQExpBufferStr(out, ",\n\t'relname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+
+ if (PQgetisnull(res, tup_num, i_attname))
+ pg_fatal("attname cannot be NULL");
+ attname = PQgetvalue(res, tup_num, i_attname);
+
+ /*
+ * Indexes look up attname in indAttNames to derive attnum, all others use
+ * attname directly. We must specify attnum for indexes, since their
+ * attnames are not necessarily stable across dump/reload.
+ */
+ if (rsinfo->nindAttNames == 0)
+ {
+ appendPQExpBuffer(out, ",\n\t'attname', ");
+ appendStringLiteralAH(out, attname, fout);
+ }
+ else
+ {
+ bool found = false;
+
+ for (int i = 0; i < rsinfo->nindAttNames; i++)
+ if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
+ {
+ appendPQExpBuffer(out, ",\n\t'attnum', '%d'::smallint",
+ i + 1);
+ found = true;
+ break;
+ }
+
+ if (!found)
+ pg_fatal("could not find index attname \"%s\"", attname);
+ }
+
+ if (!PQgetisnull(res, tup_num, i_inherited))
+ appendNamedArgument(out, fout, "inherited", "boolean",
+ PQgetvalue(res, tup_num, i_inherited));
+ if (!PQgetisnull(res, tup_num, i_null_frac))
+ appendNamedArgument(out, fout, "null_frac", "real",
+ PQgetvalue(res, tup_num, i_null_frac));
+ if (!PQgetisnull(res, tup_num, i_avg_width))
+ appendNamedArgument(out, fout, "avg_width", "integer",
+ PQgetvalue(res, tup_num, i_avg_width));
+ if (!PQgetisnull(res, tup_num, i_n_distinct))
+ appendNamedArgument(out, fout, "n_distinct", "real",
+ PQgetvalue(res, tup_num, i_n_distinct));
+ if (!PQgetisnull(res, tup_num, i_most_common_vals))
+ appendNamedArgument(out, fout, "most_common_vals", "text",
+ PQgetvalue(res, tup_num, i_most_common_vals));
+ if (!PQgetisnull(res, tup_num, i_most_common_freqs))
+ appendNamedArgument(out, fout, "most_common_freqs", "real[]",
+ PQgetvalue(res, tup_num, i_most_common_freqs));
+ if (!PQgetisnull(res, tup_num, i_histogram_bounds))
+ appendNamedArgument(out, fout, "histogram_bounds", "text",
+ PQgetvalue(res, tup_num, i_histogram_bounds));
+ if (!PQgetisnull(res, tup_num, i_correlation))
+ appendNamedArgument(out, fout, "correlation", "real",
+ PQgetvalue(res, tup_num, i_correlation));
+ if (!PQgetisnull(res, tup_num, i_most_common_elems))
+ appendNamedArgument(out, fout, "most_common_elems", "text",
+ PQgetvalue(res, tup_num, i_most_common_elems));
+ if (!PQgetisnull(res, tup_num, i_most_common_elem_freqs))
+ appendNamedArgument(out, fout, "most_common_elem_freqs", "real[]",
+ PQgetvalue(res, tup_num, i_most_common_elem_freqs));
+ if (!PQgetisnull(res, tup_num, i_elem_count_histogram))
+ appendNamedArgument(out, fout, "elem_count_histogram", "real[]",
+ PQgetvalue(res, tup_num, i_elem_count_histogram));
+ if (fout->remoteVersion >= 170000)
+ {
+ if (!PQgetisnull(res, tup_num, i_range_length_histogram))
+ appendNamedArgument(out, fout, "range_length_histogram", "text",
+ PQgetvalue(res, tup_num, i_range_length_histogram));
+ if (!PQgetisnull(res, tup_num, i_range_empty_frac))
+ appendNamedArgument(out, fout, "range_empty_frac", "real",
+ PQgetvalue(res, tup_num, i_range_empty_frac));
+ if (!PQgetisnull(res, tup_num, i_range_bounds_histogram))
+ appendNamedArgument(out, fout, "range_bounds_histogram", "text",
+ PQgetvalue(res, tup_num, i_range_bounds_histogram));
+ }
+ appendPQExpBufferStr(out, "\n);\n");
+}
+
+
+
+/*
+ * printRelationStats --
*
* Generate the SQL statements needed to restore a relation's statistics.
*/
@@ -10485,64 +10802,21 @@ static char *
printRelationStats(Archive *fout, const void *userArg)
{
const RelStatsInfo *rsinfo = (RelStatsInfo *) userArg;
- const DumpableObject *dobj = &rsinfo->dobj;
+ const DumpableObject *dobj;
+ const char *relschema;
+ const char *relname;
+
+ ArchiveHandle *AH = (ArchiveHandle *) fout;
- PQExpBufferData query;
PQExpBufferData out;
- PGresult *res;
-
- static bool first_query = true;
- static int i_attname;
- static int i_inherited;
- static int i_null_frac;
- static int i_avg_width;
- static int i_n_distinct;
- static int i_most_common_vals;
- static int i_most_common_freqs;
- static int i_histogram_bounds;
- static int i_correlation;
- static int i_most_common_elems;
- static int i_most_common_elem_freqs;
- static int i_elem_count_histogram;
- static int i_range_length_histogram;
- static int i_range_empty_frac;
- static int i_range_bounds_histogram;
-
- initPQExpBuffer(&query);
-
- if (first_query)
- {
- appendPQExpBufferStr(&query,
- "PREPARE getAttributeStats(pg_catalog.text, pg_catalog.text) AS\n"
- "SELECT s.attname, s.inherited, "
- "s.null_frac, s.avg_width, s.n_distinct, "
- "s.most_common_vals, s.most_common_freqs, "
- "s.histogram_bounds, s.correlation, "
- "s.most_common_elems, s.most_common_elem_freqs, "
- "s.elem_count_histogram, ");
-
- if (fout->remoteVersion >= 170000)
- appendPQExpBufferStr(&query,
- "s.range_length_histogram, "
- "s.range_empty_frac, "
- "s.range_bounds_histogram ");
- else
- appendPQExpBufferStr(&query,
- "NULL AS range_length_histogram,"
- "NULL AS range_empty_frac,"
- "NULL AS range_bounds_histogram ");
-
- appendPQExpBufferStr(&query,
- "FROM pg_catalog.pg_stats s "
- "WHERE s.schemaname = $1 "
- "AND s.tablename = $2 "
- "ORDER BY s.attname, s.inherited");
-
- ExecuteSqlStatement(fout, query.data);
-
- resetPQExpBuffer(&query);
- }
+ Assert(rsinfo != NULL);
+ dobj = &rsinfo->dobj;
+ Assert(dobj != NULL);
+ relschema = dobj->namespace->dobj.name;
+ Assert(relschema != NULL);
+ relname = dobj->name;
+ Assert(relname != NULL);
initPQExpBuffer(&out);
@@ -10561,132 +10835,72 @@ printRelationStats(Archive *fout, const void *userArg)
appendPQExpBuffer(&out, "\t'relallvisible', '%d'::integer\n);\n",
rsinfo->relallvisible);
- /* fetch attribute stats */
- appendPQExpBufferStr(&query, "EXECUTE getAttributeStats(");
- appendStringLiteralAH(&query, dobj->namespace->dobj.name, fout);
- appendPQExpBufferStr(&query, ", ");
- appendStringLiteralAH(&query, dobj->name, fout);
- appendPQExpBufferStr(&query, ")");
+ AH->txnCount++;
- res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK);
+ if (attrstats.state == STATSBUF_UNINITIALIZED)
+ initAttributeStats(fout);
- if (first_query)
+ /*
+ * Because the query returns rows in the same order as the relations
+ * requested, and because every relation gets at least one row in the
+ * result set, the first row for this relation must correspond either to
+ * the current row of this result set (if one exists) or the first row of
+ * the next result set (if this one is already consumed).
+ */
+ if (attrstats.state != STATSBUF_ACTIVE)
+ pg_fatal("Exhausted getAttributeStats() before processing %s.%s",
+ rsinfo->dobj.namespace->dobj.name,
+ rsinfo->dobj.name);
+
+ /*
+ * If the current result set has been fully consumed, then the row(s) we
+ * need (if any) would be found in the next one. This will update
+ * attrstats.res and attrstats.idx.
+ */
+ if (PQntuples(attrstats.res) <= attrstats.idx)
+ fetchNextAttributeStats(fout);
+
+ while (true)
{
- i_attname = PQfnumber(res, "attname");
- i_inherited = PQfnumber(res, "inherited");
- i_null_frac = PQfnumber(res, "null_frac");
- i_avg_width = PQfnumber(res, "avg_width");
- i_n_distinct = PQfnumber(res, "n_distinct");
- i_most_common_vals = PQfnumber(res, "most_common_vals");
- i_most_common_freqs = PQfnumber(res, "most_common_freqs");
- i_histogram_bounds = PQfnumber(res, "histogram_bounds");
- i_correlation = PQfnumber(res, "correlation");
- i_most_common_elems = PQfnumber(res, "most_common_elems");
- i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
- i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
- i_range_length_histogram = PQfnumber(res, "range_length_histogram");
- i_range_empty_frac = PQfnumber(res, "range_empty_frac");
- i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
- first_query = false;
- }
-
- /* restore attribute stats */
- for (int rownum = 0; rownum < PQntuples(res); rownum++)
- {
- const char *attname;
-
- appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
- appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n",
- fout->remoteVersion);
- appendPQExpBufferStr(&out, "\t'schemaname', ");
- appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
- appendPQExpBufferStr(&out, ",\n\t'relname', ");
- appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
-
- if (PQgetisnull(res, rownum, i_attname))
- pg_fatal("attname cannot be NULL");
- attname = PQgetvalue(res, rownum, i_attname);
+ int i_schemaname;
+ int i_tablename;
+ char *schemaname;
+ char *tablename; /* misnomer, following pg_stats naming */
/*
- * Indexes look up attname in indAttNames to derive attnum, all others
- * use attname directly. We must specify attnum for indexes, since
- * their attnames are not necessarily stable across dump/reload.
+ * If we hit the end of the result set, then there are no more records
+ * for this relation, so we should stop, but first get the next result
+ * set for the next batch of relations.
*/
- if (rsinfo->nindAttNames == 0)
+ if (PQntuples(attrstats.res) <= attrstats.idx)
{
- appendPQExpBuffer(&out, ",\n\t'attname', ");
- appendStringLiteralAH(&out, attname, fout);
- }
- else
- {
- bool found = false;
-
- for (int i = 0; i < rsinfo->nindAttNames; i++)
- {
- if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
- {
- appendPQExpBuffer(&out, ",\n\t'attnum', '%d'::smallint",
- i + 1);
- found = true;
- break;
- }
- }
-
- if (!found)
- pg_fatal("could not find index attname \"%s\"", attname);
+ fetchNextAttributeStats(fout);
+ break;
}
- if (!PQgetisnull(res, rownum, i_inherited))
- appendNamedArgument(&out, fout, "inherited", "boolean",
- PQgetvalue(res, rownum, i_inherited));
- if (!PQgetisnull(res, rownum, i_null_frac))
- appendNamedArgument(&out, fout, "null_frac", "real",
- PQgetvalue(res, rownum, i_null_frac));
- if (!PQgetisnull(res, rownum, i_avg_width))
- appendNamedArgument(&out, fout, "avg_width", "integer",
- PQgetvalue(res, rownum, i_avg_width));
- if (!PQgetisnull(res, rownum, i_n_distinct))
- appendNamedArgument(&out, fout, "n_distinct", "real",
- PQgetvalue(res, rownum, i_n_distinct));
- if (!PQgetisnull(res, rownum, i_most_common_vals))
- appendNamedArgument(&out, fout, "most_common_vals", "text",
- PQgetvalue(res, rownum, i_most_common_vals));
- if (!PQgetisnull(res, rownum, i_most_common_freqs))
- appendNamedArgument(&out, fout, "most_common_freqs", "real[]",
- PQgetvalue(res, rownum, i_most_common_freqs));
- if (!PQgetisnull(res, rownum, i_histogram_bounds))
- appendNamedArgument(&out, fout, "histogram_bounds", "text",
- PQgetvalue(res, rownum, i_histogram_bounds));
- if (!PQgetisnull(res, rownum, i_correlation))
- appendNamedArgument(&out, fout, "correlation", "real",
- PQgetvalue(res, rownum, i_correlation));
- if (!PQgetisnull(res, rownum, i_most_common_elems))
- appendNamedArgument(&out, fout, "most_common_elems", "text",
- PQgetvalue(res, rownum, i_most_common_elems));
- if (!PQgetisnull(res, rownum, i_most_common_elem_freqs))
- appendNamedArgument(&out, fout, "most_common_elem_freqs", "real[]",
- PQgetvalue(res, rownum, i_most_common_elem_freqs));
- if (!PQgetisnull(res, rownum, i_elem_count_histogram))
- appendNamedArgument(&out, fout, "elem_count_histogram", "real[]",
- PQgetvalue(res, rownum, i_elem_count_histogram));
- if (fout->remoteVersion >= 170000)
- {
- if (!PQgetisnull(res, rownum, i_range_length_histogram))
- appendNamedArgument(&out, fout, "range_length_histogram", "text",
- PQgetvalue(res, rownum, i_range_length_histogram));
- if (!PQgetisnull(res, rownum, i_range_empty_frac))
- appendNamedArgument(&out, fout, "range_empty_frac", "real",
- PQgetvalue(res, rownum, i_range_empty_frac));
- if (!PQgetisnull(res, rownum, i_range_bounds_histogram))
- appendNamedArgument(&out, fout, "range_bounds_histogram", "text",
- PQgetvalue(res, rownum, i_range_bounds_histogram));
- }
- appendPQExpBufferStr(&out, "\n);\n");
+ i_schemaname = PQfnumber(attrstats.res, "schemaname");
+ Assert(i_schemaname >= 0);
+ i_tablename = PQfnumber(attrstats.res, "tablename");
+ Assert(i_tablename >= 0);
+
+ if (PQgetisnull(attrstats.res, attrstats.idx, i_schemaname))
+ pg_fatal("getAttributeStats() schemaname cannot be NULL");
+
+ if (PQgetisnull(attrstats.res, attrstats.idx, i_tablename))
+ pg_fatal("getAttributeStats() tablename cannot be NULL");
+
+ schemaname = PQgetvalue(attrstats.res, attrstats.idx, i_schemaname);
+ tablename = PQgetvalue(attrstats.res, attrstats.idx, i_tablename);
+
+ /* stop if current stat row isn't for this relation */
+ if (strcmp(relname, tablename) != 0 || strcmp(relschema, schemaname) != 0)
+ break;
+
+ appendAttributeStats(fout, &out, rsinfo);
+ AH->txnCount++;
+ attrstats.idx++;
}
- PQclear(res);
-
- termPQExpBuffer(&query);
return out.data;
}
--
2.48.1
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-16 20:33 Nathan Bossart <[email protected]>
parent: Corey Huinker <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Nathan Bossart @ 2025-03-16 20:33 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
Thanks for working on this, Corey.
On Fri, Mar 14, 2025 at 04:03:16PM -0400, Corey Huinker wrote:
> 0003 -
>
> Storing the restore function calls in the archive entry hogged a lot of
> memory and made people nervous. This introduces a new function pointer that
> generates those restore SQL calls right before they're written to disk,
> thus reducing the memory load from "stats for every object to be dumped" to
> just one object. Thanks to Nathan for diagnosing some weird quirks with
> various formats.
>
> 0004 -
>
> This replaces the query in the prepared statement with one that batches
> them 100 relations at a time, and then maintains that result set until it
> is consumed. It seems to have obvious speedups.
I've been doing a variety of tests with my toy database of 100K relations
[0], and I'm seeing around 20% less memory usage. That's still 20% more
than without stats, but that's still a pretty nice improvement.
I'd propose two small changes to the design:
* I tested a variety of batch sizes, and to my suprise, I saw the best
results with around 64 relations per batch. I imagine the absolute best
batch size will vary greatly depending on the workload. It might also
depend on work_mem and friends.
* The custom format actually does two WriteToc() calls, and since these
patches move the queries to this part of pg_dump, it means we'll run all
the queries twice. The comments around this code suggest that the second
pass isn't strictly necessary and that it is really only useful for
data/parallel restore, so we could probably skip it for no-data dumps.
With those two changes, a pg_upgrade-style dump of my test database goes
from ~21.6 seconds without these patches to ~11.2 seconds with them. For
reference, the same dump without stats takes ~7 seconds on HEAD.
[0] https://postgr.es/m/Z9R9-mFbxukqKmg4%40nathan
--
nathan
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-16 21:32 Corey Huinker <[email protected]>
parent: Nathan Bossart <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Corey Huinker @ 2025-03-16 21:32 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
>
> * The custom format actually does two WriteToc() calls, and since these
> patches move the queries to this part of pg_dump, it means we'll run all
> the queries twice. The comments around this code suggest that the second
> pass isn't strictly necessary and that it is really only useful for
> data/parallel restore, so we could probably skip it for no-data dumps.
>
Is there any reason we couldn't have stats objects remove themselves from
the list after completion?
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-17 14:23 Nathan Bossart <[email protected]>
parent: Corey Huinker <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Nathan Bossart @ 2025-03-17 14:23 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Sun, Mar 16, 2025 at 05:32:15PM -0400, Corey Huinker wrote:
>>
>> * The custom format actually does two WriteToc() calls, and since these
>> patches move the queries to this part of pg_dump, it means we'll run all
>> the queries twice. The comments around this code suggest that the second
>> pass isn't strictly necessary and that it is really only useful for
>> data/parallel restore, so we could probably skip it for no-data dumps.
>>
>
> Is there any reason we couldn't have stats objects remove themselves from
> the list after completion?
I'm assuming that writing a completely different TOC on the second pass
would corrupt the dump file. Perhaps we could teach it to skip stats
entries on the second pass or something, but I'm not too wild about adding
to the list of invasive changes we're making last-minute for v18.
--
nathan
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-17 23:24 Corey Huinker <[email protected]>
parent: Nathan Bossart <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Corey Huinker @ 2025-03-17 23:24 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Mon, Mar 17, 2025 at 10:24 AM Nathan Bossart <[email protected]>
wrote:
> On Sun, Mar 16, 2025 at 05:32:15PM -0400, Corey Huinker wrote:
> >>
> >> * The custom format actually does two WriteToc() calls, and since these
> >> patches move the queries to this part of pg_dump, it means we'll run
> all
> >> the queries twice. The comments around this code suggest that the
> second
> >> pass isn't strictly necessary and that it is really only useful for
> >> data/parallel restore, so we could probably skip it for no-data dumps.
> >>
> >
> > Is there any reason we couldn't have stats objects remove themselves from
> > the list after completion?
>
> I'm assuming that writing a completely different TOC on the second pass
> would corrupt the dump file. Perhaps we could teach it to skip stats
> entries on the second pass or something, but I'm not too wild about adding
> to the list of invasive changes we're making last-minute for v18.
I'm confused, are they needed in both places? If so, would it make sense to
write out each stat entry to a file and then re-read the file on the second
pass, or maybe do a \i filename in the sql script?
Not suggesting we do any of this for v18, but when I hear about doing
something twice when that thing was painful the first time, I look for ways
to avoid doing it, or set pan_is_hot = true for the next person.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-18 01:01 Nathan Bossart <[email protected]>
parent: Corey Huinker <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Nathan Bossart @ 2025-03-18 01:01 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Mon, Mar 17, 2025 at 07:24:46PM -0400, Corey Huinker wrote:
> On Mon, Mar 17, 2025 at 10:24 AM Nathan Bossart <[email protected]>
> wrote:
>> I'm assuming that writing a completely different TOC on the second pass
>> would corrupt the dump file. Perhaps we could teach it to skip stats
>> entries on the second pass or something, but I'm not too wild about adding
>> to the list of invasive changes we're making last-minute for v18.
>
> I'm confused, are they needed in both places?
AFAICT yes. The second pass rewrites the TOC to udpate the data offset
information. If we wrote a different TOC the second time around, then the
dump file would be broken, right?
/*
* If possible, re-write the TOC in order to update the data offset
* information. This is not essential, as pg_restore can cope in most
* cases without it; but it can make pg_restore significantly faster
* in some situations (especially parallel restore).
*/
if (ctx->hasSeek &&
fseeko(AH->FH, tpos, SEEK_SET) == 0)
WriteToc(AH);
--
nathan
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-19 22:17 Jeff Davis <[email protected]>
parent: Corey Huinker <[email protected]>
0 siblings, 2 replies; 199+ messages in thread
From: Jeff Davis @ 2025-03-19 22:17 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Sat, 2025-03-15 at 21:37 -0400, Corey Huinker wrote:
> > 0001 - no changes, but the longer I go the more I'm certain this is
> > something we want to do.
This replaces regclassin with custom lookups of the namespace and
relname, but misses some of the complexities that regclassin is
handling. For instance, it calls RangeVarGetRelid(), which calls
LookupExplicitNamespace(), which handles temp tables and
InvokeNamespaceSearchHook().
At first it looked like a bit too much code to copy, but regclassin()
passes NoLock, which means we basically just have to call
LookupExplicitNamespace().
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-19 22:35 Corey Huinker <[email protected]>
parent: Jeff Davis <[email protected]>
1 sibling, 0 replies; 199+ messages in thread
From: Corey Huinker @ 2025-03-19 22:35 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
>
> This replaces regclassin with custom lookups of the namespace and
> relname, but misses some of the complexities that regclassin is
> handling. For instance, it calls RangeVarGetRelid(), which calls
> LookupExplicitNamespace(), which handles temp tables and
> InvokeNamespaceSearchHook().
>
> At first it looked like a bit too much code to copy, but regclassin()
> passes NoLock, which means we basically just have to call
> LookupExplicitNamespace().
To be clear, LookupExplicitNamespace() can call aclcheck_error(), which is
something we cannot presently step-down into a WARNING, so an aclcheck
failure inside a restore/upgrade would fail the upgrade. I want to make
sure we can live with that because it might be hard to explain what's an
error we can nerf and what isn't.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-25 05:32 Jeff Davis <[email protected]>
parent: Corey Huinker <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Jeff Davis @ 2025-03-25 05:32 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; Robert Treat <[email protected]>; +Cc: Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Sat, 2025-03-08 at 14:09 -0500, Corey Huinker wrote:
> >
> > except it is perfectly clear that you *asked for* data and
> > statistics, so you get what you asked for. however the user
> > conjures in their heads what they are looking for, the logic is
> > simple, you get what you asked for.
> >
>
>
> They *asked for* that because they didn't have the mechanism to say
> "hold the mayo" or "everything except pickles". That's reducing their
> choice, and then blaming them for their choice.
Can we reach a decision here and move forward?
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-25 06:53 Jeff Davis <[email protected]>
parent: Jeff Davis <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Jeff Davis @ 2025-03-25 06:53 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Wed, 2025-03-19 at 15:17 -0700, Jeff Davis wrote:
> On Sat, 2025-03-15 at 21:37 -0400, Corey Huinker wrote:
> > > 0001 - no changes, but the longer I go the more I'm certain this
> > > is
> > > something we want to do.
>
> This replaces regclassin with custom lookups of the namespace and
> relname, but misses some of the complexities that regclassin is
> handling. For instance, it calls RangeVarGetRelid(), which calls
> LookupExplicitNamespace(), which handles temp tables and
> InvokeNamespaceSearchHook().
>
> At first it looked like a bit too much code to copy, but regclassin()
> passes NoLock, which means we basically just have to call
> LookupExplicitNamespace().
Attached new version 9j:
* Changed to use LookupExplicitNamespace()
* Added test for temp tables
* Doc fixes
Regards,
Jeff Davis
Attachments:
[text/x-patch] v9j-0001-Stats-use-schemaname-relname-instead-of-regclass.patch (68.4K, ../../[email protected]/2-v9j-0001-Stats-use-schemaname-relname-instead-of-regclass.patch)
download | inline diff:
From 72d4b9fc128e6d4ef73bb24ebba41797d06a7d9e Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Tue, 4 Mar 2025 22:16:52 -0500
Subject: [PATCH v9j] Stats: use schemaname/relname instead of regclass.
For import and export, use schemaname/relname rather than
regclass.
This is more natural during export, fits with the other arguments
better, and it gives better control over error handling in case we
need to downgrade more errors to warnings.
Also, use text for the argument types for schemaname, relname, and
attname so that casts to "name" are not required.
Author: Corey Huinker <[email protected]>
Discussion: https://postgr.es/m/CADkLM=ceOSsx_=oe73QQ-BxUFR2Cwqum7-UP_fPe22DBY0NerA@mail.gmail.com
---
doc/src/sgml/func.sgml | 50 +--
src/backend/statistics/attribute_stats.c | 87 +++--
src/backend/statistics/relation_stats.c | 65 ++--
src/backend/statistics/stat_utils.c | 37 +++
src/bin/pg_dump/pg_dump.c | 25 +-
src/bin/pg_dump/t/002_pg_dump.pl | 6 +-
src/include/catalog/catversion.h | 2 +-
src/include/catalog/pg_proc.dat | 8 +-
src/include/statistics/stat_utils.h | 2 +
src/test/regress/expected/stats_import.out | 353 ++++++++++++++-------
src/test/regress/sql/stats_import.sql | 306 ++++++++++++------
11 files changed, 647 insertions(+), 294 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 6fa1d6586b8..f8c1deb04ee 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -30364,22 +30364,24 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<structname>mytable</structname>:
<programlisting>
SELECT pg_restore_relation_stats(
- 'relation', 'mytable'::regclass,
- 'relpages', 173::integer,
- 'reltuples', 10000::real);
+ 'schemaname', 'myschema',
+ 'relname', 'mytable',
+ 'relpages', 173::integer,
+ 'reltuples', 10000::real);
</programlisting>
</para>
<para>
- The argument <literal>relation</literal> with a value of type
- <type>regclass</type> is required, and specifies the table. Other
+ The arguments <literal>schemaname</literal> and
+ <literal>relname</literal> are required, and specify the table. Other
arguments are the names and values of statistics corresponding to
certain columns in <link
linkend="catalog-pg-class"><structname>pg_class</structname></link>.
The currently-supported relation statistics are
<literal>relpages</literal> with a value of type
<type>integer</type>, <literal>reltuples</literal> with a value of
- type <type>real</type>, and <literal>relallvisible</literal> with a
- value of type <type>integer</type>.
+ type <type>real</type>, <literal>relallvisible</literal> with a value
+ of type <type>integer</type>, and <literal>relallfrozen</literal>
+ with a value of type <type>integer</type>.
</para>
<para>
Additionally, this function accepts argument name
@@ -30407,7 +30409,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<indexterm>
<primary>pg_clear_relation_stats</primary>
</indexterm>
- <function>pg_clear_relation_stats</function> ( <parameter>relation</parameter> <type>regclass</type> )
+ <function>pg_clear_relation_stats</function> ( <parameter>schemaname</parameter> <type>text</type>, <parameter>relname</parameter> <type>text</type> )
<returnvalue>void</returnvalue>
</para>
<para>
@@ -30456,22 +30458,23 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<structname>mytable</structname>:
<programlisting>
SELECT pg_restore_attribute_stats(
- 'relation', 'mytable'::regclass,
- 'attname', 'col1'::name,
- 'inherited', false,
- 'avg_width', 125::integer,
- 'null_frac', 0.5::real);
+ 'schemaname', 'myschema',
+ 'relname', 'mytable',
+ 'attname', 'col1',
+ 'inherited', false,
+ 'avg_width', 125::integer,
+ 'null_frac', 0.5::real);
</programlisting>
</para>
<para>
- The required arguments are <literal>relation</literal> with a value
- of type <type>regclass</type>, which specifies the table; either
- <literal>attname</literal> with a value of type <type>name</type> or
- <literal>attnum</literal> with a value of type <type>smallint</type>,
- which specifies the column; and <literal>inherited</literal>, which
- specifies whether the statistics include values from child tables.
- Other arguments are the names and values of statistics corresponding
- to columns in <link
+ The required arguments are <literal>schemaname</literal> and
+ <literal>relname</literal> with a value of type <type>text</type>
+ which specify the table; either <literal>attname</literal> with a
+ value of type <type>text</type> or <literal>attnum</literal> with a
+ value of type <type>smallint</type>, which specifies the column; and
+ <literal>inherited</literal>, which specifies whether the statistics
+ include values from child tables. Other arguments are the names and
+ values of statistics corresponding to columns in <link
linkend="view-pg-stats"><structname>pg_stats</structname></link>.
</para>
<para>
@@ -30501,8 +30504,9 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<primary>pg_clear_attribute_stats</primary>
</indexterm>
<function>pg_clear_attribute_stats</function> (
- <parameter>relation</parameter> <type>regclass</type>,
- <parameter>attname</parameter> <type>name</type>,
+ <parameter>schemaname</parameter> <type>text</type>,
+ <parameter>relname</parameter> <type>text</type>,
+ <parameter>attname</parameter> <type>text</type>,
<parameter>inherited</parameter> <type>boolean</type> )
<returnvalue>void</returnvalue>
</para>
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index 6bcbee0edba..f87db2d6102 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -36,7 +36,8 @@
enum attribute_stats_argnum
{
- ATTRELATION_ARG = 0,
+ ATTRELSCHEMA_ARG = 0,
+ ATTRELNAME_ARG,
ATTNAME_ARG,
ATTNUM_ARG,
INHERITED_ARG,
@@ -58,8 +59,9 @@ enum attribute_stats_argnum
static struct StatsArgInfo attarginfo[] =
{
- [ATTRELATION_ARG] = {"relation", REGCLASSOID},
- [ATTNAME_ARG] = {"attname", NAMEOID},
+ [ATTRELSCHEMA_ARG] = {"schemaname", TEXTOID},
+ [ATTRELNAME_ARG] = {"relname", TEXTOID},
+ [ATTNAME_ARG] = {"attname", TEXTOID},
[ATTNUM_ARG] = {"attnum", INT2OID},
[INHERITED_ARG] = {"inherited", BOOLOID},
[NULL_FRAC_ARG] = {"null_frac", FLOAT4OID},
@@ -80,7 +82,8 @@ static struct StatsArgInfo attarginfo[] =
enum clear_attribute_stats_argnum
{
- C_ATTRELATION_ARG = 0,
+ C_ATTRELSCHEMA_ARG = 0,
+ C_ATTRELNAME_ARG,
C_ATTNAME_ARG,
C_INHERITED_ARG,
C_NUM_ATTRIBUTE_STATS_ARGS
@@ -88,8 +91,9 @@ enum clear_attribute_stats_argnum
static struct StatsArgInfo cleararginfo[] =
{
- [C_ATTRELATION_ARG] = {"relation", REGCLASSOID},
- [C_ATTNAME_ARG] = {"attname", NAMEOID},
+ [C_ATTRELSCHEMA_ARG] = {"relation", TEXTOID},
+ [C_ATTRELNAME_ARG] = {"relation", TEXTOID},
+ [C_ATTNAME_ARG] = {"attname", TEXTOID},
[C_INHERITED_ARG] = {"inherited", BOOLOID},
[C_NUM_ATTRIBUTE_STATS_ARGS] = {0}
};
@@ -133,6 +137,9 @@ static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
static bool
attribute_statistics_update(FunctionCallInfo fcinfo)
{
+ char *nspname;
+ Oid nspoid;
+ char *relname;
Oid reloid;
char *attname;
AttrNumber attnum;
@@ -170,8 +177,23 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
bool result = true;
- stats_check_required_arg(fcinfo, attarginfo, ATTRELATION_ARG);
- reloid = PG_GETARG_OID(ATTRELATION_ARG);
+ stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG);
+ stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG);
+
+ nspname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELSCHEMA_ARG));
+ nspoid = stats_schema_check_privileges(nspname);
+ if (nspoid == InvalidOid)
+ return false;
+
+ relname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELNAME_ARG));
+ reloid = get_relname_relid(relname, nspoid);
+ if (reloid == InvalidOid)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
+ return false;
+ }
if (RecoveryInProgress())
ereport(ERROR,
@@ -185,21 +207,18 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
/* user can specify either attname or attnum, but not both */
if (!PG_ARGISNULL(ATTNAME_ARG))
{
- Name attnamename;
-
if (!PG_ARGISNULL(ATTNUM_ARG))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("cannot specify both attname and attnum")));
- attnamename = PG_GETARG_NAME(ATTNAME_ARG);
- attname = NameStr(*attnamename);
+ attname = TextDatumGetCString(PG_GETARG_DATUM(ATTNAME_ARG));
attnum = get_attnum(reloid, attname);
/* note that this test covers attisdropped cases too: */
if (attnum == InvalidAttrNumber)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
- attname, get_rel_name(reloid))));
+ errmsg("column \"%s\" of relation \"%s\".\"%s\" does not exist",
+ attname, nspname, relname)));
}
else if (!PG_ARGISNULL(ATTNUM_ARG))
{
@@ -210,8 +229,8 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
!SearchSysCacheExistsAttName(reloid, attname))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column %d of relation \"%s\" does not exist",
- attnum, get_rel_name(reloid))));
+ errmsg("column %d of relation \"%s\".\"%s\" does not exist",
+ attnum, nspname, relname)));
}
else
{
@@ -900,13 +919,33 @@ init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
Datum
pg_clear_attribute_stats(PG_FUNCTION_ARGS)
{
+ char *nspname;
+ Oid nspoid;
+ char *relname;
Oid reloid;
- Name attname;
+ char *attname;
AttrNumber attnum;
bool inherited;
- stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELATION_ARG);
- reloid = PG_GETARG_OID(C_ATTRELATION_ARG);
+ stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG);
+ stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG);
+ stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
+ stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
+
+ nspname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELSCHEMA_ARG));
+ nspoid = stats_schema_check_privileges(nspname);
+ if (!OidIsValid(nspoid))
+ return false;
+
+ relname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELNAME_ARG));
+ reloid = get_relname_relid(relname, nspoid);
+ if (!OidIsValid(reloid))
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
+ return false;
+ }
if (RecoveryInProgress())
ereport(ERROR,
@@ -916,23 +955,21 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
stats_lock_check_privileges(reloid);
- stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
- attname = PG_GETARG_NAME(C_ATTNAME_ARG);
- attnum = get_attnum(reloid, NameStr(*attname));
+ attname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTNAME_ARG));
+ attnum = get_attnum(reloid, attname);
if (attnum < 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot clear statistics on system column \"%s\"",
- NameStr(*attname))));
+ attname)));
if (attnum == InvalidAttrNumber)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
- NameStr(*attname), get_rel_name(reloid))));
+ attname, get_rel_name(reloid))));
- stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
inherited = PG_GETARG_BOOL(C_INHERITED_ARG);
delete_pg_statistic(reloid, attnum, inherited);
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index 52dfa477187..fdc69bc93e2 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -19,9 +19,12 @@
#include "access/heapam.h"
#include "catalog/indexing.h"
+#include "catalog/namespace.h"
#include "statistics/stat_utils.h"
+#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/fmgrprotos.h"
+#include "utils/lsyscache.h"
#include "utils/syscache.h"
@@ -32,7 +35,8 @@
enum relation_stats_argnum
{
- RELATION_ARG = 0,
+ RELSCHEMA_ARG = 0,
+ RELNAME_ARG,
RELPAGES_ARG,
RELTUPLES_ARG,
RELALLVISIBLE_ARG,
@@ -42,7 +46,8 @@ enum relation_stats_argnum
static struct StatsArgInfo relarginfo[] =
{
- [RELATION_ARG] = {"relation", REGCLASSOID},
+ [RELSCHEMA_ARG] = {"schemaname", TEXTOID},
+ [RELNAME_ARG] = {"relname", TEXTOID},
[RELPAGES_ARG] = {"relpages", INT4OID},
[RELTUPLES_ARG] = {"reltuples", FLOAT4OID},
[RELALLVISIBLE_ARG] = {"relallvisible", INT4OID},
@@ -59,6 +64,9 @@ static bool
relation_statistics_update(FunctionCallInfo fcinfo)
{
bool result = true;
+ char *nspname;
+ Oid nspoid;
+ char *relname;
Oid reloid;
Relation crel;
BlockNumber relpages = 0;
@@ -76,6 +84,32 @@ relation_statistics_update(FunctionCallInfo fcinfo)
bool nulls[4] = {0};
int nreplaces = 0;
+ stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG);
+ stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG);
+
+ nspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
+ nspoid = stats_schema_check_privileges(nspname);
+ if (!OidIsValid(nspoid))
+ return false;
+
+ relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
+ reloid = get_relname_relid(relname, nspoid);
+ if (!OidIsValid(reloid))
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
+ return false;
+ }
+
+ if (RecoveryInProgress())
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("recovery is in progress"),
+ errhint("Statistics cannot be modified during recovery.")));
+
+ stats_lock_check_privileges(reloid);
+
if (!PG_ARGISNULL(RELPAGES_ARG))
{
relpages = PG_GETARG_UINT32(RELPAGES_ARG);
@@ -108,17 +142,6 @@ relation_statistics_update(FunctionCallInfo fcinfo)
update_relallfrozen = true;
}
- stats_check_required_arg(fcinfo, relarginfo, RELATION_ARG);
- reloid = PG_GETARG_OID(RELATION_ARG);
-
- if (RecoveryInProgress())
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("recovery is in progress"),
- errhint("Statistics cannot be modified during recovery.")));
-
- stats_lock_check_privileges(reloid);
-
/*
* Take RowExclusiveLock on pg_class, consistent with
* vac_update_relstats().
@@ -187,20 +210,22 @@ relation_statistics_update(FunctionCallInfo fcinfo)
Datum
pg_clear_relation_stats(PG_FUNCTION_ARGS)
{
- LOCAL_FCINFO(newfcinfo, 5);
+ LOCAL_FCINFO(newfcinfo, 6);
- InitFunctionCallInfoData(*newfcinfo, NULL, 5, InvalidOid, NULL, NULL);
+ InitFunctionCallInfoData(*newfcinfo, NULL, 6, InvalidOid, NULL, NULL);
- newfcinfo->args[0].value = PG_GETARG_OID(0);
+ newfcinfo->args[0].value = PG_GETARG_DATUM(0);
newfcinfo->args[0].isnull = PG_ARGISNULL(0);
- newfcinfo->args[1].value = UInt32GetDatum(0);
- newfcinfo->args[1].isnull = false;
- newfcinfo->args[2].value = Float4GetDatum(-1.0);
+ newfcinfo->args[1].value = PG_GETARG_DATUM(1);
+ newfcinfo->args[1].isnull = PG_ARGISNULL(1);
+ newfcinfo->args[2].value = UInt32GetDatum(0);
newfcinfo->args[2].isnull = false;
- newfcinfo->args[3].value = UInt32GetDatum(0);
+ newfcinfo->args[3].value = Float4GetDatum(-1.0);
newfcinfo->args[3].isnull = false;
newfcinfo->args[4].value = UInt32GetDatum(0);
newfcinfo->args[4].isnull = false;
+ newfcinfo->args[5].value = UInt32GetDatum(0);
+ newfcinfo->args[5].isnull = false;
relation_statistics_update(newfcinfo);
PG_RETURN_VOID();
diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c
index 9647f5108b3..b444f6871df 100644
--- a/src/backend/statistics/stat_utils.c
+++ b/src/backend/statistics/stat_utils.c
@@ -18,7 +18,9 @@
#include "access/relation.h"
#include "catalog/index.h"
+#include "catalog/namespace.h"
#include "catalog/pg_database.h"
+#include "catalog/pg_namespace.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "statistics/stat_utils.h"
@@ -213,6 +215,41 @@ stats_lock_check_privileges(Oid reloid)
relation_close(table, NoLock);
}
+
+/*
+ * Resolve a schema name into an Oid, ensure that the user has usage privs on
+ * that schema.
+ */
+Oid
+stats_schema_check_privileges(const char *nspname)
+{
+ Oid nspoid;
+ AclResult aclresult;
+
+ nspoid = LookupExplicitNamespace(nspname, true);
+
+ if (nspoid == InvalidOid)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_SCHEMA_NAME),
+ errmsg("schema %s does not exist", nspname)));
+ return InvalidOid;
+ }
+
+ aclresult = object_aclcheck(NamespaceRelationId, nspoid, GetUserId(), ACL_USAGE);
+
+ if (aclresult != ACLCHECK_OK)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied for schema %s", nspname)));
+ return InvalidOid;
+ }
+
+ return nspoid;
+}
+
+
/*
* Find the argument number for the given argument name, returning -1 if not
* found.
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 428ed2d60fc..239664c459d 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -10498,7 +10498,6 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
PQExpBuffer out;
DumpId *deps = NULL;
int ndeps = 0;
- char *qualified_name;
int i_attname;
int i_inherited;
int i_null_frac;
@@ -10563,15 +10562,16 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
out = createPQExpBuffer();
- qualified_name = pg_strdup(fmtQualifiedDumpable(rsinfo));
-
/* restore relation stats */
appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
fout->remoteVersion);
- appendPQExpBufferStr(out, "\t'relation', ");
- appendStringLiteralAH(out, qualified_name, fout);
- appendPQExpBufferStr(out, "::regclass,\n");
+ appendPQExpBufferStr(out, "\t'schemaname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+ appendPQExpBufferStr(out, ",\n");
+ appendPQExpBufferStr(out, "\t'relname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+ appendPQExpBufferStr(out, ",\n");
appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer\n);\n",
@@ -10610,9 +10610,10 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
fout->remoteVersion);
- appendPQExpBufferStr(out, "\t'relation', ");
- appendStringLiteralAH(out, qualified_name, fout);
- appendPQExpBufferStr(out, "::regclass");
+ appendPQExpBufferStr(out, "\t'schemaname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+ appendPQExpBufferStr(out, ",\n\t'relname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.name, fout);
if (PQgetisnull(res, rownum, i_attname))
pg_fatal("attname cannot be NULL");
@@ -10624,7 +10625,10 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
* their attnames are not necessarily stable across dump/reload.
*/
if (rsinfo->nindAttNames == 0)
- appendNamedArgument(out, fout, "attname", "name", attname);
+ {
+ appendPQExpBuffer(out, ",\n\t'attname', ");
+ appendStringLiteralAH(out, attname, fout);
+ }
else
{
bool found = false;
@@ -10704,7 +10708,6 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
.deps = deps,
.nDeps = ndeps));
- free(qualified_name);
destroyPQExpBuffer(out);
destroyPQExpBuffer(query);
}
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index d281e27aa67..d3e84f44c6c 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -4741,14 +4741,16 @@ my %tests = (
regexp => qr/^
\QSELECT * FROM pg_catalog.pg_restore_relation_stats(\E\s+
'version',\s'\d+'::integer,\s+
- 'relation',\s'dump_test.dup_test_post_data_ix'::regclass,\s+
+ 'schemaname',\s'dump_test',\s+
+ 'relname',\s'dup_test_post_data_ix',\s+
'relpages',\s'\d+'::integer,\s+
'reltuples',\s'\d+'::real,\s+
'relallvisible',\s'\d+'::integer\s+
\);\s+
\QSELECT * FROM pg_catalog.pg_restore_attribute_stats(\E\s+
'version',\s'\d+'::integer,\s+
- 'relation',\s'dump_test.dup_test_post_data_ix'::regclass,\s+
+ 'schemaname',\s'dump_test',\s+
+ 'relname',\s'dup_test_post_data_ix',\s+
'attnum',\s'2'::smallint,\s+
'inherited',\s'f'::boolean,\s+
'null_frac',\s'0'::real,\s+
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index cf381867e40..c68ff9cbf83 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -57,6 +57,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 202503241
+#define CATALOG_VERSION_NO 202503242
#endif
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0d29ef50ff2..3f7b82e02bb 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12453,8 +12453,8 @@
descr => 'clear statistics on relation',
proname => 'pg_clear_relation_stats', provolatile => 'v', proisstrict => 'f',
proparallel => 'u', prorettype => 'void',
- proargtypes => 'regclass',
- proargnames => '{relation}',
+ proargtypes => 'text text',
+ proargnames => '{schemaname,relname}',
prosrc => 'pg_clear_relation_stats' },
{ oid => '8461',
descr => 'restore statistics on attribute',
@@ -12469,8 +12469,8 @@
descr => 'clear statistics on attribute',
proname => 'pg_clear_attribute_stats', provolatile => 'v', proisstrict => 'f',
proparallel => 'u', prorettype => 'void',
- proargtypes => 'regclass name bool',
- proargnames => '{relation,attname,inherited}',
+ proargtypes => 'text text text bool',
+ proargnames => '{schemaname,relname,attname,inherited}',
prosrc => 'pg_clear_attribute_stats' },
# GiST stratnum implementations
diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h
index 0eb4decfcac..ba09b431c11 100644
--- a/src/include/statistics/stat_utils.h
+++ b/src/include/statistics/stat_utils.h
@@ -32,6 +32,8 @@ extern bool stats_check_arg_pair(FunctionCallInfo fcinfo,
extern void stats_lock_check_privileges(Oid reloid);
+extern Oid stats_schema_check_privileges(const char *nspname);
+
extern bool stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
FunctionCallInfo positional_fcinfo,
struct StatsArgInfo *arginfo);
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 1f46d5e7854..302e77743e3 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -14,7 +14,8 @@ CREATE TABLE stats_import.test(
) WITH (autovacuum_enabled = false);
SELECT
pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', 18::integer,
'reltuples', 21::real,
'relallvisible', 24::integer,
@@ -36,7 +37,7 @@ ORDER BY relname;
test | 18 | 21 | 24 | 27
(1 row)
-SELECT pg_clear_relation_stats('stats_import.test'::regclass);
+SELECT pg_clear_relation_stats('stats_import', 'test');
pg_clear_relation_stats
-------------------------
@@ -45,33 +46,54 @@ SELECT pg_clear_relation_stats('stats_import.test'::regclass);
--
-- relstats tests
--
---- error: relation is wrong type
+-- error: schemaname missing
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid,
+ 'relname', 'test',
'relpages', 17::integer);
-WARNING: argument "relation" has type "oid", expected type "regclass"
-ERROR: "relation" cannot be NULL
+ERROR: "schemaname" cannot be NULL
+-- error: relname missing
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 'stats_import',
+ 'relpages', 17::integer);
+ERROR: "relname" cannot be NULL
+--- error: schemaname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 3.6::float,
+ 'relname', 'test',
+ 'relpages', 17::integer);
+WARNING: argument "schemaname" has type "double precision", expected type "text"
+ERROR: "schemaname" cannot be NULL
+--- error: relname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 0::oid,
+ 'relpages', 17::integer);
+WARNING: argument "relname" has type "oid", expected type "text"
+ERROR: "relname" cannot be NULL
-- error: relation not found
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'nope',
'relpages', 17::integer);
-ERROR: could not open relation with OID 0
+WARNING: Relation "stats_import"."nope" not found.
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
-- error: odd number of variadic arguments cannot be pairs
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relallvisible');
ERROR: variadic arguments must be name/value pairs
HINT: Provide an even number of variadic arguments that can be divided into pairs.
-- error: argument name is NULL
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
NULL, '17'::integer);
-ERROR: name at variadic position 3 is NULL
--- error: argument name is not a text type
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 17, '17'::integer);
-ERROR: name at variadic position 3 has type "integer", expected type "text"
+ERROR: name at variadic position 5 is NULL
-- starting stats
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
@@ -84,7 +106,8 @@ WHERE oid = 'stats_import.test_i'::regclass;
-- regular indexes have special case locking rules
BEGIN;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.test_i'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test_i',
'relpages', 18::integer);
pg_restore_relation_stats
---------------------------
@@ -132,7 +155,8 @@ WHERE oid = 'stats_import.part_parent'::regclass;
--
BEGIN;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent_i'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'part_parent_i',
'relpages', 2::integer);
pg_restore_relation_stats
---------------------------
@@ -166,7 +190,8 @@ WHERE oid = 'stats_import.part_parent_i'::regclass;
-- ok: set all relstats, with version, no bounds checking
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'version', 150000::integer,
'relpages', '-17'::integer,
'reltuples', 400::real,
@@ -187,7 +212,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just relpages, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', '16'::integer);
pg_restore_relation_stats
---------------------------
@@ -204,7 +230,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just reltuples, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'reltuples', '500'::real);
pg_restore_relation_stats
---------------------------
@@ -221,7 +248,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just relallvisible, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relallvisible', 5::integer);
pg_restore_relation_stats
---------------------------
@@ -238,7 +266,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: just relallfrozen
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'version', 150000::integer,
'relallfrozen', 3::integer);
pg_restore_relation_stats
@@ -256,7 +285,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- warn: bad relpages type, rest updated
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', 'nope'::text,
'reltuples', 400.0::real,
'relallvisible', 4::integer,
@@ -277,7 +307,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- unrecognized argument name, rest ok
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', '171'::integer,
'nope', 10::integer);
WARNING: unrecognized argument name: "nope"
@@ -295,8 +326,7 @@ WHERE oid = 'stats_import.test'::regclass;
(1 row)
-- ok: clear stats
-SELECT pg_catalog.pg_clear_relation_stats(
- relation => 'stats_import.test'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'test');
pg_clear_relation_stats
-------------------------
@@ -313,87 +343,123 @@ WHERE oid = 'stats_import.test'::regclass;
-- invalid relkinds for statistics
CREATE SEQUENCE stats_import.testseq;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.testseq'::regclass);
+ 'schemaname', 'stats_import',
+ 'relname', 'testseq');
ERROR: cannot modify statistics for relation "testseq"
DETAIL: This operation is not supported for sequences.
-SELECT pg_catalog.pg_clear_relation_stats(
- 'stats_import.testseq'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
ERROR: cannot modify statistics for relation "testseq"
DETAIL: This operation is not supported for sequences.
CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
-SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.testview'::regclass);
-ERROR: cannot modify statistics for relation "testview"
-DETAIL: This operation is not supported for views.
-SELECT pg_catalog.pg_clear_relation_stats(
- 'stats_import.testview'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
ERROR: cannot modify statistics for relation "testview"
DETAIL: This operation is not supported for views.
--
-- attribute stats
--
--- error: object does not exist
+-- error: schemaname missing
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relname', 'test',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: "schemaname" cannot be NULL
+-- error: schema does not exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'nope',
+ 'relname', 'test',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+WARNING: schema nope does not exist
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- error: relname missing
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+ERROR: "relname" cannot be NULL
+-- error: relname does not exist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', '0'::oid::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'nope',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: could not open relation with OID 0
--- error: relation null
+WARNING: Relation "stats_import"."nope" not found.
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- error: relname null
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', NULL::oid::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', NULL,
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: "relation" cannot be NULL
+ERROR: "relname" cannot be NULL
-- error: NULL attname
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', NULL::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', NULL,
'inherited', false::boolean,
'null_frac', 0.1::real);
ERROR: must specify either attname or attnum
-- error: attname doesn't exist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'nope'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'nope',
'inherited', false::boolean,
'null_frac', 0.1::real,
'avg_width', 2::integer,
'n_distinct', 0.3::real);
-ERROR: column "nope" of relation "test" does not exist
+ERROR: column "nope" of relation "stats_import"."test" does not exist
-- error: both attname and attnum
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.1::real);
ERROR: cannot specify both attname and attnum
-- error: neither attname nor attnum
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'inherited', false::boolean,
'null_frac', 0.1::real);
ERROR: must specify either attname or attnum
-- error: attribute is system column
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'xmin'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'xmin',
'inherited', false::boolean,
'null_frac', 0.1::real);
ERROR: cannot modify statistics on system column "xmin"
-- error: inherited null
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', NULL::boolean,
'null_frac', 0.1::real);
ERROR: "inherited" cannot be NULL
-- ok: just the fixed values, with version, no stakinds
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'version', 150000::integer,
'null_frac', 0.2::real,
@@ -421,7 +487,8 @@ AND attname = 'id';
-- for any stat-having relation.
--
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.4::real);
@@ -443,8 +510,9 @@ AND attname = 'id';
-- warn: unrecognized argument name, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.2::real,
'nope', 0.5::real);
@@ -467,8 +535,9 @@ AND attname = 'id';
-- warn: mcv / mcf null mismatch part 1, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.21::real,
'most_common_freqs', '{0.1,0.2,0.3}'::real[]
@@ -492,8 +561,9 @@ AND attname = 'id';
-- warn: mcv / mcf null mismatch part 2, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.21::real,
'most_common_vals', '{1,2,3}'::text
@@ -517,8 +587,9 @@ AND attname = 'id';
-- warn: mcf type mismatch, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.22::real,
'most_common_vals', '{2,1,3}'::text,
@@ -544,8 +615,9 @@ AND attname = 'id';
-- warn: mcv cast failure, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.23::real,
'most_common_vals', '{2,four,3}'::text,
@@ -570,8 +642,9 @@ AND attname = 'id';
-- ok: mcv+mcf
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
@@ -594,8 +667,9 @@ AND attname = 'id';
-- warn: NULL in histogram array, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.24::real,
'histogram_bounds', '{1,NULL,3,4}'::text
@@ -619,8 +693,9 @@ AND attname = 'id';
-- ok: histogram_bounds
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'histogram_bounds', '{1,2,3,4}'::text
);
@@ -642,8 +717,9 @@ AND attname = 'id';
-- warn: elem_count_histogram null element, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.25::real,
'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[]
@@ -667,8 +743,9 @@ AND attname = 'tags';
-- ok: elem_count_histogram
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.26::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -691,8 +768,9 @@ AND attname = 'tags';
-- warn: range stats on a scalar type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.27::real,
'range_empty_frac', 0.5::real,
@@ -718,8 +796,9 @@ AND attname = 'id';
-- warn: range_empty_frac range_length_hist null mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.28::real,
'range_length_histogram', '{399,499,Infinity}'::text
@@ -743,8 +822,9 @@ AND attname = 'arange';
-- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.29::real,
'range_empty_frac', 0.5::real
@@ -768,8 +848,9 @@ AND attname = 'arange';
-- ok: range_empty_frac + range_length_hist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'range_empty_frac', 0.5::real,
'range_length_histogram', '{399,499,Infinity}'::text
@@ -792,8 +873,9 @@ AND attname = 'arange';
-- warn: range bounds histogram on scalar, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.31::real,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
@@ -818,8 +900,9 @@ AND attname = 'id';
-- ok: range_bounds_histogram
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
);
@@ -841,8 +924,9 @@ AND attname = 'arange';
-- warn: cannot set most_common_elems for range type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.32::real,
'most_common_elems', '{3,1}'::text,
@@ -868,8 +952,9 @@ AND attname = 'arange';
-- warn: scalars can't have mcelem, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.33::real,
'most_common_elems', '{1,3}'::text,
@@ -895,8 +980,9 @@ AND attname = 'id';
-- warn: mcelem / mcelem mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.34::real,
'most_common_elems', '{one,two}'::text
@@ -920,8 +1006,9 @@ AND attname = 'tags';
-- warn: mcelem / mcelem null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.35::real,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
@@ -945,8 +1032,9 @@ AND attname = 'tags';
-- ok: mcelem
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'most_common_elems', '{one,three}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
@@ -969,8 +1057,9 @@ AND attname = 'tags';
-- warn: scalars can't have elem_count_histogram, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.36::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -1022,8 +1111,9 @@ SELECT s.schemaname, s.tablename, s.attname, s.inherited, r.*
FROM pg_catalog.pg_stats AS s
CROSS JOIN LATERAL
pg_catalog.pg_restore_attribute_stats(
- 'relation', ('stats_import.' || s.tablename || '_clone')::regclass,
- 'attname', s.attname,
+ 'schemaname', 'stats_import',
+ 'relname', s.tablename::text || '_clone',
+ 'attname', s.attname::text,
'inherited', s.inherited,
'version', 150000,
'null_frac', s.null_frac,
@@ -1200,9 +1290,10 @@ AND attname = 'arange';
(1 row)
SELECT pg_catalog.pg_clear_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean);
+ schemaname => 'stats_import',
+ relname => 'test',
+ attname => 'arange',
+ inherited => false);
pg_clear_attribute_stats
--------------------------
@@ -1219,6 +1310,52 @@ AND attname = 'arange';
0
(1 row)
+-- temp tables
+CREATE TEMP TABLE stats_temp(i int);
+SELECT pg_restore_relation_stats(
+ 'schemaname', 'pg_temp',
+ 'relname', 'stats_temp',
+ 'relpages', '-19'::integer,
+ 'reltuples', 401::real,
+ 'relallvisible', 5::integer,
+ 'relallfrozen', 3::integer);
+ pg_restore_relation_stats
+---------------------------
+ t
+(1 row)
+
+SELECT relname, relpages, reltuples, relallvisible, relallfrozen
+FROM pg_class
+WHERE oid = 'pg_temp.stats_temp'::regclass
+ORDER BY relname;
+ relname | relpages | reltuples | relallvisible | relallfrozen
+------------+----------+-----------+---------------+--------------
+ stats_temp | -19 | 401 | 5 | 3
+(1 row)
+
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'pg_temp',
+ 'relname', 'stats_temp',
+ 'attname', 'i',
+ 'inherited', false::boolean,
+ 'null_frac', 0.0123::real
+ );
+ pg_restore_attribute_stats
+----------------------------
+ t
+(1 row)
+
+SELECT tablename, null_frac
+FROM pg_stats
+WHERE schemaname like 'pg_temp%'
+AND tablename = 'stats_temp'
+AND inherited = false
+AND attname = 'i';
+ tablename | null_frac
+------------+-----------
+ stats_temp | 0.0123
+(1 row)
+
DROP SCHEMA stats_import CASCADE;
NOTICE: drop cascades to 6 other objects
DETAIL: drop cascades to type stats_import.complex_type
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index 0ec590688c2..35a9a1e3e7a 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -17,7 +17,8 @@ CREATE TABLE stats_import.test(
SELECT
pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', 18::integer,
'reltuples', 21::real,
'relallvisible', 24::integer,
@@ -32,37 +33,52 @@ FROM pg_class
WHERE oid = 'stats_import.test'::regclass
ORDER BY relname;
-SELECT pg_clear_relation_stats('stats_import.test'::regclass);
+SELECT pg_clear_relation_stats('stats_import', 'test');
--
-- relstats tests
--
---- error: relation is wrong type
+-- error: schemaname missing
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid,
+ 'relname', 'test',
+ 'relpages', 17::integer);
+
+-- error: relname missing
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 'stats_import',
+ 'relpages', 17::integer);
+
+--- error: schemaname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 3.6::float,
+ 'relname', 'test',
+ 'relpages', 17::integer);
+
+--- error: relname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 0::oid,
'relpages', 17::integer);
-- error: relation not found
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 0::oid::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'nope',
'relpages', 17::integer);
-- error: odd number of variadic arguments cannot be pairs
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relallvisible');
-- error: argument name is NULL
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
NULL, '17'::integer);
--- error: argument name is not a text type
-SELECT pg_restore_relation_stats(
- 'relation', '0'::oid::regclass,
- 17, '17'::integer);
-
-- starting stats
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
@@ -71,7 +87,8 @@ WHERE oid = 'stats_import.test_i'::regclass;
-- regular indexes have special case locking rules
BEGIN;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.test_i'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test_i',
'relpages', 18::integer);
SELECT mode FROM pg_locks
@@ -108,7 +125,8 @@ WHERE oid = 'stats_import.part_parent'::regclass;
BEGIN;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.part_parent_i'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'part_parent_i',
'relpages', 2::integer);
SELECT mode FROM pg_locks
@@ -127,7 +145,8 @@ WHERE oid = 'stats_import.part_parent_i'::regclass;
-- ok: set all relstats, with version, no bounds checking
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'version', 150000::integer,
'relpages', '-17'::integer,
'reltuples', 400::real,
@@ -140,7 +159,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just relpages, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', '16'::integer);
SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -149,7 +169,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just reltuples, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'reltuples', '500'::real);
SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -158,7 +179,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: set just relallvisible, rest stay same
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relallvisible', 5::integer);
SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -167,7 +189,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- ok: just relallfrozen
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'version', 150000::integer,
'relallfrozen', 3::integer);
@@ -177,7 +200,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- warn: bad relpages type, rest updated
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', 'nope'::text,
'reltuples', 400.0::real,
'relallvisible', 4::integer,
@@ -189,7 +213,8 @@ WHERE oid = 'stats_import.test'::regclass;
-- unrecognized argument name, rest ok
SELECT pg_restore_relation_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'relpages', '171'::integer,
'nope', 10::integer);
@@ -198,8 +223,7 @@ FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
-- ok: clear stats
-SELECT pg_catalog.pg_clear_relation_stats(
- relation => 'stats_import.test'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'test');
SELECT relpages, reltuples, relallvisible
FROM pg_class
@@ -209,48 +233,70 @@ WHERE oid = 'stats_import.test'::regclass;
CREATE SEQUENCE stats_import.testseq;
SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.testseq'::regclass);
+ 'schemaname', 'stats_import',
+ 'relname', 'testseq');
-SELECT pg_catalog.pg_clear_relation_stats(
- 'stats_import.testseq'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
-SELECT pg_catalog.pg_restore_relation_stats(
- 'relation', 'stats_import.testview'::regclass);
-
-SELECT pg_catalog.pg_clear_relation_stats(
- 'stats_import.testview'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
--
-- attribute stats
--
--- error: object does not exist
+-- error: schemaname missing
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relname', 'test',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: schema does not exist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', '0'::oid::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'nope',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: relation null
+-- error: relname missing
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', NULL::oid::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: relname does not exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'nope',
+ 'attname', 'id',
+ 'inherited', false::boolean,
+ 'null_frac', 0.1::real);
+
+-- error: relname null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'relname', NULL,
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: NULL attname
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', NULL::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', NULL,
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: attname doesn't exist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'nope'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'nope',
'inherited', false::boolean,
'null_frac', 0.1::real,
'avg_width', 2::integer,
@@ -258,36 +304,41 @@ SELECT pg_catalog.pg_restore_attribute_stats(
-- error: both attname and attnum
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: neither attname nor attnum
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: attribute is system column
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'xmin'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'xmin',
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: inherited null
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', NULL::boolean,
'null_frac', 0.1::real);
-- ok: just the fixed values, with version, no stakinds
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'version', 150000::integer,
'null_frac', 0.2::real,
@@ -307,7 +358,8 @@ AND attname = 'id';
-- for any stat-having relation.
--
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.4::real);
@@ -321,8 +373,9 @@ AND attname = 'id';
-- warn: unrecognized argument name, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.2::real,
'nope', 0.5::real);
@@ -336,8 +389,9 @@ AND attname = 'id';
-- warn: mcv / mcf null mismatch part 1, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.21::real,
'most_common_freqs', '{0.1,0.2,0.3}'::real[]
@@ -352,8 +406,9 @@ AND attname = 'id';
-- warn: mcv / mcf null mismatch part 2, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.21::real,
'most_common_vals', '{1,2,3}'::text
@@ -368,8 +423,9 @@ AND attname = 'id';
-- warn: mcf type mismatch, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.22::real,
'most_common_vals', '{2,1,3}'::text,
@@ -385,8 +441,9 @@ AND attname = 'id';
-- warn: mcv cast failure, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.23::real,
'most_common_vals', '{2,four,3}'::text,
@@ -402,8 +459,9 @@ AND attname = 'id';
-- ok: mcv+mcf
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
@@ -418,8 +476,9 @@ AND attname = 'id';
-- warn: NULL in histogram array, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.24::real,
'histogram_bounds', '{1,NULL,3,4}'::text
@@ -434,8 +493,9 @@ AND attname = 'id';
-- ok: histogram_bounds
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'histogram_bounds', '{1,2,3,4}'::text
);
@@ -449,8 +509,9 @@ AND attname = 'id';
-- warn: elem_count_histogram null element, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.25::real,
'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[]
@@ -465,8 +526,9 @@ AND attname = 'tags';
-- ok: elem_count_histogram
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.26::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -481,8 +543,9 @@ AND attname = 'tags';
-- warn: range stats on a scalar type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.27::real,
'range_empty_frac', 0.5::real,
@@ -498,8 +561,9 @@ AND attname = 'id';
-- warn: range_empty_frac range_length_hist null mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.28::real,
'range_length_histogram', '{399,499,Infinity}'::text
@@ -514,8 +578,9 @@ AND attname = 'arange';
-- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.29::real,
'range_empty_frac', 0.5::real
@@ -530,8 +595,9 @@ AND attname = 'arange';
-- ok: range_empty_frac + range_length_hist
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'range_empty_frac', 0.5::real,
'range_length_histogram', '{399,499,Infinity}'::text
@@ -546,8 +612,9 @@ AND attname = 'arange';
-- warn: range bounds histogram on scalar, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.31::real,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
@@ -562,8 +629,9 @@ AND attname = 'id';
-- ok: range_bounds_histogram
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
);
@@ -577,8 +645,9 @@ AND attname = 'arange';
-- warn: cannot set most_common_elems for range type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'arange'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.32::real,
'most_common_elems', '{3,1}'::text,
@@ -594,8 +663,9 @@ AND attname = 'arange';
-- warn: scalars can't have mcelem, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.33::real,
'most_common_elems', '{1,3}'::text,
@@ -611,8 +681,9 @@ AND attname = 'id';
-- warn: mcelem / mcelem mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.34::real,
'most_common_elems', '{one,two}'::text
@@ -627,8 +698,9 @@ AND attname = 'tags';
-- warn: mcelem / mcelem null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.35::real,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
@@ -643,8 +715,9 @@ AND attname = 'tags';
-- ok: mcelem
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'tags'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'tags',
'inherited', false::boolean,
'most_common_elems', '{one,three}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
@@ -659,8 +732,9 @@ AND attname = 'tags';
-- warn: scalars can't have elem_count_histogram, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
- 'relation', 'stats_import.test'::regclass,
- 'attname', 'id'::name,
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.36::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -707,8 +781,9 @@ SELECT s.schemaname, s.tablename, s.attname, s.inherited, r.*
FROM pg_catalog.pg_stats AS s
CROSS JOIN LATERAL
pg_catalog.pg_restore_attribute_stats(
- 'relation', ('stats_import.' || s.tablename || '_clone')::regclass,
- 'attname', s.attname,
+ 'schemaname', 'stats_import',
+ 'relname', s.tablename::text || '_clone',
+ 'attname', s.attname::text,
'inherited', s.inherited,
'version', 150000,
'null_frac', s.null_frac,
@@ -853,9 +928,10 @@ AND inherited = false
AND attname = 'arange';
SELECT pg_catalog.pg_clear_attribute_stats(
- relation => 'stats_import.test'::regclass,
- attname => 'arange'::name,
- inherited => false::boolean);
+ schemaname => 'stats_import',
+ relname => 'test',
+ attname => 'arange',
+ inherited => false);
SELECT COUNT(*)
FROM pg_stats
@@ -864,4 +940,34 @@ AND tablename = 'test'
AND inherited = false
AND attname = 'arange';
+-- temp tables
+CREATE TEMP TABLE stats_temp(i int);
+SELECT pg_restore_relation_stats(
+ 'schemaname', 'pg_temp',
+ 'relname', 'stats_temp',
+ 'relpages', '-19'::integer,
+ 'reltuples', 401::real,
+ 'relallvisible', 5::integer,
+ 'relallfrozen', 3::integer);
+
+SELECT relname, relpages, reltuples, relallvisible, relallfrozen
+FROM pg_class
+WHERE oid = 'pg_temp.stats_temp'::regclass
+ORDER BY relname;
+
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'pg_temp',
+ 'relname', 'stats_temp',
+ 'attname', 'i',
+ 'inherited', false::boolean,
+ 'null_frac', 0.0123::real
+ );
+
+SELECT tablename, null_frac
+FROM pg_stats
+WHERE schemaname like 'pg_temp%'
+AND tablename = 'stats_temp'
+AND inherited = false
+AND attname = 'i';
+
DROP SCHEMA stats_import CASCADE;
--
2.34.1
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-25 14:53 Corey Huinker <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Corey Huinker @ 2025-03-25 14:53 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
>
> * Changed to use LookupExplicitNamespace()
>
Seems good.
> * Added test for temp tables
>
+1
> * Doc fixes
So this patch swings the pendulum a bit back towards accepting some things
as errors. That's understandable, as we're never going to have a situation
where we can guarantee that the restore functions never generate an error,
so the best we can do is to draw the error-versus-warning line at a place
that:
* doesn't mess up flawed restores that we would otherwise expect to
complete at least partially
* is easy for us to understand
* is easy for us to explain
* we can live with for the next couple of decades
I don't know where that line should be drawn, so if people are happy with
Jeff's demarcation, then less roll with it.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-25 17:51 Robert Treat <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Robert Treat @ 2025-03-25 17:51 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Corey Huinker <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Tue, Mar 25, 2025 at 1:32 AM Jeff Davis <[email protected]> wrote:
> On Sat, 2025-03-08 at 14:09 -0500, Corey Huinker wrote:
> > >
> > > except it is perfectly clear that you *asked for* data and
> > > statistics, so you get what you asked for. however the user
> > > conjures in their heads what they are looking for, the logic is
> > > simple, you get what you asked for.
> > >
> >
> >
> > They *asked for* that because they didn't have the mechanism to say
> > "hold the mayo" or "everything except pickles". That's reducing their
> > choice, and then blaming them for their choice.
>
> Can we reach a decision here and move forward?
>
>
AFAIK the issue has been settled, or at the least we've agreed to move on.
Robert Treat
https://xzilla.net
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-25 18:42 Jeff Davis <[email protected]>
parent: Corey Huinker <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Jeff Davis @ 2025-03-25 18:42 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Tue, 2025-03-25 at 10:53 -0400, Corey Huinker wrote:
>
> So this patch swings the pendulum a bit back towards accepting some
> things as errors.
Not exactly. I see patch 0001 as a change to the function signatures
from regclass to schemaname/relname, both for usability as well as
control over ERROR vs WARNING.
There's agreement to do so, so I went ahead and committed that part.
> the best we can do is to draw the error-versus-warning line at a
> place that:
>
> * doesn't mess up flawed restores that we would otherwise expect to
> complete at least partially
> * is easy for us to understand
> * is easy for us to explain
> * we can live with for the next couple of decades
The original reason we wanted to issue warnings was to allow ourselves
a chance to change the meaning of parameters, add new parameters, or
even remove parameters without causing restore failures. If there are
any ERRORs that might limit our flexibility I think we should downgrade
those to WARNINGs.
Also, out of a sense of paranoia, it might be good to downgrade some
other ERRORs to WARNINGs, like in 0002. I don't think it's quite as
important as you seem to think, however. It doesn't make a lot of
difference unless the user is running restore with --single-transaction
or --exit-on-error, in which case they probably don't want the restore
to continue if something unexpected happens. I'm fine having the
discussion, though, or we can wait until beta to see what kinds of
problems people encounter.
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-25 19:59 Corey Huinker <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Corey Huinker @ 2025-03-25 19:59 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
>
> The original reason we wanted to issue warnings was to allow ourselves
> a chance to change the meaning of parameters, add new parameters, or
> even remove parameters without causing restore failures. If there are
> any ERRORs that might limit our flexibility I think we should downgrade
> those to WARNINGs.
>
+1
> Also, out of a sense of paranoia, it might be good to downgrade some
> other ERRORs to WARNINGs, like in 0002. I don't think it's quite as
> important as you seem to think, however. It doesn't make a lot of
> difference unless the user is running restore with --single-transaction
> or --exit-on-error, in which case they probably don't want the restore
> to continue if something unexpected happens. I'm fine having the
> discussion, though, or we can wait until beta to see what kinds of
> problems people encounter.
>
At this point, I feel I've demonstrated the limit of what can be made into
WARNINGs, giving us a range of options for now and into the beta. I'll
rebase and move the 0002 patch to be in last position so as to tee up
0003-0004 for consideration.
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-26 01:41 Corey Huinker <[email protected]>
parent: Corey Huinker <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Corey Huinker @ 2025-03-26 01:41 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
>
> At this point, I feel I've demonstrated the limit of what can be made into
> WARNINGs, giving us a range of options for now and into the beta. I'll
> rebase and move the 0002 patch to be in last position so as to tee up
> 0003-0004 for consideration.
>
And here's the rebase (after bde2fb797aaebcbe06bf60f330ba5a068f17dda7).
The order of the patches is different, but the purpose of each is the same
as before.
Attachments:
[text/x-patch] v10-0002-Batching-getAttributeStats.patch (21.5K, ../../CADkLM=ftC94muGKH+z1irdBUXO2+tmBMLdqDAAxcct=H+LE1Eg@mail.gmail.com/3-v10-0002-Batching-getAttributeStats.patch)
download | inline diff:
From 1f9b2578f55fa1233121bcf5949a6f69d6cf8cee Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Fri, 14 Mar 2025 03:54:26 -0400
Subject: [PATCH v10 2/4] Batching getAttributeStats().
The prepared statement getAttributeStats() is fairly heavyweight and
could greatly increase pg_dump/pg_upgrade runtime. To alleviate this,
create a result set buffer of all of the attribute stats fetched for a
batch of 100 relations that could potentially have stats.
The query ensures that the order of results exactly matches the needs of
the code walking the TOC to print the stats calls.
---
src/bin/pg_dump/pg_dump.c | 554 ++++++++++++++++++++++++++------------
1 file changed, 383 insertions(+), 171 deletions(-)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 224dc8c9330..e3f2dac33ec 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -143,6 +143,25 @@ typedef enum OidOptions
zeroAsNone = 4,
} OidOptions;
+typedef enum StatsBufferState
+{
+ STATSBUF_UNINITIALIZED = 0,
+ STATSBUF_ACTIVE,
+ STATSBUF_EXHAUSTED
+} StatsBufferState;
+
+typedef struct
+{
+ PGresult *res; /* results from most recent
+ * getAttributeStats() */
+ int idx; /* first un-consumed row of results */
+ TocEntry *te; /* next TOC entry to search for statsitics
+ * data */
+
+ StatsBufferState state; /* current state of the buffer */
+} AttributeStatsBuffer;
+
+
/* global decls */
static bool dosync = true; /* Issue fsync() to make dump durable on disk. */
@@ -209,6 +228,18 @@ static int nbinaryUpgradeClassOids = 0;
static SequenceItem *sequences = NULL;
static int nsequences = 0;
+static AttributeStatsBuffer attrstats =
+{
+ NULL, 0, NULL, STATSBUF_UNINITIALIZED
+};
+
+/*
+ * The maximum number of relations that should be fetched in any one
+ * getAttributeStats() call.
+ */
+
+#define MAX_ATTR_STATS_RELS 100
+
/*
* The default number of rows per INSERT when
* --inserts is specified without --rows-per-insert
@@ -222,6 +253,8 @@ static int nsequences = 0;
*/
#define MAX_BLOBS_PER_ARCHIVE_ENTRY 1000
+
+
/*
* Macro for producing quoted, schema-qualified name of a dumpable object.
*/
@@ -399,6 +432,9 @@ static void setupDumpWorker(Archive *AH);
static TableInfo *getRootTableInfo(const TableInfo *tbinfo);
static bool forcePartitionRootLoad(const TableInfo *tbinfo);
static void read_dump_filters(const char *filename, DumpOptions *dopt);
+static void appendNamedArgument(PQExpBuffer out, Archive *fout,
+ const char *argname, const char *argtype,
+ const char *argval);
int
@@ -10520,7 +10556,286 @@ statisticsDumpSection(const RelStatsInfo *rsinfo)
}
/*
- * printDumpRelationStats --
+ * Fetch next batch of rows from getAttributeStats()
+ */
+static void
+fetchNextAttributeStats(Archive *fout)
+{
+ ArchiveHandle *AH = (ArchiveHandle *) fout;
+ PQExpBufferData schemas;
+ PQExpBufferData relations;
+ int numoids = 0;
+
+ Assert(AH != NULL);
+
+ /* free last result set, if any */
+ if (attrstats.state == STATSBUF_ACTIVE)
+ PQclear(attrstats.res);
+
+ /* If we have looped around to the start of the TOC, restart */
+ if (attrstats.te == AH->toc)
+ attrstats.te = AH->toc->next;
+
+ initPQExpBuffer(&schemas);
+ initPQExpBuffer(&relations);
+
+ /*
+ * Walk ahead looking for relstats entries that are active in this
+ * section, adding the names to the schemas and relations lists.
+ */
+ while ((attrstats.te != AH->toc) && (numoids < MAX_ATTR_STATS_RELS))
+ {
+ if (attrstats.te->reqs != 0 &&
+ strcmp(attrstats.te->desc, "STATISTICS DATA") == 0)
+ {
+ RelStatsInfo *rsinfo = (RelStatsInfo *) attrstats.te->createDumperArg;
+
+ Assert(rsinfo != NULL);
+
+ if (numoids > 0)
+ {
+ appendPQExpBufferStr(&schemas, ",");
+ appendPQExpBufferStr(&relations, ",");
+ }
+ appendPQExpBufferStr(&schemas, fmtId(rsinfo->dobj.namespace->dobj.name));
+ appendPQExpBufferStr(&relations, fmtId(rsinfo->dobj.name));
+ numoids++;
+ }
+
+ attrstats.te = attrstats.te->next;
+ }
+
+ if (numoids > 0)
+ {
+ PQExpBufferData query;
+
+ initPQExpBuffer(&query);
+ appendPQExpBuffer(&query,
+ "EXECUTE getAttributeStats('{%s}'::pg_catalog.text[],'{%s}'::pg_catalog.text[])",
+ schemas.data, relations.data);
+ attrstats.res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK);
+ attrstats.idx = 0;
+ }
+ else
+ {
+ attrstats.state = STATSBUF_EXHAUSTED;
+ attrstats.res = NULL;
+ attrstats.idx = -1;
+ }
+
+ termPQExpBuffer(&schemas);
+ termPQExpBuffer(&relations);
+}
+
+/*
+ * Prepare the getAttributeStats() statement
+ *
+ * This is done automatically if the user specified dumpStatistics.
+ */
+static void
+initAttributeStats(Archive *fout)
+{
+ ArchiveHandle *AH = (ArchiveHandle *) fout;
+ PQExpBufferData query;
+
+ Assert(AH != NULL);
+ initPQExpBuffer(&query);
+
+ appendPQExpBufferStr(&query,
+ "PREPARE getAttributeStats(pg_catalog.text[], pg_catalog.text[]) AS\n"
+ "SELECT s.schemaname, s.tablename, s.attname, s.inherited, "
+ "s.null_frac, s.avg_width, s.n_distinct, s.most_common_vals, "
+ "s.most_common_freqs, s.histogram_bounds, s.correlation, "
+ "s.most_common_elems, s.most_common_elem_freqs, "
+ "s.elem_count_histogram, ");
+
+ if (fout->remoteVersion >= 170000)
+ appendPQExpBufferStr(&query,
+ "s.range_length_histogram, "
+ "s.range_empty_frac, "
+ "s.range_bounds_histogram ");
+ else
+ appendPQExpBufferStr(&query,
+ "NULL AS range_length_histogram, "
+ "NULL AS range_empty_frac, "
+ " NULL AS range_bounds_histogram ");
+
+ /*
+ * The results must be in the order of relations supplied in the
+ * parameters to ensure that they are in sync with a walk of the TOC.
+ *
+ * The redundant (and incomplete) filter clause on s.tablename = ANY(...)
+ * is a way to lead the query into using the index
+ * pg_class_relname_nsp_index which in turn allows the planner to avoid an
+ * expensive full scan of pg_stats.
+ *
+ * We may need to adjust this query for versions that are not so easily
+ * led.
+ */
+ appendPQExpBufferStr(&query,
+ "FROM pg_catalog.pg_stats AS s "
+ "JOIN unnest($1, $2) WITH ORDINALITY AS u(schemaname, tablename, ord) "
+ "ON s.schemaname = u.schemaname "
+ "AND s.tablename = u.tablename "
+ "WHERE s.tablename = ANY($2) "
+ "ORDER BY u.ord, s.attname, s.inherited");
+
+ ExecuteSqlStatement(fout, query.data);
+
+ termPQExpBuffer(&query);
+
+ attrstats.te = AH->toc->next;
+
+ fetchNextAttributeStats(fout);
+
+ attrstats.state = STATSBUF_ACTIVE;
+}
+
+
+/*
+ * append a single attribute stat to the buffer for this relation.
+ */
+static void
+appendAttributeStats(Archive *fout, PQExpBuffer out,
+ const RelStatsInfo *rsinfo)
+{
+ PGresult *res = attrstats.res;
+ int tup_num = attrstats.idx;
+
+ const char *attname;
+
+ static bool indexes_set = false;
+ static int i_attname,
+ i_inherited,
+ i_null_frac,
+ i_avg_width,
+ i_n_distinct,
+ i_most_common_vals,
+ i_most_common_freqs,
+ i_histogram_bounds,
+ i_correlation,
+ i_most_common_elems,
+ i_most_common_elem_freqs,
+ i_elem_count_histogram,
+ i_range_length_histogram,
+ i_range_empty_frac,
+ i_range_bounds_histogram;
+
+ if (!indexes_set)
+ {
+ /*
+ * It's a prepared statement, so the indexes will be the same for all
+ * result sets, so we only need to set them once.
+ */
+ i_attname = PQfnumber(res, "attname");
+ i_inherited = PQfnumber(res, "inherited");
+ i_null_frac = PQfnumber(res, "null_frac");
+ i_avg_width = PQfnumber(res, "avg_width");
+ i_n_distinct = PQfnumber(res, "n_distinct");
+ i_most_common_vals = PQfnumber(res, "most_common_vals");
+ i_most_common_freqs = PQfnumber(res, "most_common_freqs");
+ i_histogram_bounds = PQfnumber(res, "histogram_bounds");
+ i_correlation = PQfnumber(res, "correlation");
+ i_most_common_elems = PQfnumber(res, "most_common_elems");
+ i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
+ i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
+ i_range_length_histogram = PQfnumber(res, "range_length_histogram");
+ i_range_empty_frac = PQfnumber(res, "range_empty_frac");
+ i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+ indexes_set = true;
+ }
+
+ appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
+ appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+ fout->remoteVersion);
+ appendPQExpBufferStr(out, "\t'schemaname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+ appendPQExpBufferStr(out, ",\n\t'relname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+
+ if (PQgetisnull(res, tup_num, i_attname))
+ pg_fatal("attname cannot be NULL");
+ attname = PQgetvalue(res, tup_num, i_attname);
+
+ /*
+ * Indexes look up attname in indAttNames to derive attnum, all others use
+ * attname directly. We must specify attnum for indexes, since their
+ * attnames are not necessarily stable across dump/reload.
+ */
+ if (rsinfo->nindAttNames == 0)
+ {
+ appendPQExpBuffer(out, ",\n\t'attname', ");
+ appendStringLiteralAH(out, attname, fout);
+ }
+ else
+ {
+ bool found = false;
+
+ for (int i = 0; i < rsinfo->nindAttNames; i++)
+ if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
+ {
+ appendPQExpBuffer(out, ",\n\t'attnum', '%d'::smallint",
+ i + 1);
+ found = true;
+ break;
+ }
+
+ if (!found)
+ pg_fatal("could not find index attname \"%s\"", attname);
+ }
+
+ if (!PQgetisnull(res, tup_num, i_inherited))
+ appendNamedArgument(out, fout, "inherited", "boolean",
+ PQgetvalue(res, tup_num, i_inherited));
+ if (!PQgetisnull(res, tup_num, i_null_frac))
+ appendNamedArgument(out, fout, "null_frac", "real",
+ PQgetvalue(res, tup_num, i_null_frac));
+ if (!PQgetisnull(res, tup_num, i_avg_width))
+ appendNamedArgument(out, fout, "avg_width", "integer",
+ PQgetvalue(res, tup_num, i_avg_width));
+ if (!PQgetisnull(res, tup_num, i_n_distinct))
+ appendNamedArgument(out, fout, "n_distinct", "real",
+ PQgetvalue(res, tup_num, i_n_distinct));
+ if (!PQgetisnull(res, tup_num, i_most_common_vals))
+ appendNamedArgument(out, fout, "most_common_vals", "text",
+ PQgetvalue(res, tup_num, i_most_common_vals));
+ if (!PQgetisnull(res, tup_num, i_most_common_freqs))
+ appendNamedArgument(out, fout, "most_common_freqs", "real[]",
+ PQgetvalue(res, tup_num, i_most_common_freqs));
+ if (!PQgetisnull(res, tup_num, i_histogram_bounds))
+ appendNamedArgument(out, fout, "histogram_bounds", "text",
+ PQgetvalue(res, tup_num, i_histogram_bounds));
+ if (!PQgetisnull(res, tup_num, i_correlation))
+ appendNamedArgument(out, fout, "correlation", "real",
+ PQgetvalue(res, tup_num, i_correlation));
+ if (!PQgetisnull(res, tup_num, i_most_common_elems))
+ appendNamedArgument(out, fout, "most_common_elems", "text",
+ PQgetvalue(res, tup_num, i_most_common_elems));
+ if (!PQgetisnull(res, tup_num, i_most_common_elem_freqs))
+ appendNamedArgument(out, fout, "most_common_elem_freqs", "real[]",
+ PQgetvalue(res, tup_num, i_most_common_elem_freqs));
+ if (!PQgetisnull(res, tup_num, i_elem_count_histogram))
+ appendNamedArgument(out, fout, "elem_count_histogram", "real[]",
+ PQgetvalue(res, tup_num, i_elem_count_histogram));
+ if (fout->remoteVersion >= 170000)
+ {
+ if (!PQgetisnull(res, tup_num, i_range_length_histogram))
+ appendNamedArgument(out, fout, "range_length_histogram", "text",
+ PQgetvalue(res, tup_num, i_range_length_histogram));
+ if (!PQgetisnull(res, tup_num, i_range_empty_frac))
+ appendNamedArgument(out, fout, "range_empty_frac", "real",
+ PQgetvalue(res, tup_num, i_range_empty_frac));
+ if (!PQgetisnull(res, tup_num, i_range_bounds_histogram))
+ appendNamedArgument(out, fout, "range_bounds_histogram", "text",
+ PQgetvalue(res, tup_num, i_range_bounds_histogram));
+ }
+ appendPQExpBufferStr(out, "\n);\n");
+}
+
+
+
+/*
+ * printRelationStats --
*
* Generate the SQL statements needed to restore a relation's statistics.
*/
@@ -10528,64 +10843,21 @@ static char *
printRelationStats(Archive *fout, const void *userArg)
{
const RelStatsInfo *rsinfo = (RelStatsInfo *) userArg;
- const DumpableObject *dobj = &rsinfo->dobj;
+ const DumpableObject *dobj;
+ const char *relschema;
+ const char *relname;
+
+ ArchiveHandle *AH = (ArchiveHandle *) fout;
- PQExpBufferData query;
PQExpBufferData out;
- PGresult *res;
-
- static bool first_query = true;
- static int i_attname;
- static int i_inherited;
- static int i_null_frac;
- static int i_avg_width;
- static int i_n_distinct;
- static int i_most_common_vals;
- static int i_most_common_freqs;
- static int i_histogram_bounds;
- static int i_correlation;
- static int i_most_common_elems;
- static int i_most_common_elem_freqs;
- static int i_elem_count_histogram;
- static int i_range_length_histogram;
- static int i_range_empty_frac;
- static int i_range_bounds_histogram;
-
- initPQExpBuffer(&query);
-
- if (first_query)
- {
- appendPQExpBufferStr(&query,
- "PREPARE getAttributeStats(pg_catalog.text, pg_catalog.text) AS\n"
- "SELECT s.attname, s.inherited, "
- "s.null_frac, s.avg_width, s.n_distinct, "
- "s.most_common_vals, s.most_common_freqs, "
- "s.histogram_bounds, s.correlation, "
- "s.most_common_elems, s.most_common_elem_freqs, "
- "s.elem_count_histogram, ");
-
- if (fout->remoteVersion >= 170000)
- appendPQExpBufferStr(&query,
- "s.range_length_histogram, "
- "s.range_empty_frac, "
- "s.range_bounds_histogram ");
- else
- appendPQExpBufferStr(&query,
- "NULL AS range_length_histogram,"
- "NULL AS range_empty_frac,"
- "NULL AS range_bounds_histogram ");
-
- appendPQExpBufferStr(&query,
- "FROM pg_catalog.pg_stats s "
- "WHERE s.schemaname = $1 "
- "AND s.tablename = $2 "
- "ORDER BY s.attname, s.inherited");
-
- ExecuteSqlStatement(fout, query.data);
-
- resetPQExpBuffer(&query);
- }
+ Assert(rsinfo != NULL);
+ dobj = &rsinfo->dobj;
+ Assert(dobj != NULL);
+ relschema = dobj->namespace->dobj.name;
+ Assert(relschema != NULL);
+ relname = dobj->name;
+ Assert(relname != NULL);
initPQExpBuffer(&out);
@@ -10604,132 +10876,72 @@ printRelationStats(Archive *fout, const void *userArg)
appendPQExpBuffer(&out, "\t'relallvisible', '%d'::integer\n);\n",
rsinfo->relallvisible);
- /* fetch attribute stats */
- appendPQExpBufferStr(&query, "EXECUTE getAttributeStats(");
- appendStringLiteralAH(&query, dobj->namespace->dobj.name, fout);
- appendPQExpBufferStr(&query, ", ");
- appendStringLiteralAH(&query, dobj->name, fout);
- appendPQExpBufferStr(&query, ")");
+ AH->txnCount++;
- res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK);
+ if (attrstats.state == STATSBUF_UNINITIALIZED)
+ initAttributeStats(fout);
- if (first_query)
+ /*
+ * Because the query returns rows in the same order as the relations
+ * requested, and because every relation gets at least one row in the
+ * result set, the first row for this relation must correspond either to
+ * the current row of this result set (if one exists) or the first row of
+ * the next result set (if this one is already consumed).
+ */
+ if (attrstats.state != STATSBUF_ACTIVE)
+ pg_fatal("Exhausted getAttributeStats() before processing %s.%s",
+ rsinfo->dobj.namespace->dobj.name,
+ rsinfo->dobj.name);
+
+ /*
+ * If the current result set has been fully consumed, then the row(s) we
+ * need (if any) would be found in the next one. This will update
+ * attrstats.res and attrstats.idx.
+ */
+ if (PQntuples(attrstats.res) <= attrstats.idx)
+ fetchNextAttributeStats(fout);
+
+ while (true)
{
- i_attname = PQfnumber(res, "attname");
- i_inherited = PQfnumber(res, "inherited");
- i_null_frac = PQfnumber(res, "null_frac");
- i_avg_width = PQfnumber(res, "avg_width");
- i_n_distinct = PQfnumber(res, "n_distinct");
- i_most_common_vals = PQfnumber(res, "most_common_vals");
- i_most_common_freqs = PQfnumber(res, "most_common_freqs");
- i_histogram_bounds = PQfnumber(res, "histogram_bounds");
- i_correlation = PQfnumber(res, "correlation");
- i_most_common_elems = PQfnumber(res, "most_common_elems");
- i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
- i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
- i_range_length_histogram = PQfnumber(res, "range_length_histogram");
- i_range_empty_frac = PQfnumber(res, "range_empty_frac");
- i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
- first_query = false;
- }
-
- /* restore attribute stats */
- for (int rownum = 0; rownum < PQntuples(res); rownum++)
- {
- const char *attname;
-
- appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
- appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n",
- fout->remoteVersion);
- appendPQExpBufferStr(&out, "\t'schemaname', ");
- appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
- appendPQExpBufferStr(&out, ",\n\t'relname', ");
- appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
-
- if (PQgetisnull(res, rownum, i_attname))
- pg_fatal("attname cannot be NULL");
- attname = PQgetvalue(res, rownum, i_attname);
+ int i_schemaname;
+ int i_tablename;
+ char *schemaname;
+ char *tablename; /* misnomer, following pg_stats naming */
/*
- * Indexes look up attname in indAttNames to derive attnum, all others
- * use attname directly. We must specify attnum for indexes, since
- * their attnames are not necessarily stable across dump/reload.
+ * If we hit the end of the result set, then there are no more records
+ * for this relation, so we should stop, but first get the next result
+ * set for the next batch of relations.
*/
- if (rsinfo->nindAttNames == 0)
+ if (PQntuples(attrstats.res) <= attrstats.idx)
{
- appendPQExpBuffer(&out, ",\n\t'attname', ");
- appendStringLiteralAH(&out, attname, fout);
- }
- else
- {
- bool found = false;
-
- for (int i = 0; i < rsinfo->nindAttNames; i++)
- {
- if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
- {
- appendPQExpBuffer(&out, ",\n\t'attnum', '%d'::smallint",
- i + 1);
- found = true;
- break;
- }
- }
-
- if (!found)
- pg_fatal("could not find index attname \"%s\"", attname);
+ fetchNextAttributeStats(fout);
+ break;
}
- if (!PQgetisnull(res, rownum, i_inherited))
- appendNamedArgument(&out, fout, "inherited", "boolean",
- PQgetvalue(res, rownum, i_inherited));
- if (!PQgetisnull(res, rownum, i_null_frac))
- appendNamedArgument(&out, fout, "null_frac", "real",
- PQgetvalue(res, rownum, i_null_frac));
- if (!PQgetisnull(res, rownum, i_avg_width))
- appendNamedArgument(&out, fout, "avg_width", "integer",
- PQgetvalue(res, rownum, i_avg_width));
- if (!PQgetisnull(res, rownum, i_n_distinct))
- appendNamedArgument(&out, fout, "n_distinct", "real",
- PQgetvalue(res, rownum, i_n_distinct));
- if (!PQgetisnull(res, rownum, i_most_common_vals))
- appendNamedArgument(&out, fout, "most_common_vals", "text",
- PQgetvalue(res, rownum, i_most_common_vals));
- if (!PQgetisnull(res, rownum, i_most_common_freqs))
- appendNamedArgument(&out, fout, "most_common_freqs", "real[]",
- PQgetvalue(res, rownum, i_most_common_freqs));
- if (!PQgetisnull(res, rownum, i_histogram_bounds))
- appendNamedArgument(&out, fout, "histogram_bounds", "text",
- PQgetvalue(res, rownum, i_histogram_bounds));
- if (!PQgetisnull(res, rownum, i_correlation))
- appendNamedArgument(&out, fout, "correlation", "real",
- PQgetvalue(res, rownum, i_correlation));
- if (!PQgetisnull(res, rownum, i_most_common_elems))
- appendNamedArgument(&out, fout, "most_common_elems", "text",
- PQgetvalue(res, rownum, i_most_common_elems));
- if (!PQgetisnull(res, rownum, i_most_common_elem_freqs))
- appendNamedArgument(&out, fout, "most_common_elem_freqs", "real[]",
- PQgetvalue(res, rownum, i_most_common_elem_freqs));
- if (!PQgetisnull(res, rownum, i_elem_count_histogram))
- appendNamedArgument(&out, fout, "elem_count_histogram", "real[]",
- PQgetvalue(res, rownum, i_elem_count_histogram));
- if (fout->remoteVersion >= 170000)
- {
- if (!PQgetisnull(res, rownum, i_range_length_histogram))
- appendNamedArgument(&out, fout, "range_length_histogram", "text",
- PQgetvalue(res, rownum, i_range_length_histogram));
- if (!PQgetisnull(res, rownum, i_range_empty_frac))
- appendNamedArgument(&out, fout, "range_empty_frac", "real",
- PQgetvalue(res, rownum, i_range_empty_frac));
- if (!PQgetisnull(res, rownum, i_range_bounds_histogram))
- appendNamedArgument(&out, fout, "range_bounds_histogram", "text",
- PQgetvalue(res, rownum, i_range_bounds_histogram));
- }
- appendPQExpBufferStr(&out, "\n);\n");
+ i_schemaname = PQfnumber(attrstats.res, "schemaname");
+ Assert(i_schemaname >= 0);
+ i_tablename = PQfnumber(attrstats.res, "tablename");
+ Assert(i_tablename >= 0);
+
+ if (PQgetisnull(attrstats.res, attrstats.idx, i_schemaname))
+ pg_fatal("getAttributeStats() schemaname cannot be NULL");
+
+ if (PQgetisnull(attrstats.res, attrstats.idx, i_tablename))
+ pg_fatal("getAttributeStats() tablename cannot be NULL");
+
+ schemaname = PQgetvalue(attrstats.res, attrstats.idx, i_schemaname);
+ tablename = PQgetvalue(attrstats.res, attrstats.idx, i_tablename);
+
+ /* stop if current stat row isn't for this relation */
+ if (strcmp(relname, tablename) != 0 || strcmp(relschema, schemaname) != 0)
+ break;
+
+ appendAttributeStats(fout, &out, rsinfo);
+ AH->txnCount++;
+ attrstats.idx++;
}
- PQclear(res);
-
- termPQExpBuffer(&query);
return out.data;
}
--
2.49.0
[text/x-patch] v10-0004-Downgrade-many-pg_restore_-_stats-errors-to-warn.patch (30.4K, ../../CADkLM=ftC94muGKH+z1irdBUXO2+tmBMLdqDAAxcct=H+LE1Eg@mail.gmail.com/4-v10-0004-Downgrade-many-pg_restore_-_stats-errors-to-warn.patch)
download | inline diff:
From 651e70ae705d5a4f081509e66a743422d2e86ae4 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sat, 8 Mar 2025 00:52:41 -0500
Subject: [PATCH v10 4/4] Downgrade many pg_restore_*_stats errors to warnings.
We want to avoid errors that can potentially stop an otherwise
successful pg_upgrade or pg_restore operation. With that in mind, change
as many ERROR reports to WARNING + early termination with no data
updated.
---
src/include/statistics/stat_utils.h | 4 +-
src/backend/statistics/attribute_stats.c | 120 ++++++++++----
src/backend/statistics/relation_stats.c | 12 +-
src/backend/statistics/stat_utils.c | 65 ++++++--
src/test/regress/expected/stats_import.out | 184 ++++++++++++++++-----
src/test/regress/sql/stats_import.sql | 36 ++--
6 files changed, 309 insertions(+), 112 deletions(-)
diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h
index 512eb776e0e..809c8263a41 100644
--- a/src/include/statistics/stat_utils.h
+++ b/src/include/statistics/stat_utils.h
@@ -21,7 +21,7 @@ struct StatsArgInfo
Oid argtype;
};
-extern void stats_check_required_arg(FunctionCallInfo fcinfo,
+extern bool stats_check_required_arg(FunctionCallInfo fcinfo,
struct StatsArgInfo *arginfo,
int argnum);
extern bool stats_check_arg_array(FunctionCallInfo fcinfo,
@@ -30,7 +30,7 @@ extern bool stats_check_arg_pair(FunctionCallInfo fcinfo,
struct StatsArgInfo *arginfo,
int argnum1, int argnum2);
-extern void stats_lock_check_privileges(Oid reloid);
+extern bool stats_lock_check_privileges(Oid reloid);
extern Oid stats_lookup_relid(const char *nspname, const char *relname);
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index f5eb17ba42d..b7ba1622391 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -100,7 +100,7 @@ static struct StatsArgInfo cleararginfo[] =
static bool attribute_statistics_update(FunctionCallInfo fcinfo);
static Node *get_attr_expr(Relation rel, int attnum);
-static void get_attr_stat_type(Oid reloid, AttrNumber attnum,
+static bool get_attr_stat_type(Oid reloid, AttrNumber attnum,
Oid *atttypid, int32 *atttypmod,
char *atttyptype, Oid *atttypcoll,
Oid *eq_opr, Oid *lt_opr);
@@ -129,10 +129,12 @@ static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
* stored as an anyarray, and the representation of the array needs to store
* the correct element type, which must be derived from the attribute.
*
- * Major errors, such as the table not existing, the attribute not existing,
- * or a permissions failure are always reported at ERROR. Other errors, such
- * as a conversion failure on one statistic kind, are reported as a WARNING
- * and other statistic kinds may still be updated.
+ * This function is called during database upgrades and restorations, therefore
+ * it is imperative to avoid ERRORs that could potentially end the upgrade or
+ * restore unless. Major errors, such as the table not existing, the attribute
+ * not existing, or permissions failure are reported as WARNINGs with an end to
+ * the function, thus allowing the upgrade/restore to continue, but without the
+ * stats that can be regenereated once the database is online again.
*/
static bool
attribute_statistics_update(FunctionCallInfo fcinfo)
@@ -148,8 +150,8 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
HeapTuple statup;
Oid atttypid = InvalidOid;
- int32 atttypmod;
- char atttyptype;
+ int32 atttypmod = -1;
+ char atttyptype = TYPTYPE_PSEUDO; /* Not a great default, but there is no TYPTYPE_INVALID */
Oid atttypcoll = InvalidOid;
Oid eq_opr = InvalidOid;
Oid lt_opr = InvalidOid;
@@ -176,38 +178,52 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
bool result = true;
- stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG);
- stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG);
+ if (!stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG))
+ return false;
+ if (!stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG))
+ return false;
nspname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELSCHEMA_ARG));
relname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELNAME_ARG));
reloid = stats_lookup_relid(nspname, relname);
+ if (!OidIsValid(reloid))
+ return false;
if (RecoveryInProgress())
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("recovery is in progress"),
errhint("Statistics cannot be modified during recovery.")));
+ return false;
+ }
/* lock before looking up attribute */
- stats_lock_check_privileges(reloid);
+ if (!stats_lock_check_privileges(reloid))
+ return false;
/* user can specify either attname or attnum, but not both */
if (!PG_ARGISNULL(ATTNAME_ARG))
{
if (!PG_ARGISNULL(ATTNUM_ARG))
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("cannot specify both attname and attnum")));
+ return false;
+ }
attname = TextDatumGetCString(PG_GETARG_DATUM(ATTNAME_ARG));
attnum = get_attnum(reloid, attname);
/* note that this test covers attisdropped cases too: */
if (attnum == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
attname, relname)));
+ return false;
+ }
}
else if (!PG_ARGISNULL(ATTNUM_ARG))
{
@@ -216,27 +232,33 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
/* annoyingly, get_attname doesn't check attisdropped */
if (attname == NULL ||
!SearchSysCacheExistsAttName(reloid, attname))
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column %d of relation \"%s\" does not exist",
attnum, relname)));
+ return false;
+ }
}
else
{
- ereport(ERROR,
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("must specify either attname or attnum")));
- attname = NULL; /* keep compiler quiet */
- attnum = 0;
+ return false;
}
if (attnum < 0)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot modify statistics on system column \"%s\"",
attname)));
+ return false;
+ }
- stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG);
+ if (!stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG))
+ return false;
inherited = PG_GETARG_BOOL(INHERITED_ARG);
/*
@@ -285,10 +307,11 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
}
/* derive information from attribute */
- get_attr_stat_type(reloid, attnum,
- &atttypid, &atttypmod,
- &atttyptype, &atttypcoll,
- &eq_opr, <_opr);
+ if (!get_attr_stat_type(reloid, attnum,
+ &atttypid, &atttypmod,
+ &atttyptype, &atttypcoll,
+ &eq_opr, <_opr))
+ result = false;
/* if needed, derive element type */
if (do_mcelem || do_dechist)
@@ -568,7 +591,7 @@ get_attr_expr(Relation rel, int attnum)
/*
* Derive type information from the attribute.
*/
-static void
+static bool
get_attr_stat_type(Oid reloid, AttrNumber attnum,
Oid *atttypid, int32 *atttypmod,
char *atttyptype, Oid *atttypcoll,
@@ -585,18 +608,26 @@ get_attr_stat_type(Oid reloid, AttrNumber attnum,
/* Attribute not found */
if (!HeapTupleIsValid(atup))
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("attribute %d of relation \"%s\" does not exist",
attnum, RelationGetRelationName(rel))));
+ relation_close(rel, NoLock);
+ return false;
+ }
attr = (Form_pg_attribute) GETSTRUCT(atup);
if (attr->attisdropped)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("attribute %d of relation \"%s\" does not exist",
attnum, RelationGetRelationName(rel))));
+ relation_close(rel, NoLock);
+ return false;
+ }
expr = get_attr_expr(rel, attr->attnum);
@@ -645,6 +676,7 @@ get_attr_stat_type(Oid reloid, AttrNumber attnum,
*atttypcoll = DEFAULT_COLLATION_OID;
relation_close(rel, NoLock);
+ return true;
}
/*
@@ -770,6 +802,10 @@ set_stats_slot(Datum *values, bool *nulls, bool *replaces,
if (slotidx >= STATISTIC_NUM_SLOTS && first_empty >= 0)
slotidx = first_empty;
+ /*
+ * Currently there is no datatype that can have more than STATISTIC_NUM_SLOTS
+ * statistic kinds, so this can safely remain an ERROR for now.
+ */
if (slotidx >= STATISTIC_NUM_SLOTS)
ereport(ERROR,
(errmsg("maximum number of statistics slots exceeded: %d",
@@ -915,38 +951,54 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
AttrNumber attnum;
bool inherited;
- stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG);
- stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG);
- stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
- stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
+ if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG))
+ PG_RETURN_VOID();
+ if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG))
+ PG_RETURN_VOID();
+ if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG))
+ PG_RETURN_VOID();
+ if (!stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG))
+ PG_RETURN_VOID();
nspname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELSCHEMA_ARG));
relname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELNAME_ARG));
reloid = stats_lookup_relid(nspname, relname);
+ if (!OidIsValid(reloid))
+ PG_RETURN_VOID();
if (RecoveryInProgress())
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("recovery is in progress"),
errhint("Statistics cannot be modified during recovery.")));
+ PG_RETURN_VOID();
+ }
- stats_lock_check_privileges(reloid);
+ if (!stats_lock_check_privileges(reloid))
+ PG_RETURN_VOID();
attname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTNAME_ARG));
attnum = get_attnum(reloid, attname);
if (attnum < 0)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot clear statistics on system column \"%s\"",
attname)));
+ PG_RETURN_VOID();
+ }
if (attnum == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
attname, get_rel_name(reloid))));
+ PG_RETURN_VOID();
+ }
inherited = PG_GETARG_BOOL(C_INHERITED_ARG);
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index cd3a75b621a..7c47af15c9f 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -83,13 +83,18 @@ relation_statistics_update(FunctionCallInfo fcinfo)
bool nulls[4] = {0};
int nreplaces = 0;
- stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG);
- stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG);
+ if (!stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG))
+ return false;
+
+ if (!stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG))
+ return false;
nspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
reloid = stats_lookup_relid(nspname, relname);
+ if (!OidIsValid(reloid))
+ return false;
if (RecoveryInProgress())
ereport(ERROR,
@@ -97,7 +102,8 @@ relation_statistics_update(FunctionCallInfo fcinfo)
errmsg("recovery is in progress"),
errhint("Statistics cannot be modified during recovery.")));
- stats_lock_check_privileges(reloid);
+ if (!stats_lock_check_privileges(reloid))
+ return false;
if (!PG_ARGISNULL(RELPAGES_ARG))
{
diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c
index a9a3224efe6..d587e875457 100644
--- a/src/backend/statistics/stat_utils.c
+++ b/src/backend/statistics/stat_utils.c
@@ -33,16 +33,20 @@
/*
* Ensure that a given argument is not null.
*/
-void
+bool
stats_check_required_arg(FunctionCallInfo fcinfo,
struct StatsArgInfo *arginfo,
int argnum)
{
if (PG_ARGISNULL(argnum))
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("\"%s\" cannot be NULL",
arginfo[argnum].argname)));
+ return false;
+ }
+ return true;
}
/*
@@ -127,13 +131,14 @@ stats_check_arg_pair(FunctionCallInfo fcinfo,
* - the role owns the current database and the relation is not shared
* - the role has the MAINTAIN privilege on the relation
*/
-void
+bool
stats_lock_check_privileges(Oid reloid)
{
Relation table;
Oid table_oid = reloid;
Oid index_oid = InvalidOid;
LOCKMODE index_lockmode = NoLock;
+ bool ok = true;
/*
* For indexes, we follow the locking behavior in do_analyze_rel() and
@@ -173,14 +178,15 @@ stats_lock_check_privileges(Oid reloid)
case RELKIND_PARTITIONED_TABLE:
break;
default:
- ereport(ERROR,
+ ereport(WARNING,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot modify statistics for relation \"%s\"",
RelationGetRelationName(table)),
errdetail_relkind_not_supported(table->rd_rel->relkind)));
+ ok = false;
}
- if (OidIsValid(index_oid))
+ if (ok && (OidIsValid(index_oid)))
{
Relation index;
@@ -193,25 +199,33 @@ stats_lock_check_privileges(Oid reloid)
relation_close(index, NoLock);
}
- if (table->rd_rel->relisshared)
- ereport(ERROR,
+ if (ok && (table->rd_rel->relisshared))
+ {
+ ereport(WARNING,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot modify statistics for shared relation")));
+ ok = false;
+ }
- if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()))
+ if (ok && (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId())))
{
AclResult aclresult = pg_class_aclcheck(RelationGetRelid(table),
GetUserId(),
ACL_MAINTAIN);
if (aclresult != ACLCHECK_OK)
- aclcheck_error(aclresult,
- get_relkind_objtype(table->rd_rel->relkind),
- NameStr(table->rd_rel->relname));
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied for relation %s",
+ NameStr(table->rd_rel->relname))));
+ ok = false;
+ }
}
/* retain lock on table */
relation_close(table, NoLock);
+ return ok;
}
/*
@@ -223,10 +237,20 @@ stats_lookup_relid(const char *nspname, const char *relname)
Oid nspoid;
Oid reloid;
- nspoid = LookupExplicitNamespace(nspname, false);
+ nspoid = LookupExplicitNamespace(nspname, true);
+ if (!OidIsValid(nspoid))
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ nspname, relname)));
+
+ return InvalidOid;
+ }
+
reloid = get_relname_relid(relname, nspoid);
if (!OidIsValid(reloid))
- ereport(ERROR,
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s.%s\" does not exist",
nspname, relname)));
@@ -303,9 +327,12 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
&args, &types, &argnulls);
if (nargs % 2 != 0)
- ereport(ERROR,
+ {
+ ereport(WARNING,
errmsg("variadic arguments must be name/value pairs"),
errhint("Provide an even number of variadic arguments that can be divided into pairs."));
+ return false;
+ }
/*
* For each argument name/value pair, find corresponding positional
@@ -318,14 +345,20 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
char *argname;
if (argnulls[i])
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errmsg("name at variadic position %d is NULL", i + 1)));
+ return false;
+ }
if (types[i] != TEXTOID)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errmsg("name at variadic position %d has type \"%s\", expected type \"%s\"",
i + 1, format_type_be(types[i]),
format_type_be(TEXTOID))));
+ return false;
+ }
if (argnulls[i + 1])
continue;
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 48d6392b4ad..161cf67b711 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -46,49 +46,85 @@ SELECT pg_clear_relation_stats('stats_import', 'test');
--
-- relstats tests
--
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'relname', 'test',
'relpages', 17::integer);
-ERROR: "schemaname" cannot be NULL
--- error: relname missing
+WARNING: "schemaname" cannot be NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+-- warning: relname missing, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relpages', 17::integer);
-ERROR: "relname" cannot be NULL
---- error: schemaname is wrong type
+WARNING: "relname" cannot be NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+--- warning: schemaname is wrong type, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 3.6::float,
'relname', 'test',
'relpages', 17::integer);
WARNING: argument "schemaname" has type "double precision", expected type "text"
-ERROR: "schemaname" cannot be NULL
---- error: relname is wrong type
+WARNING: "schemaname" cannot be NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+--- warning: relname is wrong type, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 0::oid,
'relpages', 17::integer);
WARNING: argument "relname" has type "oid", expected type "text"
-ERROR: "relname" cannot be NULL
--- error: relation not found
+WARNING: "relname" cannot be NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+-- warning: relation not found, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'nope',
'relpages', 17::integer);
-ERROR: relation "stats_import.nope" does not exist
--- error: odd number of variadic arguments cannot be pairs
+WARNING: relation "stats_import.nope" does not exist
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+-- warning: odd number of variadic arguments cannot be pairs, nothing updated
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
'relallvisible');
-ERROR: variadic arguments must be name/value pairs
+WARNING: variadic arguments must be name/value pairs
HINT: Provide an even number of variadic arguments that can be divided into pairs.
--- error: argument name is NULL
+WARNING: "schemaname" cannot be NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+-- warning: argument name is NULL, nothing updated
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
NULL, '17'::integer);
-ERROR: name at variadic position 5 is NULL
+WARNING: name at variadic position 5 is NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
-- starting stats
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
@@ -340,65 +376,110 @@ CREATE SEQUENCE stats_import.testseq;
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'testseq');
-ERROR: cannot modify statistics for relation "testseq"
+WARNING: cannot modify statistics for relation "testseq"
DETAIL: This operation is not supported for sequences.
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
-ERROR: cannot modify statistics for relation "testseq"
+WARNING: cannot modify statistics for relation "testseq"
DETAIL: This operation is not supported for sequences.
+ pg_clear_relation_stats
+-------------------------
+
+(1 row)
+
CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
-ERROR: cannot modify statistics for relation "testview"
+WARNING: cannot modify statistics for relation "testview"
DETAIL: This operation is not supported for views.
+ pg_clear_relation_stats
+-------------------------
+
+(1 row)
+
--
-- attribute stats
--
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: "schemaname" cannot be NULL
--- error: schema does not exist
+WARNING: "schemaname" cannot be NULL
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: schema does not exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'nope',
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: schema "nope" does not exist
--- error: relname missing
+WARNING: relation "nope.test" does not exist
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: relname missing, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: "relname" cannot be NULL
--- error: relname does not exist
+WARNING: "relname" cannot be NULL
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: relname does not exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'nope',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: relation "stats_import.nope" does not exist
--- error: relname null
+WARNING: relation "stats_import.nope" does not exist
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: relname null, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', NULL,
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: "relname" cannot be NULL
--- error: NULL attname
+WARNING: "relname" cannot be NULL
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: NULL attname, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', NULL,
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: must specify either attname or attnum
--- error: attname doesn't exist
+WARNING: must specify either attname or attnum
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: attname doesn't exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -407,8 +488,13 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'null_frac', 0.1::real,
'avg_width', 2::integer,
'n_distinct', 0.3::real);
-ERROR: column "nope" of relation "test" does not exist
--- error: both attname and attnum
+WARNING: column "nope" of relation "test" does not exist
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: both attname and attnum, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -416,30 +502,50 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: cannot specify both attname and attnum
--- error: neither attname nor attnum
+WARNING: cannot specify both attname and attnum
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: neither attname nor attnum, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: must specify either attname or attnum
--- error: attribute is system column
+WARNING: must specify either attname or attnum
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: attribute is system column, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'xmin',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: cannot modify statistics on system column "xmin"
--- error: inherited null
+WARNING: cannot modify statistics on system column "xmin"
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: inherited null, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'id',
'inherited', NULL::boolean,
'null_frac', 0.1::real);
-ERROR: "inherited" cannot be NULL
+WARNING: "inherited" cannot be NULL
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
-- ok: just the fixed values, with version, no stakinds
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index d140733a750..be8045ceea5 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -39,41 +39,41 @@ SELECT pg_clear_relation_stats('stats_import', 'test');
-- relstats tests
--
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'relname', 'test',
'relpages', 17::integer);
--- error: relname missing
+-- warning: relname missing, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relpages', 17::integer);
---- error: schemaname is wrong type
+--- warning: schemaname is wrong type, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 3.6::float,
'relname', 'test',
'relpages', 17::integer);
---- error: relname is wrong type
+--- warning: relname is wrong type, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 0::oid,
'relpages', 17::integer);
--- error: relation not found
+-- warning: relation not found, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'nope',
'relpages', 17::integer);
--- error: odd number of variadic arguments cannot be pairs
+-- warning: odd number of variadic arguments cannot be pairs, nothing updated
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
'relallvisible');
--- error: argument name is NULL
+-- warning: argument name is NULL, nothing updated
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -246,14 +246,14 @@ SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname
-- attribute stats
--
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: schema does not exist
+-- warning: schema does not exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'nope',
'relname', 'test',
@@ -261,14 +261,14 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: relname missing
+-- warning: relname missing, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: relname does not exist
+-- warning: relname does not exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'nope',
@@ -276,7 +276,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: relname null
+-- warning: relname null, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', NULL,
@@ -284,7 +284,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: NULL attname
+-- warning: NULL attname, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -292,7 +292,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: attname doesn't exist
+-- warning: attname doesn't exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -302,7 +302,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'avg_width', 2::integer,
'n_distinct', 0.3::real);
--- error: both attname and attnum
+-- warning: both attname and attnum, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -311,14 +311,14 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: neither attname nor attnum
+-- warning: neither attname nor attnum, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: attribute is system column
+-- warning: attribute is system column, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -326,7 +326,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: inherited null
+-- warning: inherited null, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
--
2.49.0
[text/x-patch] v10-0001-Introduce-CreateStmtPtr.patch (17.2K, ../../CADkLM=ftC94muGKH+z1irdBUXO2+tmBMLdqDAAxcct=H+LE1Eg@mail.gmail.com/5-v10-0001-Introduce-CreateStmtPtr.patch)
download | inline diff:
From 8611beb5a7906d0f7e93fb68aa41dd58bc7ab80f Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Fri, 14 Mar 2025 01:06:19 -0400
Subject: [PATCH v10 1/4] Introduce CreateStmtPtr.
CreateStmtPtr is a function pointer that can replace the createStmt/defn
parameter. This is useful in situations where the amount of text
generated for a definition is so large that it is undesirable to hold
many such objects in memory at the same time.
Using functions of this type, the text created is then immediately
written out to the appropriate file for the given dump format.
---
src/bin/pg_dump/pg_backup.h | 2 +
src/bin/pg_dump/pg_backup_archiver.c | 22 ++-
src/bin/pg_dump/pg_backup_archiver.h | 7 +
src/bin/pg_dump/pg_dump.c | 230 +++++++++++++++------------
4 files changed, 156 insertions(+), 105 deletions(-)
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index 658986de6f8..fdcccd64a70 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -289,6 +289,8 @@ typedef int (*DataDumperPtr) (Archive *AH, const void *userArg);
typedef void (*SetupWorkerPtrType) (Archive *AH);
+typedef char *(*CreateStmtPtr) (Archive *AH, const void *userArg);
+
/*
* Main archiver interface.
*/
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 82d51c89ac6..e512201ed58 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -1264,6 +1264,9 @@ ArchiveEntry(Archive *AHX, CatalogId catalogId, DumpId dumpId,
newToc->dataDumper = opts->dumpFn;
newToc->dataDumperArg = opts->dumpArg;
newToc->hadDumper = opts->dumpFn ? true : false;
+ newToc->createDumper = opts->createFn;
+ newToc->createDumperArg = opts->createArg;
+ newToc->hadCreateDumper = opts->createFn ? true : false;
newToc->formatData = NULL;
newToc->dataLength = 0;
@@ -2620,7 +2623,17 @@ WriteToc(ArchiveHandle *AH)
WriteStr(AH, te->tag);
WriteStr(AH, te->desc);
WriteInt(AH, te->section);
- WriteStr(AH, te->defn);
+
+ if (te->hadCreateDumper)
+ {
+ char *defn = te->createDumper((Archive *) AH, te->createDumperArg);
+
+ WriteStr(AH, defn);
+ pg_free(defn);
+ }
+ else
+ WriteStr(AH, te->defn);
+
WriteStr(AH, te->dropStmt);
WriteStr(AH, te->copyStmt);
WriteStr(AH, te->namespace);
@@ -3856,6 +3869,13 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, const char *pfx)
{
IssueACLPerBlob(AH, te);
}
+ else if (te->hadCreateDumper)
+ {
+ char *ptr = te->createDumper((Archive *) AH, te->createDumperArg);
+
+ ahwrite(ptr, 1, strlen(ptr), AH);
+ pg_free(ptr);
+ }
else if (te->defn && strlen(te->defn) > 0)
{
ahprintf(AH, "%s\n\n", te->defn);
diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h
index a2064f471ed..e68db633995 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -368,6 +368,11 @@ struct _tocEntry
const void *dataDumperArg; /* Arg for above routine */
void *formatData; /* TOC Entry data specific to file format */
+ CreateStmtPtr createDumper; /* Routine for create statement creation */
+ const void *createDumperArg; /* arg for the above routine */
+ bool hadCreateDumper; /* Archiver was passed a create statement
+ * routine */
+
/* working state while dumping/restoring */
pgoff_t dataLength; /* item's data size; 0 if none or unknown */
int reqs; /* do we need schema and/or data of object
@@ -407,6 +412,8 @@ typedef struct _archiveOpts
int nDeps;
DataDumperPtr dumpFn;
const void *dumpArg;
+ CreateStmtPtr createFn;
+ const void *createArg;
} ArchiveOpts;
#define ARCHIVE_OPTS(...) &(ArchiveOpts){__VA_ARGS__}
/* Called to add a TOC entry */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e41e645f649..224dc8c9330 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -10520,51 +10520,44 @@ statisticsDumpSection(const RelStatsInfo *rsinfo)
}
/*
- * dumpRelationStats --
+ * printDumpRelationStats --
*
- * Dump command to import stats into the relation on the new database.
+ * Generate the SQL statements needed to restore a relation's statistics.
*/
-static void
-dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
+static char *
+printRelationStats(Archive *fout, const void *userArg)
{
+ const RelStatsInfo *rsinfo = (RelStatsInfo *) userArg;
const DumpableObject *dobj = &rsinfo->dobj;
+
+ PQExpBufferData query;
+ PQExpBufferData out;
+
PGresult *res;
- PQExpBuffer query;
- PQExpBuffer out;
- DumpId *deps = NULL;
- int ndeps = 0;
- int i_attname;
- int i_inherited;
- int i_null_frac;
- int i_avg_width;
- int i_n_distinct;
- int i_most_common_vals;
- int i_most_common_freqs;
- int i_histogram_bounds;
- int i_correlation;
- int i_most_common_elems;
- int i_most_common_elem_freqs;
- int i_elem_count_histogram;
- int i_range_length_histogram;
- int i_range_empty_frac;
- int i_range_bounds_histogram;
- /* nothing to do if we are not dumping statistics */
- if (!fout->dopt->dumpStatistics)
- return;
+ static bool first_query = true;
+ static int i_attname;
+ static int i_inherited;
+ static int i_null_frac;
+ static int i_avg_width;
+ static int i_n_distinct;
+ static int i_most_common_vals;
+ static int i_most_common_freqs;
+ static int i_histogram_bounds;
+ static int i_correlation;
+ static int i_most_common_elems;
+ static int i_most_common_elem_freqs;
+ static int i_elem_count_histogram;
+ static int i_range_length_histogram;
+ static int i_range_empty_frac;
+ static int i_range_bounds_histogram;
- /* dependent on the relation definition, if doing schema */
- if (fout->dopt->dumpSchema)
+ initPQExpBuffer(&query);
+
+ if (first_query)
{
- deps = dobj->dependencies;
- ndeps = dobj->nDeps;
- }
-
- query = createPQExpBuffer();
- if (!fout->is_prepared[PREPQUERY_GETATTRIBUTESTATS])
- {
- appendPQExpBufferStr(query,
- "PREPARE getAttributeStats(pg_catalog.name, pg_catalog.name) AS\n"
+ appendPQExpBufferStr(&query,
+ "PREPARE getAttributeStats(pg_catalog.text, pg_catalog.text) AS\n"
"SELECT s.attname, s.inherited, "
"s.null_frac, s.avg_width, s.n_distinct, "
"s.most_common_vals, s.most_common_freqs, "
@@ -10573,82 +10566,85 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
"s.elem_count_histogram, ");
if (fout->remoteVersion >= 170000)
- appendPQExpBufferStr(query,
+ appendPQExpBufferStr(&query,
"s.range_length_histogram, "
"s.range_empty_frac, "
"s.range_bounds_histogram ");
else
- appendPQExpBufferStr(query,
+ appendPQExpBufferStr(&query,
"NULL AS range_length_histogram,"
"NULL AS range_empty_frac,"
"NULL AS range_bounds_histogram ");
- appendPQExpBufferStr(query,
+ appendPQExpBufferStr(&query,
"FROM pg_catalog.pg_stats s "
"WHERE s.schemaname = $1 "
"AND s.tablename = $2 "
"ORDER BY s.attname, s.inherited");
- ExecuteSqlStatement(fout, query->data);
+ ExecuteSqlStatement(fout, query.data);
- fout->is_prepared[PREPQUERY_GETATTRIBUTESTATS] = true;
- resetPQExpBuffer(query);
+ resetPQExpBuffer(&query);
}
- out = createPQExpBuffer();
+ initPQExpBuffer(&out);
/* restore relation stats */
- appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
- appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+ appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
+ appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n",
fout->remoteVersion);
- appendPQExpBufferStr(out, "\t'schemaname', ");
- appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
- appendPQExpBufferStr(out, ",\n");
- appendPQExpBufferStr(out, "\t'relname', ");
- appendStringLiteralAH(out, rsinfo->dobj.name, fout);
- appendPQExpBufferStr(out, ",\n");
- appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
- appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
- appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer\n);\n",
+ appendPQExpBufferStr(&out, "\t'schemaname', ");
+ appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
+ appendPQExpBufferStr(&out, ",\n");
+ appendPQExpBufferStr(&out, "\t'relname', ");
+ appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
+ appendPQExpBufferStr(&out, ",\n");
+ appendPQExpBuffer(&out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
+ appendPQExpBuffer(&out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
+ appendPQExpBuffer(&out, "\t'relallvisible', '%d'::integer\n);\n",
rsinfo->relallvisible);
/* fetch attribute stats */
- appendPQExpBufferStr(query, "EXECUTE getAttributeStats(");
- appendStringLiteralAH(query, dobj->namespace->dobj.name, fout);
- appendPQExpBufferStr(query, ", ");
- appendStringLiteralAH(query, dobj->name, fout);
- appendPQExpBufferStr(query, ");");
+ appendPQExpBufferStr(&query, "EXECUTE getAttributeStats(");
+ appendStringLiteralAH(&query, dobj->namespace->dobj.name, fout);
+ appendPQExpBufferStr(&query, ", ");
+ appendStringLiteralAH(&query, dobj->name, fout);
+ appendPQExpBufferStr(&query, ")");
- res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+ res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK);
- i_attname = PQfnumber(res, "attname");
- i_inherited = PQfnumber(res, "inherited");
- i_null_frac = PQfnumber(res, "null_frac");
- i_avg_width = PQfnumber(res, "avg_width");
- i_n_distinct = PQfnumber(res, "n_distinct");
- i_most_common_vals = PQfnumber(res, "most_common_vals");
- i_most_common_freqs = PQfnumber(res, "most_common_freqs");
- i_histogram_bounds = PQfnumber(res, "histogram_bounds");
- i_correlation = PQfnumber(res, "correlation");
- i_most_common_elems = PQfnumber(res, "most_common_elems");
- i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
- i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
- i_range_length_histogram = PQfnumber(res, "range_length_histogram");
- i_range_empty_frac = PQfnumber(res, "range_empty_frac");
- i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+ if (first_query)
+ {
+ i_attname = PQfnumber(res, "attname");
+ i_inherited = PQfnumber(res, "inherited");
+ i_null_frac = PQfnumber(res, "null_frac");
+ i_avg_width = PQfnumber(res, "avg_width");
+ i_n_distinct = PQfnumber(res, "n_distinct");
+ i_most_common_vals = PQfnumber(res, "most_common_vals");
+ i_most_common_freqs = PQfnumber(res, "most_common_freqs");
+ i_histogram_bounds = PQfnumber(res, "histogram_bounds");
+ i_correlation = PQfnumber(res, "correlation");
+ i_most_common_elems = PQfnumber(res, "most_common_elems");
+ i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
+ i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
+ i_range_length_histogram = PQfnumber(res, "range_length_histogram");
+ i_range_empty_frac = PQfnumber(res, "range_empty_frac");
+ i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+ first_query = false;
+ }
/* restore attribute stats */
for (int rownum = 0; rownum < PQntuples(res); rownum++)
{
const char *attname;
- appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
- appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+ appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
+ appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n",
fout->remoteVersion);
- appendPQExpBufferStr(out, "\t'schemaname', ");
- appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
- appendPQExpBufferStr(out, ",\n\t'relname', ");
- appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+ appendPQExpBufferStr(&out, "\t'schemaname', ");
+ appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
+ appendPQExpBufferStr(&out, ",\n\t'relname', ");
+ appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
if (PQgetisnull(res, rownum, i_attname))
pg_fatal("attname cannot be NULL");
@@ -10661,8 +10657,8 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
*/
if (rsinfo->nindAttNames == 0)
{
- appendPQExpBuffer(out, ",\n\t'attname', ");
- appendStringLiteralAH(out, attname, fout);
+ appendPQExpBuffer(&out, ",\n\t'attname', ");
+ appendStringLiteralAH(&out, attname, fout);
}
else
{
@@ -10672,7 +10668,7 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
{
if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
{
- appendPQExpBuffer(out, ",\n\t'attnum', '%d'::smallint",
+ appendPQExpBuffer(&out, ",\n\t'attnum', '%d'::smallint",
i + 1);
found = true;
break;
@@ -10684,67 +10680,93 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
}
if (!PQgetisnull(res, rownum, i_inherited))
- appendNamedArgument(out, fout, "inherited", "boolean",
+ appendNamedArgument(&out, fout, "inherited", "boolean",
PQgetvalue(res, rownum, i_inherited));
if (!PQgetisnull(res, rownum, i_null_frac))
- appendNamedArgument(out, fout, "null_frac", "real",
+ appendNamedArgument(&out, fout, "null_frac", "real",
PQgetvalue(res, rownum, i_null_frac));
if (!PQgetisnull(res, rownum, i_avg_width))
- appendNamedArgument(out, fout, "avg_width", "integer",
+ appendNamedArgument(&out, fout, "avg_width", "integer",
PQgetvalue(res, rownum, i_avg_width));
if (!PQgetisnull(res, rownum, i_n_distinct))
- appendNamedArgument(out, fout, "n_distinct", "real",
+ appendNamedArgument(&out, fout, "n_distinct", "real",
PQgetvalue(res, rownum, i_n_distinct));
if (!PQgetisnull(res, rownum, i_most_common_vals))
- appendNamedArgument(out, fout, "most_common_vals", "text",
+ appendNamedArgument(&out, fout, "most_common_vals", "text",
PQgetvalue(res, rownum, i_most_common_vals));
if (!PQgetisnull(res, rownum, i_most_common_freqs))
- appendNamedArgument(out, fout, "most_common_freqs", "real[]",
+ appendNamedArgument(&out, fout, "most_common_freqs", "real[]",
PQgetvalue(res, rownum, i_most_common_freqs));
if (!PQgetisnull(res, rownum, i_histogram_bounds))
- appendNamedArgument(out, fout, "histogram_bounds", "text",
+ appendNamedArgument(&out, fout, "histogram_bounds", "text",
PQgetvalue(res, rownum, i_histogram_bounds));
if (!PQgetisnull(res, rownum, i_correlation))
- appendNamedArgument(out, fout, "correlation", "real",
+ appendNamedArgument(&out, fout, "correlation", "real",
PQgetvalue(res, rownum, i_correlation));
if (!PQgetisnull(res, rownum, i_most_common_elems))
- appendNamedArgument(out, fout, "most_common_elems", "text",
+ appendNamedArgument(&out, fout, "most_common_elems", "text",
PQgetvalue(res, rownum, i_most_common_elems));
if (!PQgetisnull(res, rownum, i_most_common_elem_freqs))
- appendNamedArgument(out, fout, "most_common_elem_freqs", "real[]",
+ appendNamedArgument(&out, fout, "most_common_elem_freqs", "real[]",
PQgetvalue(res, rownum, i_most_common_elem_freqs));
if (!PQgetisnull(res, rownum, i_elem_count_histogram))
- appendNamedArgument(out, fout, "elem_count_histogram", "real[]",
+ appendNamedArgument(&out, fout, "elem_count_histogram", "real[]",
PQgetvalue(res, rownum, i_elem_count_histogram));
if (fout->remoteVersion >= 170000)
{
if (!PQgetisnull(res, rownum, i_range_length_histogram))
- appendNamedArgument(out, fout, "range_length_histogram", "text",
+ appendNamedArgument(&out, fout, "range_length_histogram", "text",
PQgetvalue(res, rownum, i_range_length_histogram));
if (!PQgetisnull(res, rownum, i_range_empty_frac))
- appendNamedArgument(out, fout, "range_empty_frac", "real",
+ appendNamedArgument(&out, fout, "range_empty_frac", "real",
PQgetvalue(res, rownum, i_range_empty_frac));
if (!PQgetisnull(res, rownum, i_range_bounds_histogram))
- appendNamedArgument(out, fout, "range_bounds_histogram", "text",
+ appendNamedArgument(&out, fout, "range_bounds_histogram", "text",
PQgetvalue(res, rownum, i_range_bounds_histogram));
}
- appendPQExpBufferStr(out, "\n);\n");
+ appendPQExpBufferStr(&out, "\n);\n");
}
PQclear(res);
+ termPQExpBuffer(&query);
+ return out.data;
+}
+
+/*
+ * dumpRelationStats --
+ *
+ * Dump command to import stats into the relation on the new database.
+ */
+static void
+dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
+{
+ const DumpableObject *dobj = &rsinfo->dobj;
+
+ DumpId *deps = NULL;
+ int ndeps = 0;
+
+ /* nothing to do if we are not dumping statistics */
+ if (!fout->dopt->dumpStatistics)
+ return;
+
+ /* dependent on the relation definition, if doing schema */
+ if (fout->dopt->dumpSchema)
+ {
+ deps = dobj->dependencies;
+ ndeps = dobj->nDeps;
+ }
+
ArchiveEntry(fout, nilCatalogId, createDumpId(),
ARCHIVE_OPTS(.tag = dobj->name,
.namespace = dobj->namespace->dobj.name,
.description = "STATISTICS DATA",
.section = rsinfo->postponed_def ?
SECTION_POST_DATA : statisticsDumpSection(rsinfo),
- .createStmt = out->data,
+ .createFn = printRelationStats,
+ .createArg = rsinfo,
.deps = deps,
.nDeps = ndeps));
-
- destroyPQExpBuffer(out);
- destroyPQExpBuffer(query);
}
/*
base-commit: bde2fb797aaebcbe06bf60f330ba5a068f17dda7
--
2.49.0
[text/x-patch] v10-0003-Add-relallfrozen-to-pg_dump-statistics.patch (7.9K, ../../CADkLM=ftC94muGKH+z1irdBUXO2+tmBMLdqDAAxcct=H+LE1Eg@mail.gmail.com/6-v10-0003-Add-relallfrozen-to-pg_dump-statistics.patch)
download | inline diff:
From 87da7d4c517c3b2e63666892e64b9de2a8dbbe44 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sat, 15 Mar 2025 17:34:30 -0400
Subject: [PATCH v10 3/4] Add relallfrozen to pg_dump statistics.
The column relallfrozen was recently added to pg_class and it also
represent statistics, so we should add it to the dump/restore/upgrade
operations.
Dumps of databases prior to v18 will not attempt to restore any value to
relallfrozen, allowing pg_restore_relation_stats() to set the default it
deems appropriate.
---
src/bin/pg_dump/pg_dump.c | 52 ++++++++++++++++++++++----------
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/pg_dump/t/002_pg_dump.pl | 3 +-
3 files changed, 39 insertions(+), 17 deletions(-)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e3f2dac33ec..6c366fd55d3 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6897,7 +6897,8 @@ getFuncs(Archive *fout)
*/
static RelStatsInfo *
getRelationStatistics(Archive *fout, DumpableObject *rel, int32 relpages,
- char *reltuples, int32 relallvisible, char relkind,
+ char *reltuples, int32 relallvisible,
+ int32 relallfrozen, char relkind,
char **indAttNames, int nindAttNames)
{
if (!fout->dopt->dumpStatistics)
@@ -6926,6 +6927,7 @@ getRelationStatistics(Archive *fout, DumpableObject *rel, int32 relpages,
info->relpages = relpages;
info->reltuples = pstrdup(reltuples);
info->relallvisible = relallvisible;
+ info->relallfrozen = relallfrozen;
info->relkind = relkind;
info->indAttNames = indAttNames;
info->nindAttNames = nindAttNames;
@@ -6965,6 +6967,7 @@ getTables(Archive *fout, int *numTables)
int i_relpages;
int i_reltuples;
int i_relallvisible;
+ int i_relallfrozen;
int i_toastpages;
int i_owning_tab;
int i_owning_col;
@@ -7015,8 +7018,13 @@ getTables(Archive *fout, int *numTables)
"c.relowner, "
"c.relchecks, "
"c.relhasindex, c.relhasrules, c.relpages, "
- "c.reltuples, c.relallvisible, c.relhastriggers, "
- "c.relpersistence, "
+ "c.reltuples, c.relallvisible, ");
+
+ if (fout->remoteVersion >= 180000)
+ appendPQExpBufferStr(query, "c.relallfrozen, ");
+
+ appendPQExpBufferStr(query,
+ "c.relhastriggers, c.relpersistence, "
"c.reloftype, "
"c.relacl, "
"acldefault(CASE WHEN c.relkind = " CppAsString2(RELKIND_SEQUENCE)
@@ -7181,6 +7189,7 @@ getTables(Archive *fout, int *numTables)
i_relpages = PQfnumber(res, "relpages");
i_reltuples = PQfnumber(res, "reltuples");
i_relallvisible = PQfnumber(res, "relallvisible");
+ i_relallfrozen = PQfnumber(res, "relallfrozen");
i_toastpages = PQfnumber(res, "toastpages");
i_owning_tab = PQfnumber(res, "owning_tab");
i_owning_col = PQfnumber(res, "owning_col");
@@ -7228,6 +7237,7 @@ getTables(Archive *fout, int *numTables)
for (i = 0; i < ntups; i++)
{
int32 relallvisible = atoi(PQgetvalue(res, i, i_relallvisible));
+ int32 relallfrozen = atoi(PQgetvalue(res, i, i_relallfrozen));
tblinfo[i].dobj.objType = DO_TABLE;
tblinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_reltableoid));
@@ -7330,7 +7340,7 @@ getTables(Archive *fout, int *numTables)
if (tblinfo[i].interesting)
getRelationStatistics(fout, &tblinfo[i].dobj, tblinfo[i].relpages,
PQgetvalue(res, i, i_reltuples),
- relallvisible, tblinfo[i].relkind, NULL, 0);
+ relallvisible, relallfrozen, tblinfo[i].relkind, NULL, 0);
/*
* Read-lock target tables to make sure they aren't DROPPED or altered
@@ -7599,6 +7609,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
i_relpages,
i_reltuples,
i_relallvisible,
+ i_relallfrozen,
i_parentidx,
i_indexdef,
i_indnkeyatts,
@@ -7653,7 +7664,12 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
appendPQExpBufferStr(query,
"SELECT t.tableoid, t.oid, i.indrelid, "
"t.relname AS indexname, "
- "t.relpages, t.reltuples, t.relallvisible, "
+ "t.relpages, t.reltuples, t.relallvisible, ");
+
+ if (fout->remoteVersion >= 180000)
+ appendPQExpBufferStr(query, "t.relallfrozen, ");
+
+ appendPQExpBufferStr(query,
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
"i.indkey, i.indisclustered, "
"c.contype, c.conname, "
@@ -7769,6 +7785,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
i_relpages = PQfnumber(res, "relpages");
i_reltuples = PQfnumber(res, "reltuples");
i_relallvisible = PQfnumber(res, "relallvisible");
+ i_relallfrozen = PQfnumber(res, "relallfrozen");
i_parentidx = PQfnumber(res, "parentidx");
i_indexdef = PQfnumber(res, "indexdef");
i_indnkeyatts = PQfnumber(res, "indnkeyatts");
@@ -7840,6 +7857,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
RelStatsInfo *relstats;
int32 relpages = atoi(PQgetvalue(res, j, i_relpages));
int32 relallvisible = atoi(PQgetvalue(res, j, i_relallvisible));
+ int32 relallfrozen = atoi(PQgetvalue(res, j, i_relallfrozen));
indxinfo[j].dobj.objType = DO_INDEX;
indxinfo[j].dobj.catId.tableoid = atooid(PQgetvalue(res, j, i_tableoid));
@@ -7882,7 +7900,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
relstats = getRelationStatistics(fout, &indxinfo[j].dobj, relpages,
PQgetvalue(res, j, i_reltuples),
- relallvisible, indexkind,
+ relallvisible, relallfrozen, indexkind,
indAttNames, nindAttNames);
contype = *(PQgetvalue(res, j, i_contype));
@@ -10862,19 +10880,21 @@ printRelationStats(Archive *fout, const void *userArg)
initPQExpBuffer(&out);
/* restore relation stats */
- appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
- appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n",
+ appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(");
+ appendPQExpBuffer(&out, "\n\t'version', '%u'::integer",
fout->remoteVersion);
- appendPQExpBufferStr(&out, "\t'schemaname', ");
+ appendPQExpBufferStr(&out, ",\n\t'schemaname', ");
appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
- appendPQExpBufferStr(&out, ",\n");
- appendPQExpBufferStr(&out, "\t'relname', ");
+ appendPQExpBufferStr(&out, ",\n\t'relname', ");
appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
- appendPQExpBufferStr(&out, ",\n");
- appendPQExpBuffer(&out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
- appendPQExpBuffer(&out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
- appendPQExpBuffer(&out, "\t'relallvisible', '%d'::integer\n);\n",
- rsinfo->relallvisible);
+ appendPQExpBuffer(&out, ",\n\t'relpages', '%d'::integer", rsinfo->relpages);
+ appendPQExpBuffer(&out, ",\n\t'reltuples', '%s'::real", rsinfo->reltuples);
+ appendPQExpBuffer(&out, ",\n\t'relallvisible', '%d'::integer", rsinfo->relallvisible);
+
+ if (fout->remoteVersion >= 180000)
+ appendPQExpBuffer(&out, ",\n\t'relallfrozen', '%d'::integer", rsinfo->relallfrozen);
+
+ appendPQExpBufferStr(&out, "\n);\n");
AH->txnCount++;
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index bbdb30b5f54..82f1eb3c4b7 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -441,6 +441,7 @@ typedef struct _relStatsInfo
int32 relpages;
char *reltuples;
int32 relallvisible;
+ int32 relallfrozen;
char relkind; /* 'r', 'm', 'i', etc */
/*
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 51ebf8ad13c..576326daec7 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -4771,7 +4771,8 @@ my %tests = (
'relname',\s'dup_test_post_data_ix',\s+
'relpages',\s'\d+'::integer,\s+
'reltuples',\s'\d+'::real,\s+
- 'relallvisible',\s'\d+'::integer\s+
+ 'relallvisible',\s'\d+'::integer,\s+
+ 'relallfrozen',\s'\d+'::integer\s+
\);\s+
\QSELECT * FROM pg_catalog.pg_restore_attribute_stats(\E\s+
'version',\s'\d+'::integer,\s+
--
2.49.0
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-29 01:11 Corey Huinker <[email protected]>
parent: Corey Huinker <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Corey Huinker @ 2025-03-29 01:11 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
A rebase and a reordering of the commits to put the really-really-must-have
relallfrozen ahead of the really-must-have stats batching and both of them
head of the error->warning step-downs.
Attachments:
[text/x-patch] v11-0001-Add-relallfrozen-to-pg_dump-statistics.patch (7.2K, ../../CADkLM=desCuf3dVHasADvdUVRmb-5gO0mhMO5u9nzgv6i7U86Q@mail.gmail.com/3-v11-0001-Add-relallfrozen-to-pg_dump-statistics.patch)
download | inline diff:
From 96b10b1eb955c5619d23cadf7de8b12d2db638a9 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sat, 15 Mar 2025 17:34:30 -0400
Subject: [PATCH v11 1/4] Add relallfrozen to pg_dump statistics.
The column relallfrozen was recently added to pg_class and it also
represent statistics, so we should add it to the dump/restore/upgrade
operations.
Dumps of databases prior to v18 will not attempt to restore any value to
relallfrozen, allowing pg_restore_relation_stats() to set the default it
deems appropriate.
---
src/bin/pg_dump/pg_dump.c | 38 ++++++++++++++++++++++++++------
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/pg_dump/t/002_pg_dump.pl | 3 ++-
3 files changed, 34 insertions(+), 8 deletions(-)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 84a78625820..211cf10dbd6 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6874,7 +6874,8 @@ getFuncs(Archive *fout)
*/
static RelStatsInfo *
getRelationStatistics(Archive *fout, DumpableObject *rel, int32 relpages,
- char *reltuples, int32 relallvisible, char relkind,
+ char *reltuples, int32 relallvisible,
+ int32 relallfrozen, char relkind,
char **indAttNames, int nindAttNames)
{
if (!fout->dopt->dumpStatistics)
@@ -6903,6 +6904,7 @@ getRelationStatistics(Archive *fout, DumpableObject *rel, int32 relpages,
info->relpages = relpages;
info->reltuples = pstrdup(reltuples);
info->relallvisible = relallvisible;
+ info->relallfrozen = relallfrozen;
info->relkind = relkind;
info->indAttNames = indAttNames;
info->nindAttNames = nindAttNames;
@@ -6967,6 +6969,7 @@ getTables(Archive *fout, int *numTables)
int i_relpages;
int i_reltuples;
int i_relallvisible;
+ int i_relallfrozen;
int i_toastpages;
int i_owning_tab;
int i_owning_col;
@@ -7017,8 +7020,13 @@ getTables(Archive *fout, int *numTables)
"c.relowner, "
"c.relchecks, "
"c.relhasindex, c.relhasrules, c.relpages, "
- "c.reltuples, c.relallvisible, c.relhastriggers, "
- "c.relpersistence, "
+ "c.reltuples, c.relallvisible, ");
+
+ if (fout->remoteVersion >= 180000)
+ appendPQExpBufferStr(query, "c.relallfrozen, ");
+
+ appendPQExpBufferStr(query,
+ "c.relhastriggers, c.relpersistence, "
"c.reloftype, "
"c.relacl, "
"acldefault(CASE WHEN c.relkind = " CppAsString2(RELKIND_SEQUENCE)
@@ -7183,6 +7191,7 @@ getTables(Archive *fout, int *numTables)
i_relpages = PQfnumber(res, "relpages");
i_reltuples = PQfnumber(res, "reltuples");
i_relallvisible = PQfnumber(res, "relallvisible");
+ i_relallfrozen = PQfnumber(res, "relallfrozen");
i_toastpages = PQfnumber(res, "toastpages");
i_owning_tab = PQfnumber(res, "owning_tab");
i_owning_col = PQfnumber(res, "owning_col");
@@ -7230,6 +7239,7 @@ getTables(Archive *fout, int *numTables)
for (i = 0; i < ntups; i++)
{
int32 relallvisible = atoi(PQgetvalue(res, i, i_relallvisible));
+ int32 relallfrozen = atoi(PQgetvalue(res, i, i_relallfrozen));
tblinfo[i].dobj.objType = DO_TABLE;
tblinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_reltableoid));
@@ -7336,7 +7346,7 @@ getTables(Archive *fout, int *numTables)
stats = getRelationStatistics(fout, &tblinfo[i].dobj,
tblinfo[i].relpages,
PQgetvalue(res, i, i_reltuples),
- relallvisible,
+ relallvisible, relallfrozen,
tblinfo[i].relkind, NULL, 0);
if (tblinfo[i].relkind == RELKIND_MATVIEW)
tblinfo[i].stats = stats;
@@ -7609,6 +7619,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
i_relpages,
i_reltuples,
i_relallvisible,
+ i_relallfrozen,
i_parentidx,
i_indexdef,
i_indnkeyatts,
@@ -7663,7 +7674,12 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
appendPQExpBufferStr(query,
"SELECT t.tableoid, t.oid, i.indrelid, "
"t.relname AS indexname, "
- "t.relpages, t.reltuples, t.relallvisible, "
+ "t.relpages, t.reltuples, t.relallvisible, ");
+
+ if (fout->remoteVersion >= 180000)
+ appendPQExpBufferStr(query, "t.relallfrozen, ");
+
+ appendPQExpBufferStr(query,
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
"i.indkey, i.indisclustered, "
"c.contype, c.conname, "
@@ -7779,6 +7795,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
i_relpages = PQfnumber(res, "relpages");
i_reltuples = PQfnumber(res, "reltuples");
i_relallvisible = PQfnumber(res, "relallvisible");
+ i_relallfrozen = PQfnumber(res, "relallfrozen");
i_parentidx = PQfnumber(res, "parentidx");
i_indexdef = PQfnumber(res, "indexdef");
i_indnkeyatts = PQfnumber(res, "indnkeyatts");
@@ -7850,6 +7867,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
RelStatsInfo *relstats;
int32 relpages = atoi(PQgetvalue(res, j, i_relpages));
int32 relallvisible = atoi(PQgetvalue(res, j, i_relallvisible));
+ int32 relallfrozen = atoi(PQgetvalue(res, j, i_relallfrozen));
indxinfo[j].dobj.objType = DO_INDEX;
indxinfo[j].dobj.catId.tableoid = atooid(PQgetvalue(res, j, i_tableoid));
@@ -7892,7 +7910,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
relstats = getRelationStatistics(fout, &indxinfo[j].dobj, relpages,
PQgetvalue(res, j, i_reltuples),
- relallvisible, indexkind,
+ relallvisible, relallfrozen, indexkind,
indAttNames, nindAttNames);
contype = *(PQgetvalue(res, j, i_contype));
@@ -10618,9 +10636,15 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
appendPQExpBufferStr(out, ",\n");
appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
- appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer\n);\n",
+ appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer",
rsinfo->relallvisible);
+ if (fout->remoteVersion >= 180000)
+ appendPQExpBuffer(out, ",\n\t'relallfrozen', '%d'::integer", rsinfo->relallfrozen);
+
+ appendPQExpBufferStr(out, "\n);\n");
+
+
/* fetch attribute stats */
appendPQExpBufferStr(query, "EXECUTE getAttributeStats(");
appendStringLiteralAH(query, dobj->namespace->dobj.name, fout);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 70f7a369e4a..e6f0f86a459 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -442,6 +442,7 @@ typedef struct _relStatsInfo
int32 relpages;
char *reltuples;
int32 relallvisible;
+ int32 relallfrozen;
char relkind; /* 'r', 'm', 'i', etc */
/*
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 51ebf8ad13c..576326daec7 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -4771,7 +4771,8 @@ my %tests = (
'relname',\s'dup_test_post_data_ix',\s+
'relpages',\s'\d+'::integer,\s+
'reltuples',\s'\d+'::real,\s+
- 'relallvisible',\s'\d+'::integer\s+
+ 'relallvisible',\s'\d+'::integer,\s+
+ 'relallfrozen',\s'\d+'::integer\s+
\);\s+
\QSELECT * FROM pg_catalog.pg_restore_attribute_stats(\E\s+
'version',\s'\d+'::integer,\s+
base-commit: a0a4601765b896079eb82a9d5cfa1f41154fcfdb
--
2.49.0
[text/x-patch] v11-0004-Downgrade-many-pg_restore_-_stats-errors-to-warn.patch (30.4K, ../../CADkLM=desCuf3dVHasADvdUVRmb-5gO0mhMO5u9nzgv6i7U86Q@mail.gmail.com/4-v11-0004-Downgrade-many-pg_restore_-_stats-errors-to-warn.patch)
download | inline diff:
From fd6cd3691e21b807a299749631dc0bdddc886853 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sat, 8 Mar 2025 00:52:41 -0500
Subject: [PATCH v11 4/4] Downgrade many pg_restore_*_stats errors to warnings.
We want to avoid errors that can potentially stop an otherwise
successful pg_upgrade or pg_restore operation. With that in mind, change
as many ERROR reports to WARNING + early termination with no data
updated.
---
src/include/statistics/stat_utils.h | 4 +-
src/backend/statistics/attribute_stats.c | 120 ++++++++++----
src/backend/statistics/relation_stats.c | 12 +-
src/backend/statistics/stat_utils.c | 65 ++++++--
src/test/regress/expected/stats_import.out | 184 ++++++++++++++++-----
src/test/regress/sql/stats_import.sql | 36 ++--
6 files changed, 309 insertions(+), 112 deletions(-)
diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h
index 512eb776e0e..809c8263a41 100644
--- a/src/include/statistics/stat_utils.h
+++ b/src/include/statistics/stat_utils.h
@@ -21,7 +21,7 @@ struct StatsArgInfo
Oid argtype;
};
-extern void stats_check_required_arg(FunctionCallInfo fcinfo,
+extern bool stats_check_required_arg(FunctionCallInfo fcinfo,
struct StatsArgInfo *arginfo,
int argnum);
extern bool stats_check_arg_array(FunctionCallInfo fcinfo,
@@ -30,7 +30,7 @@ extern bool stats_check_arg_pair(FunctionCallInfo fcinfo,
struct StatsArgInfo *arginfo,
int argnum1, int argnum2);
-extern void stats_lock_check_privileges(Oid reloid);
+extern bool stats_lock_check_privileges(Oid reloid);
extern Oid stats_lookup_relid(const char *nspname, const char *relname);
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index f5eb17ba42d..b7ba1622391 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -100,7 +100,7 @@ static struct StatsArgInfo cleararginfo[] =
static bool attribute_statistics_update(FunctionCallInfo fcinfo);
static Node *get_attr_expr(Relation rel, int attnum);
-static void get_attr_stat_type(Oid reloid, AttrNumber attnum,
+static bool get_attr_stat_type(Oid reloid, AttrNumber attnum,
Oid *atttypid, int32 *atttypmod,
char *atttyptype, Oid *atttypcoll,
Oid *eq_opr, Oid *lt_opr);
@@ -129,10 +129,12 @@ static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
* stored as an anyarray, and the representation of the array needs to store
* the correct element type, which must be derived from the attribute.
*
- * Major errors, such as the table not existing, the attribute not existing,
- * or a permissions failure are always reported at ERROR. Other errors, such
- * as a conversion failure on one statistic kind, are reported as a WARNING
- * and other statistic kinds may still be updated.
+ * This function is called during database upgrades and restorations, therefore
+ * it is imperative to avoid ERRORs that could potentially end the upgrade or
+ * restore unless. Major errors, such as the table not existing, the attribute
+ * not existing, or permissions failure are reported as WARNINGs with an end to
+ * the function, thus allowing the upgrade/restore to continue, but without the
+ * stats that can be regenereated once the database is online again.
*/
static bool
attribute_statistics_update(FunctionCallInfo fcinfo)
@@ -148,8 +150,8 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
HeapTuple statup;
Oid atttypid = InvalidOid;
- int32 atttypmod;
- char atttyptype;
+ int32 atttypmod = -1;
+ char atttyptype = TYPTYPE_PSEUDO; /* Not a great default, but there is no TYPTYPE_INVALID */
Oid atttypcoll = InvalidOid;
Oid eq_opr = InvalidOid;
Oid lt_opr = InvalidOid;
@@ -176,38 +178,52 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
bool result = true;
- stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG);
- stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG);
+ if (!stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG))
+ return false;
+ if (!stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG))
+ return false;
nspname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELSCHEMA_ARG));
relname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELNAME_ARG));
reloid = stats_lookup_relid(nspname, relname);
+ if (!OidIsValid(reloid))
+ return false;
if (RecoveryInProgress())
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("recovery is in progress"),
errhint("Statistics cannot be modified during recovery.")));
+ return false;
+ }
/* lock before looking up attribute */
- stats_lock_check_privileges(reloid);
+ if (!stats_lock_check_privileges(reloid))
+ return false;
/* user can specify either attname or attnum, but not both */
if (!PG_ARGISNULL(ATTNAME_ARG))
{
if (!PG_ARGISNULL(ATTNUM_ARG))
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("cannot specify both attname and attnum")));
+ return false;
+ }
attname = TextDatumGetCString(PG_GETARG_DATUM(ATTNAME_ARG));
attnum = get_attnum(reloid, attname);
/* note that this test covers attisdropped cases too: */
if (attnum == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
attname, relname)));
+ return false;
+ }
}
else if (!PG_ARGISNULL(ATTNUM_ARG))
{
@@ -216,27 +232,33 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
/* annoyingly, get_attname doesn't check attisdropped */
if (attname == NULL ||
!SearchSysCacheExistsAttName(reloid, attname))
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column %d of relation \"%s\" does not exist",
attnum, relname)));
+ return false;
+ }
}
else
{
- ereport(ERROR,
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("must specify either attname or attnum")));
- attname = NULL; /* keep compiler quiet */
- attnum = 0;
+ return false;
}
if (attnum < 0)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot modify statistics on system column \"%s\"",
attname)));
+ return false;
+ }
- stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG);
+ if (!stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG))
+ return false;
inherited = PG_GETARG_BOOL(INHERITED_ARG);
/*
@@ -285,10 +307,11 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
}
/* derive information from attribute */
- get_attr_stat_type(reloid, attnum,
- &atttypid, &atttypmod,
- &atttyptype, &atttypcoll,
- &eq_opr, <_opr);
+ if (!get_attr_stat_type(reloid, attnum,
+ &atttypid, &atttypmod,
+ &atttyptype, &atttypcoll,
+ &eq_opr, <_opr))
+ result = false;
/* if needed, derive element type */
if (do_mcelem || do_dechist)
@@ -568,7 +591,7 @@ get_attr_expr(Relation rel, int attnum)
/*
* Derive type information from the attribute.
*/
-static void
+static bool
get_attr_stat_type(Oid reloid, AttrNumber attnum,
Oid *atttypid, int32 *atttypmod,
char *atttyptype, Oid *atttypcoll,
@@ -585,18 +608,26 @@ get_attr_stat_type(Oid reloid, AttrNumber attnum,
/* Attribute not found */
if (!HeapTupleIsValid(atup))
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("attribute %d of relation \"%s\" does not exist",
attnum, RelationGetRelationName(rel))));
+ relation_close(rel, NoLock);
+ return false;
+ }
attr = (Form_pg_attribute) GETSTRUCT(atup);
if (attr->attisdropped)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("attribute %d of relation \"%s\" does not exist",
attnum, RelationGetRelationName(rel))));
+ relation_close(rel, NoLock);
+ return false;
+ }
expr = get_attr_expr(rel, attr->attnum);
@@ -645,6 +676,7 @@ get_attr_stat_type(Oid reloid, AttrNumber attnum,
*atttypcoll = DEFAULT_COLLATION_OID;
relation_close(rel, NoLock);
+ return true;
}
/*
@@ -770,6 +802,10 @@ set_stats_slot(Datum *values, bool *nulls, bool *replaces,
if (slotidx >= STATISTIC_NUM_SLOTS && first_empty >= 0)
slotidx = first_empty;
+ /*
+ * Currently there is no datatype that can have more than STATISTIC_NUM_SLOTS
+ * statistic kinds, so this can safely remain an ERROR for now.
+ */
if (slotidx >= STATISTIC_NUM_SLOTS)
ereport(ERROR,
(errmsg("maximum number of statistics slots exceeded: %d",
@@ -915,38 +951,54 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
AttrNumber attnum;
bool inherited;
- stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG);
- stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG);
- stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
- stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
+ if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG))
+ PG_RETURN_VOID();
+ if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG))
+ PG_RETURN_VOID();
+ if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG))
+ PG_RETURN_VOID();
+ if (!stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG))
+ PG_RETURN_VOID();
nspname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELSCHEMA_ARG));
relname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELNAME_ARG));
reloid = stats_lookup_relid(nspname, relname);
+ if (!OidIsValid(reloid))
+ PG_RETURN_VOID();
if (RecoveryInProgress())
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("recovery is in progress"),
errhint("Statistics cannot be modified during recovery.")));
+ PG_RETURN_VOID();
+ }
- stats_lock_check_privileges(reloid);
+ if (!stats_lock_check_privileges(reloid))
+ PG_RETURN_VOID();
attname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTNAME_ARG));
attnum = get_attnum(reloid, attname);
if (attnum < 0)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot clear statistics on system column \"%s\"",
attname)));
+ PG_RETURN_VOID();
+ }
if (attnum == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
attname, get_rel_name(reloid))));
+ PG_RETURN_VOID();
+ }
inherited = PG_GETARG_BOOL(C_INHERITED_ARG);
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index cd3a75b621a..7c47af15c9f 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -83,13 +83,18 @@ relation_statistics_update(FunctionCallInfo fcinfo)
bool nulls[4] = {0};
int nreplaces = 0;
- stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG);
- stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG);
+ if (!stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG))
+ return false;
+
+ if (!stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG))
+ return false;
nspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
reloid = stats_lookup_relid(nspname, relname);
+ if (!OidIsValid(reloid))
+ return false;
if (RecoveryInProgress())
ereport(ERROR,
@@ -97,7 +102,8 @@ relation_statistics_update(FunctionCallInfo fcinfo)
errmsg("recovery is in progress"),
errhint("Statistics cannot be modified during recovery.")));
- stats_lock_check_privileges(reloid);
+ if (!stats_lock_check_privileges(reloid))
+ return false;
if (!PG_ARGISNULL(RELPAGES_ARG))
{
diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c
index a9a3224efe6..d587e875457 100644
--- a/src/backend/statistics/stat_utils.c
+++ b/src/backend/statistics/stat_utils.c
@@ -33,16 +33,20 @@
/*
* Ensure that a given argument is not null.
*/
-void
+bool
stats_check_required_arg(FunctionCallInfo fcinfo,
struct StatsArgInfo *arginfo,
int argnum)
{
if (PG_ARGISNULL(argnum))
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("\"%s\" cannot be NULL",
arginfo[argnum].argname)));
+ return false;
+ }
+ return true;
}
/*
@@ -127,13 +131,14 @@ stats_check_arg_pair(FunctionCallInfo fcinfo,
* - the role owns the current database and the relation is not shared
* - the role has the MAINTAIN privilege on the relation
*/
-void
+bool
stats_lock_check_privileges(Oid reloid)
{
Relation table;
Oid table_oid = reloid;
Oid index_oid = InvalidOid;
LOCKMODE index_lockmode = NoLock;
+ bool ok = true;
/*
* For indexes, we follow the locking behavior in do_analyze_rel() and
@@ -173,14 +178,15 @@ stats_lock_check_privileges(Oid reloid)
case RELKIND_PARTITIONED_TABLE:
break;
default:
- ereport(ERROR,
+ ereport(WARNING,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot modify statistics for relation \"%s\"",
RelationGetRelationName(table)),
errdetail_relkind_not_supported(table->rd_rel->relkind)));
+ ok = false;
}
- if (OidIsValid(index_oid))
+ if (ok && (OidIsValid(index_oid)))
{
Relation index;
@@ -193,25 +199,33 @@ stats_lock_check_privileges(Oid reloid)
relation_close(index, NoLock);
}
- if (table->rd_rel->relisshared)
- ereport(ERROR,
+ if (ok && (table->rd_rel->relisshared))
+ {
+ ereport(WARNING,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot modify statistics for shared relation")));
+ ok = false;
+ }
- if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()))
+ if (ok && (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId())))
{
AclResult aclresult = pg_class_aclcheck(RelationGetRelid(table),
GetUserId(),
ACL_MAINTAIN);
if (aclresult != ACLCHECK_OK)
- aclcheck_error(aclresult,
- get_relkind_objtype(table->rd_rel->relkind),
- NameStr(table->rd_rel->relname));
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied for relation %s",
+ NameStr(table->rd_rel->relname))));
+ ok = false;
+ }
}
/* retain lock on table */
relation_close(table, NoLock);
+ return ok;
}
/*
@@ -223,10 +237,20 @@ stats_lookup_relid(const char *nspname, const char *relname)
Oid nspoid;
Oid reloid;
- nspoid = LookupExplicitNamespace(nspname, false);
+ nspoid = LookupExplicitNamespace(nspname, true);
+ if (!OidIsValid(nspoid))
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ nspname, relname)));
+
+ return InvalidOid;
+ }
+
reloid = get_relname_relid(relname, nspoid);
if (!OidIsValid(reloid))
- ereport(ERROR,
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s.%s\" does not exist",
nspname, relname)));
@@ -303,9 +327,12 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
&args, &types, &argnulls);
if (nargs % 2 != 0)
- ereport(ERROR,
+ {
+ ereport(WARNING,
errmsg("variadic arguments must be name/value pairs"),
errhint("Provide an even number of variadic arguments that can be divided into pairs."));
+ return false;
+ }
/*
* For each argument name/value pair, find corresponding positional
@@ -318,14 +345,20 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
char *argname;
if (argnulls[i])
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errmsg("name at variadic position %d is NULL", i + 1)));
+ return false;
+ }
if (types[i] != TEXTOID)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errmsg("name at variadic position %d has type \"%s\", expected type \"%s\"",
i + 1, format_type_be(types[i]),
format_type_be(TEXTOID))));
+ return false;
+ }
if (argnulls[i + 1])
continue;
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 48d6392b4ad..161cf67b711 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -46,49 +46,85 @@ SELECT pg_clear_relation_stats('stats_import', 'test');
--
-- relstats tests
--
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'relname', 'test',
'relpages', 17::integer);
-ERROR: "schemaname" cannot be NULL
--- error: relname missing
+WARNING: "schemaname" cannot be NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+-- warning: relname missing, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relpages', 17::integer);
-ERROR: "relname" cannot be NULL
---- error: schemaname is wrong type
+WARNING: "relname" cannot be NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+--- warning: schemaname is wrong type, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 3.6::float,
'relname', 'test',
'relpages', 17::integer);
WARNING: argument "schemaname" has type "double precision", expected type "text"
-ERROR: "schemaname" cannot be NULL
---- error: relname is wrong type
+WARNING: "schemaname" cannot be NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+--- warning: relname is wrong type, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 0::oid,
'relpages', 17::integer);
WARNING: argument "relname" has type "oid", expected type "text"
-ERROR: "relname" cannot be NULL
--- error: relation not found
+WARNING: "relname" cannot be NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+-- warning: relation not found, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'nope',
'relpages', 17::integer);
-ERROR: relation "stats_import.nope" does not exist
--- error: odd number of variadic arguments cannot be pairs
+WARNING: relation "stats_import.nope" does not exist
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+-- warning: odd number of variadic arguments cannot be pairs, nothing updated
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
'relallvisible');
-ERROR: variadic arguments must be name/value pairs
+WARNING: variadic arguments must be name/value pairs
HINT: Provide an even number of variadic arguments that can be divided into pairs.
--- error: argument name is NULL
+WARNING: "schemaname" cannot be NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+-- warning: argument name is NULL, nothing updated
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
NULL, '17'::integer);
-ERROR: name at variadic position 5 is NULL
+WARNING: name at variadic position 5 is NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
-- starting stats
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
@@ -340,65 +376,110 @@ CREATE SEQUENCE stats_import.testseq;
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'testseq');
-ERROR: cannot modify statistics for relation "testseq"
+WARNING: cannot modify statistics for relation "testseq"
DETAIL: This operation is not supported for sequences.
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
-ERROR: cannot modify statistics for relation "testseq"
+WARNING: cannot modify statistics for relation "testseq"
DETAIL: This operation is not supported for sequences.
+ pg_clear_relation_stats
+-------------------------
+
+(1 row)
+
CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
-ERROR: cannot modify statistics for relation "testview"
+WARNING: cannot modify statistics for relation "testview"
DETAIL: This operation is not supported for views.
+ pg_clear_relation_stats
+-------------------------
+
+(1 row)
+
--
-- attribute stats
--
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: "schemaname" cannot be NULL
--- error: schema does not exist
+WARNING: "schemaname" cannot be NULL
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: schema does not exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'nope',
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: schema "nope" does not exist
--- error: relname missing
+WARNING: relation "nope.test" does not exist
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: relname missing, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: "relname" cannot be NULL
--- error: relname does not exist
+WARNING: "relname" cannot be NULL
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: relname does not exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'nope',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: relation "stats_import.nope" does not exist
--- error: relname null
+WARNING: relation "stats_import.nope" does not exist
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: relname null, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', NULL,
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: "relname" cannot be NULL
--- error: NULL attname
+WARNING: "relname" cannot be NULL
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: NULL attname, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', NULL,
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: must specify either attname or attnum
--- error: attname doesn't exist
+WARNING: must specify either attname or attnum
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: attname doesn't exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -407,8 +488,13 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'null_frac', 0.1::real,
'avg_width', 2::integer,
'n_distinct', 0.3::real);
-ERROR: column "nope" of relation "test" does not exist
--- error: both attname and attnum
+WARNING: column "nope" of relation "test" does not exist
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: both attname and attnum, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -416,30 +502,50 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: cannot specify both attname and attnum
--- error: neither attname nor attnum
+WARNING: cannot specify both attname and attnum
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: neither attname nor attnum, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: must specify either attname or attnum
--- error: attribute is system column
+WARNING: must specify either attname or attnum
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: attribute is system column, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'xmin',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: cannot modify statistics on system column "xmin"
--- error: inherited null
+WARNING: cannot modify statistics on system column "xmin"
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: inherited null, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'id',
'inherited', NULL::boolean,
'null_frac', 0.1::real);
-ERROR: "inherited" cannot be NULL
+WARNING: "inherited" cannot be NULL
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
-- ok: just the fixed values, with version, no stakinds
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index d140733a750..be8045ceea5 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -39,41 +39,41 @@ SELECT pg_clear_relation_stats('stats_import', 'test');
-- relstats tests
--
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'relname', 'test',
'relpages', 17::integer);
--- error: relname missing
+-- warning: relname missing, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relpages', 17::integer);
---- error: schemaname is wrong type
+--- warning: schemaname is wrong type, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 3.6::float,
'relname', 'test',
'relpages', 17::integer);
---- error: relname is wrong type
+--- warning: relname is wrong type, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 0::oid,
'relpages', 17::integer);
--- error: relation not found
+-- warning: relation not found, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'nope',
'relpages', 17::integer);
--- error: odd number of variadic arguments cannot be pairs
+-- warning: odd number of variadic arguments cannot be pairs, nothing updated
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
'relallvisible');
--- error: argument name is NULL
+-- warning: argument name is NULL, nothing updated
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -246,14 +246,14 @@ SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname
-- attribute stats
--
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: schema does not exist
+-- warning: schema does not exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'nope',
'relname', 'test',
@@ -261,14 +261,14 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: relname missing
+-- warning: relname missing, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: relname does not exist
+-- warning: relname does not exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'nope',
@@ -276,7 +276,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: relname null
+-- warning: relname null, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', NULL,
@@ -284,7 +284,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: NULL attname
+-- warning: NULL attname, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -292,7 +292,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: attname doesn't exist
+-- warning: attname doesn't exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -302,7 +302,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'avg_width', 2::integer,
'n_distinct', 0.3::real);
--- error: both attname and attnum
+-- warning: both attname and attnum, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -311,14 +311,14 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: neither attname nor attnum
+-- warning: neither attname nor attnum, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: attribute is system column
+-- warning: attribute is system column, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -326,7 +326,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: inherited null
+-- warning: inherited null, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
--
2.49.0
[text/x-patch] v11-0003-Batching-getAttributeStats.patch (21.5K, ../../CADkLM=desCuf3dVHasADvdUVRmb-5gO0mhMO5u9nzgv6i7U86Q@mail.gmail.com/5-v11-0003-Batching-getAttributeStats.patch)
download | inline diff:
From 7b226732d1e68b5899c3dc8fbc6eb940c0f884ad Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Fri, 14 Mar 2025 03:54:26 -0400
Subject: [PATCH v11 3/4] Batching getAttributeStats().
The prepared statement getAttributeStats() is fairly heavyweight and
could greatly increase pg_dump/pg_upgrade runtime. To alleviate this,
create a result set buffer of all of the attribute stats fetched for a
batch of 100 relations that could potentially have stats.
The query ensures that the order of results exactly matches the needs of
the code walking the TOC to print the stats calls.
---
src/bin/pg_dump/pg_dump.c | 554 ++++++++++++++++++++++++++------------
1 file changed, 383 insertions(+), 171 deletions(-)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index b7571ea15eb..bcf9dd1eb47 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -143,6 +143,25 @@ typedef enum OidOptions
zeroAsNone = 4,
} OidOptions;
+typedef enum StatsBufferState
+{
+ STATSBUF_UNINITIALIZED = 0,
+ STATSBUF_ACTIVE,
+ STATSBUF_EXHAUSTED
+} StatsBufferState;
+
+typedef struct
+{
+ PGresult *res; /* results from most recent
+ * getAttributeStats() */
+ int idx; /* first un-consumed row of results */
+ TocEntry *te; /* next TOC entry to search for statsitics
+ * data */
+
+ StatsBufferState state; /* current state of the buffer */
+} AttributeStatsBuffer;
+
+
/* global decls */
static bool dosync = true; /* Issue fsync() to make dump durable on disk. */
@@ -209,6 +228,18 @@ static int nbinaryUpgradeClassOids = 0;
static SequenceItem *sequences = NULL;
static int nsequences = 0;
+static AttributeStatsBuffer attrstats =
+{
+ NULL, 0, NULL, STATSBUF_UNINITIALIZED
+};
+
+/*
+ * The maximum number of relations that should be fetched in any one
+ * getAttributeStats() call.
+ */
+
+#define MAX_ATTR_STATS_RELS 100
+
/*
* The default number of rows per INSERT when
* --inserts is specified without --rows-per-insert
@@ -222,6 +253,8 @@ static int nsequences = 0;
*/
#define MAX_BLOBS_PER_ARCHIVE_ENTRY 1000
+
+
/*
* Macro for producing quoted, schema-qualified name of a dumpable object.
*/
@@ -399,6 +432,9 @@ static void setupDumpWorker(Archive *AH);
static TableInfo *getRootTableInfo(const TableInfo *tbinfo);
static bool forcePartitionRootLoad(const TableInfo *tbinfo);
static void read_dump_filters(const char *filename, DumpOptions *dopt);
+static void appendNamedArgument(PQExpBuffer out, Archive *fout,
+ const char *argname, const char *argtype,
+ const char *argval);
int
@@ -10556,7 +10592,286 @@ appendNamedArgument(PQExpBuffer out, Archive *fout, const char *argname,
}
/*
- * printDumpRelationStats --
+ * Fetch next batch of rows from getAttributeStats()
+ */
+static void
+fetchNextAttributeStats(Archive *fout)
+{
+ ArchiveHandle *AH = (ArchiveHandle *) fout;
+ PQExpBufferData schemas;
+ PQExpBufferData relations;
+ int numoids = 0;
+
+ Assert(AH != NULL);
+
+ /* free last result set, if any */
+ if (attrstats.state == STATSBUF_ACTIVE)
+ PQclear(attrstats.res);
+
+ /* If we have looped around to the start of the TOC, restart */
+ if (attrstats.te == AH->toc)
+ attrstats.te = AH->toc->next;
+
+ initPQExpBuffer(&schemas);
+ initPQExpBuffer(&relations);
+
+ /*
+ * Walk ahead looking for relstats entries that are active in this
+ * section, adding the names to the schemas and relations lists.
+ */
+ while ((attrstats.te != AH->toc) && (numoids < MAX_ATTR_STATS_RELS))
+ {
+ if (attrstats.te->reqs != 0 &&
+ strcmp(attrstats.te->desc, "STATISTICS DATA") == 0)
+ {
+ RelStatsInfo *rsinfo = (RelStatsInfo *) attrstats.te->createDumperArg;
+
+ Assert(rsinfo != NULL);
+
+ if (numoids > 0)
+ {
+ appendPQExpBufferStr(&schemas, ",");
+ appendPQExpBufferStr(&relations, ",");
+ }
+ appendPQExpBufferStr(&schemas, fmtId(rsinfo->dobj.namespace->dobj.name));
+ appendPQExpBufferStr(&relations, fmtId(rsinfo->dobj.name));
+ numoids++;
+ }
+
+ attrstats.te = attrstats.te->next;
+ }
+
+ if (numoids > 0)
+ {
+ PQExpBufferData query;
+
+ initPQExpBuffer(&query);
+ appendPQExpBuffer(&query,
+ "EXECUTE getAttributeStats('{%s}'::pg_catalog.text[],'{%s}'::pg_catalog.text[])",
+ schemas.data, relations.data);
+ attrstats.res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK);
+ attrstats.idx = 0;
+ }
+ else
+ {
+ attrstats.state = STATSBUF_EXHAUSTED;
+ attrstats.res = NULL;
+ attrstats.idx = -1;
+ }
+
+ termPQExpBuffer(&schemas);
+ termPQExpBuffer(&relations);
+}
+
+/*
+ * Prepare the getAttributeStats() statement
+ *
+ * This is done automatically if the user specified dumpStatistics.
+ */
+static void
+initAttributeStats(Archive *fout)
+{
+ ArchiveHandle *AH = (ArchiveHandle *) fout;
+ PQExpBufferData query;
+
+ Assert(AH != NULL);
+ initPQExpBuffer(&query);
+
+ appendPQExpBufferStr(&query,
+ "PREPARE getAttributeStats(pg_catalog.text[], pg_catalog.text[]) AS\n"
+ "SELECT s.schemaname, s.tablename, s.attname, s.inherited, "
+ "s.null_frac, s.avg_width, s.n_distinct, s.most_common_vals, "
+ "s.most_common_freqs, s.histogram_bounds, s.correlation, "
+ "s.most_common_elems, s.most_common_elem_freqs, "
+ "s.elem_count_histogram, ");
+
+ if (fout->remoteVersion >= 170000)
+ appendPQExpBufferStr(&query,
+ "s.range_length_histogram, "
+ "s.range_empty_frac, "
+ "s.range_bounds_histogram ");
+ else
+ appendPQExpBufferStr(&query,
+ "NULL AS range_length_histogram, "
+ "NULL AS range_empty_frac, "
+ " NULL AS range_bounds_histogram ");
+
+ /*
+ * The results must be in the order of relations supplied in the
+ * parameters to ensure that they are in sync with a walk of the TOC.
+ *
+ * The redundant (and incomplete) filter clause on s.tablename = ANY(...)
+ * is a way to lead the query into using the index
+ * pg_class_relname_nsp_index which in turn allows the planner to avoid an
+ * expensive full scan of pg_stats.
+ *
+ * We may need to adjust this query for versions that are not so easily
+ * led.
+ */
+ appendPQExpBufferStr(&query,
+ "FROM pg_catalog.pg_stats AS s "
+ "JOIN unnest($1, $2) WITH ORDINALITY AS u(schemaname, tablename, ord) "
+ "ON s.schemaname = u.schemaname "
+ "AND s.tablename = u.tablename "
+ "WHERE s.tablename = ANY($2) "
+ "ORDER BY u.ord, s.attname, s.inherited");
+
+ ExecuteSqlStatement(fout, query.data);
+
+ termPQExpBuffer(&query);
+
+ attrstats.te = AH->toc->next;
+
+ fetchNextAttributeStats(fout);
+
+ attrstats.state = STATSBUF_ACTIVE;
+}
+
+
+/*
+ * append a single attribute stat to the buffer for this relation.
+ */
+static void
+appendAttributeStats(Archive *fout, PQExpBuffer out,
+ const RelStatsInfo *rsinfo)
+{
+ PGresult *res = attrstats.res;
+ int tup_num = attrstats.idx;
+
+ const char *attname;
+
+ static bool indexes_set = false;
+ static int i_attname,
+ i_inherited,
+ i_null_frac,
+ i_avg_width,
+ i_n_distinct,
+ i_most_common_vals,
+ i_most_common_freqs,
+ i_histogram_bounds,
+ i_correlation,
+ i_most_common_elems,
+ i_most_common_elem_freqs,
+ i_elem_count_histogram,
+ i_range_length_histogram,
+ i_range_empty_frac,
+ i_range_bounds_histogram;
+
+ if (!indexes_set)
+ {
+ /*
+ * It's a prepared statement, so the indexes will be the same for all
+ * result sets, so we only need to set them once.
+ */
+ i_attname = PQfnumber(res, "attname");
+ i_inherited = PQfnumber(res, "inherited");
+ i_null_frac = PQfnumber(res, "null_frac");
+ i_avg_width = PQfnumber(res, "avg_width");
+ i_n_distinct = PQfnumber(res, "n_distinct");
+ i_most_common_vals = PQfnumber(res, "most_common_vals");
+ i_most_common_freqs = PQfnumber(res, "most_common_freqs");
+ i_histogram_bounds = PQfnumber(res, "histogram_bounds");
+ i_correlation = PQfnumber(res, "correlation");
+ i_most_common_elems = PQfnumber(res, "most_common_elems");
+ i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
+ i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
+ i_range_length_histogram = PQfnumber(res, "range_length_histogram");
+ i_range_empty_frac = PQfnumber(res, "range_empty_frac");
+ i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+ indexes_set = true;
+ }
+
+ appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
+ appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+ fout->remoteVersion);
+ appendPQExpBufferStr(out, "\t'schemaname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+ appendPQExpBufferStr(out, ",\n\t'relname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+
+ if (PQgetisnull(res, tup_num, i_attname))
+ pg_fatal("attname cannot be NULL");
+ attname = PQgetvalue(res, tup_num, i_attname);
+
+ /*
+ * Indexes look up attname in indAttNames to derive attnum, all others use
+ * attname directly. We must specify attnum for indexes, since their
+ * attnames are not necessarily stable across dump/reload.
+ */
+ if (rsinfo->nindAttNames == 0)
+ {
+ appendPQExpBuffer(out, ",\n\t'attname', ");
+ appendStringLiteralAH(out, attname, fout);
+ }
+ else
+ {
+ bool found = false;
+
+ for (int i = 0; i < rsinfo->nindAttNames; i++)
+ if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
+ {
+ appendPQExpBuffer(out, ",\n\t'attnum', '%d'::smallint",
+ i + 1);
+ found = true;
+ break;
+ }
+
+ if (!found)
+ pg_fatal("could not find index attname \"%s\"", attname);
+ }
+
+ if (!PQgetisnull(res, tup_num, i_inherited))
+ appendNamedArgument(out, fout, "inherited", "boolean",
+ PQgetvalue(res, tup_num, i_inherited));
+ if (!PQgetisnull(res, tup_num, i_null_frac))
+ appendNamedArgument(out, fout, "null_frac", "real",
+ PQgetvalue(res, tup_num, i_null_frac));
+ if (!PQgetisnull(res, tup_num, i_avg_width))
+ appendNamedArgument(out, fout, "avg_width", "integer",
+ PQgetvalue(res, tup_num, i_avg_width));
+ if (!PQgetisnull(res, tup_num, i_n_distinct))
+ appendNamedArgument(out, fout, "n_distinct", "real",
+ PQgetvalue(res, tup_num, i_n_distinct));
+ if (!PQgetisnull(res, tup_num, i_most_common_vals))
+ appendNamedArgument(out, fout, "most_common_vals", "text",
+ PQgetvalue(res, tup_num, i_most_common_vals));
+ if (!PQgetisnull(res, tup_num, i_most_common_freqs))
+ appendNamedArgument(out, fout, "most_common_freqs", "real[]",
+ PQgetvalue(res, tup_num, i_most_common_freqs));
+ if (!PQgetisnull(res, tup_num, i_histogram_bounds))
+ appendNamedArgument(out, fout, "histogram_bounds", "text",
+ PQgetvalue(res, tup_num, i_histogram_bounds));
+ if (!PQgetisnull(res, tup_num, i_correlation))
+ appendNamedArgument(out, fout, "correlation", "real",
+ PQgetvalue(res, tup_num, i_correlation));
+ if (!PQgetisnull(res, tup_num, i_most_common_elems))
+ appendNamedArgument(out, fout, "most_common_elems", "text",
+ PQgetvalue(res, tup_num, i_most_common_elems));
+ if (!PQgetisnull(res, tup_num, i_most_common_elem_freqs))
+ appendNamedArgument(out, fout, "most_common_elem_freqs", "real[]",
+ PQgetvalue(res, tup_num, i_most_common_elem_freqs));
+ if (!PQgetisnull(res, tup_num, i_elem_count_histogram))
+ appendNamedArgument(out, fout, "elem_count_histogram", "real[]",
+ PQgetvalue(res, tup_num, i_elem_count_histogram));
+ if (fout->remoteVersion >= 170000)
+ {
+ if (!PQgetisnull(res, tup_num, i_range_length_histogram))
+ appendNamedArgument(out, fout, "range_length_histogram", "text",
+ PQgetvalue(res, tup_num, i_range_length_histogram));
+ if (!PQgetisnull(res, tup_num, i_range_empty_frac))
+ appendNamedArgument(out, fout, "range_empty_frac", "real",
+ PQgetvalue(res, tup_num, i_range_empty_frac));
+ if (!PQgetisnull(res, tup_num, i_range_bounds_histogram))
+ appendNamedArgument(out, fout, "range_bounds_histogram", "text",
+ PQgetvalue(res, tup_num, i_range_bounds_histogram));
+ }
+ appendPQExpBufferStr(out, "\n);\n");
+}
+
+
+
+/*
+ * printRelationStats --
*
* Generate the SQL statements needed to restore a relation's statistics.
*/
@@ -10564,64 +10879,21 @@ static char *
printRelationStats(Archive *fout, const void *userArg)
{
const RelStatsInfo *rsinfo = (RelStatsInfo *) userArg;
- const DumpableObject *dobj = &rsinfo->dobj;
+ const DumpableObject *dobj;
+ const char *relschema;
+ const char *relname;
+
+ ArchiveHandle *AH = (ArchiveHandle *) fout;
- PQExpBufferData query;
PQExpBufferData out;
- PGresult *res;
-
- static bool first_query = true;
- static int i_attname;
- static int i_inherited;
- static int i_null_frac;
- static int i_avg_width;
- static int i_n_distinct;
- static int i_most_common_vals;
- static int i_most_common_freqs;
- static int i_histogram_bounds;
- static int i_correlation;
- static int i_most_common_elems;
- static int i_most_common_elem_freqs;
- static int i_elem_count_histogram;
- static int i_range_length_histogram;
- static int i_range_empty_frac;
- static int i_range_bounds_histogram;
-
- initPQExpBuffer(&query);
-
- if (first_query)
- {
- appendPQExpBufferStr(&query,
- "PREPARE getAttributeStats(pg_catalog.text, pg_catalog.text) AS\n"
- "SELECT s.attname, s.inherited, "
- "s.null_frac, s.avg_width, s.n_distinct, "
- "s.most_common_vals, s.most_common_freqs, "
- "s.histogram_bounds, s.correlation, "
- "s.most_common_elems, s.most_common_elem_freqs, "
- "s.elem_count_histogram, ");
-
- if (fout->remoteVersion >= 170000)
- appendPQExpBufferStr(&query,
- "s.range_length_histogram, "
- "s.range_empty_frac, "
- "s.range_bounds_histogram ");
- else
- appendPQExpBufferStr(&query,
- "NULL AS range_length_histogram,"
- "NULL AS range_empty_frac,"
- "NULL AS range_bounds_histogram ");
-
- appendPQExpBufferStr(&query,
- "FROM pg_catalog.pg_stats s "
- "WHERE s.schemaname = $1 "
- "AND s.tablename = $2 "
- "ORDER BY s.attname, s.inherited");
-
- ExecuteSqlStatement(fout, query.data);
-
- resetPQExpBuffer(&query);
- }
+ Assert(rsinfo != NULL);
+ dobj = &rsinfo->dobj;
+ Assert(dobj != NULL);
+ relschema = dobj->namespace->dobj.name;
+ Assert(relschema != NULL);
+ relname = dobj->name;
+ Assert(relname != NULL);
initPQExpBuffer(&out);
@@ -10642,132 +10914,72 @@ printRelationStats(Archive *fout, const void *userArg)
appendPQExpBufferStr(&out, "\n);\n");
- /* fetch attribute stats */
- appendPQExpBufferStr(&query, "EXECUTE getAttributeStats(");
- appendStringLiteralAH(&query, dobj->namespace->dobj.name, fout);
- appendPQExpBufferStr(&query, ", ");
- appendStringLiteralAH(&query, dobj->name, fout);
- appendPQExpBufferStr(&query, ")");
+ AH->txnCount++;
- res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK);
+ if (attrstats.state == STATSBUF_UNINITIALIZED)
+ initAttributeStats(fout);
- if (first_query)
+ /*
+ * Because the query returns rows in the same order as the relations
+ * requested, and because every relation gets at least one row in the
+ * result set, the first row for this relation must correspond either to
+ * the current row of this result set (if one exists) or the first row of
+ * the next result set (if this one is already consumed).
+ */
+ if (attrstats.state != STATSBUF_ACTIVE)
+ pg_fatal("Exhausted getAttributeStats() before processing %s.%s",
+ rsinfo->dobj.namespace->dobj.name,
+ rsinfo->dobj.name);
+
+ /*
+ * If the current result set has been fully consumed, then the row(s) we
+ * need (if any) would be found in the next one. This will update
+ * attrstats.res and attrstats.idx.
+ */
+ if (PQntuples(attrstats.res) <= attrstats.idx)
+ fetchNextAttributeStats(fout);
+
+ while (true)
{
- i_attname = PQfnumber(res, "attname");
- i_inherited = PQfnumber(res, "inherited");
- i_null_frac = PQfnumber(res, "null_frac");
- i_avg_width = PQfnumber(res, "avg_width");
- i_n_distinct = PQfnumber(res, "n_distinct");
- i_most_common_vals = PQfnumber(res, "most_common_vals");
- i_most_common_freqs = PQfnumber(res, "most_common_freqs");
- i_histogram_bounds = PQfnumber(res, "histogram_bounds");
- i_correlation = PQfnumber(res, "correlation");
- i_most_common_elems = PQfnumber(res, "most_common_elems");
- i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
- i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
- i_range_length_histogram = PQfnumber(res, "range_length_histogram");
- i_range_empty_frac = PQfnumber(res, "range_empty_frac");
- i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
- first_query = false;
- }
-
- /* restore attribute stats */
- for (int rownum = 0; rownum < PQntuples(res); rownum++)
- {
- const char *attname;
-
- appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
- appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n",
- fout->remoteVersion);
- appendPQExpBufferStr(&out, "\t'schemaname', ");
- appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
- appendPQExpBufferStr(&out, ",\n\t'relname', ");
- appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
-
- if (PQgetisnull(res, rownum, i_attname))
- pg_fatal("attname cannot be NULL");
- attname = PQgetvalue(res, rownum, i_attname);
+ int i_schemaname;
+ int i_tablename;
+ char *schemaname;
+ char *tablename; /* misnomer, following pg_stats naming */
/*
- * Indexes look up attname in indAttNames to derive attnum, all others
- * use attname directly. We must specify attnum for indexes, since
- * their attnames are not necessarily stable across dump/reload.
+ * If we hit the end of the result set, then there are no more records
+ * for this relation, so we should stop, but first get the next result
+ * set for the next batch of relations.
*/
- if (rsinfo->nindAttNames == 0)
+ if (PQntuples(attrstats.res) <= attrstats.idx)
{
- appendPQExpBuffer(&out, ",\n\t'attname', ");
- appendStringLiteralAH(&out, attname, fout);
- }
- else
- {
- bool found = false;
-
- for (int i = 0; i < rsinfo->nindAttNames; i++)
- {
- if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
- {
- appendPQExpBuffer(&out, ",\n\t'attnum', '%d'::smallint",
- i + 1);
- found = true;
- break;
- }
- }
-
- if (!found)
- pg_fatal("could not find index attname \"%s\"", attname);
+ fetchNextAttributeStats(fout);
+ break;
}
- if (!PQgetisnull(res, rownum, i_inherited))
- appendNamedArgument(&out, fout, "inherited", "boolean",
- PQgetvalue(res, rownum, i_inherited));
- if (!PQgetisnull(res, rownum, i_null_frac))
- appendNamedArgument(&out, fout, "null_frac", "real",
- PQgetvalue(res, rownum, i_null_frac));
- if (!PQgetisnull(res, rownum, i_avg_width))
- appendNamedArgument(&out, fout, "avg_width", "integer",
- PQgetvalue(res, rownum, i_avg_width));
- if (!PQgetisnull(res, rownum, i_n_distinct))
- appendNamedArgument(&out, fout, "n_distinct", "real",
- PQgetvalue(res, rownum, i_n_distinct));
- if (!PQgetisnull(res, rownum, i_most_common_vals))
- appendNamedArgument(&out, fout, "most_common_vals", "text",
- PQgetvalue(res, rownum, i_most_common_vals));
- if (!PQgetisnull(res, rownum, i_most_common_freqs))
- appendNamedArgument(&out, fout, "most_common_freqs", "real[]",
- PQgetvalue(res, rownum, i_most_common_freqs));
- if (!PQgetisnull(res, rownum, i_histogram_bounds))
- appendNamedArgument(&out, fout, "histogram_bounds", "text",
- PQgetvalue(res, rownum, i_histogram_bounds));
- if (!PQgetisnull(res, rownum, i_correlation))
- appendNamedArgument(&out, fout, "correlation", "real",
- PQgetvalue(res, rownum, i_correlation));
- if (!PQgetisnull(res, rownum, i_most_common_elems))
- appendNamedArgument(&out, fout, "most_common_elems", "text",
- PQgetvalue(res, rownum, i_most_common_elems));
- if (!PQgetisnull(res, rownum, i_most_common_elem_freqs))
- appendNamedArgument(&out, fout, "most_common_elem_freqs", "real[]",
- PQgetvalue(res, rownum, i_most_common_elem_freqs));
- if (!PQgetisnull(res, rownum, i_elem_count_histogram))
- appendNamedArgument(&out, fout, "elem_count_histogram", "real[]",
- PQgetvalue(res, rownum, i_elem_count_histogram));
- if (fout->remoteVersion >= 170000)
- {
- if (!PQgetisnull(res, rownum, i_range_length_histogram))
- appendNamedArgument(&out, fout, "range_length_histogram", "text",
- PQgetvalue(res, rownum, i_range_length_histogram));
- if (!PQgetisnull(res, rownum, i_range_empty_frac))
- appendNamedArgument(&out, fout, "range_empty_frac", "real",
- PQgetvalue(res, rownum, i_range_empty_frac));
- if (!PQgetisnull(res, rownum, i_range_bounds_histogram))
- appendNamedArgument(&out, fout, "range_bounds_histogram", "text",
- PQgetvalue(res, rownum, i_range_bounds_histogram));
- }
- appendPQExpBufferStr(&out, "\n);\n");
+ i_schemaname = PQfnumber(attrstats.res, "schemaname");
+ Assert(i_schemaname >= 0);
+ i_tablename = PQfnumber(attrstats.res, "tablename");
+ Assert(i_tablename >= 0);
+
+ if (PQgetisnull(attrstats.res, attrstats.idx, i_schemaname))
+ pg_fatal("getAttributeStats() schemaname cannot be NULL");
+
+ if (PQgetisnull(attrstats.res, attrstats.idx, i_tablename))
+ pg_fatal("getAttributeStats() tablename cannot be NULL");
+
+ schemaname = PQgetvalue(attrstats.res, attrstats.idx, i_schemaname);
+ tablename = PQgetvalue(attrstats.res, attrstats.idx, i_tablename);
+
+ /* stop if current stat row isn't for this relation */
+ if (strcmp(relname, tablename) != 0 || strcmp(relschema, schemaname) != 0)
+ break;
+
+ appendAttributeStats(fout, &out, rsinfo);
+ AH->txnCount++;
+ attrstats.idx++;
}
- PQclear(res);
-
- termPQExpBuffer(&query);
return out.data;
}
--
2.49.0
[text/x-patch] v11-0002-Introduce-CreateStmtPtr.patch (17.2K, ../../CADkLM=desCuf3dVHasADvdUVRmb-5gO0mhMO5u9nzgv6i7U86Q@mail.gmail.com/6-v11-0002-Introduce-CreateStmtPtr.patch)
download | inline diff:
From c71426ee2068d37237b79a65d1a0764b7cd3d60f Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Fri, 14 Mar 2025 01:06:19 -0400
Subject: [PATCH v11 2/4] Introduce CreateStmtPtr.
CreateStmtPtr is a function pointer that can replace the createStmt/defn
parameter. This is useful in situations where the amount of text
generated for a definition is so large that it is undesirable to hold
many such objects in memory at the same time.
Using functions of this type, the text created is then immediately
written out to the appropriate file for the given dump format.
---
src/bin/pg_dump/pg_backup.h | 2 +
src/bin/pg_dump/pg_backup_archiver.c | 22 ++-
src/bin/pg_dump/pg_backup_archiver.h | 7 +
src/bin/pg_dump/pg_dump.c | 229 +++++++++++++++------------
4 files changed, 158 insertions(+), 102 deletions(-)
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index 658986de6f8..fdcccd64a70 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -289,6 +289,8 @@ typedef int (*DataDumperPtr) (Archive *AH, const void *userArg);
typedef void (*SetupWorkerPtrType) (Archive *AH);
+typedef char *(*CreateStmtPtr) (Archive *AH, const void *userArg);
+
/*
* Main archiver interface.
*/
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 1d131e5a57d..1b4c62fd7d7 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -1265,6 +1265,9 @@ ArchiveEntry(Archive *AHX, CatalogId catalogId, DumpId dumpId,
newToc->dataDumper = opts->dumpFn;
newToc->dataDumperArg = opts->dumpArg;
newToc->hadDumper = opts->dumpFn ? true : false;
+ newToc->createDumper = opts->createFn;
+ newToc->createDumperArg = opts->createArg;
+ newToc->hadCreateDumper = opts->createFn ? true : false;
newToc->formatData = NULL;
newToc->dataLength = 0;
@@ -2621,7 +2624,17 @@ WriteToc(ArchiveHandle *AH)
WriteStr(AH, te->tag);
WriteStr(AH, te->desc);
WriteInt(AH, te->section);
- WriteStr(AH, te->defn);
+
+ if (te->hadCreateDumper)
+ {
+ char *defn = te->createDumper((Archive *) AH, te->createDumperArg);
+
+ WriteStr(AH, defn);
+ pg_free(defn);
+ }
+ else
+ WriteStr(AH, te->defn);
+
WriteStr(AH, te->dropStmt);
WriteStr(AH, te->copyStmt);
WriteStr(AH, te->namespace);
@@ -3877,6 +3890,13 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, const char *pfx)
{
IssueACLPerBlob(AH, te);
}
+ else if (te->hadCreateDumper)
+ {
+ char *ptr = te->createDumper((Archive *) AH, te->createDumperArg);
+
+ ahwrite(ptr, 1, strlen(ptr), AH);
+ pg_free(ptr);
+ }
else if (te->defn && strlen(te->defn) > 0)
{
ahprintf(AH, "%s\n\n", te->defn);
diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h
index a2064f471ed..e68db633995 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -368,6 +368,11 @@ struct _tocEntry
const void *dataDumperArg; /* Arg for above routine */
void *formatData; /* TOC Entry data specific to file format */
+ CreateStmtPtr createDumper; /* Routine for create statement creation */
+ const void *createDumperArg; /* arg for the above routine */
+ bool hadCreateDumper; /* Archiver was passed a create statement
+ * routine */
+
/* working state while dumping/restoring */
pgoff_t dataLength; /* item's data size; 0 if none or unknown */
int reqs; /* do we need schema and/or data of object
@@ -407,6 +412,8 @@ typedef struct _archiveOpts
int nDeps;
DataDumperPtr dumpFn;
const void *dumpArg;
+ CreateStmtPtr createFn;
+ const void *createArg;
} ArchiveOpts;
#define ARCHIVE_OPTS(...) &(ArchiveOpts){__VA_ARGS__}
/* Called to add a TOC entry */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 211cf10dbd6..b7571ea15eb 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -10556,42 +10556,44 @@ appendNamedArgument(PQExpBuffer out, Archive *fout, const char *argname,
}
/*
- * dumpRelationStats --
+ * printDumpRelationStats --
*
- * Dump command to import stats into the relation on the new database.
+ * Generate the SQL statements needed to restore a relation's statistics.
*/
-static void
-dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
+static char *
+printRelationStats(Archive *fout, const void *userArg)
{
+ const RelStatsInfo *rsinfo = (RelStatsInfo *) userArg;
const DumpableObject *dobj = &rsinfo->dobj;
+
+ PQExpBufferData query;
+ PQExpBufferData out;
+
PGresult *res;
- PQExpBuffer query;
- PQExpBuffer out;
- int i_attname;
- int i_inherited;
- int i_null_frac;
- int i_avg_width;
- int i_n_distinct;
- int i_most_common_vals;
- int i_most_common_freqs;
- int i_histogram_bounds;
- int i_correlation;
- int i_most_common_elems;
- int i_most_common_elem_freqs;
- int i_elem_count_histogram;
- int i_range_length_histogram;
- int i_range_empty_frac;
- int i_range_bounds_histogram;
- /* nothing to do if we are not dumping statistics */
- if (!fout->dopt->dumpStatistics)
- return;
+ static bool first_query = true;
+ static int i_attname;
+ static int i_inherited;
+ static int i_null_frac;
+ static int i_avg_width;
+ static int i_n_distinct;
+ static int i_most_common_vals;
+ static int i_most_common_freqs;
+ static int i_histogram_bounds;
+ static int i_correlation;
+ static int i_most_common_elems;
+ static int i_most_common_elem_freqs;
+ static int i_elem_count_histogram;
+ static int i_range_length_histogram;
+ static int i_range_empty_frac;
+ static int i_range_bounds_histogram;
- query = createPQExpBuffer();
- if (!fout->is_prepared[PREPQUERY_GETATTRIBUTESTATS])
+ initPQExpBuffer(&query);
+
+ if (first_query)
{
- appendPQExpBufferStr(query,
- "PREPARE getAttributeStats(pg_catalog.name, pg_catalog.name) AS\n"
+ appendPQExpBufferStr(&query,
+ "PREPARE getAttributeStats(pg_catalog.text, pg_catalog.text) AS\n"
"SELECT s.attname, s.inherited, "
"s.null_frac, s.avg_width, s.n_distinct, "
"s.most_common_vals, s.most_common_freqs, "
@@ -10600,88 +10602,87 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
"s.elem_count_histogram, ");
if (fout->remoteVersion >= 170000)
- appendPQExpBufferStr(query,
+ appendPQExpBufferStr(&query,
"s.range_length_histogram, "
"s.range_empty_frac, "
"s.range_bounds_histogram ");
else
- appendPQExpBufferStr(query,
+ appendPQExpBufferStr(&query,
"NULL AS range_length_histogram,"
"NULL AS range_empty_frac,"
"NULL AS range_bounds_histogram ");
- appendPQExpBufferStr(query,
+ appendPQExpBufferStr(&query,
"FROM pg_catalog.pg_stats s "
"WHERE s.schemaname = $1 "
"AND s.tablename = $2 "
"ORDER BY s.attname, s.inherited");
- ExecuteSqlStatement(fout, query->data);
+ ExecuteSqlStatement(fout, query.data);
- fout->is_prepared[PREPQUERY_GETATTRIBUTESTATS] = true;
- resetPQExpBuffer(query);
+ resetPQExpBuffer(&query);
}
- out = createPQExpBuffer();
+ initPQExpBuffer(&out);
/* restore relation stats */
- appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
- appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
- fout->remoteVersion);
- appendPQExpBufferStr(out, "\t'schemaname', ");
- appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
- appendPQExpBufferStr(out, ",\n");
- appendPQExpBufferStr(out, "\t'relname', ");
- appendStringLiteralAH(out, rsinfo->dobj.name, fout);
- appendPQExpBufferStr(out, ",\n");
- appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
- appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
- appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer",
- rsinfo->relallvisible);
+ appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(");
+ appendPQExpBuffer(&out, "\n\t'version', '%u'::integer", fout->remoteVersion);
+ appendPQExpBufferStr(&out, ",\n\t'schemaname', ");
+ appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
+ appendPQExpBufferStr(&out, ",\n\t'relname', ");
+ appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
+ appendPQExpBuffer(&out, ",\n\t'relpages', '%d'::integer", rsinfo->relpages);
+ appendPQExpBuffer(&out, ",\n\t'reltuples', '%s'::real", rsinfo->reltuples);
+ appendPQExpBuffer(&out, ",\n\t'relallvisible', '%d'::integer", rsinfo->relallvisible);
if (fout->remoteVersion >= 180000)
- appendPQExpBuffer(out, ",\n\t'relallfrozen', '%d'::integer", rsinfo->relallfrozen);
+ appendPQExpBuffer(&out, ",\n\t'relallfrozen', '%d'::integer", rsinfo->relallfrozen);
- appendPQExpBufferStr(out, "\n);\n");
+ appendPQExpBufferStr(&out, "\n);\n");
/* fetch attribute stats */
- appendPQExpBufferStr(query, "EXECUTE getAttributeStats(");
- appendStringLiteralAH(query, dobj->namespace->dobj.name, fout);
- appendPQExpBufferStr(query, ", ");
- appendStringLiteralAH(query, dobj->name, fout);
- appendPQExpBufferStr(query, ");");
+ appendPQExpBufferStr(&query, "EXECUTE getAttributeStats(");
+ appendStringLiteralAH(&query, dobj->namespace->dobj.name, fout);
+ appendPQExpBufferStr(&query, ", ");
+ appendStringLiteralAH(&query, dobj->name, fout);
+ appendPQExpBufferStr(&query, ")");
- res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+ res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK);
- i_attname = PQfnumber(res, "attname");
- i_inherited = PQfnumber(res, "inherited");
- i_null_frac = PQfnumber(res, "null_frac");
- i_avg_width = PQfnumber(res, "avg_width");
- i_n_distinct = PQfnumber(res, "n_distinct");
- i_most_common_vals = PQfnumber(res, "most_common_vals");
- i_most_common_freqs = PQfnumber(res, "most_common_freqs");
- i_histogram_bounds = PQfnumber(res, "histogram_bounds");
- i_correlation = PQfnumber(res, "correlation");
- i_most_common_elems = PQfnumber(res, "most_common_elems");
- i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
- i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
- i_range_length_histogram = PQfnumber(res, "range_length_histogram");
- i_range_empty_frac = PQfnumber(res, "range_empty_frac");
- i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+ if (first_query)
+ {
+ i_attname = PQfnumber(res, "attname");
+ i_inherited = PQfnumber(res, "inherited");
+ i_null_frac = PQfnumber(res, "null_frac");
+ i_avg_width = PQfnumber(res, "avg_width");
+ i_n_distinct = PQfnumber(res, "n_distinct");
+ i_most_common_vals = PQfnumber(res, "most_common_vals");
+ i_most_common_freqs = PQfnumber(res, "most_common_freqs");
+ i_histogram_bounds = PQfnumber(res, "histogram_bounds");
+ i_correlation = PQfnumber(res, "correlation");
+ i_most_common_elems = PQfnumber(res, "most_common_elems");
+ i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
+ i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
+ i_range_length_histogram = PQfnumber(res, "range_length_histogram");
+ i_range_empty_frac = PQfnumber(res, "range_empty_frac");
+ i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+ first_query = false;
+ }
/* restore attribute stats */
for (int rownum = 0; rownum < PQntuples(res); rownum++)
{
const char *attname;
- appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
- appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+ appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
+ appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n",
fout->remoteVersion);
- appendPQExpBufferStr(out, "\t'schemaname', ");
- appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
- appendPQExpBufferStr(out, ",\n\t'relname', ");
- appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+ appendPQExpBufferStr(&out, "\t'schemaname', ");
+ appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
+ appendPQExpBufferStr(&out, ",\n\t'relname', ");
+ appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
if (PQgetisnull(res, rownum, i_attname))
pg_fatal("attname cannot be NULL");
@@ -10694,8 +10695,8 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
*/
if (rsinfo->nindAttNames == 0)
{
- appendPQExpBuffer(out, ",\n\t'attname', ");
- appendStringLiteralAH(out, attname, fout);
+ appendPQExpBuffer(&out, ",\n\t'attname', ");
+ appendStringLiteralAH(&out, attname, fout);
}
else
{
@@ -10705,7 +10706,7 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
{
if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
{
- appendPQExpBuffer(out, ",\n\t'attnum', '%d'::smallint",
+ appendPQExpBuffer(&out, ",\n\t'attnum', '%d'::smallint",
i + 1);
found = true;
break;
@@ -10717,66 +10718,92 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
}
if (!PQgetisnull(res, rownum, i_inherited))
- appendNamedArgument(out, fout, "inherited", "boolean",
+ appendNamedArgument(&out, fout, "inherited", "boolean",
PQgetvalue(res, rownum, i_inherited));
if (!PQgetisnull(res, rownum, i_null_frac))
- appendNamedArgument(out, fout, "null_frac", "real",
+ appendNamedArgument(&out, fout, "null_frac", "real",
PQgetvalue(res, rownum, i_null_frac));
if (!PQgetisnull(res, rownum, i_avg_width))
- appendNamedArgument(out, fout, "avg_width", "integer",
+ appendNamedArgument(&out, fout, "avg_width", "integer",
PQgetvalue(res, rownum, i_avg_width));
if (!PQgetisnull(res, rownum, i_n_distinct))
- appendNamedArgument(out, fout, "n_distinct", "real",
+ appendNamedArgument(&out, fout, "n_distinct", "real",
PQgetvalue(res, rownum, i_n_distinct));
if (!PQgetisnull(res, rownum, i_most_common_vals))
- appendNamedArgument(out, fout, "most_common_vals", "text",
+ appendNamedArgument(&out, fout, "most_common_vals", "text",
PQgetvalue(res, rownum, i_most_common_vals));
if (!PQgetisnull(res, rownum, i_most_common_freqs))
- appendNamedArgument(out, fout, "most_common_freqs", "real[]",
+ appendNamedArgument(&out, fout, "most_common_freqs", "real[]",
PQgetvalue(res, rownum, i_most_common_freqs));
if (!PQgetisnull(res, rownum, i_histogram_bounds))
- appendNamedArgument(out, fout, "histogram_bounds", "text",
+ appendNamedArgument(&out, fout, "histogram_bounds", "text",
PQgetvalue(res, rownum, i_histogram_bounds));
if (!PQgetisnull(res, rownum, i_correlation))
- appendNamedArgument(out, fout, "correlation", "real",
+ appendNamedArgument(&out, fout, "correlation", "real",
PQgetvalue(res, rownum, i_correlation));
if (!PQgetisnull(res, rownum, i_most_common_elems))
- appendNamedArgument(out, fout, "most_common_elems", "text",
+ appendNamedArgument(&out, fout, "most_common_elems", "text",
PQgetvalue(res, rownum, i_most_common_elems));
if (!PQgetisnull(res, rownum, i_most_common_elem_freqs))
- appendNamedArgument(out, fout, "most_common_elem_freqs", "real[]",
+ appendNamedArgument(&out, fout, "most_common_elem_freqs", "real[]",
PQgetvalue(res, rownum, i_most_common_elem_freqs));
if (!PQgetisnull(res, rownum, i_elem_count_histogram))
- appendNamedArgument(out, fout, "elem_count_histogram", "real[]",
+ appendNamedArgument(&out, fout, "elem_count_histogram", "real[]",
PQgetvalue(res, rownum, i_elem_count_histogram));
if (fout->remoteVersion >= 170000)
{
if (!PQgetisnull(res, rownum, i_range_length_histogram))
- appendNamedArgument(out, fout, "range_length_histogram", "text",
+ appendNamedArgument(&out, fout, "range_length_histogram", "text",
PQgetvalue(res, rownum, i_range_length_histogram));
if (!PQgetisnull(res, rownum, i_range_empty_frac))
- appendNamedArgument(out, fout, "range_empty_frac", "real",
+ appendNamedArgument(&out, fout, "range_empty_frac", "real",
PQgetvalue(res, rownum, i_range_empty_frac));
if (!PQgetisnull(res, rownum, i_range_bounds_histogram))
- appendNamedArgument(out, fout, "range_bounds_histogram", "text",
+ appendNamedArgument(&out, fout, "range_bounds_histogram", "text",
PQgetvalue(res, rownum, i_range_bounds_histogram));
}
- appendPQExpBufferStr(out, "\n);\n");
+ appendPQExpBufferStr(&out, "\n);\n");
}
PQclear(res);
+ termPQExpBuffer(&query);
+ return out.data;
+}
+
+/*
+ * dumpRelationStats --
+ *
+ * Dump command to import stats into the relation on the new database.
+ */
+static void
+dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
+{
+ const DumpableObject *dobj = &rsinfo->dobj;
+
+ DumpId *deps = NULL;
+ int ndeps = 0;
+
+ /* nothing to do if we are not dumping statistics */
+ if (!fout->dopt->dumpStatistics)
+ return;
+
+ /* dependent on the relation definition, if doing schema */
+ if (fout->dopt->dumpSchema)
+ {
+ deps = dobj->dependencies;
+ ndeps = dobj->nDeps;
+ }
+
ArchiveEntry(fout, nilCatalogId, createDumpId(),
ARCHIVE_OPTS(.tag = dobj->name,
.namespace = dobj->namespace->dobj.name,
.description = "STATISTICS DATA",
.section = rsinfo->section,
- .createStmt = out->data,
- .deps = dobj->dependencies,
- .nDeps = dobj->nDeps));
-
- destroyPQExpBuffer(out);
- destroyPQExpBuffer(query);
+ .createFn = printRelationStats,
+ .createArg = rsinfo,
+ .deps = deps,
+ .nDeps = ndeps));
}
/*
--
2.49.0
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-29 05:29 Jeff Davis <[email protected]>
parent: Corey Huinker <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Jeff Davis @ 2025-03-29 05:29 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
On Fri, 2025-03-28 at 21:11 -0400, Corey Huinker wrote:
> A rebase and a reordering of the commits to put the really-really-
> must-have relallfrozen ahead of the really-must-have stats batching
> and both of them head of the error->warning step-downs.
v11-0001 has a couple issues:
The first is that i_relallfrozen is undefined in versions earlier than
18. That's trivial to fix, we just add "0 AS relallfrozen," in the
earlier versions, but still refrain from outputting it.
The second is that the pg_upgrade test (when run with
olddump/oldinstall) compares the before and after dumps, and if the
"before" version is 17, then it will not have the relallfrozen argument
to pg_restore_relation_stats. We might need a filtering step in
adjust_new_dumpfile?
Attached new v11j-0001
Regards,
Jeff Davis
Attachments:
[text/x-patch] v11j-0001-Add-relallfrozen-to-pg_dump-statistics.patch (7.7K, ../../[email protected]/2-v11j-0001-Add-relallfrozen-to-pg_dump-statistics.patch)
download | inline diff:
From 154b8b5c10ec330c26ccd9006c434a7db1feef04 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sat, 15 Mar 2025 17:34:30 -0400
Subject: [PATCH v11j] Add relallfrozen to pg_dump statistics.
Author: Corey Huinker <[email protected]>
Discussion: https://postgr.es/m/CADkLM=desCuf3dVHasADvdUVRmb-5gO0mhMO5u9nzgv6i7U86Q@mail.gmail.com
---
src/bin/pg_dump/pg_dump.c | 42 +++++++++++++++----
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/pg_dump/t/002_pg_dump.pl | 3 +-
.../perl/PostgreSQL/Test/AdjustUpgrade.pm | 5 +++
4 files changed, 43 insertions(+), 8 deletions(-)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 84a78625820..4ca34be230c 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6874,7 +6874,8 @@ getFuncs(Archive *fout)
*/
static RelStatsInfo *
getRelationStatistics(Archive *fout, DumpableObject *rel, int32 relpages,
- char *reltuples, int32 relallvisible, char relkind,
+ char *reltuples, int32 relallvisible,
+ int32 relallfrozen, char relkind,
char **indAttNames, int nindAttNames)
{
if (!fout->dopt->dumpStatistics)
@@ -6903,6 +6904,7 @@ getRelationStatistics(Archive *fout, DumpableObject *rel, int32 relpages,
info->relpages = relpages;
info->reltuples = pstrdup(reltuples);
info->relallvisible = relallvisible;
+ info->relallfrozen = relallfrozen;
info->relkind = relkind;
info->indAttNames = indAttNames;
info->nindAttNames = nindAttNames;
@@ -6967,6 +6969,7 @@ getTables(Archive *fout, int *numTables)
int i_relpages;
int i_reltuples;
int i_relallvisible;
+ int i_relallfrozen;
int i_toastpages;
int i_owning_tab;
int i_owning_col;
@@ -7017,8 +7020,15 @@ getTables(Archive *fout, int *numTables)
"c.relowner, "
"c.relchecks, "
"c.relhasindex, c.relhasrules, c.relpages, "
- "c.reltuples, c.relallvisible, c.relhastriggers, "
- "c.relpersistence, "
+ "c.reltuples, c.relallvisible, ");
+
+ if (fout->remoteVersion >= 180000)
+ appendPQExpBufferStr(query, "c.relallfrozen, ");
+ else
+ appendPQExpBufferStr(query, "0 AS relallfrozen, ");
+
+ appendPQExpBufferStr(query,
+ "c.relhastriggers, c.relpersistence, "
"c.reloftype, "
"c.relacl, "
"acldefault(CASE WHEN c.relkind = " CppAsString2(RELKIND_SEQUENCE)
@@ -7183,6 +7193,7 @@ getTables(Archive *fout, int *numTables)
i_relpages = PQfnumber(res, "relpages");
i_reltuples = PQfnumber(res, "reltuples");
i_relallvisible = PQfnumber(res, "relallvisible");
+ i_relallfrozen = PQfnumber(res, "relallfrozen");
i_toastpages = PQfnumber(res, "toastpages");
i_owning_tab = PQfnumber(res, "owning_tab");
i_owning_col = PQfnumber(res, "owning_col");
@@ -7230,6 +7241,7 @@ getTables(Archive *fout, int *numTables)
for (i = 0; i < ntups; i++)
{
int32 relallvisible = atoi(PQgetvalue(res, i, i_relallvisible));
+ int32 relallfrozen = atoi(PQgetvalue(res, i, i_relallfrozen));
tblinfo[i].dobj.objType = DO_TABLE;
tblinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_reltableoid));
@@ -7336,7 +7348,7 @@ getTables(Archive *fout, int *numTables)
stats = getRelationStatistics(fout, &tblinfo[i].dobj,
tblinfo[i].relpages,
PQgetvalue(res, i, i_reltuples),
- relallvisible,
+ relallvisible, relallfrozen,
tblinfo[i].relkind, NULL, 0);
if (tblinfo[i].relkind == RELKIND_MATVIEW)
tblinfo[i].stats = stats;
@@ -7609,6 +7621,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
i_relpages,
i_reltuples,
i_relallvisible,
+ i_relallfrozen,
i_parentidx,
i_indexdef,
i_indnkeyatts,
@@ -7663,7 +7676,14 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
appendPQExpBufferStr(query,
"SELECT t.tableoid, t.oid, i.indrelid, "
"t.relname AS indexname, "
- "t.relpages, t.reltuples, t.relallvisible, "
+ "t.relpages, t.reltuples, t.relallvisible, ");
+
+ if (fout->remoteVersion >= 180000)
+ appendPQExpBufferStr(query, "t.relallfrozen, ");
+ else
+ appendPQExpBufferStr(query, "0 AS relallfrozen, ");
+
+ appendPQExpBufferStr(query,
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
"i.indkey, i.indisclustered, "
"c.contype, c.conname, "
@@ -7779,6 +7799,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
i_relpages = PQfnumber(res, "relpages");
i_reltuples = PQfnumber(res, "reltuples");
i_relallvisible = PQfnumber(res, "relallvisible");
+ i_relallfrozen = PQfnumber(res, "relallfrozen");
i_parentidx = PQfnumber(res, "parentidx");
i_indexdef = PQfnumber(res, "indexdef");
i_indnkeyatts = PQfnumber(res, "indnkeyatts");
@@ -7850,6 +7871,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
RelStatsInfo *relstats;
int32 relpages = atoi(PQgetvalue(res, j, i_relpages));
int32 relallvisible = atoi(PQgetvalue(res, j, i_relallvisible));
+ int32 relallfrozen = atoi(PQgetvalue(res, j, i_relallfrozen));
indxinfo[j].dobj.objType = DO_INDEX;
indxinfo[j].dobj.catId.tableoid = atooid(PQgetvalue(res, j, i_tableoid));
@@ -7892,7 +7914,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
relstats = getRelationStatistics(fout, &indxinfo[j].dobj, relpages,
PQgetvalue(res, j, i_reltuples),
- relallvisible, indexkind,
+ relallvisible, relallfrozen, indexkind,
indAttNames, nindAttNames);
contype = *(PQgetvalue(res, j, i_contype));
@@ -10618,9 +10640,15 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
appendPQExpBufferStr(out, ",\n");
appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
- appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer\n);\n",
+ appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer",
rsinfo->relallvisible);
+ if (fout->remoteVersion >= 180000)
+ appendPQExpBuffer(out, ",\n\t'relallfrozen', '%d'::integer", rsinfo->relallfrozen);
+
+ appendPQExpBufferStr(out, "\n);\n");
+
+
/* fetch attribute stats */
appendPQExpBufferStr(query, "EXECUTE getAttributeStats(");
appendStringLiteralAH(query, dobj->namespace->dobj.name, fout);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 70f7a369e4a..e6f0f86a459 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -442,6 +442,7 @@ typedef struct _relStatsInfo
int32 relpages;
char *reltuples;
int32 relallvisible;
+ int32 relallfrozen;
char relkind; /* 'r', 'm', 'i', etc */
/*
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 51ebf8ad13c..576326daec7 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -4771,7 +4771,8 @@ my %tests = (
'relname',\s'dup_test_post_data_ix',\s+
'relpages',\s'\d+'::integer,\s+
'reltuples',\s'\d+'::real,\s+
- 'relallvisible',\s'\d+'::integer\s+
+ 'relallvisible',\s'\d+'::integer,\s+
+ 'relallfrozen',\s'\d+'::integer\s+
\);\s+
\QSELECT * FROM pg_catalog.pg_restore_attribute_stats(\E\s+
'version',\s'\d+'::integer,\s+
diff --git a/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm b/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm
index 81a8f44aa9f..07550295a82 100644
--- a/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm
+++ b/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm
@@ -648,6 +648,11 @@ sub adjust_new_dumpfile
$dump =~ s {\n(\s+'version',) '\d+'::integer,$}
{$1 '000000'::integer,}mg;
+ if ($old_version < 18)
+ {
+ $dump =~ s {,\n(\s+'relallfrozen',) '\d+'::integer$}{}mg;
+ }
+
# pre-v16 dumps do not know about XMLSERIALIZE(NO INDENT).
if ($old_version < 16)
{
--
2.34.1
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-29 05:44 Corey Huinker <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Corey Huinker @ 2025-03-29 05:44 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
>
> The first is that i_relallfrozen is undefined in versions earlier than
> 18. That's trivial to fix, we just add "0 AS relallfrozen," in the
> earlier versions, but still refrain from outputting it.
>
Ok, so long as we refrain from outputting it, I'm cool with whatever we
store internally.
> The second is that the pg_upgrade test (when run with
> olddump/oldinstall) compares the before and after dumps, and if the
> "before" version is 17, then it will not have the relallfrozen argument
> to pg_restore_relation_stats. We might need a filtering step in
> adjust_new_dumpfile?
>
That sounds trickier. Do we already have filtering steps that are sensitive
to the "before" version dump?
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-31 15:11 Corey Huinker <[email protected]>
parent: Corey Huinker <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Corey Huinker @ 2025-03-31 15:11 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]
>
> The second is that the pg_upgrade test (when run with
>> olddump/oldinstall) compares the before and after dumps, and if the
>> "before" version is 17, then it will not have the relallfrozen argument
>> to pg_restore_relation_stats. We might need a filtering step in
>> adjust_new_dumpfile?
>>
>
> That sounds trickier.
>
Narrator: It was not trickier.
In light of v11-0001 being committed as 4694aedf63bf, I've rebased the
remaining patches.
Attachments:
[text/x-patch] v12-0001-Introduce-CreateStmtPtr.patch (17.3K, ../../CADkLM=domd5+CvjKMHGbOfvSuY6J8G-x+9M2D6Ss2HamYefE9w@mail.gmail.com/3-v12-0001-Introduce-CreateStmtPtr.patch)
download | inline diff:
From 607984bdcc91fa31fb7a12e9b24fb8704aa14975 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Fri, 14 Mar 2025 01:06:19 -0400
Subject: [PATCH v12 1/3] Introduce CreateStmtPtr.
CreateStmtPtr is a function pointer that can replace the createStmt/defn
parameter. This is useful in situations where the amount of text
generated for a definition is so large that it is undesirable to hold
many such objects in memory at the same time.
Using functions of this type, the text created is then immediately
written out to the appropriate file for the given dump format.
---
src/bin/pg_dump/pg_backup.h | 2 +
src/bin/pg_dump/pg_backup_archiver.c | 22 ++-
src/bin/pg_dump/pg_backup_archiver.h | 7 +
src/bin/pg_dump/pg_dump.c | 229 +++++++++++++++------------
4 files changed, 158 insertions(+), 102 deletions(-)
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index 658986de6f8..fdcccd64a70 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -289,6 +289,8 @@ typedef int (*DataDumperPtr) (Archive *AH, const void *userArg);
typedef void (*SetupWorkerPtrType) (Archive *AH);
+typedef char *(*CreateStmtPtr) (Archive *AH, const void *userArg);
+
/*
* Main archiver interface.
*/
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 1d131e5a57d..1b4c62fd7d7 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -1265,6 +1265,9 @@ ArchiveEntry(Archive *AHX, CatalogId catalogId, DumpId dumpId,
newToc->dataDumper = opts->dumpFn;
newToc->dataDumperArg = opts->dumpArg;
newToc->hadDumper = opts->dumpFn ? true : false;
+ newToc->createDumper = opts->createFn;
+ newToc->createDumperArg = opts->createArg;
+ newToc->hadCreateDumper = opts->createFn ? true : false;
newToc->formatData = NULL;
newToc->dataLength = 0;
@@ -2621,7 +2624,17 @@ WriteToc(ArchiveHandle *AH)
WriteStr(AH, te->tag);
WriteStr(AH, te->desc);
WriteInt(AH, te->section);
- WriteStr(AH, te->defn);
+
+ if (te->hadCreateDumper)
+ {
+ char *defn = te->createDumper((Archive *) AH, te->createDumperArg);
+
+ WriteStr(AH, defn);
+ pg_free(defn);
+ }
+ else
+ WriteStr(AH, te->defn);
+
WriteStr(AH, te->dropStmt);
WriteStr(AH, te->copyStmt);
WriteStr(AH, te->namespace);
@@ -3877,6 +3890,13 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, const char *pfx)
{
IssueACLPerBlob(AH, te);
}
+ else if (te->hadCreateDumper)
+ {
+ char *ptr = te->createDumper((Archive *) AH, te->createDumperArg);
+
+ ahwrite(ptr, 1, strlen(ptr), AH);
+ pg_free(ptr);
+ }
else if (te->defn && strlen(te->defn) > 0)
{
ahprintf(AH, "%s\n\n", te->defn);
diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h
index a2064f471ed..e68db633995 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -368,6 +368,11 @@ struct _tocEntry
const void *dataDumperArg; /* Arg for above routine */
void *formatData; /* TOC Entry data specific to file format */
+ CreateStmtPtr createDumper; /* Routine for create statement creation */
+ const void *createDumperArg; /* arg for the above routine */
+ bool hadCreateDumper; /* Archiver was passed a create statement
+ * routine */
+
/* working state while dumping/restoring */
pgoff_t dataLength; /* item's data size; 0 if none or unknown */
int reqs; /* do we need schema and/or data of object
@@ -407,6 +412,8 @@ typedef struct _archiveOpts
int nDeps;
DataDumperPtr dumpFn;
const void *dumpArg;
+ CreateStmtPtr createFn;
+ const void *createArg;
} ArchiveOpts;
#define ARCHIVE_OPTS(...) &(ArchiveOpts){__VA_ARGS__}
/* Called to add a TOC entry */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4ca34be230c..cc195d6cd9e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -10560,42 +10560,44 @@ appendNamedArgument(PQExpBuffer out, Archive *fout, const char *argname,
}
/*
- * dumpRelationStats --
+ * printDumpRelationStats --
*
- * Dump command to import stats into the relation on the new database.
+ * Generate the SQL statements needed to restore a relation's statistics.
*/
-static void
-dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
+static char *
+printRelationStats(Archive *fout, const void *userArg)
{
+ const RelStatsInfo *rsinfo = (RelStatsInfo *) userArg;
const DumpableObject *dobj = &rsinfo->dobj;
+
+ PQExpBufferData query;
+ PQExpBufferData out;
+
PGresult *res;
- PQExpBuffer query;
- PQExpBuffer out;
- int i_attname;
- int i_inherited;
- int i_null_frac;
- int i_avg_width;
- int i_n_distinct;
- int i_most_common_vals;
- int i_most_common_freqs;
- int i_histogram_bounds;
- int i_correlation;
- int i_most_common_elems;
- int i_most_common_elem_freqs;
- int i_elem_count_histogram;
- int i_range_length_histogram;
- int i_range_empty_frac;
- int i_range_bounds_histogram;
- /* nothing to do if we are not dumping statistics */
- if (!fout->dopt->dumpStatistics)
- return;
+ static bool first_query = true;
+ static int i_attname;
+ static int i_inherited;
+ static int i_null_frac;
+ static int i_avg_width;
+ static int i_n_distinct;
+ static int i_most_common_vals;
+ static int i_most_common_freqs;
+ static int i_histogram_bounds;
+ static int i_correlation;
+ static int i_most_common_elems;
+ static int i_most_common_elem_freqs;
+ static int i_elem_count_histogram;
+ static int i_range_length_histogram;
+ static int i_range_empty_frac;
+ static int i_range_bounds_histogram;
- query = createPQExpBuffer();
- if (!fout->is_prepared[PREPQUERY_GETATTRIBUTESTATS])
+ initPQExpBuffer(&query);
+
+ if (first_query)
{
- appendPQExpBufferStr(query,
- "PREPARE getAttributeStats(pg_catalog.name, pg_catalog.name) AS\n"
+ appendPQExpBufferStr(&query,
+ "PREPARE getAttributeStats(pg_catalog.text, pg_catalog.text) AS\n"
"SELECT s.attname, s.inherited, "
"s.null_frac, s.avg_width, s.n_distinct, "
"s.most_common_vals, s.most_common_freqs, "
@@ -10604,88 +10606,87 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
"s.elem_count_histogram, ");
if (fout->remoteVersion >= 170000)
- appendPQExpBufferStr(query,
+ appendPQExpBufferStr(&query,
"s.range_length_histogram, "
"s.range_empty_frac, "
"s.range_bounds_histogram ");
else
- appendPQExpBufferStr(query,
+ appendPQExpBufferStr(&query,
"NULL AS range_length_histogram,"
"NULL AS range_empty_frac,"
"NULL AS range_bounds_histogram ");
- appendPQExpBufferStr(query,
+ appendPQExpBufferStr(&query,
"FROM pg_catalog.pg_stats s "
"WHERE s.schemaname = $1 "
"AND s.tablename = $2 "
"ORDER BY s.attname, s.inherited");
- ExecuteSqlStatement(fout, query->data);
+ ExecuteSqlStatement(fout, query.data);
- fout->is_prepared[PREPQUERY_GETATTRIBUTESTATS] = true;
- resetPQExpBuffer(query);
+ resetPQExpBuffer(&query);
}
- out = createPQExpBuffer();
+ initPQExpBuffer(&out);
/* restore relation stats */
- appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
- appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
- fout->remoteVersion);
- appendPQExpBufferStr(out, "\t'schemaname', ");
- appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
- appendPQExpBufferStr(out, ",\n");
- appendPQExpBufferStr(out, "\t'relname', ");
- appendStringLiteralAH(out, rsinfo->dobj.name, fout);
- appendPQExpBufferStr(out, ",\n");
- appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
- appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
- appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer",
- rsinfo->relallvisible);
+ appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(");
+ appendPQExpBuffer(&out, "\n\t'version', '%u'::integer", fout->remoteVersion);
+ appendPQExpBufferStr(&out, ",\n\t'schemaname', ");
+ appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
+ appendPQExpBufferStr(&out, ",\n\t'relname', ");
+ appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
+ appendPQExpBuffer(&out, ",\n\t'relpages', '%d'::integer", rsinfo->relpages);
+ appendPQExpBuffer(&out, ",\n\t'reltuples', '%s'::real", rsinfo->reltuples);
+ appendPQExpBuffer(&out, ",\n\t'relallvisible', '%d'::integer", rsinfo->relallvisible);
if (fout->remoteVersion >= 180000)
- appendPQExpBuffer(out, ",\n\t'relallfrozen', '%d'::integer", rsinfo->relallfrozen);
+ appendPQExpBuffer(&out, ",\n\t'relallfrozen', '%d'::integer", rsinfo->relallfrozen);
- appendPQExpBufferStr(out, "\n);\n");
+ appendPQExpBufferStr(&out, "\n);\n");
/* fetch attribute stats */
- appendPQExpBufferStr(query, "EXECUTE getAttributeStats(");
- appendStringLiteralAH(query, dobj->namespace->dobj.name, fout);
- appendPQExpBufferStr(query, ", ");
- appendStringLiteralAH(query, dobj->name, fout);
- appendPQExpBufferStr(query, ");");
+ appendPQExpBufferStr(&query, "EXECUTE getAttributeStats(");
+ appendStringLiteralAH(&query, dobj->namespace->dobj.name, fout);
+ appendPQExpBufferStr(&query, ", ");
+ appendStringLiteralAH(&query, dobj->name, fout);
+ appendPQExpBufferStr(&query, ")");
- res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+ res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK);
- i_attname = PQfnumber(res, "attname");
- i_inherited = PQfnumber(res, "inherited");
- i_null_frac = PQfnumber(res, "null_frac");
- i_avg_width = PQfnumber(res, "avg_width");
- i_n_distinct = PQfnumber(res, "n_distinct");
- i_most_common_vals = PQfnumber(res, "most_common_vals");
- i_most_common_freqs = PQfnumber(res, "most_common_freqs");
- i_histogram_bounds = PQfnumber(res, "histogram_bounds");
- i_correlation = PQfnumber(res, "correlation");
- i_most_common_elems = PQfnumber(res, "most_common_elems");
- i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
- i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
- i_range_length_histogram = PQfnumber(res, "range_length_histogram");
- i_range_empty_frac = PQfnumber(res, "range_empty_frac");
- i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+ if (first_query)
+ {
+ i_attname = PQfnumber(res, "attname");
+ i_inherited = PQfnumber(res, "inherited");
+ i_null_frac = PQfnumber(res, "null_frac");
+ i_avg_width = PQfnumber(res, "avg_width");
+ i_n_distinct = PQfnumber(res, "n_distinct");
+ i_most_common_vals = PQfnumber(res, "most_common_vals");
+ i_most_common_freqs = PQfnumber(res, "most_common_freqs");
+ i_histogram_bounds = PQfnumber(res, "histogram_bounds");
+ i_correlation = PQfnumber(res, "correlation");
+ i_most_common_elems = PQfnumber(res, "most_common_elems");
+ i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
+ i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
+ i_range_length_histogram = PQfnumber(res, "range_length_histogram");
+ i_range_empty_frac = PQfnumber(res, "range_empty_frac");
+ i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+ first_query = false;
+ }
/* restore attribute stats */
for (int rownum = 0; rownum < PQntuples(res); rownum++)
{
const char *attname;
- appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
- appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+ appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
+ appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n",
fout->remoteVersion);
- appendPQExpBufferStr(out, "\t'schemaname', ");
- appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
- appendPQExpBufferStr(out, ",\n\t'relname', ");
- appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+ appendPQExpBufferStr(&out, "\t'schemaname', ");
+ appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
+ appendPQExpBufferStr(&out, ",\n\t'relname', ");
+ appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
if (PQgetisnull(res, rownum, i_attname))
pg_fatal("attname cannot be NULL");
@@ -10698,8 +10699,8 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
*/
if (rsinfo->nindAttNames == 0)
{
- appendPQExpBuffer(out, ",\n\t'attname', ");
- appendStringLiteralAH(out, attname, fout);
+ appendPQExpBuffer(&out, ",\n\t'attname', ");
+ appendStringLiteralAH(&out, attname, fout);
}
else
{
@@ -10709,7 +10710,7 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
{
if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
{
- appendPQExpBuffer(out, ",\n\t'attnum', '%d'::smallint",
+ appendPQExpBuffer(&out, ",\n\t'attnum', '%d'::smallint",
i + 1);
found = true;
break;
@@ -10721,66 +10722,92 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
}
if (!PQgetisnull(res, rownum, i_inherited))
- appendNamedArgument(out, fout, "inherited", "boolean",
+ appendNamedArgument(&out, fout, "inherited", "boolean",
PQgetvalue(res, rownum, i_inherited));
if (!PQgetisnull(res, rownum, i_null_frac))
- appendNamedArgument(out, fout, "null_frac", "real",
+ appendNamedArgument(&out, fout, "null_frac", "real",
PQgetvalue(res, rownum, i_null_frac));
if (!PQgetisnull(res, rownum, i_avg_width))
- appendNamedArgument(out, fout, "avg_width", "integer",
+ appendNamedArgument(&out, fout, "avg_width", "integer",
PQgetvalue(res, rownum, i_avg_width));
if (!PQgetisnull(res, rownum, i_n_distinct))
- appendNamedArgument(out, fout, "n_distinct", "real",
+ appendNamedArgument(&out, fout, "n_distinct", "real",
PQgetvalue(res, rownum, i_n_distinct));
if (!PQgetisnull(res, rownum, i_most_common_vals))
- appendNamedArgument(out, fout, "most_common_vals", "text",
+ appendNamedArgument(&out, fout, "most_common_vals", "text",
PQgetvalue(res, rownum, i_most_common_vals));
if (!PQgetisnull(res, rownum, i_most_common_freqs))
- appendNamedArgument(out, fout, "most_common_freqs", "real[]",
+ appendNamedArgument(&out, fout, "most_common_freqs", "real[]",
PQgetvalue(res, rownum, i_most_common_freqs));
if (!PQgetisnull(res, rownum, i_histogram_bounds))
- appendNamedArgument(out, fout, "histogram_bounds", "text",
+ appendNamedArgument(&out, fout, "histogram_bounds", "text",
PQgetvalue(res, rownum, i_histogram_bounds));
if (!PQgetisnull(res, rownum, i_correlation))
- appendNamedArgument(out, fout, "correlation", "real",
+ appendNamedArgument(&out, fout, "correlation", "real",
PQgetvalue(res, rownum, i_correlation));
if (!PQgetisnull(res, rownum, i_most_common_elems))
- appendNamedArgument(out, fout, "most_common_elems", "text",
+ appendNamedArgument(&out, fout, "most_common_elems", "text",
PQgetvalue(res, rownum, i_most_common_elems));
if (!PQgetisnull(res, rownum, i_most_common_elem_freqs))
- appendNamedArgument(out, fout, "most_common_elem_freqs", "real[]",
+ appendNamedArgument(&out, fout, "most_common_elem_freqs", "real[]",
PQgetvalue(res, rownum, i_most_common_elem_freqs));
if (!PQgetisnull(res, rownum, i_elem_count_histogram))
- appendNamedArgument(out, fout, "elem_count_histogram", "real[]",
+ appendNamedArgument(&out, fout, "elem_count_histogram", "real[]",
PQgetvalue(res, rownum, i_elem_count_histogram));
if (fout->remoteVersion >= 170000)
{
if (!PQgetisnull(res, rownum, i_range_length_histogram))
- appendNamedArgument(out, fout, "range_length_histogram", "text",
+ appendNamedArgument(&out, fout, "range_length_histogram", "text",
PQgetvalue(res, rownum, i_range_length_histogram));
if (!PQgetisnull(res, rownum, i_range_empty_frac))
- appendNamedArgument(out, fout, "range_empty_frac", "real",
+ appendNamedArgument(&out, fout, "range_empty_frac", "real",
PQgetvalue(res, rownum, i_range_empty_frac));
if (!PQgetisnull(res, rownum, i_range_bounds_histogram))
- appendNamedArgument(out, fout, "range_bounds_histogram", "text",
+ appendNamedArgument(&out, fout, "range_bounds_histogram", "text",
PQgetvalue(res, rownum, i_range_bounds_histogram));
}
- appendPQExpBufferStr(out, "\n);\n");
+ appendPQExpBufferStr(&out, "\n);\n");
}
PQclear(res);
+ termPQExpBuffer(&query);
+ return out.data;
+}
+
+/*
+ * dumpRelationStats --
+ *
+ * Dump command to import stats into the relation on the new database.
+ */
+static void
+dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
+{
+ const DumpableObject *dobj = &rsinfo->dobj;
+
+ DumpId *deps = NULL;
+ int ndeps = 0;
+
+ /* nothing to do if we are not dumping statistics */
+ if (!fout->dopt->dumpStatistics)
+ return;
+
+ /* dependent on the relation definition, if doing schema */
+ if (fout->dopt->dumpSchema)
+ {
+ deps = dobj->dependencies;
+ ndeps = dobj->nDeps;
+ }
+
ArchiveEntry(fout, nilCatalogId, createDumpId(),
ARCHIVE_OPTS(.tag = dobj->name,
.namespace = dobj->namespace->dobj.name,
.description = "STATISTICS DATA",
.section = rsinfo->section,
- .createStmt = out->data,
- .deps = dobj->dependencies,
- .nDeps = dobj->nDeps));
-
- destroyPQExpBuffer(out);
- destroyPQExpBuffer(query);
+ .createFn = printRelationStats,
+ .createArg = rsinfo,
+ .deps = deps,
+ .nDeps = ndeps));
}
/*
base-commit: e2809e3a1015697832ee4d37b75ba1cd0caac0f0
--
2.49.0
[text/x-patch] v12-0002-Batching-getAttributeStats.patch (21.5K, ../../CADkLM=domd5+CvjKMHGbOfvSuY6J8G-x+9M2D6Ss2HamYefE9w@mail.gmail.com/4-v12-0002-Batching-getAttributeStats.patch)
download | inline diff:
From 410171805037718c3adcae778bba56c485038e3f Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Fri, 14 Mar 2025 03:54:26 -0400
Subject: [PATCH v12 2/3] Batching getAttributeStats().
The prepared statement getAttributeStats() is fairly heavyweight and
could greatly increase pg_dump/pg_upgrade runtime. To alleviate this,
create a result set buffer of all of the attribute stats fetched for a
batch of 100 relations that could potentially have stats.
The query ensures that the order of results exactly matches the needs of
the code walking the TOC to print the stats calls.
---
src/bin/pg_dump/pg_dump.c | 554 ++++++++++++++++++++++++++------------
1 file changed, 383 insertions(+), 171 deletions(-)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index cc195d6cd9e..26144371b1b 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -143,6 +143,25 @@ typedef enum OidOptions
zeroAsNone = 4,
} OidOptions;
+typedef enum StatsBufferState
+{
+ STATSBUF_UNINITIALIZED = 0,
+ STATSBUF_ACTIVE,
+ STATSBUF_EXHAUSTED
+} StatsBufferState;
+
+typedef struct
+{
+ PGresult *res; /* results from most recent
+ * getAttributeStats() */
+ int idx; /* first un-consumed row of results */
+ TocEntry *te; /* next TOC entry to search for statsitics
+ * data */
+
+ StatsBufferState state; /* current state of the buffer */
+} AttributeStatsBuffer;
+
+
/* global decls */
static bool dosync = true; /* Issue fsync() to make dump durable on disk. */
@@ -209,6 +228,18 @@ static int nbinaryUpgradeClassOids = 0;
static SequenceItem *sequences = NULL;
static int nsequences = 0;
+static AttributeStatsBuffer attrstats =
+{
+ NULL, 0, NULL, STATSBUF_UNINITIALIZED
+};
+
+/*
+ * The maximum number of relations that should be fetched in any one
+ * getAttributeStats() call.
+ */
+
+#define MAX_ATTR_STATS_RELS 100
+
/*
* The default number of rows per INSERT when
* --inserts is specified without --rows-per-insert
@@ -222,6 +253,8 @@ static int nsequences = 0;
*/
#define MAX_BLOBS_PER_ARCHIVE_ENTRY 1000
+
+
/*
* Macro for producing quoted, schema-qualified name of a dumpable object.
*/
@@ -399,6 +432,9 @@ static void setupDumpWorker(Archive *AH);
static TableInfo *getRootTableInfo(const TableInfo *tbinfo);
static bool forcePartitionRootLoad(const TableInfo *tbinfo);
static void read_dump_filters(const char *filename, DumpOptions *dopt);
+static void appendNamedArgument(PQExpBuffer out, Archive *fout,
+ const char *argname, const char *argtype,
+ const char *argval);
int
@@ -10560,7 +10596,286 @@ appendNamedArgument(PQExpBuffer out, Archive *fout, const char *argname,
}
/*
- * printDumpRelationStats --
+ * Fetch next batch of rows from getAttributeStats()
+ */
+static void
+fetchNextAttributeStats(Archive *fout)
+{
+ ArchiveHandle *AH = (ArchiveHandle *) fout;
+ PQExpBufferData schemas;
+ PQExpBufferData relations;
+ int numoids = 0;
+
+ Assert(AH != NULL);
+
+ /* free last result set, if any */
+ if (attrstats.state == STATSBUF_ACTIVE)
+ PQclear(attrstats.res);
+
+ /* If we have looped around to the start of the TOC, restart */
+ if (attrstats.te == AH->toc)
+ attrstats.te = AH->toc->next;
+
+ initPQExpBuffer(&schemas);
+ initPQExpBuffer(&relations);
+
+ /*
+ * Walk ahead looking for relstats entries that are active in this
+ * section, adding the names to the schemas and relations lists.
+ */
+ while ((attrstats.te != AH->toc) && (numoids < MAX_ATTR_STATS_RELS))
+ {
+ if (attrstats.te->reqs != 0 &&
+ strcmp(attrstats.te->desc, "STATISTICS DATA") == 0)
+ {
+ RelStatsInfo *rsinfo = (RelStatsInfo *) attrstats.te->createDumperArg;
+
+ Assert(rsinfo != NULL);
+
+ if (numoids > 0)
+ {
+ appendPQExpBufferStr(&schemas, ",");
+ appendPQExpBufferStr(&relations, ",");
+ }
+ appendPQExpBufferStr(&schemas, fmtId(rsinfo->dobj.namespace->dobj.name));
+ appendPQExpBufferStr(&relations, fmtId(rsinfo->dobj.name));
+ numoids++;
+ }
+
+ attrstats.te = attrstats.te->next;
+ }
+
+ if (numoids > 0)
+ {
+ PQExpBufferData query;
+
+ initPQExpBuffer(&query);
+ appendPQExpBuffer(&query,
+ "EXECUTE getAttributeStats('{%s}'::pg_catalog.text[],'{%s}'::pg_catalog.text[])",
+ schemas.data, relations.data);
+ attrstats.res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK);
+ attrstats.idx = 0;
+ }
+ else
+ {
+ attrstats.state = STATSBUF_EXHAUSTED;
+ attrstats.res = NULL;
+ attrstats.idx = -1;
+ }
+
+ termPQExpBuffer(&schemas);
+ termPQExpBuffer(&relations);
+}
+
+/*
+ * Prepare the getAttributeStats() statement
+ *
+ * This is done automatically if the user specified dumpStatistics.
+ */
+static void
+initAttributeStats(Archive *fout)
+{
+ ArchiveHandle *AH = (ArchiveHandle *) fout;
+ PQExpBufferData query;
+
+ Assert(AH != NULL);
+ initPQExpBuffer(&query);
+
+ appendPQExpBufferStr(&query,
+ "PREPARE getAttributeStats(pg_catalog.text[], pg_catalog.text[]) AS\n"
+ "SELECT s.schemaname, s.tablename, s.attname, s.inherited, "
+ "s.null_frac, s.avg_width, s.n_distinct, s.most_common_vals, "
+ "s.most_common_freqs, s.histogram_bounds, s.correlation, "
+ "s.most_common_elems, s.most_common_elem_freqs, "
+ "s.elem_count_histogram, ");
+
+ if (fout->remoteVersion >= 170000)
+ appendPQExpBufferStr(&query,
+ "s.range_length_histogram, "
+ "s.range_empty_frac, "
+ "s.range_bounds_histogram ");
+ else
+ appendPQExpBufferStr(&query,
+ "NULL AS range_length_histogram, "
+ "NULL AS range_empty_frac, "
+ " NULL AS range_bounds_histogram ");
+
+ /*
+ * The results must be in the order of relations supplied in the
+ * parameters to ensure that they are in sync with a walk of the TOC.
+ *
+ * The redundant (and incomplete) filter clause on s.tablename = ANY(...)
+ * is a way to lead the query into using the index
+ * pg_class_relname_nsp_index which in turn allows the planner to avoid an
+ * expensive full scan of pg_stats.
+ *
+ * We may need to adjust this query for versions that are not so easily
+ * led.
+ */
+ appendPQExpBufferStr(&query,
+ "FROM pg_catalog.pg_stats AS s "
+ "JOIN unnest($1, $2) WITH ORDINALITY AS u(schemaname, tablename, ord) "
+ "ON s.schemaname = u.schemaname "
+ "AND s.tablename = u.tablename "
+ "WHERE s.tablename = ANY($2) "
+ "ORDER BY u.ord, s.attname, s.inherited");
+
+ ExecuteSqlStatement(fout, query.data);
+
+ termPQExpBuffer(&query);
+
+ attrstats.te = AH->toc->next;
+
+ fetchNextAttributeStats(fout);
+
+ attrstats.state = STATSBUF_ACTIVE;
+}
+
+
+/*
+ * append a single attribute stat to the buffer for this relation.
+ */
+static void
+appendAttributeStats(Archive *fout, PQExpBuffer out,
+ const RelStatsInfo *rsinfo)
+{
+ PGresult *res = attrstats.res;
+ int tup_num = attrstats.idx;
+
+ const char *attname;
+
+ static bool indexes_set = false;
+ static int i_attname,
+ i_inherited,
+ i_null_frac,
+ i_avg_width,
+ i_n_distinct,
+ i_most_common_vals,
+ i_most_common_freqs,
+ i_histogram_bounds,
+ i_correlation,
+ i_most_common_elems,
+ i_most_common_elem_freqs,
+ i_elem_count_histogram,
+ i_range_length_histogram,
+ i_range_empty_frac,
+ i_range_bounds_histogram;
+
+ if (!indexes_set)
+ {
+ /*
+ * It's a prepared statement, so the indexes will be the same for all
+ * result sets, so we only need to set them once.
+ */
+ i_attname = PQfnumber(res, "attname");
+ i_inherited = PQfnumber(res, "inherited");
+ i_null_frac = PQfnumber(res, "null_frac");
+ i_avg_width = PQfnumber(res, "avg_width");
+ i_n_distinct = PQfnumber(res, "n_distinct");
+ i_most_common_vals = PQfnumber(res, "most_common_vals");
+ i_most_common_freqs = PQfnumber(res, "most_common_freqs");
+ i_histogram_bounds = PQfnumber(res, "histogram_bounds");
+ i_correlation = PQfnumber(res, "correlation");
+ i_most_common_elems = PQfnumber(res, "most_common_elems");
+ i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
+ i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
+ i_range_length_histogram = PQfnumber(res, "range_length_histogram");
+ i_range_empty_frac = PQfnumber(res, "range_empty_frac");
+ i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+ indexes_set = true;
+ }
+
+ appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
+ appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+ fout->remoteVersion);
+ appendPQExpBufferStr(out, "\t'schemaname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+ appendPQExpBufferStr(out, ",\n\t'relname', ");
+ appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+
+ if (PQgetisnull(res, tup_num, i_attname))
+ pg_fatal("attname cannot be NULL");
+ attname = PQgetvalue(res, tup_num, i_attname);
+
+ /*
+ * Indexes look up attname in indAttNames to derive attnum, all others use
+ * attname directly. We must specify attnum for indexes, since their
+ * attnames are not necessarily stable across dump/reload.
+ */
+ if (rsinfo->nindAttNames == 0)
+ {
+ appendPQExpBuffer(out, ",\n\t'attname', ");
+ appendStringLiteralAH(out, attname, fout);
+ }
+ else
+ {
+ bool found = false;
+
+ for (int i = 0; i < rsinfo->nindAttNames; i++)
+ if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
+ {
+ appendPQExpBuffer(out, ",\n\t'attnum', '%d'::smallint",
+ i + 1);
+ found = true;
+ break;
+ }
+
+ if (!found)
+ pg_fatal("could not find index attname \"%s\"", attname);
+ }
+
+ if (!PQgetisnull(res, tup_num, i_inherited))
+ appendNamedArgument(out, fout, "inherited", "boolean",
+ PQgetvalue(res, tup_num, i_inherited));
+ if (!PQgetisnull(res, tup_num, i_null_frac))
+ appendNamedArgument(out, fout, "null_frac", "real",
+ PQgetvalue(res, tup_num, i_null_frac));
+ if (!PQgetisnull(res, tup_num, i_avg_width))
+ appendNamedArgument(out, fout, "avg_width", "integer",
+ PQgetvalue(res, tup_num, i_avg_width));
+ if (!PQgetisnull(res, tup_num, i_n_distinct))
+ appendNamedArgument(out, fout, "n_distinct", "real",
+ PQgetvalue(res, tup_num, i_n_distinct));
+ if (!PQgetisnull(res, tup_num, i_most_common_vals))
+ appendNamedArgument(out, fout, "most_common_vals", "text",
+ PQgetvalue(res, tup_num, i_most_common_vals));
+ if (!PQgetisnull(res, tup_num, i_most_common_freqs))
+ appendNamedArgument(out, fout, "most_common_freqs", "real[]",
+ PQgetvalue(res, tup_num, i_most_common_freqs));
+ if (!PQgetisnull(res, tup_num, i_histogram_bounds))
+ appendNamedArgument(out, fout, "histogram_bounds", "text",
+ PQgetvalue(res, tup_num, i_histogram_bounds));
+ if (!PQgetisnull(res, tup_num, i_correlation))
+ appendNamedArgument(out, fout, "correlation", "real",
+ PQgetvalue(res, tup_num, i_correlation));
+ if (!PQgetisnull(res, tup_num, i_most_common_elems))
+ appendNamedArgument(out, fout, "most_common_elems", "text",
+ PQgetvalue(res, tup_num, i_most_common_elems));
+ if (!PQgetisnull(res, tup_num, i_most_common_elem_freqs))
+ appendNamedArgument(out, fout, "most_common_elem_freqs", "real[]",
+ PQgetvalue(res, tup_num, i_most_common_elem_freqs));
+ if (!PQgetisnull(res, tup_num, i_elem_count_histogram))
+ appendNamedArgument(out, fout, "elem_count_histogram", "real[]",
+ PQgetvalue(res, tup_num, i_elem_count_histogram));
+ if (fout->remoteVersion >= 170000)
+ {
+ if (!PQgetisnull(res, tup_num, i_range_length_histogram))
+ appendNamedArgument(out, fout, "range_length_histogram", "text",
+ PQgetvalue(res, tup_num, i_range_length_histogram));
+ if (!PQgetisnull(res, tup_num, i_range_empty_frac))
+ appendNamedArgument(out, fout, "range_empty_frac", "real",
+ PQgetvalue(res, tup_num, i_range_empty_frac));
+ if (!PQgetisnull(res, tup_num, i_range_bounds_histogram))
+ appendNamedArgument(out, fout, "range_bounds_histogram", "text",
+ PQgetvalue(res, tup_num, i_range_bounds_histogram));
+ }
+ appendPQExpBufferStr(out, "\n);\n");
+}
+
+
+
+/*
+ * printRelationStats --
*
* Generate the SQL statements needed to restore a relation's statistics.
*/
@@ -10568,64 +10883,21 @@ static char *
printRelationStats(Archive *fout, const void *userArg)
{
const RelStatsInfo *rsinfo = (RelStatsInfo *) userArg;
- const DumpableObject *dobj = &rsinfo->dobj;
+ const DumpableObject *dobj;
+ const char *relschema;
+ const char *relname;
+
+ ArchiveHandle *AH = (ArchiveHandle *) fout;
- PQExpBufferData query;
PQExpBufferData out;
- PGresult *res;
-
- static bool first_query = true;
- static int i_attname;
- static int i_inherited;
- static int i_null_frac;
- static int i_avg_width;
- static int i_n_distinct;
- static int i_most_common_vals;
- static int i_most_common_freqs;
- static int i_histogram_bounds;
- static int i_correlation;
- static int i_most_common_elems;
- static int i_most_common_elem_freqs;
- static int i_elem_count_histogram;
- static int i_range_length_histogram;
- static int i_range_empty_frac;
- static int i_range_bounds_histogram;
-
- initPQExpBuffer(&query);
-
- if (first_query)
- {
- appendPQExpBufferStr(&query,
- "PREPARE getAttributeStats(pg_catalog.text, pg_catalog.text) AS\n"
- "SELECT s.attname, s.inherited, "
- "s.null_frac, s.avg_width, s.n_distinct, "
- "s.most_common_vals, s.most_common_freqs, "
- "s.histogram_bounds, s.correlation, "
- "s.most_common_elems, s.most_common_elem_freqs, "
- "s.elem_count_histogram, ");
-
- if (fout->remoteVersion >= 170000)
- appendPQExpBufferStr(&query,
- "s.range_length_histogram, "
- "s.range_empty_frac, "
- "s.range_bounds_histogram ");
- else
- appendPQExpBufferStr(&query,
- "NULL AS range_length_histogram,"
- "NULL AS range_empty_frac,"
- "NULL AS range_bounds_histogram ");
-
- appendPQExpBufferStr(&query,
- "FROM pg_catalog.pg_stats s "
- "WHERE s.schemaname = $1 "
- "AND s.tablename = $2 "
- "ORDER BY s.attname, s.inherited");
-
- ExecuteSqlStatement(fout, query.data);
-
- resetPQExpBuffer(&query);
- }
+ Assert(rsinfo != NULL);
+ dobj = &rsinfo->dobj;
+ Assert(dobj != NULL);
+ relschema = dobj->namespace->dobj.name;
+ Assert(relschema != NULL);
+ relname = dobj->name;
+ Assert(relname != NULL);
initPQExpBuffer(&out);
@@ -10646,132 +10918,72 @@ printRelationStats(Archive *fout, const void *userArg)
appendPQExpBufferStr(&out, "\n);\n");
- /* fetch attribute stats */
- appendPQExpBufferStr(&query, "EXECUTE getAttributeStats(");
- appendStringLiteralAH(&query, dobj->namespace->dobj.name, fout);
- appendPQExpBufferStr(&query, ", ");
- appendStringLiteralAH(&query, dobj->name, fout);
- appendPQExpBufferStr(&query, ")");
+ AH->txnCount++;
- res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK);
+ if (attrstats.state == STATSBUF_UNINITIALIZED)
+ initAttributeStats(fout);
- if (first_query)
+ /*
+ * Because the query returns rows in the same order as the relations
+ * requested, and because every relation gets at least one row in the
+ * result set, the first row for this relation must correspond either to
+ * the current row of this result set (if one exists) or the first row of
+ * the next result set (if this one is already consumed).
+ */
+ if (attrstats.state != STATSBUF_ACTIVE)
+ pg_fatal("Exhausted getAttributeStats() before processing %s.%s",
+ rsinfo->dobj.namespace->dobj.name,
+ rsinfo->dobj.name);
+
+ /*
+ * If the current result set has been fully consumed, then the row(s) we
+ * need (if any) would be found in the next one. This will update
+ * attrstats.res and attrstats.idx.
+ */
+ if (PQntuples(attrstats.res) <= attrstats.idx)
+ fetchNextAttributeStats(fout);
+
+ while (true)
{
- i_attname = PQfnumber(res, "attname");
- i_inherited = PQfnumber(res, "inherited");
- i_null_frac = PQfnumber(res, "null_frac");
- i_avg_width = PQfnumber(res, "avg_width");
- i_n_distinct = PQfnumber(res, "n_distinct");
- i_most_common_vals = PQfnumber(res, "most_common_vals");
- i_most_common_freqs = PQfnumber(res, "most_common_freqs");
- i_histogram_bounds = PQfnumber(res, "histogram_bounds");
- i_correlation = PQfnumber(res, "correlation");
- i_most_common_elems = PQfnumber(res, "most_common_elems");
- i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
- i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
- i_range_length_histogram = PQfnumber(res, "range_length_histogram");
- i_range_empty_frac = PQfnumber(res, "range_empty_frac");
- i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
- first_query = false;
- }
-
- /* restore attribute stats */
- for (int rownum = 0; rownum < PQntuples(res); rownum++)
- {
- const char *attname;
-
- appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
- appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n",
- fout->remoteVersion);
- appendPQExpBufferStr(&out, "\t'schemaname', ");
- appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
- appendPQExpBufferStr(&out, ",\n\t'relname', ");
- appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
-
- if (PQgetisnull(res, rownum, i_attname))
- pg_fatal("attname cannot be NULL");
- attname = PQgetvalue(res, rownum, i_attname);
+ int i_schemaname;
+ int i_tablename;
+ char *schemaname;
+ char *tablename; /* misnomer, following pg_stats naming */
/*
- * Indexes look up attname in indAttNames to derive attnum, all others
- * use attname directly. We must specify attnum for indexes, since
- * their attnames are not necessarily stable across dump/reload.
+ * If we hit the end of the result set, then there are no more records
+ * for this relation, so we should stop, but first get the next result
+ * set for the next batch of relations.
*/
- if (rsinfo->nindAttNames == 0)
+ if (PQntuples(attrstats.res) <= attrstats.idx)
{
- appendPQExpBuffer(&out, ",\n\t'attname', ");
- appendStringLiteralAH(&out, attname, fout);
- }
- else
- {
- bool found = false;
-
- for (int i = 0; i < rsinfo->nindAttNames; i++)
- {
- if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
- {
- appendPQExpBuffer(&out, ",\n\t'attnum', '%d'::smallint",
- i + 1);
- found = true;
- break;
- }
- }
-
- if (!found)
- pg_fatal("could not find index attname \"%s\"", attname);
+ fetchNextAttributeStats(fout);
+ break;
}
- if (!PQgetisnull(res, rownum, i_inherited))
- appendNamedArgument(&out, fout, "inherited", "boolean",
- PQgetvalue(res, rownum, i_inherited));
- if (!PQgetisnull(res, rownum, i_null_frac))
- appendNamedArgument(&out, fout, "null_frac", "real",
- PQgetvalue(res, rownum, i_null_frac));
- if (!PQgetisnull(res, rownum, i_avg_width))
- appendNamedArgument(&out, fout, "avg_width", "integer",
- PQgetvalue(res, rownum, i_avg_width));
- if (!PQgetisnull(res, rownum, i_n_distinct))
- appendNamedArgument(&out, fout, "n_distinct", "real",
- PQgetvalue(res, rownum, i_n_distinct));
- if (!PQgetisnull(res, rownum, i_most_common_vals))
- appendNamedArgument(&out, fout, "most_common_vals", "text",
- PQgetvalue(res, rownum, i_most_common_vals));
- if (!PQgetisnull(res, rownum, i_most_common_freqs))
- appendNamedArgument(&out, fout, "most_common_freqs", "real[]",
- PQgetvalue(res, rownum, i_most_common_freqs));
- if (!PQgetisnull(res, rownum, i_histogram_bounds))
- appendNamedArgument(&out, fout, "histogram_bounds", "text",
- PQgetvalue(res, rownum, i_histogram_bounds));
- if (!PQgetisnull(res, rownum, i_correlation))
- appendNamedArgument(&out, fout, "correlation", "real",
- PQgetvalue(res, rownum, i_correlation));
- if (!PQgetisnull(res, rownum, i_most_common_elems))
- appendNamedArgument(&out, fout, "most_common_elems", "text",
- PQgetvalue(res, rownum, i_most_common_elems));
- if (!PQgetisnull(res, rownum, i_most_common_elem_freqs))
- appendNamedArgument(&out, fout, "most_common_elem_freqs", "real[]",
- PQgetvalue(res, rownum, i_most_common_elem_freqs));
- if (!PQgetisnull(res, rownum, i_elem_count_histogram))
- appendNamedArgument(&out, fout, "elem_count_histogram", "real[]",
- PQgetvalue(res, rownum, i_elem_count_histogram));
- if (fout->remoteVersion >= 170000)
- {
- if (!PQgetisnull(res, rownum, i_range_length_histogram))
- appendNamedArgument(&out, fout, "range_length_histogram", "text",
- PQgetvalue(res, rownum, i_range_length_histogram));
- if (!PQgetisnull(res, rownum, i_range_empty_frac))
- appendNamedArgument(&out, fout, "range_empty_frac", "real",
- PQgetvalue(res, rownum, i_range_empty_frac));
- if (!PQgetisnull(res, rownum, i_range_bounds_histogram))
- appendNamedArgument(&out, fout, "range_bounds_histogram", "text",
- PQgetvalue(res, rownum, i_range_bounds_histogram));
- }
- appendPQExpBufferStr(&out, "\n);\n");
+ i_schemaname = PQfnumber(attrstats.res, "schemaname");
+ Assert(i_schemaname >= 0);
+ i_tablename = PQfnumber(attrstats.res, "tablename");
+ Assert(i_tablename >= 0);
+
+ if (PQgetisnull(attrstats.res, attrstats.idx, i_schemaname))
+ pg_fatal("getAttributeStats() schemaname cannot be NULL");
+
+ if (PQgetisnull(attrstats.res, attrstats.idx, i_tablename))
+ pg_fatal("getAttributeStats() tablename cannot be NULL");
+
+ schemaname = PQgetvalue(attrstats.res, attrstats.idx, i_schemaname);
+ tablename = PQgetvalue(attrstats.res, attrstats.idx, i_tablename);
+
+ /* stop if current stat row isn't for this relation */
+ if (strcmp(relname, tablename) != 0 || strcmp(relschema, schemaname) != 0)
+ break;
+
+ appendAttributeStats(fout, &out, rsinfo);
+ AH->txnCount++;
+ attrstats.idx++;
}
- PQclear(res);
-
- termPQExpBuffer(&query);
return out.data;
}
--
2.49.0
[text/x-patch] v12-0003-Downgrade-many-pg_restore_-_stats-errors-to-warn.patch (30.4K, ../../CADkLM=domd5+CvjKMHGbOfvSuY6J8G-x+9M2D6Ss2HamYefE9w@mail.gmail.com/5-v12-0003-Downgrade-many-pg_restore_-_stats-errors-to-warn.patch)
download | inline diff:
From 16794820dedd79ec58f8692da5b50a4d8976620a Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sat, 8 Mar 2025 00:52:41 -0500
Subject: [PATCH v12 3/3] Downgrade many pg_restore_*_stats errors to warnings.
We want to avoid errors that can potentially stop an otherwise
successful pg_upgrade or pg_restore operation. With that in mind, change
as many ERROR reports to WARNING + early termination with no data
updated.
---
src/include/statistics/stat_utils.h | 4 +-
src/backend/statistics/attribute_stats.c | 120 ++++++++++----
src/backend/statistics/relation_stats.c | 12 +-
src/backend/statistics/stat_utils.c | 65 ++++++--
src/test/regress/expected/stats_import.out | 184 ++++++++++++++++-----
src/test/regress/sql/stats_import.sql | 36 ++--
6 files changed, 309 insertions(+), 112 deletions(-)
diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h
index 512eb776e0e..809c8263a41 100644
--- a/src/include/statistics/stat_utils.h
+++ b/src/include/statistics/stat_utils.h
@@ -21,7 +21,7 @@ struct StatsArgInfo
Oid argtype;
};
-extern void stats_check_required_arg(FunctionCallInfo fcinfo,
+extern bool stats_check_required_arg(FunctionCallInfo fcinfo,
struct StatsArgInfo *arginfo,
int argnum);
extern bool stats_check_arg_array(FunctionCallInfo fcinfo,
@@ -30,7 +30,7 @@ extern bool stats_check_arg_pair(FunctionCallInfo fcinfo,
struct StatsArgInfo *arginfo,
int argnum1, int argnum2);
-extern void stats_lock_check_privileges(Oid reloid);
+extern bool stats_lock_check_privileges(Oid reloid);
extern Oid stats_lookup_relid(const char *nspname, const char *relname);
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index f5eb17ba42d..b7ba1622391 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -100,7 +100,7 @@ static struct StatsArgInfo cleararginfo[] =
static bool attribute_statistics_update(FunctionCallInfo fcinfo);
static Node *get_attr_expr(Relation rel, int attnum);
-static void get_attr_stat_type(Oid reloid, AttrNumber attnum,
+static bool get_attr_stat_type(Oid reloid, AttrNumber attnum,
Oid *atttypid, int32 *atttypmod,
char *atttyptype, Oid *atttypcoll,
Oid *eq_opr, Oid *lt_opr);
@@ -129,10 +129,12 @@ static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
* stored as an anyarray, and the representation of the array needs to store
* the correct element type, which must be derived from the attribute.
*
- * Major errors, such as the table not existing, the attribute not existing,
- * or a permissions failure are always reported at ERROR. Other errors, such
- * as a conversion failure on one statistic kind, are reported as a WARNING
- * and other statistic kinds may still be updated.
+ * This function is called during database upgrades and restorations, therefore
+ * it is imperative to avoid ERRORs that could potentially end the upgrade or
+ * restore unless. Major errors, such as the table not existing, the attribute
+ * not existing, or permissions failure are reported as WARNINGs with an end to
+ * the function, thus allowing the upgrade/restore to continue, but without the
+ * stats that can be regenereated once the database is online again.
*/
static bool
attribute_statistics_update(FunctionCallInfo fcinfo)
@@ -148,8 +150,8 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
HeapTuple statup;
Oid atttypid = InvalidOid;
- int32 atttypmod;
- char atttyptype;
+ int32 atttypmod = -1;
+ char atttyptype = TYPTYPE_PSEUDO; /* Not a great default, but there is no TYPTYPE_INVALID */
Oid atttypcoll = InvalidOid;
Oid eq_opr = InvalidOid;
Oid lt_opr = InvalidOid;
@@ -176,38 +178,52 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
bool result = true;
- stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG);
- stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG);
+ if (!stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG))
+ return false;
+ if (!stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG))
+ return false;
nspname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELSCHEMA_ARG));
relname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELNAME_ARG));
reloid = stats_lookup_relid(nspname, relname);
+ if (!OidIsValid(reloid))
+ return false;
if (RecoveryInProgress())
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("recovery is in progress"),
errhint("Statistics cannot be modified during recovery.")));
+ return false;
+ }
/* lock before looking up attribute */
- stats_lock_check_privileges(reloid);
+ if (!stats_lock_check_privileges(reloid))
+ return false;
/* user can specify either attname or attnum, but not both */
if (!PG_ARGISNULL(ATTNAME_ARG))
{
if (!PG_ARGISNULL(ATTNUM_ARG))
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("cannot specify both attname and attnum")));
+ return false;
+ }
attname = TextDatumGetCString(PG_GETARG_DATUM(ATTNAME_ARG));
attnum = get_attnum(reloid, attname);
/* note that this test covers attisdropped cases too: */
if (attnum == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
attname, relname)));
+ return false;
+ }
}
else if (!PG_ARGISNULL(ATTNUM_ARG))
{
@@ -216,27 +232,33 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
/* annoyingly, get_attname doesn't check attisdropped */
if (attname == NULL ||
!SearchSysCacheExistsAttName(reloid, attname))
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column %d of relation \"%s\" does not exist",
attnum, relname)));
+ return false;
+ }
}
else
{
- ereport(ERROR,
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("must specify either attname or attnum")));
- attname = NULL; /* keep compiler quiet */
- attnum = 0;
+ return false;
}
if (attnum < 0)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot modify statistics on system column \"%s\"",
attname)));
+ return false;
+ }
- stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG);
+ if (!stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG))
+ return false;
inherited = PG_GETARG_BOOL(INHERITED_ARG);
/*
@@ -285,10 +307,11 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
}
/* derive information from attribute */
- get_attr_stat_type(reloid, attnum,
- &atttypid, &atttypmod,
- &atttyptype, &atttypcoll,
- &eq_opr, <_opr);
+ if (!get_attr_stat_type(reloid, attnum,
+ &atttypid, &atttypmod,
+ &atttyptype, &atttypcoll,
+ &eq_opr, <_opr))
+ result = false;
/* if needed, derive element type */
if (do_mcelem || do_dechist)
@@ -568,7 +591,7 @@ get_attr_expr(Relation rel, int attnum)
/*
* Derive type information from the attribute.
*/
-static void
+static bool
get_attr_stat_type(Oid reloid, AttrNumber attnum,
Oid *atttypid, int32 *atttypmod,
char *atttyptype, Oid *atttypcoll,
@@ -585,18 +608,26 @@ get_attr_stat_type(Oid reloid, AttrNumber attnum,
/* Attribute not found */
if (!HeapTupleIsValid(atup))
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("attribute %d of relation \"%s\" does not exist",
attnum, RelationGetRelationName(rel))));
+ relation_close(rel, NoLock);
+ return false;
+ }
attr = (Form_pg_attribute) GETSTRUCT(atup);
if (attr->attisdropped)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("attribute %d of relation \"%s\" does not exist",
attnum, RelationGetRelationName(rel))));
+ relation_close(rel, NoLock);
+ return false;
+ }
expr = get_attr_expr(rel, attr->attnum);
@@ -645,6 +676,7 @@ get_attr_stat_type(Oid reloid, AttrNumber attnum,
*atttypcoll = DEFAULT_COLLATION_OID;
relation_close(rel, NoLock);
+ return true;
}
/*
@@ -770,6 +802,10 @@ set_stats_slot(Datum *values, bool *nulls, bool *replaces,
if (slotidx >= STATISTIC_NUM_SLOTS && first_empty >= 0)
slotidx = first_empty;
+ /*
+ * Currently there is no datatype that can have more than STATISTIC_NUM_SLOTS
+ * statistic kinds, so this can safely remain an ERROR for now.
+ */
if (slotidx >= STATISTIC_NUM_SLOTS)
ereport(ERROR,
(errmsg("maximum number of statistics slots exceeded: %d",
@@ -915,38 +951,54 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
AttrNumber attnum;
bool inherited;
- stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG);
- stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG);
- stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
- stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
+ if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG))
+ PG_RETURN_VOID();
+ if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG))
+ PG_RETURN_VOID();
+ if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG))
+ PG_RETURN_VOID();
+ if (!stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG))
+ PG_RETURN_VOID();
nspname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELSCHEMA_ARG));
relname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELNAME_ARG));
reloid = stats_lookup_relid(nspname, relname);
+ if (!OidIsValid(reloid))
+ PG_RETURN_VOID();
if (RecoveryInProgress())
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("recovery is in progress"),
errhint("Statistics cannot be modified during recovery.")));
+ PG_RETURN_VOID();
+ }
- stats_lock_check_privileges(reloid);
+ if (!stats_lock_check_privileges(reloid))
+ PG_RETURN_VOID();
attname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTNAME_ARG));
attnum = get_attnum(reloid, attname);
if (attnum < 0)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot clear statistics on system column \"%s\"",
attname)));
+ PG_RETURN_VOID();
+ }
if (attnum == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
attname, get_rel_name(reloid))));
+ PG_RETURN_VOID();
+ }
inherited = PG_GETARG_BOOL(C_INHERITED_ARG);
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index cd3a75b621a..7c47af15c9f 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -83,13 +83,18 @@ relation_statistics_update(FunctionCallInfo fcinfo)
bool nulls[4] = {0};
int nreplaces = 0;
- stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG);
- stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG);
+ if (!stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG))
+ return false;
+
+ if (!stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG))
+ return false;
nspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
reloid = stats_lookup_relid(nspname, relname);
+ if (!OidIsValid(reloid))
+ return false;
if (RecoveryInProgress())
ereport(ERROR,
@@ -97,7 +102,8 @@ relation_statistics_update(FunctionCallInfo fcinfo)
errmsg("recovery is in progress"),
errhint("Statistics cannot be modified during recovery.")));
- stats_lock_check_privileges(reloid);
+ if (!stats_lock_check_privileges(reloid))
+ return false;
if (!PG_ARGISNULL(RELPAGES_ARG))
{
diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c
index a9a3224efe6..d587e875457 100644
--- a/src/backend/statistics/stat_utils.c
+++ b/src/backend/statistics/stat_utils.c
@@ -33,16 +33,20 @@
/*
* Ensure that a given argument is not null.
*/
-void
+bool
stats_check_required_arg(FunctionCallInfo fcinfo,
struct StatsArgInfo *arginfo,
int argnum)
{
if (PG_ARGISNULL(argnum))
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("\"%s\" cannot be NULL",
arginfo[argnum].argname)));
+ return false;
+ }
+ return true;
}
/*
@@ -127,13 +131,14 @@ stats_check_arg_pair(FunctionCallInfo fcinfo,
* - the role owns the current database and the relation is not shared
* - the role has the MAINTAIN privilege on the relation
*/
-void
+bool
stats_lock_check_privileges(Oid reloid)
{
Relation table;
Oid table_oid = reloid;
Oid index_oid = InvalidOid;
LOCKMODE index_lockmode = NoLock;
+ bool ok = true;
/*
* For indexes, we follow the locking behavior in do_analyze_rel() and
@@ -173,14 +178,15 @@ stats_lock_check_privileges(Oid reloid)
case RELKIND_PARTITIONED_TABLE:
break;
default:
- ereport(ERROR,
+ ereport(WARNING,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot modify statistics for relation \"%s\"",
RelationGetRelationName(table)),
errdetail_relkind_not_supported(table->rd_rel->relkind)));
+ ok = false;
}
- if (OidIsValid(index_oid))
+ if (ok && (OidIsValid(index_oid)))
{
Relation index;
@@ -193,25 +199,33 @@ stats_lock_check_privileges(Oid reloid)
relation_close(index, NoLock);
}
- if (table->rd_rel->relisshared)
- ereport(ERROR,
+ if (ok && (table->rd_rel->relisshared))
+ {
+ ereport(WARNING,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot modify statistics for shared relation")));
+ ok = false;
+ }
- if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()))
+ if (ok && (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId())))
{
AclResult aclresult = pg_class_aclcheck(RelationGetRelid(table),
GetUserId(),
ACL_MAINTAIN);
if (aclresult != ACLCHECK_OK)
- aclcheck_error(aclresult,
- get_relkind_objtype(table->rd_rel->relkind),
- NameStr(table->rd_rel->relname));
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied for relation %s",
+ NameStr(table->rd_rel->relname))));
+ ok = false;
+ }
}
/* retain lock on table */
relation_close(table, NoLock);
+ return ok;
}
/*
@@ -223,10 +237,20 @@ stats_lookup_relid(const char *nspname, const char *relname)
Oid nspoid;
Oid reloid;
- nspoid = LookupExplicitNamespace(nspname, false);
+ nspoid = LookupExplicitNamespace(nspname, true);
+ if (!OidIsValid(nspoid))
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ nspname, relname)));
+
+ return InvalidOid;
+ }
+
reloid = get_relname_relid(relname, nspoid);
if (!OidIsValid(reloid))
- ereport(ERROR,
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s.%s\" does not exist",
nspname, relname)));
@@ -303,9 +327,12 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
&args, &types, &argnulls);
if (nargs % 2 != 0)
- ereport(ERROR,
+ {
+ ereport(WARNING,
errmsg("variadic arguments must be name/value pairs"),
errhint("Provide an even number of variadic arguments that can be divided into pairs."));
+ return false;
+ }
/*
* For each argument name/value pair, find corresponding positional
@@ -318,14 +345,20 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
char *argname;
if (argnulls[i])
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errmsg("name at variadic position %d is NULL", i + 1)));
+ return false;
+ }
if (types[i] != TEXTOID)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errmsg("name at variadic position %d has type \"%s\", expected type \"%s\"",
i + 1, format_type_be(types[i]),
format_type_be(TEXTOID))));
+ return false;
+ }
if (argnulls[i + 1])
continue;
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 48d6392b4ad..161cf67b711 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -46,49 +46,85 @@ SELECT pg_clear_relation_stats('stats_import', 'test');
--
-- relstats tests
--
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'relname', 'test',
'relpages', 17::integer);
-ERROR: "schemaname" cannot be NULL
--- error: relname missing
+WARNING: "schemaname" cannot be NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+-- warning: relname missing, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relpages', 17::integer);
-ERROR: "relname" cannot be NULL
---- error: schemaname is wrong type
+WARNING: "relname" cannot be NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+--- warning: schemaname is wrong type, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 3.6::float,
'relname', 'test',
'relpages', 17::integer);
WARNING: argument "schemaname" has type "double precision", expected type "text"
-ERROR: "schemaname" cannot be NULL
---- error: relname is wrong type
+WARNING: "schemaname" cannot be NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+--- warning: relname is wrong type, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 0::oid,
'relpages', 17::integer);
WARNING: argument "relname" has type "oid", expected type "text"
-ERROR: "relname" cannot be NULL
--- error: relation not found
+WARNING: "relname" cannot be NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+-- warning: relation not found, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'nope',
'relpages', 17::integer);
-ERROR: relation "stats_import.nope" does not exist
--- error: odd number of variadic arguments cannot be pairs
+WARNING: relation "stats_import.nope" does not exist
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+-- warning: odd number of variadic arguments cannot be pairs, nothing updated
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
'relallvisible');
-ERROR: variadic arguments must be name/value pairs
+WARNING: variadic arguments must be name/value pairs
HINT: Provide an even number of variadic arguments that can be divided into pairs.
--- error: argument name is NULL
+WARNING: "schemaname" cannot be NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+-- warning: argument name is NULL, nothing updated
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
NULL, '17'::integer);
-ERROR: name at variadic position 5 is NULL
+WARNING: name at variadic position 5 is NULL
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
-- starting stats
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
@@ -340,65 +376,110 @@ CREATE SEQUENCE stats_import.testseq;
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'testseq');
-ERROR: cannot modify statistics for relation "testseq"
+WARNING: cannot modify statistics for relation "testseq"
DETAIL: This operation is not supported for sequences.
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
-ERROR: cannot modify statistics for relation "testseq"
+WARNING: cannot modify statistics for relation "testseq"
DETAIL: This operation is not supported for sequences.
+ pg_clear_relation_stats
+-------------------------
+
+(1 row)
+
CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
-ERROR: cannot modify statistics for relation "testview"
+WARNING: cannot modify statistics for relation "testview"
DETAIL: This operation is not supported for views.
+ pg_clear_relation_stats
+-------------------------
+
+(1 row)
+
--
-- attribute stats
--
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: "schemaname" cannot be NULL
--- error: schema does not exist
+WARNING: "schemaname" cannot be NULL
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: schema does not exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'nope',
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: schema "nope" does not exist
--- error: relname missing
+WARNING: relation "nope.test" does not exist
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: relname missing, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: "relname" cannot be NULL
--- error: relname does not exist
+WARNING: "relname" cannot be NULL
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: relname does not exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'nope',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: relation "stats_import.nope" does not exist
--- error: relname null
+WARNING: relation "stats_import.nope" does not exist
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: relname null, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', NULL,
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: "relname" cannot be NULL
--- error: NULL attname
+WARNING: "relname" cannot be NULL
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: NULL attname, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', NULL,
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: must specify either attname or attnum
--- error: attname doesn't exist
+WARNING: must specify either attname or attnum
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: attname doesn't exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -407,8 +488,13 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'null_frac', 0.1::real,
'avg_width', 2::integer,
'n_distinct', 0.3::real);
-ERROR: column "nope" of relation "test" does not exist
--- error: both attname and attnum
+WARNING: column "nope" of relation "test" does not exist
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: both attname and attnum, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -416,30 +502,50 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: cannot specify both attname and attnum
--- error: neither attname nor attnum
+WARNING: cannot specify both attname and attnum
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: neither attname nor attnum, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: must specify either attname or attnum
--- error: attribute is system column
+WARNING: must specify either attname or attnum
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: attribute is system column, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'xmin',
'inherited', false::boolean,
'null_frac', 0.1::real);
-ERROR: cannot modify statistics on system column "xmin"
--- error: inherited null
+WARNING: cannot modify statistics on system column "xmin"
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warning: inherited null, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'id',
'inherited', NULL::boolean,
'null_frac', 0.1::real);
-ERROR: "inherited" cannot be NULL
+WARNING: "inherited" cannot be NULL
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
-- ok: just the fixed values, with version, no stakinds
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index d140733a750..be8045ceea5 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -39,41 +39,41 @@ SELECT pg_clear_relation_stats('stats_import', 'test');
-- relstats tests
--
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'relname', 'test',
'relpages', 17::integer);
--- error: relname missing
+-- warning: relname missing, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relpages', 17::integer);
---- error: schemaname is wrong type
+--- warning: schemaname is wrong type, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 3.6::float,
'relname', 'test',
'relpages', 17::integer);
---- error: relname is wrong type
+--- warning: relname is wrong type, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 0::oid,
'relpages', 17::integer);
--- error: relation not found
+-- warning: relation not found, nothing updated
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'nope',
'relpages', 17::integer);
--- error: odd number of variadic arguments cannot be pairs
+-- warning: odd number of variadic arguments cannot be pairs, nothing updated
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
'relallvisible');
--- error: argument name is NULL
+-- warning: argument name is NULL, nothing updated
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -246,14 +246,14 @@ SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname
-- attribute stats
--
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: schema does not exist
+-- warning: schema does not exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'nope',
'relname', 'test',
@@ -261,14 +261,14 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: relname missing
+-- warning: relname missing, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: relname does not exist
+-- warning: relname does not exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'nope',
@@ -276,7 +276,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: relname null
+-- warning: relname null, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', NULL,
@@ -284,7 +284,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: NULL attname
+-- warning: NULL attname, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -292,7 +292,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: attname doesn't exist
+-- warning: attname doesn't exist, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -302,7 +302,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'avg_width', 2::integer,
'n_distinct', 0.3::real);
--- error: both attname and attnum
+-- warning: both attname and attnum, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -311,14 +311,14 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: neither attname nor attnum
+-- warning: neither attname nor attnum, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: attribute is system column
+-- warning: attribute is system column, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
@@ -326,7 +326,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'inherited', false::boolean,
'null_frac', 0.1::real);
--- error: inherited null
+-- warning: inherited null, nothing updated
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
--
2.49.0
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-31 17:39 Robert Haas <[email protected]>
parent: Greg Sabino Mullane <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Robert Haas @ 2025-03-31 17:39 UTC (permalink / raw)
To: Greg Sabino Mullane <[email protected]>; +Cc: Jeff Davis <[email protected]>; Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>
On Thu, Feb 27, 2025 at 10:43 PM Greg Sabino Mullane <[email protected]> wrote:
> I know I'm coming late to this, but I would like us to rethink having statistics dumped by default.
+1. I think I said this before, but I don't think it's correct to
regard the statistics as part of the database. It's great for
pg_upgrade to preserve them, but I think doing so for a regular dump
should be opt-in.
--
Robert Haas
EDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-03-31 22:04 Jeff Davis <[email protected]>
parent: Robert Haas <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Jeff Davis @ 2025-03-31 22:04 UTC (permalink / raw)
To: Robert Haas <[email protected]>; Greg Sabino Mullane <[email protected]>; +Cc: Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>
On Mon, 2025-03-31 at 13:39 -0400, Robert Haas wrote:
> +1. I think I said this before, but I don't think it's correct to
> regard the statistics as part of the database. It's great for
> pg_upgrade to preserve them, but I think doing so for a regular dump
> should be opt-in.
I'm confused about the timing of this message -- we already have an
Open Item for 18 to make this decision. After commit bde2fb797a,
changing the default is a one-line change, so there's no technical
problem.
I thought the general plan was to decide during beta. Would you like to
make the decision now for some reason?
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-04-01 13:37 Robert Haas <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Robert Haas @ 2025-04-01 13:37 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Greg Sabino Mullane <[email protected]>; Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>
On Mon, Mar 31, 2025 at 6:04 PM Jeff Davis <[email protected]> wrote:
> On Mon, 2025-03-31 at 13:39 -0400, Robert Haas wrote:
> > +1. I think I said this before, but I don't think it's correct to
> > regard the statistics as part of the database. It's great for
> > pg_upgrade to preserve them, but I think doing so for a regular dump
> > should be opt-in.
>
> I'm confused about the timing of this message -- we already have an
> Open Item for 18 to make this decision. After commit bde2fb797a,
> changing the default is a one-line change, so there's no technical
> problem.
>
> I thought the general plan was to decide during beta. Would you like to
> make the decision now for some reason?
I don't think I was aware of the open item; I was just catching up on
email. But I also don't really see the value of waiting until beta to
make this decision. I seriously doubt that my opinion is going to
change. Maybe other people's will, though: I can only speak for
myself.
--
Robert Haas
EDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-04-01 20:24 Jeff Davis <[email protected]>
parent: Robert Haas <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Jeff Davis @ 2025-04-01 20:24 UTC (permalink / raw)
To: Robert Haas <[email protected]>; +Cc: Greg Sabino Mullane <[email protected]>; Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>
On Tue, 2025-04-01 at 09:37 -0400, Robert Haas wrote:
> I don't think I was aware of the open item; I was just catching up on
> email.
I lean towards making it opt-in for pg_dump and opt-out for pg_upgrade.
But I think we should leave open the possibility for changing the
default to opt-out for pg_dump in the future.
My reasoning for pg_dump is that releasing with stats as opt-in doesn't
put us in a worse position for making it opt-out later, so long as we
have the right set of both positive and negative options. It may even
be a better position because people have time to make their scripts
future proof by using the right combination of options.
> But I also don't really see the value of waiting until beta to
> make this decision. I seriously doubt that my opinion is going to
> change. Maybe other people's will, though: I can only speak for
> myself.
I don't think the last week before feature freeze, deep in a 400-email
thread is the best way to make decisions like this. Let's at least have
a focused thread on this topic and see if we can solicit opinions from
both sides.
Also, waiting to see if the performance improvements make it in, or
waiting for beta reports, may yield some new information that could
change minds.
Mid-beta might be too long, but let's wait for the final CF to settle
and give people the chance to respond to a top-level thread?
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-04-02 02:24 Robert Haas <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Robert Haas @ 2025-04-02 02:24 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Greg Sabino Mullane <[email protected]>; Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>
On Tue, Apr 1, 2025 at 4:24 PM Jeff Davis <[email protected]> wrote:
> On Tue, 2025-04-01 at 09:37 -0400, Robert Haas wrote:
> > I don't think I was aware of the open item; I was just catching up on
> > email.
>
> I lean towards making it opt-in for pg_dump and opt-out for pg_upgrade.
Big +1.
> But I think we should leave open the possibility for changing the
> default to opt-out for pg_dump in the future.
We can always decide to change things, but this is different from some
other cases. Sometimes we're waiting to enable a feature by default
until, say, we think it's stable. This case is more about user
expectations, which might not be so prone to changing over time (but
you never know).
> Mid-beta might be too long, but let's wait for the final CF to settle
> and give people the chance to respond to a top-level thread?
wfm, thanks.
--
Robert Haas
EDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-05-10 19:51 Greg Sabino Mullane <[email protected]>
parent: Robert Haas <[email protected]>
0 siblings, 1 reply; 199+ messages in thread
From: Greg Sabino Mullane @ 2025-05-10 19:51 UTC (permalink / raw)
To: Robert Haas <[email protected]>; +Cc: Jeff Davis <[email protected]>; Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>
On Tue, Apr 1, 2025 at 10:24 PM Robert Haas <[email protected]> wrote:
> On Tue, Apr 1, 2025 at 4:24 PM Jeff Davis <[email protected]> wrote:
> > On Tue, 2025-04-01 at 09:37 -0400, Robert Haas wrote:
> > > I don't think I was aware of the open item; I was just catching up on
> > > email.
> >
> > I lean towards making it opt-in for pg_dump and opt-out for pg_upgrade.
>
> Big +1.
>
I may have missed something (we seem to have a lot of threads for this
subject), but we are in beta and both pg_dump and pg_upgrade seem to be
opt-out? I still object strongly to this; pg_dump is meant to be a
canonical representation of the schema and data. Adding metadata that can
change from dump to dump seems wrong, and should be opt-in. I've not been
convinced otherwise why stats should be output by default.
To be clear, I 100% want it to be the default for pg_upgrade.
Maybe we are just leaving it enabled to see if anyone complains in beta,
but I don't want us to forget about it. :)
Cheers,
Greg
--
Crunchy Data - https://www.crunchydata.com
Enterprise Postgres Software Products & Tech Support
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-05-22 14:20 Robert Haas <[email protected]>
parent: Greg Sabino Mullane <[email protected]>
0 siblings, 2 replies; 199+ messages in thread
From: Robert Haas @ 2025-05-22 14:20 UTC (permalink / raw)
To: Greg Sabino Mullane <[email protected]>; +Cc: Jeff Davis <[email protected]>; Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>
On Sat, May 10, 2025 at 3:51 PM Greg Sabino Mullane <[email protected]> wrote:
> I may have missed something (we seem to have a lot of threads for this subject), but we are in beta and both pg_dump and pg_upgrade seem to be opt-out? I still object strongly to this; pg_dump is meant to be a canonical representation of the schema and data. Adding metadata that can change from dump to dump seems wrong, and should be opt-in. I've not been convinced otherwise why stats should be output by default.
>
> To be clear, I 100% want it to be the default for pg_upgrade.
>
> Maybe we are just leaving it enabled to see if anyone complains in beta, but I don't want us to forget about it. :)
Yeah. This could use comments from a few more people, but I really
hope we don't ship the final release this way. We do have a "Enable
statistics in pg_dump by default" item in the open items list under
"Decisions to Recheck Mid-Beta", but that's arguably now. It also sort
of looks like we might have a consensus anyway: Jeff said "I lean
towards making it opt-in for pg_dump and opt-out for pg_upgrade" and I
agree with that and it seems you do, too. So perhaps Jeff should make
it so?
--
Robert Haas
EDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-05-22 14:30 Nathan Bossart <[email protected]>
parent: Robert Haas <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Nathan Bossart @ 2025-05-22 14:30 UTC (permalink / raw)
To: Robert Haas <[email protected]>; +Cc: Greg Sabino Mullane <[email protected]>; Jeff Davis <[email protected]>; Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>
On Thu, May 22, 2025 at 10:20:16AM -0400, Robert Haas wrote:
> It also sort
> of looks like we might have a consensus anyway: Jeff said "I lean
> towards making it opt-in for pg_dump and opt-out for pg_upgrade" and I
> agree with that and it seems you do, too. So perhaps Jeff should make
> it so?
+1, I think we should go ahead and do this. If Jeff can't get to it, I'm
happy to pick it up in the next week or so.
--
nathan
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-05-22 14:53 Tom Lane <[email protected]>
parent: Nathan Bossart <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Tom Lane @ 2025-05-22 14:53 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: Robert Haas <[email protected]>; Greg Sabino Mullane <[email protected]>; Jeff Davis <[email protected]>; Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>
Nathan Bossart <[email protected]> writes:
> On Thu, May 22, 2025 at 10:20:16AM -0400, Robert Haas wrote:
>> It also sort
>> of looks like we might have a consensus anyway: Jeff said "I lean
>> towards making it opt-in for pg_dump and opt-out for pg_upgrade" and I
>> agree with that and it seems you do, too. So perhaps Jeff should make
>> it so?
> +1, I think we should go ahead and do this. If Jeff can't get to it, I'm
> happy to pick it up in the next week or so.
Works for me, too.
regards, tom lane
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-05-22 18:52 Jeff Davis <[email protected]>
parent: Robert Haas <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Jeff Davis @ 2025-05-22 18:52 UTC (permalink / raw)
To: Robert Haas <[email protected]>; Greg Sabino Mullane <[email protected]>; +Cc: Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>
On Thu, 2025-05-22 at 10:20 -0400, Robert Haas wrote:
> Yeah. This could use comments from a few more people, but I really
> hope we don't ship the final release this way. We do have a "Enable
> statistics in pg_dump by default" item in the open items list under
> "Decisions to Recheck Mid-Beta", but that's arguably now. It also
> sort
> of looks like we might have a consensus anyway: Jeff said "I lean
> towards making it opt-in for pg_dump and opt-out for pg_upgrade" and
> I
> agree with that and it seems you do, too. So perhaps Jeff should make
> it so?
Patch attached.
A couple minor points:
* The default for pg_restore is --no-statistics. That could cause a
minor surprise if the user specifies --with-statistics for pg_dump and
not for pg_restore. An argument could be made that "if the stats are
there, restore them", and I don't have a strong opinion about this
point, but defaulting to --no-statistics seems more consistent with
pg_dump.
* I added --with-statistics to most of the pg_dump tests. We can be
more judicious about which tests exercise statistics as a separate
commit, but I didn't want to change the test results as a part of this
commit.
Regards,
Jeff Davis
Attachments:
[text/x-patch] v1-0001-Change-defaults-for-statistics-export.patch (15.1K, ../../[email protected]/2-v1-0001-Change-defaults-for-statistics-export.patch)
download | inline diff:
From b76cb91441e2eefe278249e23fcd703d27a85a06 Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Thu, 22 May 2025 11:03:03 -0700
Subject: [PATCH v1] Change defaults for statistics export.
Set the default behavior of pg_dump, pg_dumpall, and pg_restore to be
--no-statistics. Leave the default for pg_upgrade to be
--with-statistics.
Discussion: https://postgr.es/m/CA+TgmoZ9=RnWcCOZiKYYjZs_AW1P4QXCw--h4dOLLHuf1Omung@mail.gmail.com
---
src/bin/pg_dump/pg_backup_archiver.c | 4 +-
src/bin/pg_dump/t/002_pg_dump.pl | 59 ++++++++++++++++++++++++++++
src/bin/pg_upgrade/dump.c | 2 +-
src/bin/pg_upgrade/pg_upgrade.c | 6 ++-
4 files changed, 66 insertions(+), 5 deletions(-)
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index afa42337b11..a66d88bbc51 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -152,7 +152,7 @@ InitDumpOptions(DumpOptions *opts)
opts->dumpSections = DUMP_UNSECTIONED;
opts->dumpSchema = true;
opts->dumpData = true;
- opts->dumpStatistics = true;
+ opts->dumpStatistics = false;
}
/*
@@ -1101,7 +1101,7 @@ NewRestoreOptions(void)
opts->compression_spec.level = 0;
opts->dumpSchema = true;
opts->dumpData = true;
- opts->dumpStatistics = true;
+ opts->dumpStatistics = false;
return opts;
}
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index cf34f71ea11..386e21e0c59 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -68,6 +68,7 @@ my %pgdump_runs = (
'--no-data',
'--sequence-data',
'--binary-upgrade',
+ '--with-statistics',
'--dbname' => 'postgres', # alternative way to specify database
],
restore_cmd => [
@@ -75,6 +76,7 @@ my %pgdump_runs = (
'--format' => 'custom',
'--verbose',
'--file' => "$tempdir/binary_upgrade.sql",
+ '--with-statistics',
"$tempdir/binary_upgrade.dump",
],
},
@@ -88,11 +90,13 @@ my %pgdump_runs = (
'--format' => 'custom',
'--compress' => '1',
'--file' => "$tempdir/compression_gzip_custom.dump",
+ '--with-statistics',
'postgres',
],
restore_cmd => [
'pg_restore',
'--file' => "$tempdir/compression_gzip_custom.sql",
+ '--with-statistics',
"$tempdir/compression_gzip_custom.dump",
],
command_like => {
@@ -115,6 +119,7 @@ my %pgdump_runs = (
'--format' => 'directory',
'--compress' => 'gzip:1',
'--file' => "$tempdir/compression_gzip_dir",
+ '--with-statistics',
'postgres',
],
# Give coverage for manually compressed blobs.toc files during
@@ -132,6 +137,7 @@ my %pgdump_runs = (
'pg_restore',
'--jobs' => '2',
'--file' => "$tempdir/compression_gzip_dir.sql",
+ '--with-statistics',
"$tempdir/compression_gzip_dir",
],
},
@@ -144,6 +150,7 @@ my %pgdump_runs = (
'--format' => 'plain',
'--compress' => '1',
'--file' => "$tempdir/compression_gzip_plain.sql.gz",
+ '--with-statistics',
'postgres',
],
# Decompress the generated file to run through the tests.
@@ -162,11 +169,13 @@ my %pgdump_runs = (
'--format' => 'custom',
'--compress' => 'lz4',
'--file' => "$tempdir/compression_lz4_custom.dump",
+ '--with-statistics',
'postgres',
],
restore_cmd => [
'pg_restore',
'--file' => "$tempdir/compression_lz4_custom.sql",
+ '--with-statistics',
"$tempdir/compression_lz4_custom.dump",
],
command_like => {
@@ -189,6 +198,7 @@ my %pgdump_runs = (
'--format' => 'directory',
'--compress' => 'lz4:1',
'--file' => "$tempdir/compression_lz4_dir",
+ '--with-statistics',
'postgres',
],
# Verify that data files were compressed
@@ -200,6 +210,7 @@ my %pgdump_runs = (
'pg_restore',
'--jobs' => '2',
'--file' => "$tempdir/compression_lz4_dir.sql",
+ '--with-statistics',
"$tempdir/compression_lz4_dir",
],
},
@@ -212,6 +223,7 @@ my %pgdump_runs = (
'--format' => 'plain',
'--compress' => 'lz4',
'--file' => "$tempdir/compression_lz4_plain.sql.lz4",
+ '--with-statistics',
'postgres',
],
# Decompress the generated file to run through the tests.
@@ -233,11 +245,13 @@ my %pgdump_runs = (
'--format' => 'custom',
'--compress' => 'zstd',
'--file' => "$tempdir/compression_zstd_custom.dump",
+ '--with-statistics',
'postgres',
],
restore_cmd => [
'pg_restore',
'--file' => "$tempdir/compression_zstd_custom.sql",
+ '--with-statistics',
"$tempdir/compression_zstd_custom.dump",
],
command_like => {
@@ -259,6 +273,7 @@ my %pgdump_runs = (
'--format' => 'directory',
'--compress' => 'zstd:1',
'--file' => "$tempdir/compression_zstd_dir",
+ '--with-statistics',
'postgres',
],
# Give coverage for manually compressed blobs.toc files during
@@ -279,6 +294,7 @@ my %pgdump_runs = (
'pg_restore',
'--jobs' => '2',
'--file' => "$tempdir/compression_zstd_dir.sql",
+ '--with-statistics',
"$tempdir/compression_zstd_dir",
],
},
@@ -292,6 +308,7 @@ my %pgdump_runs = (
'--format' => 'plain',
'--compress' => 'zstd:long',
'--file' => "$tempdir/compression_zstd_plain.sql.zst",
+ '--with-statistics',
'postgres',
],
# Decompress the generated file to run through the tests.
@@ -310,6 +327,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
'--file' => "$tempdir/clean.sql",
'--clean',
+ '--with-statistics',
'--dbname' => 'postgres', # alternative way to specify database
],
},
@@ -320,6 +338,7 @@ my %pgdump_runs = (
'--clean',
'--if-exists',
'--encoding' => 'UTF8', # no-op, just for testing
+ '--with-statistics',
'postgres',
],
},
@@ -338,6 +357,7 @@ my %pgdump_runs = (
'--create',
'--no-reconnect', # no-op, just for testing
'--verbose',
+ '--with-statistics',
'postgres',
],
},
@@ -356,6 +376,7 @@ my %pgdump_runs = (
dump_cmd => [
'pg_dump', '--no-sync',
'--file' => "$tempdir/defaults.sql",
+ '--with-statistics',
'postgres',
],
},
@@ -364,6 +385,7 @@ my %pgdump_runs = (
dump_cmd => [
'pg_dump', '--no-sync',
'--file' => "$tempdir/defaults_no_public.sql",
+ '--with-statistics',
'regress_pg_dump_test',
],
},
@@ -373,6 +395,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
'--clean',
'--file' => "$tempdir/defaults_no_public_clean.sql",
+ '--with-statistics',
'regress_pg_dump_test',
],
},
@@ -381,6 +404,7 @@ my %pgdump_runs = (
dump_cmd => [
'pg_dump', '--no-sync',
'--file' => "$tempdir/defaults_public_owner.sql",
+ '--with-statistics',
'regress_public_owner',
],
},
@@ -395,12 +419,14 @@ my %pgdump_runs = (
'pg_dump',
'--format' => 'custom',
'--file' => "$tempdir/defaults_custom_format.dump",
+ '--with-statistics',
'postgres',
],
restore_cmd => [
'pg_restore',
'--format' => 'custom',
'--file' => "$tempdir/defaults_custom_format.sql",
+ '--with-statistics',
"$tempdir/defaults_custom_format.dump",
],
command_like => {
@@ -425,12 +451,14 @@ my %pgdump_runs = (
'pg_dump',
'--format' => 'directory',
'--file' => "$tempdir/defaults_dir_format",
+ '--with-statistics',
'postgres',
],
restore_cmd => [
'pg_restore',
'--format' => 'directory',
'--file' => "$tempdir/defaults_dir_format.sql",
+ '--with-statistics',
"$tempdir/defaults_dir_format",
],
command_like => {
@@ -456,11 +484,13 @@ my %pgdump_runs = (
'--format' => 'directory',
'--jobs' => 2,
'--file' => "$tempdir/defaults_parallel",
+ '--with-statistics',
'postgres',
],
restore_cmd => [
'pg_restore',
'--file' => "$tempdir/defaults_parallel.sql",
+ '--with-statistics',
"$tempdir/defaults_parallel",
],
},
@@ -472,12 +502,14 @@ my %pgdump_runs = (
'pg_dump',
'--format' => 'tar',
'--file' => "$tempdir/defaults_tar_format.tar",
+ '--with-statistics',
'postgres',
],
restore_cmd => [
'pg_restore',
'--format' => 'tar',
'--file' => "$tempdir/defaults_tar_format.sql",
+ '--with-statistics',
"$tempdir/defaults_tar_format.tar",
],
},
@@ -486,6 +518,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
'--file' => "$tempdir/exclude_dump_test_schema.sql",
'--exclude-schema' => 'dump_test',
+ '--with-statistics',
'postgres',
],
},
@@ -494,6 +527,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
'--file' => "$tempdir/exclude_test_table.sql",
'--exclude-table' => 'dump_test.test_table',
+ '--with-statistics',
'postgres',
],
},
@@ -502,6 +536,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
'--file' => "$tempdir/exclude_measurement.sql",
'--exclude-table-and-children' => 'dump_test.measurement',
+ '--with-statistics',
'postgres',
],
},
@@ -511,6 +546,7 @@ my %pgdump_runs = (
'--file' => "$tempdir/exclude_measurement_data.sql",
'--exclude-table-data-and-children' => 'dump_test.measurement',
'--no-unlogged-table-data',
+ '--with-statistics',
'postgres',
],
},
@@ -520,6 +556,7 @@ my %pgdump_runs = (
'--file' => "$tempdir/exclude_test_table_data.sql",
'--exclude-table-data' => 'dump_test.test_table',
'--no-unlogged-table-data',
+ '--with-statistics',
'postgres',
],
},
@@ -538,6 +575,7 @@ my %pgdump_runs = (
'--file' => "$tempdir/pg_dumpall_globals.sql",
'--globals-only',
'--no-sync',
+ '--with-statistics',
],
},
pg_dumpall_globals_clean => {
@@ -547,12 +585,14 @@ my %pgdump_runs = (
'--globals-only',
'--clean',
'--no-sync',
+ '--with-statistics',
],
},
pg_dumpall_dbprivs => {
dump_cmd => [
'pg_dumpall', '--no-sync',
'--file' => "$tempdir/pg_dumpall_dbprivs.sql",
+ '--with-statistics',
],
},
pg_dumpall_exclude => {
@@ -562,6 +602,7 @@ my %pgdump_runs = (
'--file' => "$tempdir/pg_dumpall_exclude.sql",
'--exclude-database' => '*dump_test*',
'--no-sync',
+ '--with-statistics',
],
},
no_toast_compression => {
@@ -569,6 +610,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
'--file' => "$tempdir/no_toast_compression.sql",
'--no-toast-compression',
+ '--with-statistics',
'postgres',
],
},
@@ -577,6 +619,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
'--file' => "$tempdir/no_large_objects.sql",
'--no-large-objects',
+ '--with-statistics',
'postgres',
],
},
@@ -585,6 +628,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
'--file' => "$tempdir/no_policies.sql",
'--no-policies',
+ '--with-statistics',
'postgres',
],
},
@@ -593,6 +637,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
'--file' => "$tempdir/no_privs.sql",
'--no-privileges',
+ '--with-statistics',
'postgres',
],
},
@@ -601,6 +646,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
'--file' => "$tempdir/no_owner.sql",
'--no-owner',
+ '--with-statistics',
'postgres',
],
},
@@ -609,6 +655,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
'--file' => "$tempdir/no_table_access_method.sql",
'--no-table-access-method',
+ '--with-statistics',
'postgres',
],
},
@@ -617,6 +664,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
'--file' => "$tempdir/only_dump_test_schema.sql",
'--schema' => 'dump_test',
+ '--with-statistics',
'postgres',
],
},
@@ -627,6 +675,7 @@ my %pgdump_runs = (
'--table' => 'dump_test.test_table',
'--lock-wait-timeout' =>
(1000 * $PostgreSQL::Test::Utils::timeout_default),
+ '--with-statistics',
'postgres',
],
},
@@ -637,6 +686,7 @@ my %pgdump_runs = (
'--table-and-children' => 'dump_test.measurement',
'--lock-wait-timeout' =>
(1000 * $PostgreSQL::Test::Utils::timeout_default),
+ '--with-statistics',
'postgres',
],
},
@@ -646,6 +696,7 @@ my %pgdump_runs = (
'--file' => "$tempdir/role.sql",
'--role' => 'regress_dump_test_role',
'--schema' => 'dump_test_second_schema',
+ '--with-statistics',
'postgres',
],
},
@@ -658,11 +709,13 @@ my %pgdump_runs = (
'--file' => "$tempdir/role_parallel",
'--role' => 'regress_dump_test_role',
'--schema' => 'dump_test_second_schema',
+ '--with-statistics',
'postgres',
],
restore_cmd => [
'pg_restore',
'--file' => "$tempdir/role_parallel.sql",
+ '--with-statistics',
"$tempdir/role_parallel",
],
},
@@ -691,6 +744,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
'--file' => "$tempdir/section_pre_data.sql",
'--section' => 'pre-data',
+ '--with-statistics',
'postgres',
],
},
@@ -699,6 +753,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
'--file' => "$tempdir/section_data.sql",
'--section' => 'data',
+ '--with-statistics',
'postgres',
],
},
@@ -707,6 +762,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
'--file' => "$tempdir/section_post_data.sql",
'--section' => 'post-data',
+ '--with-statistics',
'postgres',
],
},
@@ -717,6 +773,7 @@ my %pgdump_runs = (
'--schema' => 'dump_test',
'--large-objects',
'--no-large-objects',
+ '--with-statistics',
'postgres',
],
},
@@ -732,6 +789,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
"--file=$tempdir/no_data_no_schema.sql", '--no-data',
'--no-schema', 'postgres',
+ '--with-statistics',
],
},
statistics_only => {
@@ -752,6 +810,7 @@ my %pgdump_runs = (
dump_cmd => [
'pg_dump', '--no-sync',
"--file=$tempdir/no_schema.sql", '--no-schema',
+ '--with-statistics',
'postgres',
],
},);
diff --git a/src/bin/pg_upgrade/dump.c b/src/bin/pg_upgrade/dump.c
index 23cb08e8347..183f08ce1e8 100644
--- a/src/bin/pg_upgrade/dump.c
+++ b/src/bin/pg_upgrade/dump.c
@@ -58,7 +58,7 @@ generate_old_dump(void)
(user_opts.transfer_mode == TRANSFER_MODE_SWAP) ?
"" : "--sequence-data",
log_opts.verbose ? "--verbose" : "",
- user_opts.do_statistics ? "" : "--no-statistics",
+ user_opts.do_statistics ? "--with-statistics" : "--no-statistics",
log_opts.dumpdir,
sql_file_name, escaped_connstr.data);
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 536e49d2616..81a394f249d 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -618,12 +618,13 @@ create_new_objects(void)
NULL,
true,
true,
- "\"%s/pg_restore\" %s %s --exit-on-error --verbose "
+ "\"%s/pg_restore\" %s %s %s --exit-on-error --verbose "
"--transaction-size=%d "
"--dbname postgres \"%s/%s\"",
new_cluster.bindir,
cluster_conn_opts(&new_cluster),
create_opts,
+ user_opts.do_statistics ? "--with-statistics" : "--no-statistics",
RESTORE_TRANSACTION_SIZE,
log_opts.dumpdir,
sql_file_name);
@@ -672,12 +673,13 @@ create_new_objects(void)
parallel_exec_prog(log_file_name,
NULL,
- "\"%s/pg_restore\" %s %s --exit-on-error --verbose "
+ "\"%s/pg_restore\" %s %s %s --exit-on-error --verbose "
"--transaction-size=%d "
"--dbname template1 \"%s/%s\"",
new_cluster.bindir,
cluster_conn_opts(&new_cluster),
create_opts,
+ user_opts.do_statistics ? "--with-statistics" : "--no-statistics",
txn_size,
log_opts.dumpdir,
sql_file_name);
--
2.43.0
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-05-22 19:29 Greg Sabino Mullane <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 2 replies; 199+ messages in thread
From: Greg Sabino Mullane @ 2025-05-22 19:29 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: Robert Haas <[email protected]>; Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>
On Thu, May 22, 2025 at 2:52 PM Jeff Davis <[email protected]> wrote:
> * The default for pg_restore is --no-statistics. That could cause a minor
> surprise if the user specifies --with-statistics for pg_dump and
> not for pg_restore. An argument could be made that "if the stats are
> there, restore them", and I don't have a strong opinion about this point,
> but defaulting to --no-statistics seems more consistent with pg_dump.
>
Hm...somewhat to my own surprise, I don't like this. If it's in the dump,
restore it.
Cheers,
Greg
--
Crunchy Data - https://www.crunchydata.com
Enterprise Postgres Software Products & Tech Support
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-05-22 19:36 Nathan Bossart <[email protected]>
parent: Greg Sabino Mullane <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Nathan Bossart @ 2025-05-22 19:36 UTC (permalink / raw)
To: Greg Sabino Mullane <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Haas <[email protected]>; Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>
On Thu, May 22, 2025 at 03:29:38PM -0400, Greg Sabino Mullane wrote:
> On Thu, May 22, 2025 at 2:52 PM Jeff Davis <[email protected]> wrote:
>> * The default for pg_restore is --no-statistics. That could cause a minor
>> surprise if the user specifies --with-statistics for pg_dump and
>> not for pg_restore. An argument could be made that "if the stats are
>> there, restore them", and I don't have a strong opinion about this point,
>> but defaulting to --no-statistics seems more consistent with pg_dump.
>
> Hm...somewhat to my own surprise, I don't like this. If it's in the dump,
> restore it.
+1, I think defaulting to restoring everything in the dump file is much
less surprising than the alternative.
--
nathan
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-05-22 19:36 Robert Haas <[email protected]>
parent: Nathan Bossart <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Robert Haas @ 2025-05-22 19:36 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: Greg Sabino Mullane <[email protected]>; Jeff Davis <[email protected]>; Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>
On Thu, May 22, 2025 at 3:36 PM Nathan Bossart <[email protected]> wrote:
> +1, I think defaulting to restoring everything in the dump file is much
> less surprising than the alternative.
+1.
--
Robert Haas
EDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-05-22 19:41 Tom Lane <[email protected]>
parent: Greg Sabino Mullane <[email protected]>
1 sibling, 1 reply; 199+ messages in thread
From: Tom Lane @ 2025-05-22 19:41 UTC (permalink / raw)
To: Greg Sabino Mullane <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Haas <[email protected]>; Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>
Greg Sabino Mullane <[email protected]> writes:
> On Thu, May 22, 2025 at 2:52 PM Jeff Davis <[email protected]> wrote:
>> * The default for pg_restore is --no-statistics. That could cause a minor
>> surprise if the user specifies --with-statistics for pg_dump and
>> not for pg_restore.
> Hm...somewhat to my own surprise, I don't like this. If it's in the dump,
> restore it.
Yeah, I tend to lean that way too. If the user went out of their way
to say --with-statistics for pg_dump, how likely is it that they
don't want the statistics restored?
Another argument pointing in that direction is that the definition
Jeff proposes creates an inconsistency in the output between text
mode:
pg_dump --with-statistics ... | psql
and non-text mode:
pg_dump -Fc --with-statistics ... | pg_restore
There is no additional filter in text mode, so I think pg_restore's
default behavior should also be "no additional filter".
regards, tom lane
^ permalink raw reply [nested|flat] 199+ messages in thread
* Re: Statistics Import and Export
@ 2025-05-22 21:29 Jeff Davis <[email protected]>
parent: Tom Lane <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Jeff Davis @ 2025-05-22 21:29 UTC (permalink / raw)
To: Tom Lane <[email protected]>; Greg Sabino Mullane <[email protected]>; +Cc: Robert Haas <[email protected]>; Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>
On Thu, 2025-05-22 at 15:41 -0400, Tom Lane wrote:
> There is no additional filter in text mode, so I think pg_restore's
> default behavior should also be "no additional filter".
Attached. Only the defaults for pg_dump and pg_dumpall are changed, and
pg_upgrade explicitly specifies --with-statistics.
Regards,
Jeff Davis
Attachments:
[text/x-patch] v2-0001-Change-defaults-for-statistics-export.patch (15.3K, ../../[email protected]/2-v2-0001-Change-defaults-for-statistics-export.patch)
download | inline diff:
From 5b73253f8848638f1754f4b9da82e90e8814b4b1 Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Thu, 22 May 2025 11:03:03 -0700
Subject: [PATCH v2] Change defaults for statistics export.
Set the default behavior of pg_dump, pg_dumpall, and pg_restore to be
--no-statistics. Leave the default for pg_upgrade to be
--with-statistics.
Discussion: https://postgr.es/m/CA+TgmoZ9=RnWcCOZiKYYjZs_AW1P4QXCw--h4dOLLHuf1Omung@mail.gmail.com
---
doc/src/sgml/ref/pg_dump.sgml | 4 +-
doc/src/sgml/ref/pg_dumpall.sgml | 4 +-
src/bin/pg_dump/pg_backup_archiver.c | 2 +-
src/bin/pg_dump/t/002_pg_dump.pl | 59 ++++++++++++++++++++++++++++
src/bin/pg_upgrade/dump.c | 2 +-
5 files changed, 65 insertions(+), 6 deletions(-)
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index c10bca63e55..995d8f9a040 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1134,7 +1134,7 @@ PostgreSQL documentation
<term><option>--no-statistics</option></term>
<listitem>
<para>
- Do not dump statistics.
+ Do not dump statistics. This is the default.
</para>
</listitem>
</varlistentry>
@@ -1461,7 +1461,7 @@ PostgreSQL documentation
<term><option>--with-statistics</option></term>
<listitem>
<para>
- Dump statistics. This is the default.
+ Dump statistics.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/pg_dumpall.sgml b/doc/src/sgml/ref/pg_dumpall.sgml
index 8c5141d036c..81d34df3386 100644
--- a/doc/src/sgml/ref/pg_dumpall.sgml
+++ b/doc/src/sgml/ref/pg_dumpall.sgml
@@ -567,7 +567,7 @@ exclude database <replaceable class="parameter">PATTERN</replaceable>
<term><option>--no-statistics</option></term>
<listitem>
<para>
- Do not dump statistics.
+ Do not dump statistics. This is the default.
</para>
</listitem>
</varlistentry>
@@ -741,7 +741,7 @@ exclude database <replaceable class="parameter">PATTERN</replaceable>
<term><option>--with-statistics</option></term>
<listitem>
<para>
- Dump statistics. This is the default.
+ Dump statistics.
</para>
</listitem>
</varlistentry>
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index afa42337b11..175fe9c4273 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -152,7 +152,7 @@ InitDumpOptions(DumpOptions *opts)
opts->dumpSections = DUMP_UNSECTIONED;
opts->dumpSchema = true;
opts->dumpData = true;
- opts->dumpStatistics = true;
+ opts->dumpStatistics = false;
}
/*
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index cf34f71ea11..386e21e0c59 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -68,6 +68,7 @@ my %pgdump_runs = (
'--no-data',
'--sequence-data',
'--binary-upgrade',
+ '--with-statistics',
'--dbname' => 'postgres', # alternative way to specify database
],
restore_cmd => [
@@ -75,6 +76,7 @@ my %pgdump_runs = (
'--format' => 'custom',
'--verbose',
'--file' => "$tempdir/binary_upgrade.sql",
+ '--with-statistics',
"$tempdir/binary_upgrade.dump",
],
},
@@ -88,11 +90,13 @@ my %pgdump_runs = (
'--format' => 'custom',
'--compress' => '1',
'--file' => "$tempdir/compression_gzip_custom.dump",
+ '--with-statistics',
'postgres',
],
restore_cmd => [
'pg_restore',
'--file' => "$tempdir/compression_gzip_custom.sql",
+ '--with-statistics',
"$tempdir/compression_gzip_custom.dump",
],
command_like => {
@@ -115,6 +119,7 @@ my %pgdump_runs = (
'--format' => 'directory',
'--compress' => 'gzip:1',
'--file' => "$tempdir/compression_gzip_dir",
+ '--with-statistics',
'postgres',
],
# Give coverage for manually compressed blobs.toc files during
@@ -132,6 +137,7 @@ my %pgdump_runs = (
'pg_restore',
'--jobs' => '2',
'--file' => "$tempdir/compression_gzip_dir.sql",
+ '--with-statistics',
"$tempdir/compression_gzip_dir",
],
},
@@ -144,6 +150,7 @@ my %pgdump_runs = (
'--format' => 'plain',
'--compress' => '1',
'--file' => "$tempdir/compression_gzip_plain.sql.gz",
+ '--with-statistics',
'postgres',
],
# Decompress the generated file to run through the tests.
@@ -162,11 +169,13 @@ my %pgdump_runs = (
'--format' => 'custom',
'--compress' => 'lz4',
'--file' => "$tempdir/compression_lz4_custom.dump",
+ '--with-statistics',
'postgres',
],
restore_cmd => [
'pg_restore',
'--file' => "$tempdir/compression_lz4_custom.sql",
+ '--with-statistics',
"$tempdir/compression_lz4_custom.dump",
],
command_like => {
@@ -189,6 +198,7 @@ my %pgdump_runs = (
'--format' => 'directory',
'--compress' => 'lz4:1',
'--file' => "$tempdir/compression_lz4_dir",
+ '--with-statistics',
'postgres',
],
# Verify that data files were compressed
@@ -200,6 +210,7 @@ my %pgdump_runs = (
'pg_restore',
'--jobs' => '2',
'--file' => "$tempdir/compression_lz4_dir.sql",
+ '--with-statistics',
"$tempdir/compression_lz4_dir",
],
},
@@ -212,6 +223,7 @@ my %pgdump_runs = (
'--format' => 'plain',
'--compress' => 'lz4',
'--file' => "$tempdir/compression_lz4_plain.sql.lz4",
+ '--with-statistics',
'postgres',
],
# Decompress the generated file to run through the tests.
@@ -233,11 +245,13 @@ my %pgdump_runs = (
'--format' => 'custom',
'--compress' => 'zstd',
'--file' => "$tempdir/compression_zstd_custom.dump",
+ '--with-statistics',
'postgres',
],
restore_cmd => [
'pg_restore',
'--file' => "$tempdir/compression_zstd_custom.sql",
+ '--with-statistics',
"$tempdir/compression_zstd_custom.dump",
],
command_like => {
@@ -259,6 +273,7 @@ my %pgdump_runs = (
'--format' => 'directory',
'--compress' => 'zstd:1',
'--file' => "$tempdir/compression_zstd_dir",
+ '--with-statistics',
'postgres',
],
# Give coverage for manually compressed blobs.toc files during
@@ -279,6 +294,7 @@ my %pgdump_runs = (
'pg_restore',
'--jobs' => '2',
'--file' => "$tempdir/compression_zstd_dir.sql",
+ '--with-statistics',
"$tempdir/compression_zstd_dir",
],
},
@@ -292,6 +308,7 @@ my %pgdump_runs = (
'--format' => 'plain',
'--compress' => 'zstd:long',
'--file' => "$tempdir/compression_zstd_plain.sql.zst",
+ '--with-statistics',
'postgres',
],
# Decompress the generated file to run through the tests.
@@ -310,6 +327,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
'--file' => "$tempdir/clean.sql",
'--clean',
+ '--with-statistics',
'--dbname' => 'postgres', # alternative way to specify database
],
},
@@ -320,6 +338,7 @@ my %pgdump_runs = (
'--clean',
'--if-exists',
'--encoding' => 'UTF8', # no-op, just for testing
+ '--with-statistics',
'postgres',
],
},
@@ -338,6 +357,7 @@ my %pgdump_runs = (
'--create',
'--no-reconnect', # no-op, just for testing
'--verbose',
+ '--with-statistics',
'postgres',
],
},
@@ -356,6 +376,7 @@ my %pgdump_runs = (
dump_cmd => [
'pg_dump', '--no-sync',
'--file' => "$tempdir/defaults.sql",
+ '--with-statistics',
'postgres',
],
},
@@ -364,6 +385,7 @@ my %pgdump_runs = (
dump_cmd => [
'pg_dump', '--no-sync',
'--file' => "$tempdir/defaults_no_public.sql",
+ '--with-statistics',
'regress_pg_dump_test',
],
},
@@ -373,6 +395,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
'--clean',
'--file' => "$tempdir/defaults_no_public_clean.sql",
+ '--with-statistics',
'regress_pg_dump_test',
],
},
@@ -381,6 +404,7 @@ my %pgdump_runs = (
dump_cmd => [
'pg_dump', '--no-sync',
'--file' => "$tempdir/defaults_public_owner.sql",
+ '--with-statistics',
'regress_public_owner',
],
},
@@ -395,12 +419,14 @@ my %pgdump_runs = (
'pg_dump',
'--format' => 'custom',
'--file' => "$tempdir/defaults_custom_format.dump",
+ '--with-statistics',
'postgres',
],
restore_cmd => [
'pg_restore',
'--format' => 'custom',
'--file' => "$tempdir/defaults_custom_format.sql",
+ '--with-statistics',
"$tempdir/defaults_custom_format.dump",
],
command_like => {
@@ -425,12 +451,14 @@ my %pgdump_runs = (
'pg_dump',
'--format' => 'directory',
'--file' => "$tempdir/defaults_dir_format",
+ '--with-statistics',
'postgres',
],
restore_cmd => [
'pg_restore',
'--format' => 'directory',
'--file' => "$tempdir/defaults_dir_format.sql",
+ '--with-statistics',
"$tempdir/defaults_dir_format",
],
command_like => {
@@ -456,11 +484,13 @@ my %pgdump_runs = (
'--format' => 'directory',
'--jobs' => 2,
'--file' => "$tempdir/defaults_parallel",
+ '--with-statistics',
'postgres',
],
restore_cmd => [
'pg_restore',
'--file' => "$tempdir/defaults_parallel.sql",
+ '--with-statistics',
"$tempdir/defaults_parallel",
],
},
@@ -472,12 +502,14 @@ my %pgdump_runs = (
'pg_dump',
'--format' => 'tar',
'--file' => "$tempdir/defaults_tar_format.tar",
+ '--with-statistics',
'postgres',
],
restore_cmd => [
'pg_restore',
'--format' => 'tar',
'--file' => "$tempdir/defaults_tar_format.sql",
+ '--with-statistics',
"$tempdir/defaults_tar_format.tar",
],
},
@@ -486,6 +518,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
'--file' => "$tempdir/exclude_dump_test_schema.sql",
'--exclude-schema' => 'dump_test',
+ '--with-statistics',
'postgres',
],
},
@@ -494,6 +527,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
'--file' => "$tempdir/exclude_test_table.sql",
'--exclude-table' => 'dump_test.test_table',
+ '--with-statistics',
'postgres',
],
},
@@ -502,6 +536,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
'--file' => "$tempdir/exclude_measurement.sql",
'--exclude-table-and-children' => 'dump_test.measurement',
+ '--with-statistics',
'postgres',
],
},
@@ -511,6 +546,7 @@ my %pgdump_runs = (
'--file' => "$tempdir/exclude_measurement_data.sql",
'--exclude-table-data-and-children' => 'dump_test.measurement',
'--no-unlogged-table-data',
+ '--with-statistics',
'postgres',
],
},
@@ -520,6 +556,7 @@ my %pgdump_runs = (
'--file' => "$tempdir/exclude_test_table_data.sql",
'--exclude-table-data' => 'dump_test.test_table',
'--no-unlogged-table-data',
+ '--with-statistics',
'postgres',
],
},
@@ -538,6 +575,7 @@ my %pgdump_runs = (
'--file' => "$tempdir/pg_dumpall_globals.sql",
'--globals-only',
'--no-sync',
+ '--with-statistics',
],
},
pg_dumpall_globals_clean => {
@@ -547,12 +585,14 @@ my %pgdump_runs = (
'--globals-only',
'--clean',
'--no-sync',
+ '--with-statistics',
],
},
pg_dumpall_dbprivs => {
dump_cmd => [
'pg_dumpall', '--no-sync',
'--file' => "$tempdir/pg_dumpall_dbprivs.sql",
+ '--with-statistics',
],
},
pg_dumpall_exclude => {
@@ -562,6 +602,7 @@ my %pgdump_runs = (
'--file' => "$tempdir/pg_dumpall_exclude.sql",
'--exclude-database' => '*dump_test*',
'--no-sync',
+ '--with-statistics',
],
},
no_toast_compression => {
@@ -569,6 +610,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
'--file' => "$tempdir/no_toast_compression.sql",
'--no-toast-compression',
+ '--with-statistics',
'postgres',
],
},
@@ -577,6 +619,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
'--file' => "$tempdir/no_large_objects.sql",
'--no-large-objects',
+ '--with-statistics',
'postgres',
],
},
@@ -585,6 +628,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
'--file' => "$tempdir/no_policies.sql",
'--no-policies',
+ '--with-statistics',
'postgres',
],
},
@@ -593,6 +637,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
'--file' => "$tempdir/no_privs.sql",
'--no-privileges',
+ '--with-statistics',
'postgres',
],
},
@@ -601,6 +646,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
'--file' => "$tempdir/no_owner.sql",
'--no-owner',
+ '--with-statistics',
'postgres',
],
},
@@ -609,6 +655,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
'--file' => "$tempdir/no_table_access_method.sql",
'--no-table-access-method',
+ '--with-statistics',
'postgres',
],
},
@@ -617,6 +664,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
'--file' => "$tempdir/only_dump_test_schema.sql",
'--schema' => 'dump_test',
+ '--with-statistics',
'postgres',
],
},
@@ -627,6 +675,7 @@ my %pgdump_runs = (
'--table' => 'dump_test.test_table',
'--lock-wait-timeout' =>
(1000 * $PostgreSQL::Test::Utils::timeout_default),
+ '--with-statistics',
'postgres',
],
},
@@ -637,6 +686,7 @@ my %pgdump_runs = (
'--table-and-children' => 'dump_test.measurement',
'--lock-wait-timeout' =>
(1000 * $PostgreSQL::Test::Utils::timeout_default),
+ '--with-statistics',
'postgres',
],
},
@@ -646,6 +696,7 @@ my %pgdump_runs = (
'--file' => "$tempdir/role.sql",
'--role' => 'regress_dump_test_role',
'--schema' => 'dump_test_second_schema',
+ '--with-statistics',
'postgres',
],
},
@@ -658,11 +709,13 @@ my %pgdump_runs = (
'--file' => "$tempdir/role_parallel",
'--role' => 'regress_dump_test_role',
'--schema' => 'dump_test_second_schema',
+ '--with-statistics',
'postgres',
],
restore_cmd => [
'pg_restore',
'--file' => "$tempdir/role_parallel.sql",
+ '--with-statistics',
"$tempdir/role_parallel",
],
},
@@ -691,6 +744,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
'--file' => "$tempdir/section_pre_data.sql",
'--section' => 'pre-data',
+ '--with-statistics',
'postgres',
],
},
@@ -699,6 +753,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
'--file' => "$tempdir/section_data.sql",
'--section' => 'data',
+ '--with-statistics',
'postgres',
],
},
@@ -707,6 +762,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
'--file' => "$tempdir/section_post_data.sql",
'--section' => 'post-data',
+ '--with-statistics',
'postgres',
],
},
@@ -717,6 +773,7 @@ my %pgdump_runs = (
'--schema' => 'dump_test',
'--large-objects',
'--no-large-objects',
+ '--with-statistics',
'postgres',
],
},
@@ -732,6 +789,7 @@ my %pgdump_runs = (
'pg_dump', '--no-sync',
"--file=$tempdir/no_data_no_schema.sql", '--no-data',
'--no-schema', 'postgres',
+ '--with-statistics',
],
},
statistics_only => {
@@ -752,6 +810,7 @@ my %pgdump_runs = (
dump_cmd => [
'pg_dump', '--no-sync',
"--file=$tempdir/no_schema.sql", '--no-schema',
+ '--with-statistics',
'postgres',
],
},);
diff --git a/src/bin/pg_upgrade/dump.c b/src/bin/pg_upgrade/dump.c
index 23cb08e8347..183f08ce1e8 100644
--- a/src/bin/pg_upgrade/dump.c
+++ b/src/bin/pg_upgrade/dump.c
@@ -58,7 +58,7 @@ generate_old_dump(void)
(user_opts.transfer_mode == TRANSFER_MODE_SWAP) ?
"" : "--sequence-data",
log_opts.verbose ? "--verbose" : "",
- user_opts.do_statistics ? "" : "--no-statistics",
+ user_opts.do_statistics ? "--with-statistics" : "--no-statistics",
log_opts.dumpdir,
sql_file_name, escaped_connstr.data);
--
2.43.0
^ permalink raw reply [nested|flat] 199+ messages in thread
* [PATCH 1/3] stress test for repack concurrently
@ 2025-12-13 17:13 Mikhail Nikalayeu <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Mikhail Nikalayeu @ 2025-12-13 17:13 UTC (permalink / raw)
---
contrib/amcheck/meson.build | 1 +
contrib/amcheck/t/007_repack_1.pl | 121 ++++++++++++++++++++++++++++++
doc/src/sgml/regress.sgml | 16 ++++
3 files changed, 138 insertions(+)
create mode 100644 contrib/amcheck/t/007_repack_1.pl
diff --git a/contrib/amcheck/meson.build b/contrib/amcheck/meson.build
index d5137ef691d..cb4bc32e98a 100644
--- a/contrib/amcheck/meson.build
+++ b/contrib/amcheck/meson.build
@@ -50,6 +50,7 @@ tests += {
't/004_verify_nbtree_unique.pl',
't/005_pitr.pl',
't/006_verify_gin.pl',
+ 't/007_repack_1.pl',
],
},
}
diff --git a/contrib/amcheck/t/007_repack_1.pl b/contrib/amcheck/t/007_repack_1.pl
new file mode 100644
index 00000000000..17f3caa3a38
--- /dev/null
+++ b/contrib/amcheck/t/007_repack_1.pl
@@ -0,0 +1,121 @@
+# Copyright (c) 2021-2026, PostgreSQL Global Development Group
+
+# Test REPACK CONCURRENTLY with concurrent modifications
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+
+use Test::More;
+
+# PG_TEST_EXTRA carries a numerical stress value; 0 disables the test,
+# 1 means the test takes about 6 seconds, and the increase should be
+# roughly linear.
+my $matched = ($ENV{PG_TEST_EXTRA} // '') =~ /\bstress_concurrently(?:=(?<stressval>[0-9]*))?\b/;
+my $stressval = $+{stressval} // 1;
+if (!$matched or $stressval == 0)
+{
+ plan skip_all => 'skipping disabled REPACK CONCURRENTLY stress test';
+}
+
+note "stressval is $stressval";
+
+my $node;
+
+# Test set-up
+$node = PostgreSQL::Test::Cluster->new('CIC_test');
+$node->init;
+$node->append_conf('postgresql.conf',
+ 'lock_timeout = ' . (1000 * $PostgreSQL::Test::Utils::timeout_default));
+$node->append_conf(
+ 'postgresql.conf', qq(
+wal_level = logical
+));
+
+my $nrows = 10_000 * $stressval;
+my $duration = 6 * $stressval;
+my $no_hot = int(rand(2));
+
+$node->start;
+$node->safe_psql('postgres', q(CREATE TABLE tbl(id int PRIMARY KEY, val int)));
+
+if ($no_hot)
+{
+ $node->safe_psql('postgres', q(CREATE INDEX test_idx ON tbl(val);));
+}
+else
+{
+ $node->safe_psql('postgres', q(CREATE INDEX test_idx ON tbl(id);));
+}
+
+
+# Load amcheck
+$node->safe_psql('postgres', q(CREATE EXTENSION amcheck));
+
+# Insert $nrows rows into tbl
+$node->safe_psql('postgres', qq(
+ INSERT INTO tbl SELECT g, g FROM generate_series(1, $nrows) g
+));
+
+my $sum = $node->safe_psql('postgres', q(
+ SELECT SUM(val) AS sum FROM tbl
+));
+
+
+$node->pgbench(
+"--no-vacuum --client=30 --jobs=4 --exit-on-abort -T $duration",
+0,
+[qr{actually processed}],
+[qr{^$}],
+'concurrent operations with REINDEX/CREATE INDEX CONCURRENTLY',
+{
+ 'concurrent_ops' => qq(
+ SELECT pg_try_advisory_lock(42)::integer AS gotlock \\gset
+ \\if :gotlock
+ REPACK (CONCURRENTLY) tbl USING INDEX tbl_pkey;
+ SELECT bt_index_parent_check('tbl_pkey', heapallindexed => true);
+ SELECT bt_index_parent_check('test_idx', heapallindexed => true);
+ \\sleep 10 ms
+
+ REPACK (CONCURRENTLY) tbl USING INDEX test_idx;
+ SELECT bt_index_parent_check('tbl_pkey', heapallindexed => true);
+ SELECT bt_index_parent_check('test_idx', heapallindexed => true);
+ \\sleep 10 ms
+
+ REPACK (CONCURRENTLY) tbl;
+ SELECT bt_index_parent_check('tbl_pkey', heapallindexed => true);
+ SELECT bt_index_parent_check('test_idx', heapallindexed => true);
+ \\sleep 10 ms
+
+ SELECT pg_advisory_unlock(42);
+ \\else
+ \\set num_a random(1, $nrows)
+ \\set num_b random(1, $nrows)
+ \\set diff random(1, 10000)
+ BEGIN;
+ UPDATE tbl SET val = val + :diff WHERE id = :num_a;
+ \\sleep 1 ms
+ UPDATE tbl SET val = val - :diff WHERE id = :num_b;
+ \\sleep 1 ms
+ COMMIT;
+
+ BEGIN
+ --TRANSACTION ISOLATION LEVEL REPEATABLE READ
+ ;
+ SELECT 1;
+ \\sleep 1 ms
+ SELECT COALESCE(SUM(val), 0) AS sum FROM tbl \\gset p_
+ \\if :p_sum != $sum
+ COMMIT;
+ SELECT (:p_sum) / 0;
+ \\endif
+
+ COMMIT;
+ \\endif
+ )
+});
+
+$node->stop;
+
+done_testing();
diff --git a/doc/src/sgml/regress.sgml b/doc/src/sgml/regress.sgml
index c74941bfbf2..c5b1b79a60c 100644
--- a/doc/src/sgml/regress.sgml
+++ b/doc/src/sgml/regress.sgml
@@ -386,6 +386,22 @@ make check-world PG_TEST_EXTRA='kerberos ldap ssl load_balance libpq_encryption'
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>stress_concurrently</literal><optional>=VALUE</optional></term>
+ <listitem>
+ <para>
+ Run some additional, moderately expensive tests for commands with a
+ <literal>CONCURRENTLY</literal> option. Optionally, an integer can be
+ given after an equal sign, which affects how long each such test
+ runs for.
+ Each test is calibrated so that, when given a value of 10,
+ it runs for approximately 60 seconds.
+ If no value is given, 1 is assumed.
+ Explicitly giving a value of 0 causes the test to be skipped.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>wal_consistency_checking</literal></term>
<listitem>
--
2.47.3
--tbdehtstrqwksfdh
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0002-one-more-stress-test-for-repack-concurrently.patch"
^ permalink raw reply [nested|flat] 199+ messages in thread
* [PATCH 1/3] stress test for repack concurrently
@ 2025-12-13 17:13 Mikhail Nikalayeu <[email protected]>
0 siblings, 0 replies; 199+ messages in thread
From: Mikhail Nikalayeu @ 2025-12-13 17:13 UTC (permalink / raw)
---
contrib/amcheck/meson.build | 1 +
contrib/amcheck/t/007_repack_1.pl | 121 ++++++++++++++++++++++++++++++
doc/src/sgml/regress.sgml | 16 ++++
3 files changed, 138 insertions(+)
create mode 100644 contrib/amcheck/t/007_repack_1.pl
diff --git a/contrib/amcheck/meson.build b/contrib/amcheck/meson.build
index d5137ef691d..cb4bc32e98a 100644
--- a/contrib/amcheck/meson.build
+++ b/contrib/amcheck/meson.build
@@ -50,6 +50,7 @@ tests += {
't/004_verify_nbtree_unique.pl',
't/005_pitr.pl',
't/006_verify_gin.pl',
+ 't/007_repack_1.pl',
],
},
}
diff --git a/contrib/amcheck/t/007_repack_1.pl b/contrib/amcheck/t/007_repack_1.pl
new file mode 100644
index 00000000000..17f3caa3a38
--- /dev/null
+++ b/contrib/amcheck/t/007_repack_1.pl
@@ -0,0 +1,121 @@
+# Copyright (c) 2021-2026, PostgreSQL Global Development Group
+
+# Test REPACK CONCURRENTLY with concurrent modifications
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+
+use Test::More;
+
+# PG_TEST_EXTRA carries a numerical stress value; 0 disables the test,
+# 1 means the test takes about 6 seconds, and the increase should be
+# roughly linear.
+my $matched = ($ENV{PG_TEST_EXTRA} // '') =~ /\bstress_concurrently(?:=(?<stressval>[0-9]*))?\b/;
+my $stressval = $+{stressval} // 1;
+if (!$matched or $stressval == 0)
+{
+ plan skip_all => 'skipping disabled REPACK CONCURRENTLY stress test';
+}
+
+note "stressval is $stressval";
+
+my $node;
+
+# Test set-up
+$node = PostgreSQL::Test::Cluster->new('CIC_test');
+$node->init;
+$node->append_conf('postgresql.conf',
+ 'lock_timeout = ' . (1000 * $PostgreSQL::Test::Utils::timeout_default));
+$node->append_conf(
+ 'postgresql.conf', qq(
+wal_level = logical
+));
+
+my $nrows = 10_000 * $stressval;
+my $duration = 6 * $stressval;
+my $no_hot = int(rand(2));
+
+$node->start;
+$node->safe_psql('postgres', q(CREATE TABLE tbl(id int PRIMARY KEY, val int)));
+
+if ($no_hot)
+{
+ $node->safe_psql('postgres', q(CREATE INDEX test_idx ON tbl(val);));
+}
+else
+{
+ $node->safe_psql('postgres', q(CREATE INDEX test_idx ON tbl(id);));
+}
+
+
+# Load amcheck
+$node->safe_psql('postgres', q(CREATE EXTENSION amcheck));
+
+# Insert $nrows rows into tbl
+$node->safe_psql('postgres', qq(
+ INSERT INTO tbl SELECT g, g FROM generate_series(1, $nrows) g
+));
+
+my $sum = $node->safe_psql('postgres', q(
+ SELECT SUM(val) AS sum FROM tbl
+));
+
+
+$node->pgbench(
+"--no-vacuum --client=30 --jobs=4 --exit-on-abort -T $duration",
+0,
+[qr{actually processed}],
+[qr{^$}],
+'concurrent operations with REINDEX/CREATE INDEX CONCURRENTLY',
+{
+ 'concurrent_ops' => qq(
+ SELECT pg_try_advisory_lock(42)::integer AS gotlock \\gset
+ \\if :gotlock
+ REPACK (CONCURRENTLY) tbl USING INDEX tbl_pkey;
+ SELECT bt_index_parent_check('tbl_pkey', heapallindexed => true);
+ SELECT bt_index_parent_check('test_idx', heapallindexed => true);
+ \\sleep 10 ms
+
+ REPACK (CONCURRENTLY) tbl USING INDEX test_idx;
+ SELECT bt_index_parent_check('tbl_pkey', heapallindexed => true);
+ SELECT bt_index_parent_check('test_idx', heapallindexed => true);
+ \\sleep 10 ms
+
+ REPACK (CONCURRENTLY) tbl;
+ SELECT bt_index_parent_check('tbl_pkey', heapallindexed => true);
+ SELECT bt_index_parent_check('test_idx', heapallindexed => true);
+ \\sleep 10 ms
+
+ SELECT pg_advisory_unlock(42);
+ \\else
+ \\set num_a random(1, $nrows)
+ \\set num_b random(1, $nrows)
+ \\set diff random(1, 10000)
+ BEGIN;
+ UPDATE tbl SET val = val + :diff WHERE id = :num_a;
+ \\sleep 1 ms
+ UPDATE tbl SET val = val - :diff WHERE id = :num_b;
+ \\sleep 1 ms
+ COMMIT;
+
+ BEGIN
+ --TRANSACTION ISOLATION LEVEL REPEATABLE READ
+ ;
+ SELECT 1;
+ \\sleep 1 ms
+ SELECT COALESCE(SUM(val), 0) AS sum FROM tbl \\gset p_
+ \\if :p_sum != $sum
+ COMMIT;
+ SELECT (:p_sum) / 0;
+ \\endif
+
+ COMMIT;
+ \\endif
+ )
+});
+
+$node->stop;
+
+done_testing();
diff --git a/doc/src/sgml/regress.sgml b/doc/src/sgml/regress.sgml
index c74941bfbf2..c5b1b79a60c 100644
--- a/doc/src/sgml/regress.sgml
+++ b/doc/src/sgml/regress.sgml
@@ -386,6 +386,22 @@ make check-world PG_TEST_EXTRA='kerberos ldap ssl load_balance libpq_encryption'
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>stress_concurrently</literal><optional>=VALUE</optional></term>
+ <listitem>
+ <para>
+ Run some additional, moderately expensive tests for commands with a
+ <literal>CONCURRENTLY</literal> option. Optionally, an integer can be
+ given after an equal sign, which affects how long each such test
+ runs for.
+ Each test is calibrated so that, when given a value of 10,
+ it runs for approximately 60 seconds.
+ If no value is given, 1 is assumed.
+ Explicitly giving a value of 0 causes the test to be skipped.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>wal_consistency_checking</literal></term>
<listitem>
--
2.47.3
--tbdehtstrqwksfdh
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0002-one-more-stress-test-for-repack-concurrently.patch"
^ permalink raw reply [nested|flat] 199+ messages in thread
end of thread, other threads:[~2025-12-13 17:13 UTC | newest]
Thread overview: 199+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2019-09-05 11:21 [PATCH v9 1/4] Move callback-call from ReadPageInternal to XLogReadRecord. Kyotaro Horiguchi <[email protected]>
2019-09-05 11:21 [PATCH v7 1/4] Move callback-call from ReadPageInternal to XLogReadRecord. Kyotaro Horiguchi <[email protected]>
2019-09-05 11:21 [PATCH v8 1/4] Move callback-call from ReadPageInternal to XLogReadRecord. Kyotaro Horiguchi <[email protected]>
2019-09-05 11:21 [PATCH v7 1/4] Move callback-call from ReadPageInternal to XLogReadRecord. Kyotaro Horiguchi <[email protected]>
2019-09-05 11:21 [PATCH v11 1/4] Move callback-call from ReadPageInternal to XLogReadRecord. Kyotaro Horiguchi <[email protected]>
2019-09-05 11:21 [PATCH v5 1/3] Move callback-call from ReadPageInternal to XLogReadRecord. Kyotaro Horiguchi <[email protected]>
2019-09-05 11:21 [PATCH v6 1/4] Move callback-call from ReadPageInternal to XLogReadRecord. Kyotaro Horiguchi <[email protected]>
2019-09-05 11:21 [PATCH v12 1/4] Move callback-call from ReadPageInternal to XLogReadRecord. Kyotaro Horiguchi <[email protected]>
2022-04-05 09:45 ` Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs) Michael Paquier <[email protected]>
2022-06-13 13:56 ` Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs) Nitin Jadhav <[email protected]>
2022-06-06 06:03 ` Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs) Nitin Jadhav <[email protected]>
2022-06-13 13:38 ` Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs) Nitin Jadhav <[email protected]>
2022-07-07 00:04 ` Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs) Andres Freund <[email protected]>
2022-07-28 09:38 ` Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs) Nitin Jadhav <[email protected]>
2022-11-04 08:25 ` Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs) Drouvot, Bertrand <[email protected]>
2022-11-15 11:41 ` Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs) Nitin Jadhav <[email protected]>
2022-11-15 20:04 ` Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs) Robert Haas <[email protected]>
2022-11-16 10:31 ` Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs) Bharath Rupireddy <[email protected]>
2022-11-16 18:14 ` Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs) Andres Freund <[email protected]>
2022-11-16 19:19 ` Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs) Robert Haas <[email protected]>
2022-11-16 19:52 ` Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs) Andres Freund <[email protected]>
2022-11-17 14:03 ` Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs) Robert Haas <[email protected]>
2022-11-17 16:24 ` Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs) Andres Freund <[email protected]>
2022-11-17 17:18 ` Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs) Tom Lane <[email protected]>
2022-11-17 17:21 ` Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs) Robert Haas <[email protected]>
2022-11-17 13:31 ` Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs) Bharath Rupireddy <[email protected]>
2022-11-15 20:18 ` Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs) Andres Freund <[email protected]>
2022-12-07 19:03 ` Re: Report checkpoint progress with pg_stat_progress_checkpoint (was: Report checkpoint progress in server logs) Andres Freund <[email protected]>
2025-02-24 10:11 Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-02-24 14:54 ` Re: Statistics Import and Export Andres Freund <[email protected]>
2025-02-24 16:15 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-02-24 17:50 ` Re: Statistics Import and Export Tom Lane <[email protected]>
2025-02-24 18:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-02-24 18:47 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-02-24 18:54 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-02-24 19:34 ` Re: Statistics Import and Export Tom Lane <[email protected]>
2025-02-24 20:36 ` Re: Statistics Import and Export Andres Freund <[email protected]>
2025-02-24 20:41 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-02-24 20:45 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-02-24 20:53 ` Re: Statistics Import and Export Andres Freund <[email protected]>
2025-02-24 21:01 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-02-25 03:30 ` Re: Statistics Import and Export jian he <[email protected]>
2025-02-25 05:41 ` Re: Statistics Import and Export Ashutosh Bapat <[email protected]>
2025-02-28 02:32 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-02-28 03:42 ` Re: Statistics Import and Export Greg Sabino Mullane <[email protected]>
2025-02-28 20:54 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-03-01 18:52 ` Re: Statistics Import and Export Greg Sabino Mullane <[email protected]>
2025-03-01 20:48 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-03-01 21:23 ` Re: Statistics Import and Export Tom Lane <[email protected]>
2025-03-03 19:00 ` Re: Statistics Import and Export Greg Sabino Mullane <[email protected]>
2025-03-05 08:08 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-02 14:38 ` Re: Statistics Import and Export Magnus Hagander <[email protected]>
2025-03-02 20:29 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-31 17:39 ` Re: Statistics Import and Export Robert Haas <[email protected]>
2025-03-31 22:04 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-04-01 13:37 ` Re: Statistics Import and Export Robert Haas <[email protected]>
2025-04-01 20:24 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-04-02 02:24 ` Re: Statistics Import and Export Robert Haas <[email protected]>
2025-05-10 19:51 ` Re: Statistics Import and Export Greg Sabino Mullane <[email protected]>
2025-05-22 14:20 ` Re: Statistics Import and Export Robert Haas <[email protected]>
2025-05-22 14:30 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-05-22 14:53 ` Re: Statistics Import and Export Tom Lane <[email protected]>
2025-05-22 18:52 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-05-22 19:29 ` Re: Statistics Import and Export Greg Sabino Mullane <[email protected]>
2025-05-22 19:36 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-05-22 19:36 ` Re: Statistics Import and Export Robert Haas <[email protected]>
2025-05-22 19:41 ` Re: Statistics Import and Export Tom Lane <[email protected]>
2025-05-22 21:29 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-02-28 09:21 ` Re: Statistics Import and Export: difference in statistics dumped Ashutosh Bapat <[email protected]>
2025-02-28 20:10 ` Re: Statistics Import and Export: difference in statistics dumped Jeff Davis <[email protected]>
2025-03-03 16:34 ` Re: Statistics Import and Export: difference in statistics dumped Ashutosh Bapat <[email protected]>
2025-03-04 00:55 ` Re: Statistics Import and Export: difference in statistics dumped Jeff Davis <[email protected]>
2025-03-04 04:58 ` Re: Statistics Import and Export: difference in statistics dumped Ashutosh Bapat <[email protected]>
2025-03-04 18:15 ` Re: Statistics Import and Export: difference in statistics dumped Jeff Davis <[email protected]>
2025-03-05 09:52 ` Re: Statistics Import and Export: difference in statistics dumped Ashutosh Bapat <[email protected]>
2025-03-06 06:52 ` Re: Statistics Import and Export: difference in statistics dumped Jeff Davis <[email protected]>
2025-03-07 19:14 ` Re: Statistics Import and Export: difference in statistics dumped Tom Lane <[email protected]>
2025-03-10 21:53 ` Re: Statistics Import and Export: difference in statistics dumped Tom Lane <[email protected]>
2025-03-10 23:53 ` Re: Statistics Import and Export: difference in statistics dumped Jeff Davis <[email protected]>
2025-03-11 10:20 ` Re: Statistics Import and Export: difference in statistics dumped Ashutosh Bapat <[email protected]>
2025-02-25 08:10 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-02-24 21:01 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-02-26 20:15 ` Re: Statistics Import and Export Robert Haas <[email protected]>
2025-02-26 20:37 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-02-26 20:57 ` Re: Statistics Import and Export Robert Haas <[email protected]>
2025-02-26 21:32 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-02-26 21:44 ` Re: Statistics Import and Export Tom Lane <[email protected]>
2025-02-25 18:22 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-02-25 20:31 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-02-25 23:40 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-02-26 19:54 ` Re: Statistics Import and Export Melanie Plageman <[email protected]>
2025-02-26 21:46 ` Re: Statistics Import and Export Tom Lane <[email protected]>
2025-02-26 21:58 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-02-27 02:19 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-02-27 23:27 ` Re: Statistics Import and Export Melanie Plageman <[email protected]>
2025-02-28 03:01 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-02-28 04:34 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-02-26 21:57 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-02-26 00:17 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-02-24 17:57 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-02-24 19:45 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-02-24 20:03 ` Re: Statistics Import and Export Tom Lane <[email protected]>
2025-02-24 20:20 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-02-24 20:40 ` Re: Statistics Import and Export Tom Lane <[email protected]>
2025-02-24 21:07 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-02-24 21:13 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-02-26 02:00 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-02-26 02:29 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-02-26 03:40 ` Re: Statistics Import and Export Tom Lane <[email protected]>
2025-02-26 03:55 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-02-26 04:05 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-02-26 04:36 ` Re: Statistics Import and Export Tom Lane <[email protected]>
2025-02-26 04:57 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-02-26 05:05 ` Re: Statistics Import and Export Tom Lane <[email protected]>
2025-02-26 09:25 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-02-26 16:13 ` Re: Statistics Import and Export Tom Lane <[email protected]>
2025-02-26 16:16 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-02-26 16:23 ` Re: Statistics Import and Export Tom Lane <[email protected]>
2025-02-26 16:43 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-02-26 18:02 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-02-26 18:06 ` Re: Statistics Import and Export Tom Lane <[email protected]>
2025-02-26 18:44 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-03-06 01:17 ` Re: Statistics Import and Export Andres Freund <[email protected]>
2025-03-06 01:30 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-06 01:36 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-03-06 01:54 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-06 02:18 ` Re: Statistics Import and Export Andres Freund <[email protected]>
2025-03-06 03:00 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-06 03:41 ` Re: Statistics Import and Export Andres Freund <[email protected]>
2025-03-06 04:04 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-06 08:07 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-03-06 08:48 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-03-06 13:49 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-07 00:58 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-03-07 01:47 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-06 14:29 ` Re: Statistics Import and Export Andres Freund <[email protected]>
2025-03-06 16:15 ` Re: Statistics Import and Export Robert Haas <[email protected]>
2025-03-06 17:16 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-06 17:59 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-03-06 18:04 ` Re: Statistics Import and Export Andres Freund <[email protected]>
2025-03-06 18:22 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-03-06 18:47 ` Re: Statistics Import and Export Tom Lane <[email protected]>
2025-03-06 19:33 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-03-06 19:34 ` Re: Statistics Import and Export Andres Freund <[email protected]>
2025-03-06 19:51 ` Re: Statistics Import and Export Tom Lane <[email protected]>
2025-03-06 18:47 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-06 19:04 ` Re: Statistics Import and Export Andres Freund <[email protected]>
2025-03-06 19:08 ` Re: Statistics Import and Export Tom Lane <[email protected]>
2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-03-07 02:56 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-07 16:22 ` Re: Statistics Import and Export Andres Freund <[email protected]>
2025-03-07 16:53 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-03-07 17:41 ` Re: Statistics Import and Export Robert Treat <[email protected]>
2025-03-07 18:41 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-03-07 20:46 ` Re: Statistics Import and Export Robert Treat <[email protected]>
2025-03-07 21:43 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-03-08 03:43 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-08 05:51 ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
2025-03-08 07:51 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-08 07:56 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-08 03:40 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-08 15:56 ` Re: Statistics Import and Export Robert Treat <[email protected]>
2025-03-08 19:09 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-25 05:32 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-03-25 17:51 ` Re: Statistics Import and Export Robert Treat <[email protected]>
2025-03-09 17:00 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-03-14 20:03 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-16 01:37 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-19 22:17 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-03-19 22:35 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-25 06:53 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-03-25 14:53 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-25 18:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-03-25 19:59 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-26 01:41 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-29 01:11 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-29 05:29 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-03-29 05:44 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-31 15:11 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-16 20:33 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-03-16 21:32 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-17 14:23 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-03-17 23:24 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-18 01:01 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-03-06 17:04 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-06 17:16 ` Re: Statistics Import and Export Andres Freund <[email protected]>
2025-03-06 18:00 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-06 18:08 ` Re: Statistics Import and Export Andres Freund <[email protected]>
2025-03-06 18:07 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-03-06 18:23 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-06 18:23 ` Re: Statistics Import and Export Andres Freund <[email protected]>
2025-03-06 02:19 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-03-01 17:00 ` Re: Statistics Import and Export Alexander Lakhin <[email protected]>
2025-03-01 18:04 ` Re: Statistics Import and Export Tom Lane <[email protected]>
2025-03-01 18:20 ` Re: Statistics Import and Export Alexander Lakhin <[email protected]>
2025-03-01 18:56 ` Re: Statistics Import and Export Tom Lane <[email protected]>
2025-03-02 01:55 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-12-13 17:13 [PATCH 1/3] stress test for repack concurrently Mikhail Nikalayeu <[email protected]>
2025-12-13 17:13 [PATCH 1/3] stress test for repack concurrently Mikhail Nikalayeu <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox