public inbox for [email protected]
help / color / mirror / Atom feedRe: Add 64-bit XIDs into PostgreSQL 15
7+ messages / 5 participants
[nested] [flat]
* Re: Add 64-bit XIDs into PostgreSQL 15
@ 2022-02-02 15:10 Pavel Borisov <[email protected]>
2022-03-02 13:25 ` Re: Add 64-bit XIDs into PostgreSQL 15 Aleksander Alekseev <[email protected]>
0 siblings, 1 reply; 7+ messages in thread
From: Pavel Borisov @ 2022-02-02 15:10 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Ilya Anfimov <[email protected]>; Postgres hackers <[email protected]>; pgsql-hackers
Hi, Andres!
I've revised the README a little bit to address your corrections and
questions. Thanks for this very much!
A patchset with changed README is attached as v8 here (the code is
unchanged and identical to v7).
> > +The downside of this is that we can not use tuple's XMIN and XMAX right
> away.
> > +We often need to re-read t_xmin and t_xmax - which could actually be
> pointers
> > +into a page in shared buffers and therefore they could be updated by
> any other
> > +backend.
>
> Ugh, that's not great.
>
Agree. This part is one of the candidates for revision as per proposals
above [1] i.e :
"2A. Probably refactor it to store precalculated XMIN/XMAX in memory
tuple representation instead of t_xid_base/t_multi_base".
We are working on this change.
> What happens if the first access happens on a replica?
>
> What is the approach for dealing with multixact files? They have xids
> embedded? And currently the SLRUs will break if you just let the offsets
> SLRU
> grow without bounds.
>
> Wait. So you just modify the page without WAL logging or marking it dirty
> on a
> standby? I fail to see how that can be correct.
>
> Imagine the cluster is promoted, the page is dirtied, and we write it
> out. You'll have written out a completely changed page, without any WAL
> logging. There's plenty other scenarios.
>
In this part, I suppose you've found a definite bug. Thanks! There are a
couple
of ways how it could be fixed:
1. If we enforce checkpoint at replica promotion then we force full-page
writes after each page modification afterward.
2. Maybe it's worth using BufferDesc bit to mark the page as converted to
64xid but not yet written to disk? For example, one of four bits from
BUF_USAGECOUNT.
BM_MAX_USAGE_COUNT = 5 so it will be enough 3 bits to store it. This will
change in-memory page representation but will not need WAL-logging which is
impossible on a replica.
What do you think about it?
[1]
https://www.postgresql.org/message-id/CALT9ZEHy9yFQEwptCUznPLciqM9ZSs91yTnNSSiG22m%3DBgCpNA%40mail.g...
Attachments:
[application/octet-stream] v8-0003-README.XID64.patch (7.0K, ../../CALT9ZEEsj54k9+xmcSAYLe7YsEHYbzFUuDwpVzkjmR6CCPHpww@mail.gmail.com/3-v8-0003-README.XID64.patch)
download | inline diff:
From e7681ff5aeae1b93d5a27e65505cbf46059242e5 Mon Sep 17 00:00:00 2001
From: Pavel Borisov <[email protected]>
Date: Mon, 10 Jan 2022 23:20:11 +0400
Subject: [PATCH v8 3/3] README.XID64
Authors:
- Alexander Korotkov <[email protected]>
- Teodor Sigaev <[email protected]>
- Nikita Glukhov <[email protected]>
- Maxim Orlov <[email protected]>
- Pavel Borisov <[email protected]>
- Yura Sokolov <[email protected]> <[email protected]>
---
src/backend/access/heap/README.XID64 | 135 +++++++++++++++++++++++++++
1 file changed, 135 insertions(+)
create mode 100644 src/backend/access/heap/README.XID64
diff --git a/src/backend/access/heap/README.XID64 b/src/backend/access/heap/README.XID64
new file mode 100644
index 00000000000..c6fc5e0429a
--- /dev/null
+++ b/src/backend/access/heap/README.XID64
@@ -0,0 +1,135 @@
+src/backend/access/heap/README.XID64
+
+64-bit Transaction ID's (XID)
+=============================
+
+A limited number (N = 2^32) of XID's required to do vacuum freeze to prevent
+wraparound every N/2 transactions. This causes performance degradation due
+to the need to read and rewrite all not yet frozen pages tables while being
+vacuumed. In each wraparound cycle, SLRU buffers are also being cut.
+
+With 64-bit XID's wraparound is effectively postponed to a very distant
+future. Even in highly loaded systems that had 2^32 transactions per day
+it will take huge 2^31 days before the first enforced "vacuum to prevent
+wraparound"). Buffers cutting and routine vacuum are not enforced, and DBA
+can plan them independently at the time with the least system load and least
+critical for database performance. Also, it can be done less frequently
+(several times a year vs every several days) on systems with transaction rates
+similar to those mentioned above.
+
+On-disk tuple and page format
+-----------------------------
+
+On-disk tuple format remains unchanged. 32-bit t_xmin and t_xmax store the
+lower parts of 64-bit XMIN and XMAX values. Each heap page has additional
+64-bit pd_xid_base and pd_multi_base which are common for all tuples on a page.
+They are placed into a pd_special area - 16 bytes in the end of a heap page.
+Actual XMIN/XMAX for a tuple are calculated upon reading a tuple from a page
+as follows:
+
+XMIN = t_xmin + pd_xid_base. (1)
+XMAX = t_xmax + pd_xid_base/pd_multi_base. (2)
+
+"Double XMAX" page format
+---------------------------------
+
+At first read of a heap page after pg_upgrade from 32-bit XID PostgreSQL
+version pd_special area with a size of 16 bytes should be added to a page.
+Though a page may not have space for this. Then it can be converted to a
+temporary format called "double XMAX".
+
+All tuples after pg-upgrade doesn't need t_xmin anymore as no older transactions
+could be running. So we don't need tuple header t_xmin field and we reuse
+t_xmin to store higher 32 bits of its XMAX.
+
+Double XMAX format is only for full pages that don't have 16 bytes for
+pd_special. So it neither has a place for a single tuple. Insert and HOT update
+for double XMAX pages is impossible and not supported. We can only read or
+delete tuples from it.
+
+When we are able to prune page double XMAX it will be converted from it to
+general 64-bit XID page format with all operations on its tuples supported.
+
+In-memory tuple format
+----------------------
+
+In-memory tuple representation consists of two parts:
+- HeapTupleHeader from disk page (contains all heap tuple contents, not only
+header)
+- HeapTuple with additional in-memory fields
+
+HeapTuple for each tuple in memory stores t_xid_base/t_multi_base - a copies of
+page's pd_xid_base/pd_multi_base. With tuple's 32-bit t_xmin and t_xmax from
+HeapTupleHeader they are used to calculate actual 64-bit XMIN and XMAX:
+
+XMIN = t_xmin + t_xid_base. (3)
+XMAX = t_xmax + t_xid_base/t_multi_base. (4)
+
+The filling of XMIN and XMAX in HeapTuple is done in the same way as the other
+fields of HeapTuple struct. It is done in all cases of HeapTuple manipulation.
+
+The downside of this is that we can not use tuple's XMIN and XMAX right away.
+We often need to re-read t_xmin and t_xmax - which could actually be pointers
+into a page in shared buffers and therefore they could be updated by any other
+backend.
+
+Update/delete with 64-bit XIDs and 32-bit t_xmin/t_xmax
+--------------------------------------------------------------
+
+When we try to delete/update a tuple, we check that XMAX for a page fits (2).
+I.e. that t_xmax will not be over MaxShortTransactionId relative to
+pd_xid_base/pd_multi_base of a its page.
+
+If the current XID doesn't fit a range
+(pd_xid_base, pd_xid_base + MaxShortTransactionId) (5):
+
+- heap_page_prepare_for_xid() will try to increase pd_xid_base/pd_multi_base on
+a page and update all t_xmin/t_xmax of the other tuples on the page to
+correspond new pd_xid_base/pd_multi_base.
+
+- If it was impossible, it will try to prune and freeze tuples on a page.
+
+- If this is unsuccessful it will throw an error. Normally this is very
+unlikely but if there is a very old living transaction with an age of around
+2^32 this can arise. Basically, this is a behavior similar to one during the
+vacuum to prevent wraparound when XID was 32-bit. Dba should take care and
+avoid very-long-living transactions with an age close to 2^32. So long-living
+transactions often they are most likely defunct.
+
+Insert with 64-bit XIDs and 32-bit t_xmin/t_xmax
+------------------------------------------------
+
+On insert we check if current XID fits a range (5). Otherwise:
+
+- heap_page_prepare_for_xid() will try to increase pd_xid_base for t_xmin will
+not be over MaxShortTransactionId.
+
+- If it is impossible, then it will try to prune and freeze tuples on a page.
+
+Known issue: if pd_xid_base could not be shifted to accommodate a tuple being
+inserted due to a very long-running transaction, we just throw an error. We
+neither try to insert a tuple into another page nor mark the current page as
+full. So, in this (unlikely) case we will get regular insert errors on the next
+tries to insert to the page 'locked' by this very long-running transaction.
+
+Upgrade from 32-bit XID versions
+--------------------------------
+
+pg_upgrade doesn't change pages format itself. It is done lazily after.
+
+1. At first heap page read, tuples on a page are repacked to free 16 bytes
+at the end of a page, possibly freeing space from dead tuples.
+
+2A. 16 bytes of pd_special is added if there is a place for it
+
+2B. Page is converted to "Double XMAX" format if there is no place for
+pd_special
+
+3. If a page is in double XMAX format after its first read, and vacuum (or
+micro-vacuum at select query) could prune some tuples and free space for
+pd_special, prune_page will add pd_special and convert page from double XMAX
+to general 64-bit XID page format.
+
+This lazy conversion is called only on pages being read. This can slow down
+performance after upgrade, but just for a short period of time while "hot"
+pages are read (and therefore converted to 64-bit format).
--
2.24.3 (Apple Git-128)
[application/octet-stream] v8-0001-64-bit-GUCs.patch (25.6K, ../../CALT9ZEEsj54k9+xmcSAYLe7YsEHYbzFUuDwpVzkjmR6CCPHpww@mail.gmail.com/4-v8-0001-64-bit-GUCs.patch)
download | inline diff:
From 0a8f72abbe20ca1e45e6cd00dcd5be6e25f42f06 Mon Sep 17 00:00:00 2001
From: Yura Sokolov <[email protected]>
Date: Wed, 29 Dec 2021 18:44:34 +0300
Subject: [PATCH v8 1/3] 64-bit GUCs
Authors:
- Alexander Korotkov <[email protected]>
- Teodor Sigaev <[email protected]>
- Nikita Glukhov <[email protected]>
- Maxim Orlov <[email protected]>
- Pavel Borisov <[email protected]>
- Yura Sokolov <[email protected]> <[email protected]>
---
src/backend/access/common/reloptions.c | 67 ++++
src/backend/utils/misc/guc.c | 432 +++++++++++++++++++++++++
src/include/access/reloptions.h | 13 +
src/include/utils/guc.h | 17 +
src/include/utils/guc_tables.h | 18 ++
5 files changed, 547 insertions(+)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index d592655258a..e7062d4da30 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -373,7 +373,12 @@ static relopt_int intRelOpts[] =
},
-1, 0, 1024
},
+ /* list terminator */
+ {{NULL}}
+};
+static relopt_int64 int64RelOpts[] =
+{
/* list terminator */
{{NULL}}
};
@@ -588,6 +593,12 @@ initialize_reloptions(void)
intRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; int64RelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(int64RelOpts[i].gen.lockmode,
+ int64RelOpts[i].gen.lockmode));
+ j++;
+ }
for (i = 0; realRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(realRelOpts[i].gen.lockmode,
@@ -630,6 +641,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; int64RelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &int64RelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_INT64;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; realRelOpts[i].gen.name; i++)
{
relOpts[j] = &realRelOpts[i].gen;
@@ -785,6 +804,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
+ case RELOPT_TYPE_INT64:
+ size = sizeof(relopt_int64);
+ break;
case RELOPT_TYPE_REAL:
size = sizeof(relopt_real);
break;
@@ -939,6 +961,25 @@ init_real_reloption(bits32 kinds, const char *name, const char *desc,
return newoption;
}
+/*
+ * add_int64_reloption
+ * Add a new 64-bit integer reloption
+ */
+void
+add_int64_reloption(bits32 kinds, char *name, char *desc, int64 default_val,
+ int64 min_val, int64 max_val, LOCKMODE lockmode)
+{
+ relopt_int64 *newoption;
+
+ newoption = (relopt_int64 *) allocate_reloption(kinds, RELOPT_TYPE_INT64,
+ name, desc, lockmode);
+ newoption->default_val = default_val;
+ newoption->min = min_val;
+ newoption->max = max_val;
+
+ add_reloption((relopt_gen *) newoption);
+}
+
/*
* add_real_reloption
* Add a new float reloption
@@ -1613,6 +1654,27 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
optint->min, optint->max)));
}
break;
+ case RELOPT_TYPE_INT64:
+ {
+ relopt_int64 *optint = (relopt_int64 *) option->gen;
+
+ parsed = parse_int64(value, &option->values.int64_val, 0, NULL);
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for 64-bit integer option \"%s\": %s",
+ option->gen->name, value)));
+ if (validate && (option->values.int64_val < optint->min ||
+ option->values.int64_val > optint->max))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("value %s out of bounds for option \"%s\"",
+ value, option->gen->name),
+ errdetail("Valid values are between \"" INT64_FORMAT
+ "\" and \"" INT64_FORMAT "\".",
+ optint->min, optint->max)));
+ }
+ break;
case RELOPT_TYPE_REAL:
{
relopt_real *optreal = (relopt_real *) option->gen;
@@ -1768,6 +1830,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.int_val :
((relopt_int *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_INT64:
+ *(int64 *) itempos = options[i].isset ?
+ options[i].values.int64_val :
+ ((relopt_int64 *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_REAL:
*(double *) itempos = options[i].isset ?
options[i].values.real_val :
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 4c94f09c645..fea143834e3 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -162,6 +162,8 @@ static bool call_bool_check_hook(struct config_bool *conf, bool *newval,
void **extra, GucSource source, int elevel);
static bool call_int_check_hook(struct config_int *conf, int *newval,
void **extra, GucSource source, int elevel);
+static bool call_int64_check_hook(struct config_int64 *conf, int64 *newval,
+ void **extra, GucSource source, int elevel);
static bool call_real_check_hook(struct config_real *conf, double *newval,
void **extra, GucSource source, int elevel);
static bool call_string_check_hook(struct config_string *conf, char **newval,
@@ -834,6 +836,7 @@ const char *const config_type_names[] =
{
/* PGC_BOOL */ "bool",
/* PGC_INT */ "integer",
+ /* PGC_INT64 */ "int64",
/* PGC_REAL */ "real",
/* PGC_STRING */ "string",
/* PGC_ENUM */ "enum"
@@ -940,6 +943,8 @@ static const unit_conversion time_unit_conversion_table[] =
{""} /* end of table marker */
};
+static const char *int64_unit_overflow_hint = gettext_noop("Int64 value with units should be positive number < 2^53");
+
/*
* Contents of GUC tables
*
@@ -3876,6 +3881,15 @@ static struct config_real ConfigureNamesReal[] =
};
+static struct config_int64 ConfigureNamesInt64[] =
+{
+ /* End-of-list marker */
+ {
+ {NULL, 0, 0, NULL, NULL}, NULL, 0.0, 0.0, 0.0, NULL, NULL, NULL
+ }
+};
+
+
static struct config_string ConfigureNamesString[] =
{
{
@@ -5197,6 +5211,10 @@ extra_field_used(struct config_generic *gconf, void *extra)
if (extra == ((struct config_int *) gconf)->reset_extra)
return true;
break;
+ case PGC_INT64:
+ if (extra == ((struct config_int64 *) gconf)->reset_extra)
+ return true;
+ break;
case PGC_REAL:
if (extra == ((struct config_real *) gconf)->reset_extra)
return true;
@@ -5258,6 +5276,10 @@ set_stack_value(struct config_generic *gconf, config_var_value *val)
val->val.intval =
*((struct config_int *) gconf)->variable;
break;
+ case PGC_INT64:
+ val->val.int64val =
+ *((struct config_int64 *) gconf)->variable;
+ break;
case PGC_REAL:
val->val.realval =
*((struct config_real *) gconf)->variable;
@@ -5286,6 +5308,7 @@ discard_stack_value(struct config_generic *gconf, config_var_value *val)
{
case PGC_BOOL:
case PGC_INT:
+ case PGC_INT64:
case PGC_REAL:
case PGC_ENUM:
/* no need to do anything */
@@ -5340,6 +5363,14 @@ build_guc_variables(void)
num_vars++;
}
+ for (i = 0; ConfigureNamesInt64[i].gen.name; i++)
+ {
+ struct config_int64 *conf = &ConfigureNamesInt64[i];
+
+ conf->gen.vartype = PGC_INT64;
+ num_vars++;
+ }
+
for (i = 0; ConfigureNamesReal[i].gen.name; i++)
{
struct config_real *conf = &ConfigureNamesReal[i];
@@ -5380,6 +5411,9 @@ build_guc_variables(void)
for (i = 0; ConfigureNamesInt[i].gen.name; i++)
guc_vars[num_vars++] = &ConfigureNamesInt[i].gen;
+ for (i = 0; ConfigureNamesInt64[i].gen.name; i++)
+ guc_vars[num_vars++] = &ConfigureNamesInt64[i].gen;
+
for (i = 0; ConfigureNamesReal[i].gen.name; i++)
guc_vars[num_vars++] = &ConfigureNamesReal[i].gen;
@@ -5805,6 +5839,24 @@ InitializeOneGUCOption(struct config_generic *gconf)
conf->gen.extra = conf->reset_extra = extra;
break;
}
+ case PGC_INT64:
+ {
+ struct config_int64 *conf = (struct config_int64 *) gconf;
+ int64 newval = conf->boot_val;
+ void *extra = NULL;
+
+ Assert(newval >= conf->min);
+ Assert(newval <= conf->max);
+ if (!call_int64_check_hook(conf, &newval, &extra,
+ PGC_S_DEFAULT, LOG))
+ elog(FATAL, "failed to initialize %s to " INT64_FORMAT,
+ conf->gen.name, newval);
+ if (conf->assign_hook)
+ (*conf->assign_hook) (newval, extra);
+ *conf->variable = conf->reset_val = newval;
+ conf->gen.extra = conf->reset_extra = extra;
+ break;
+ }
case PGC_REAL:
{
struct config_real *conf = (struct config_real *) gconf;
@@ -6092,6 +6144,18 @@ ResetAllOptions(void)
{
struct config_int *conf = (struct config_int *) gconf;
+ if (conf->assign_hook)
+ (*conf->assign_hook) (conf->reset_val,
+ conf->reset_extra);
+ *conf->variable = conf->reset_val;
+ set_extra_field(&conf->gen, &conf->gen.extra,
+ conf->reset_extra);
+ break;
+ }
+ case PGC_INT64:
+ {
+ struct config_int64 *conf = (struct config_int64 *) gconf;
+
if (conf->assign_hook)
conf->assign_hook(conf->reset_val,
conf->reset_extra);
@@ -6438,6 +6502,24 @@ AtEOXact_GUC(bool isCommit, int nestLevel)
int newval = newvalue.val.intval;
void *newextra = newvalue.extra;
+ if (*conf->variable != newval ||
+ conf->gen.extra != newextra)
+ {
+ if (conf->assign_hook)
+ (*conf->assign_hook) (newval, newextra);
+ *conf->variable = newval;
+ set_extra_field(&conf->gen, &conf->gen.extra,
+ newextra);
+ changed = true;
+ }
+ break;
+ }
+ case PGC_INT64:
+ {
+ struct config_int64 *conf = (struct config_int64 *) gconf;
+ int64 newval = newvalue.val.int64val;
+ void *newextra = newvalue.extra;
+
if (*conf->variable != newval ||
conf->gen.extra != newextra)
{
@@ -6966,6 +7048,79 @@ parse_int(const char *value, int *result, int flags, const char **hintmsg)
return true;
}
+/*
+ * Try to parse value as an 64-bit integer. The accepted format is
+ * decimal number.
+ *
+ * If the string parses okay, return true, else false.
+ * If okay and result is not NULL, return the value in *result.
+ * If not okay and hintmsg is not NULL, *hintmsg is set to a suitable
+ * HINT message, or NULL if no hint provided.
+ */
+bool
+parse_int64(const char *value, int64 *result, int flags, const char **hintmsg)
+{
+ int64 val;
+ char *endptr;
+
+ /* To suppress compiler warnings, always set output params */
+ if (result)
+ *result = 0;
+ if (hintmsg)
+ *hintmsg = NULL;
+
+ /* We assume here that int64 is at least as wide as long */
+ errno = 0;
+#ifdef _MSC_VER /* MSVC only */
+ val = _strtoi64(value, &endptr, 0);
+#elif defined(HAVE_STRTOLL) && SIZEOF_LONG < 8
+ val = strtoll(value, &endptr, 0);
+#else
+ val = strtol(value, &endptr, 0);
+#endif
+
+ if (endptr == value)
+ return false; /* no HINT for integer syntax error */
+
+ if (errno == ERANGE)
+ {
+ if (hintmsg)
+ *hintmsg = gettext_noop("Value exceeds 64-bit integer range.");
+ return false;
+ }
+
+ /*
+ * got double format and/or units.
+ * For now we attempts parse it as double and throw error on 53bit overflow
+ */
+ if (*endptr != '\0')
+ {
+ double dval;
+ bool ok;
+
+ ok = parse_real(value, &dval, flags, hintmsg);
+ if (!ok)
+ return false;
+
+ dval = rint(val);
+
+ if (fabs(dval) >= (double)((uint64)1<<53))
+ {
+ *hintmsg = int64_unit_overflow_hint;
+ return false;
+ }
+
+ val = (int64) dval;
+ }
+
+
+ if (result)
+ *result = val;
+ return true;
+}
+
+
+
/*
* Try to parse value as a floating point number in the usual format.
* Optionally, the value can be followed by a unit name if "flags" indicates
@@ -7207,6 +7362,37 @@ parse_and_validate_value(struct config_generic *record,
return false;
}
break;
+ case PGC_INT64:
+ {
+ struct config_int64 *conf = (struct config_int64 *) record;
+ const char *hintmsg;
+
+ if (!parse_int64(value, &newval->int64val, conf->gen.flags, &hintmsg))
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for parameter \"%s\": \"%s\"",
+ name, value),
+ hintmsg ? errhint("%s", _(hintmsg)) : 0));
+ return false;
+ }
+
+ if (newval->int64val < conf->min || newval->int64val > conf->max)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg(INT64_FORMAT " is outside the valid range for parameter \"%s\" ("
+ INT64_FORMAT " .. " INT64_FORMAT ")",
+ newval->int64val, name,
+ conf->min, conf->max)));
+ return false;
+ }
+
+ if (!call_int64_check_hook(conf, &newval->int64val, newextra,
+ source, elevel))
+ return false;
+ }
+ break;
case PGC_REAL:
{
struct config_real *conf = (struct config_real *) record;
@@ -7773,6 +7959,96 @@ set_config_option(const char *name, const char *value,
free(newextra);
break;
+#undef newval
+ }
+
+ case PGC_INT64:
+ {
+ struct config_int64 *conf = (struct config_int64 *) record;
+
+#define newval (newval_union.int64val)
+
+ if (value)
+ {
+ if (!parse_and_validate_value(record, name, value,
+ source, elevel,
+ &newval_union, &newextra))
+ return 0;
+ }
+ else if (source == PGC_S_DEFAULT)
+ {
+ newval = conf->boot_val;
+ if (!call_int64_check_hook(conf, &newval, &newextra,
+ source, elevel))
+ return 0;
+ }
+ else
+ {
+ newval = conf->reset_val;
+ newextra = conf->reset_extra;
+ source = conf->gen.reset_source;
+ context = conf->gen.reset_scontext;
+ }
+
+ if (prohibitValueChange)
+ {
+ if (*conf->variable != newval)
+ {
+ record->status |= GUC_PENDING_RESTART;
+ ereport(elevel,
+ (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM),
+ errmsg("parameter \"%s\" cannot be changed without restarting the server",
+ name)));
+ return 0;
+ }
+ record->status &= ~GUC_PENDING_RESTART;
+ return -1;
+ }
+
+ if (changeVal)
+ {
+ /* Save old value to support transaction abort */
+ if (!makeDefault)
+ push_old_value(&conf->gen, action);
+
+ if (conf->assign_hook)
+ (*conf->assign_hook) (newval, newextra);
+ *conf->variable = newval;
+ set_extra_field(&conf->gen, &conf->gen.extra,
+ newextra);
+ conf->gen.source = source;
+ conf->gen.scontext = context;
+ }
+ if (makeDefault)
+ {
+ GucStack *stack;
+
+ if (conf->gen.reset_source <= source)
+ {
+ conf->reset_val = newval;
+ set_extra_field(&conf->gen, &conf->reset_extra,
+ newextra);
+ conf->gen.reset_source = source;
+ conf->gen.reset_scontext = context;
+ }
+ for (stack = conf->gen.stack; stack; stack = stack->prev)
+ {
+ if (stack->source <= source)
+ {
+ stack->prior.val.intval = newval;
+ set_extra_field(&conf->gen, &stack->prior.extra,
+ newextra);
+ stack->source = source;
+ stack->scontext = context;
+ }
+ }
+ }
+
+ /* Perhaps we didn't install newextra anywhere */
+ if (newextra && !extra_field_used(&conf->gen, newextra))
+ free(newextra);
+ break;
+
#undef newval
}
@@ -8188,6 +8464,11 @@ GetConfigOption(const char *name, bool missing_ok, bool restrict_privileged)
*((struct config_int *) record)->variable);
return buffer;
+ case PGC_INT64:
+ snprintf(buffer, sizeof(buffer), INT64_FORMAT,
+ *((struct config_int64 *) record)->variable);
+ return buffer;
+
case PGC_REAL:
snprintf(buffer, sizeof(buffer), "%g",
*((struct config_real *) record)->variable);
@@ -8235,6 +8516,11 @@ GetConfigOptionResetString(const char *name)
((struct config_int *) record)->reset_val);
return buffer;
+ case PGC_INT64:
+ snprintf(buffer, sizeof(buffer), INT64_FORMAT,
+ ((struct config_int64 *) record)->reset_val);
+ return buffer;
+
case PGC_REAL:
snprintf(buffer, sizeof(buffer), "%g",
((struct config_real *) record)->reset_val);
@@ -9260,6 +9546,36 @@ DefineCustomIntVariable(const char *name,
define_custom_variable(&var->gen);
}
+void
+DefineCustomInt64Variable(const char *name,
+ const char *short_desc,
+ const char *long_desc,
+ int64 *valueAddr,
+ int64 bootValue,
+ int64 minValue,
+ int64 maxValue,
+ GucContext context,
+ int flags,
+ GucInt64CheckHook check_hook,
+ GucInt64AssignHook assign_hook,
+ GucShowHook show_hook)
+{
+ struct config_int64 *var;
+
+ var = (struct config_int64 *)
+ init_custom_variable(name, short_desc, long_desc, context, flags,
+ PGC_INT64, sizeof(struct config_int64));
+ var->variable = valueAddr;
+ var->boot_val = bootValue;
+ var->reset_val = bootValue;
+ var->min = minValue;
+ var->max = maxValue;
+ var->check_hook = check_hook;
+ var->assign_hook = assign_hook;
+ var->show_hook = show_hook;
+ define_custom_variable(&var->gen);
+}
+
void
DefineCustomRealVariable(const char *name,
const char *short_desc,
@@ -9737,6 +10053,31 @@ GetConfigOptionByNum(int varnum, const char **values, bool *noshow)
}
break;
+ case PGC_INT64:
+ {
+ struct config_int64 *lconf = (struct config_int64 *) conf;
+
+ /* min_val */
+ snprintf(buffer, sizeof(buffer), INT64_FORMAT, lconf->min);
+ values[9] = pstrdup(buffer);
+
+ /* max_val */
+ snprintf(buffer, sizeof(buffer), INT64_FORMAT, lconf->max);
+ values[10] = pstrdup(buffer);
+
+ /* enumvals */
+ values[11] = NULL;
+
+ /* boot_val */
+ snprintf(buffer, sizeof(buffer), INT64_FORMAT, lconf->boot_val);
+ values[12] = pstrdup(buffer);
+
+ /* reset_val */
+ snprintf(buffer, sizeof(buffer), INT64_FORMAT, lconf->reset_val);
+ values[13] = pstrdup(buffer);
+ }
+ break;
+
case PGC_REAL:
{
struct config_real *lconf = (struct config_real *) conf;
@@ -10209,6 +10550,21 @@ _ShowOption(struct config_generic *record, bool use_units)
}
break;
+ case PGC_INT64:
+ {
+ struct config_int64 *conf = (struct config_int64 *) record;
+
+ if (conf->show_hook)
+ val = (*conf->show_hook) ();
+ else
+ {
+ snprintf(buffer, sizeof(buffer), INT64_FORMAT,
+ *conf->variable);
+ val = buffer;
+ }
+ }
+ break;
+
case PGC_REAL:
{
struct config_real *conf = (struct config_real *) record;
@@ -10311,6 +10667,14 @@ write_one_nondefault_variable(FILE *fp, struct config_generic *gconf)
}
break;
+ case PGC_INT64:
+ {
+ struct config_int64 *conf = (struct config_int64 *) gconf;
+
+ fprintf(fp, INT64_FORMAT, *conf->variable);
+ }
+ break;
+
case PGC_REAL:
{
struct config_real *conf = (struct config_real *) gconf;
@@ -10582,6 +10946,24 @@ estimate_variable_size(struct config_generic *gconf)
}
break;
+ case PGC_INT64:
+ {
+ struct config_int64 *conf = (struct config_int64 *) gconf;
+
+ /*
+ * Instead of getting the exact display length, use max
+ * length. Also reduce the max length for typical ranges of
+ * small values. Maximum value is 2^63, i.e. 20 chars.
+ * Include one byte for sign.
+ */
+ if (Abs(*conf->variable) < 1000)
+ valsize = 3 + 1;
+ else
+ valsize = 20 + 1;
+ }
+ break;
+
+
case PGC_REAL:
{
/*
@@ -10739,6 +11121,14 @@ serialize_variable(char **destptr, Size *maxbytes,
}
break;
+ case PGC_INT64:
+ {
+ struct config_int64 *conf = (struct config_int64 *) gconf;
+
+ do_serialize(destptr, maxbytes, INT64_FORMAT, *conf->variable);
+ }
+ break;
+
case PGC_REAL:
{
struct config_real *conf = (struct config_real *) gconf;
@@ -10944,6 +11334,14 @@ RestoreGUCState(void *gucstate)
{
struct config_int *conf = (struct config_int *) gconf;
+ if (conf->reset_extra && conf->reset_extra != gconf->extra)
+ free(conf->reset_extra);
+ break;
+ }
+ case PGC_INT64:
+ {
+ struct config_int64 *conf = (struct config_int64 *) gconf;
+
if (conf->reset_extra && conf->reset_extra != gconf->extra)
free(conf->reset_extra);
break;
@@ -11518,6 +11916,40 @@ call_int_check_hook(struct config_int *conf, int *newval, void **extra,
return true;
}
+static bool
+call_int64_check_hook(struct config_int64 * conf, int64 *newval, void **extra,
+ GucSource source, int elevel)
+{
+ /* Quick success if no hook */
+ if (!conf->check_hook)
+ return true;
+
+ /* Reset variables that might be set by hook */
+ GUC_check_errcode_value = ERRCODE_INVALID_PARAMETER_VALUE;
+ GUC_check_errmsg_string = NULL;
+ GUC_check_errdetail_string = NULL;
+ GUC_check_errhint_string = NULL;
+
+ if (!(*conf->check_hook) (newval, extra, source))
+ {
+ ereport(elevel,
+ (errcode(GUC_check_errcode_value),
+ GUC_check_errmsg_string ?
+ errmsg_internal("%s", GUC_check_errmsg_string) :
+ errmsg("invalid value for parameter \"%s\": " INT64_FORMAT,
+ conf->gen.name, *newval),
+ GUC_check_errdetail_string ?
+ errdetail_internal("%s", GUC_check_errdetail_string) : 0,
+ GUC_check_errhint_string ?
+ errhint("%s", GUC_check_errhint_string) : 0));
+ /* Flush any strings created in ErrorContext */
+ FlushErrorState();
+ return false;
+ }
+
+ return true;
+}
+
static bool
call_real_check_hook(struct config_real *conf, double *newval, void **extra,
GucSource source, int elevel)
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index f7405133032..08a3c1a28ef 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -30,6 +30,7 @@ typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
RELOPT_TYPE_INT,
+ RELOPT_TYPE_INT64,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
RELOPT_TYPE_STRING
@@ -81,6 +82,7 @@ typedef struct relopt_value
{
bool bool_val;
int int_val;
+ int64 int64_val;
double real_val;
int enum_val;
char *string_val; /* allocated separately */
@@ -102,6 +104,14 @@ typedef struct relopt_int
int max;
} relopt_int;
+typedef struct relopt_int64
+{
+ relopt_gen gen;
+ int64 default_val;
+ int64 min;
+ int64 max;
+} relopt_int64;
+
typedef struct relopt_real
{
relopt_gen gen;
@@ -185,6 +195,9 @@ extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
+extern void add_int64_reloption(bits32 kinds, char *name, char *desc,
+ int64 default_val, int64 min_val, int64 max_val,
+ LOCKMODE lockmode);
extern void add_real_reloption(bits32 kinds, const char *name, const char *desc,
double default_val, double min_val, double max_val,
LOCKMODE lockmode);
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 6bb81707b09..661f8fda5e0 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -173,12 +173,14 @@ struct config_enum_entry
*/
typedef bool (*GucBoolCheckHook) (bool *newval, void **extra, GucSource source);
typedef bool (*GucIntCheckHook) (int *newval, void **extra, GucSource source);
+typedef bool (*GucInt64CheckHook) (int64 *newval, void **extra, GucSource source);
typedef bool (*GucRealCheckHook) (double *newval, void **extra, GucSource source);
typedef bool (*GucStringCheckHook) (char **newval, void **extra, GucSource source);
typedef bool (*GucEnumCheckHook) (int *newval, void **extra, GucSource source);
typedef void (*GucBoolAssignHook) (bool newval, void *extra);
typedef void (*GucIntAssignHook) (int newval, void *extra);
+typedef void (*GucInt64AssignHook) (int64 newval, void *extra);
typedef void (*GucRealAssignHook) (double newval, void *extra);
typedef void (*GucStringAssignHook) (const char *newval, void *extra);
typedef void (*GucEnumAssignHook) (int newval, void *extra);
@@ -318,6 +320,19 @@ extern void DefineCustomIntVariable(const char *name,
GucIntAssignHook assign_hook,
GucShowHook show_hook);
+extern void DefineCustomInt64Variable(const char *name,
+ const char *short_desc,
+ const char *long_desc,
+ int64 *valueAddr,
+ int64 bootValue,
+ int64 minValue,
+ int64 maxValue,
+ GucContext context,
+ int flags,
+ GucInt64CheckHook check_hook,
+ GucInt64AssignHook assign_hook,
+ GucShowHook show_hook);
+
extern void DefineCustomRealVariable(const char *name,
const char *short_desc,
const char *long_desc,
@@ -372,6 +387,8 @@ extern void ReportChangedGUCOptions(void);
extern void ParseLongOption(const char *string, char **name, char **value);
extern bool parse_int(const char *value, int *result, int flags,
const char **hintmsg);
+extern bool parse_int64(const char *value, int64 *result, int flags,
+ const char **hintmsg);
extern bool parse_real(const char *value, double *result, int flags,
const char **hintmsg);
extern int set_config_option(const char *name, const char *value,
diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h
index f7e54a87b7b..e59e2739ac0 100644
--- a/src/include/utils/guc_tables.h
+++ b/src/include/utils/guc_tables.h
@@ -23,6 +23,7 @@ enum config_type
{
PGC_BOOL,
PGC_INT,
+ PGC_INT64,
PGC_REAL,
PGC_STRING,
PGC_ENUM
@@ -32,6 +33,7 @@ union config_var_val
{
bool boolval;
int intval;
+ int64 int64val;
double realval;
char *stringval;
int enumval;
@@ -202,6 +204,22 @@ struct config_int
void *reset_extra;
};
+struct config_int64
+{
+ struct config_generic gen;
+ /* constant fields, must be set correctly in initial value: */
+ int64 *variable;
+ int64 boot_val;
+ int64 min;
+ int64 max;
+ GucInt64CheckHook check_hook;
+ GucInt64AssignHook assign_hook;
+ GucShowHook show_hook;
+ /* variable fields, initialized at runtime: */
+ int64 reset_val;
+ void *reset_extra;
+};
+
struct config_real
{
struct config_generic gen;
--
2.24.3 (Apple Git-128)
[application/octet-stream] v8-0002-Add-64bit-xid.patch (735.9K, ../../CALT9ZEEsj54k9+xmcSAYLe7YsEHYbzFUuDwpVzkjmR6CCPHpww@mail.gmail.com/5-v8-0002-Add-64bit-xid.patch)
download | inline diff:
From ccb59a5545dc1c94956395783e4447e9954a34d6 Mon Sep 17 00:00:00 2001
From: Yura Sokolov <[email protected]>
Date: Wed, 29 Dec 2021 18:54:31 +0300
Subject: [PATCH v8 2/3] Add 64bit xid.
- change TransactionId to 64bit
- disk tuple format (HeapTupleHeader) is (almost) unchanged: xmin and xmax remains 32bit
-- now 32bit xid is named ShortTransactionId
- heap page format is changed to contain xid and multixact base value, tuple's
xmin and xmax are offsets from.
-- xid_base and multi_base are stored as a page special data. PageHeader remains unmodified.
- in-memory tuple (HeapTuple) were enriched with copy of xid_base and multi_base from a page.
-- todo: replace xid_base/multi_base with precalculated 64bit xmin/xmax.
Authors:
- Alexander Korotkov <[email protected]>
- Teodor Sigaev <[email protected]>
- Nikita Glukhov <[email protected]>
- Maxim Orlov <[email protected]>
- Pavel Borisov <[email protected]>
- Yura Sokolov<[email protected]> <[email protected]>
---
contrib/amcheck/verify_heapam.c | 91 +-
contrib/amcheck/verify_nbtree.c | 2 +-
contrib/hstore/hstore_io.c | 2 +
contrib/pageinspect/Makefile | 3 +-
contrib/pageinspect/brinfuncs.c | 10 +-
contrib/pageinspect/btreefuncs.c | 25 +-
contrib/pageinspect/expected/hash_1.out | 166 +++
.../pageinspect/expected/oldextversions.out | 10 +-
contrib/pageinspect/expected/page.out | 28 +-
contrib/pageinspect/fsmfuncs.c | 2 +-
contrib/pageinspect/heapfuncs.c | 9 +-
.../pageinspect/pageinspect--1.10--1.11.sql | 145 +++
contrib/pageinspect/pageinspect--1.5.sql | 2 +
contrib/pageinspect/pageinspect.control | 2 +-
contrib/pageinspect/rawpage.c | 26 +-
contrib/pg_surgery/heap_surgery.c | 10 +-
.../pg_visibility/expected/pg_visibility.out | 17 +
contrib/pg_visibility/pg_visibility.c | 3 +-
contrib/pg_visibility/sql/pg_visibility.sql | 18 +
contrib/pgrowlocks/pgrowlocks.c | 8 +-
contrib/pgstattuple/pgstatapprox.c | 1 +
contrib/pgstattuple/pgstatindex.c | 2 +-
.../postgres_fdw/expected/postgres_fdw.out | 55 +-
contrib/postgres_fdw/postgres_fdw.c | 9 +-
contrib/postgres_fdw/sql/postgres_fdw.sql | 15 +-
contrib/test_decoding/test_decoding.c | 24 +-
src/backend/access/common/heaptuple.c | 10 +-
src/backend/access/common/reloptions.c | 118 +-
src/backend/access/hash/hashvalidate.c | 5 +-
src/backend/access/heap/heapam.c | 1059 ++++++++++++++---
src/backend/access/heap/heapam_handler.c | 30 +-
src/backend/access/heap/heapam_visibility.c | 173 +--
src/backend/access/heap/heaptoast.c | 3 +
src/backend/access/heap/hio.c | 16 +-
src/backend/access/heap/pruneheap.c | 60 +-
src/backend/access/heap/rewriteheap.c | 56 +-
src/backend/access/heap/vacuumlazy.c | 116 +-
src/backend/access/nbtree/nbtpage.c | 2 +
src/backend/access/nbtree/nbtsplitloc.c | 13 +-
src/backend/access/nbtree/nbtxlog.c | 2 +
src/backend/access/rmgrdesc/clogdesc.c | 8 +-
src/backend/access/rmgrdesc/committsdesc.c | 8 +-
src/backend/access/rmgrdesc/gistdesc.c | 8 +-
src/backend/access/rmgrdesc/hashdesc.c | 2 +-
src/backend/access/rmgrdesc/heapdesc.c | 50 +-
src/backend/access/rmgrdesc/mxactdesc.c | 12 +-
src/backend/access/rmgrdesc/nbtdesc.c | 8 +-
src/backend/access/rmgrdesc/spgdesc.c | 2 +-
src/backend/access/rmgrdesc/standbydesc.c | 6 +-
src/backend/access/rmgrdesc/xactdesc.c | 16 +-
src/backend/access/rmgrdesc/xlogdesc.c | 9 +-
src/backend/access/transam/clog.c | 92 +-
src/backend/access/transam/commit_ts.c | 70 +-
src/backend/access/transam/multixact.c | 885 +++-----------
src/backend/access/transam/slru.c | 125 +-
src/backend/access/transam/subtrans.c | 51 +-
src/backend/access/transam/transam.c | 68 +-
src/backend/access/transam/twophase.c | 31 +-
src/backend/access/transam/varsup.c | 153 +--
src/backend/access/transam/xact.c | 50 +-
src/backend/access/transam/xlog.c | 40 +-
src/backend/access/transam/xlogreader.c | 34 -
src/backend/bootstrap/bootstrap.c | 29 +-
src/backend/catalog/heap.c | 8 +-
src/backend/catalog/pg_inherits.c | 2 +-
src/backend/commands/async.c | 16 +-
src/backend/commands/sequence.c | 44 +-
src/backend/commands/vacuum.c | 76 +-
src/backend/executor/execExprInterp.c | 1 +
src/backend/executor/execUtils.c | 1 +
src/backend/executor/nodeModifyTable.c | 1 +
src/backend/executor/spi.c | 1 +
src/backend/nodes/outfuncs.c | 4 +-
src/backend/optimizer/util/plancat.c | 2 +-
src/backend/postmaster/autovacuum.c | 72 +-
src/backend/replication/logical/decode.c | 20 +-
src/backend/replication/logical/logical.c | 2 +-
src/backend/replication/logical/proto.c | 4 +-
.../replication/logical/reorderbuffer.c | 29 +-
src/backend/replication/logical/snapbuild.c | 18 +-
src/backend/replication/logical/worker.c | 12 +-
src/backend/replication/walreceiver.c | 27 +-
src/backend/replication/walsender.c | 63 +-
src/backend/statistics/extended_stats.c | 1 +
src/backend/storage/buffer/Makefile | 3 +-
src/backend/storage/buffer/bufmgr.c | 19 +-
src/backend/storage/buffer/heap_convert.c | 496 ++++++++
src/backend/storage/ipc/procarray.c | 120 +-
src/backend/storage/ipc/standby.c | 10 +-
src/backend/storage/lmgr/lmgr.c | 13 +-
src/backend/storage/lmgr/predicate.c | 28 +-
src/backend/storage/page/bufpage.c | 199 +++-
src/backend/utils/adt/enum.c | 2 +-
src/backend/utils/adt/jsonfuncs.c | 2 +
src/backend/utils/adt/lockfuncs.c | 11 +-
src/backend/utils/adt/numutils.c | 22 +
src/backend/utils/adt/pgstatfuncs.c | 1 +
src/backend/utils/adt/rowtypes.c | 12 +
src/backend/utils/adt/xid.c | 27 +-
src/backend/utils/adt/xid8funcs.c | 57 +-
src/backend/utils/cache/catcache.c | 1 +
src/backend/utils/cache/relcache.c | 5 +-
src/backend/utils/error/csvlog.c | 4 +-
src/backend/utils/error/elog.c | 8 +-
src/backend/utils/error/jsonlog.c | 4 +-
src/backend/utils/fmgr/fmgr.c | 4 +-
src/backend/utils/misc/guc.c | 184 +--
src/backend/utils/misc/help_config.c | 8 +-
src/backend/utils/misc/pg_controldata.c | 3 +-
src/backend/utils/misc/postgresql.conf.sample | 4 +-
src/backend/utils/sort/tuplesort.c | 8 +-
src/backend/utils/time/combocid.c | 18 +-
src/backend/utils/time/snapmgr.c | 23 +-
src/bin/initdb/initdb.c | 67 +-
src/bin/pg_amcheck/t/004_verify_heapam.pl | 184 ++-
src/bin/pg_controldata/pg_controldata.c | 17 +-
src/bin/pg_dump/pg_dump.c | 51 +-
src/bin/pg_dump/pg_dump.h | 8 +-
src/bin/pg_resetwal/pg_resetwal.c | 143 ++-
src/bin/pg_upgrade/Makefile | 1 +
src/bin/pg_upgrade/check.c | 114 ++
src/bin/pg_upgrade/controldata.c | 28 +-
src/bin/pg_upgrade/file.c | 172 ++-
src/bin/pg_upgrade/pg_upgrade.c | 132 +-
src/bin/pg_upgrade/pg_upgrade.h | 24 +-
src/bin/pg_upgrade/segresize.c | 571 +++++++++
src/bin/pg_upgrade/test.sh | 19 +-
src/bin/pg_upgrade/util.c | 17 +
src/bin/pg_upgrade/version.c | 134 ++-
src/bin/pg_verifybackup/t/003_corruption.pl | 2 +-
src/bin/pg_waldump/pg_waldump.c | 4 +-
src/include/access/clog.h | 2 +-
src/include/access/commit_ts.h | 2 +-
src/include/access/ginblock.h | 11 +-
src/include/access/gist.h | 2 +-
src/include/access/heapam.h | 16 +-
src/include/access/heapam_xlog.h | 22 +-
src/include/access/heaptoast.h | 2 +-
src/include/access/htup.h | 16 +-
src/include/access/htup_details.h | 142 ++-
src/include/access/multixact.h | 14 +-
src/include/access/nbtree.h | 10 +
src/include/access/rewriteheap.h | 2 +-
src/include/access/rmgrlist.h | 1 +
src/include/access/slru.h | 28 +-
src/include/access/tableam.h | 2 +-
src/include/access/transam.h | 76 +-
src/include/access/xact.h | 13 +-
src/include/access/xlog.h | 8 +
src/include/access/xlogreader.h | 4 -
src/include/c.h | 44 +-
src/include/catalog/pg_amproc.dat | 4 +-
src/include/catalog/pg_control.h | 6 +
src/include/catalog/pg_operator.dat | 8 +-
src/include/catalog/pg_proc.dat | 12 +-
src/include/catalog/pg_type.dat | 4 +-
src/include/commands/vacuum.h | 28 +-
src/include/fmgr.h | 2 +
src/include/pg_config.h.in | 3 +
src/include/postgres.h | 6 +-
src/include/postmaster/autovacuum.h | 4 +-
src/include/storage/bufmgr.h | 2 +
src/include/storage/bufpage.h | 105 +-
src/include/storage/itemid.h | 2 +
src/include/storage/lock.h | 14 +-
src/include/storage/proc.h | 2 +-
src/include/storage/standby.h | 2 +-
src/include/storage/sync.h | 2 +-
src/include/utils/builtins.h | 1 +
src/include/utils/combocid.h | 2 +-
src/include/utils/rel.h | 12 +-
src/pl/plperl/plperl.c | 4 +-
src/pl/plpgsql/src/pl_comp.c | 4 +-
src/pl/plpgsql/src/pl_exec.c | 2 +
src/pl/plpython/plpy_procedure.c | 4 +-
src/pl/tcl/pltcl.c | 4 +-
src/test/Makefile | 3 +-
src/test/perl/PostgreSQL/Test/Cluster.pm | 1 +
src/test/recovery/t/003_recovery_targets.pl | 2 +-
src/test/regress/expected/indirect_toast.out | 8 +
src/test/regress/expected/insert.out | 16 +-
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/expected/select_views.out | 70 +-
src/test/regress/expected/txid.out | 8 +-
src/test/regress/expected/xid.out | 14 +-
src/test/regress/pg_regress.c | 2 +-
src/test/regress/regress.c | 1 +
src/test/regress/sql/indirect_toast.sql | 11 +
src/test/regress/sql/insert.sql | 17 +-
src/test/regress/sql/select_views.sql | 2 +-
src/test/xid-64/Makefile | 22 +
src/test/xid-64/README | 16 +
src/test/xid-64/t/001_test_large_xids.pl | 53 +
src/test/xid-64/t/002_test_gucs.pl | 78 ++
src/test/xid-64/t/003_test_integrity.pl | 56 +
src/tools/msvc/Solution.pm | 1 +
196 files changed, 5645 insertions(+), 2891 deletions(-)
create mode 100644 contrib/pageinspect/expected/hash_1.out
create mode 100644 contrib/pageinspect/pageinspect--1.10--1.11.sql
create mode 100644 src/backend/storage/buffer/heap_convert.c
create mode 100644 src/bin/pg_upgrade/segresize.c
create mode 100644 src/test/xid-64/Makefile
create mode 100644 src/test/xid-64/README
create mode 100644 src/test/xid-64/t/001_test_large_xids.pl
create mode 100644 src/test/xid-64/t/002_test_gucs.pl
create mode 100644 src/test/xid-64/t/003_test_integrity.pl
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index f996f9a5727..95ea09b931e 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -126,6 +126,7 @@ typedef struct HeapCheckContext
uint16 lp_len;
uint16 lp_off;
HeapTupleHeader tuphdr;
+ HeapTupleData tuple;
int natts;
/* Values for iterating over attributes within the tuple */
@@ -523,6 +524,11 @@ verify_heapam(PG_FUNCTION_ARGS)
ctx.tuphdr = (HeapTupleHeader) PageGetItem(ctx.page, ctx.itemid);
ctx.natts = HeapTupleHeaderGetNatts(ctx.tuphdr);
+ ctx.tuple.t_data = ctx.tuphdr;
+ ctx.tuple.t_len = ItemIdGetLength(ctx.itemid);
+ ctx.tuple.t_tableOid = RelationGetRelid(ctx.rel);
+ HeapTupleCopyBaseFromPage(&ctx.tuple, ctx.page);
+
/* Ok, ready to check this next tuple */
check_tuple(&ctx);
}
@@ -768,12 +774,13 @@ check_tuple_visibility(HeapCheckContext *ctx)
XidCommitStatus xmin_status;
XidCommitStatus xvac_status;
XidCommitStatus xmax_status;
+ HeapTuple tuple = &ctx->tuple;
HeapTupleHeader tuphdr = ctx->tuphdr;
ctx->tuple_could_be_pruned = true; /* have not yet proven otherwise */
/* If xmin is normal, it should be within valid range */
- xmin = HeapTupleHeaderGetXmin(tuphdr);
+ xmin = HeapTupleGetXmin(tuple);
switch (get_xid_status(xmin, ctx, &xmin_status))
{
case XID_INVALID:
@@ -781,23 +788,20 @@ check_tuple_visibility(HeapCheckContext *ctx)
break;
case XID_IN_FUTURE:
report_corruption(ctx,
- psprintf("xmin %u equals or exceeds next valid transaction ID %u:%u",
+ psprintf("xmin " XID_FMT " equals or exceeds next valid transaction ID " XID_FMT,
xmin,
- EpochFromFullTransactionId(ctx->next_fxid),
XidFromFullTransactionId(ctx->next_fxid)));
return false;
case XID_PRECEDES_CLUSTERMIN:
report_corruption(ctx,
- psprintf("xmin %u precedes oldest valid transaction ID %u:%u",
+ psprintf("xmin " XID_FMT " precedes oldest valid transaction ID " XID_FMT,
xmin,
- EpochFromFullTransactionId(ctx->oldest_fxid),
XidFromFullTransactionId(ctx->oldest_fxid)));
return false;
case XID_PRECEDES_RELMIN:
report_corruption(ctx,
- psprintf("xmin %u precedes relation freeze threshold %u:%u",
+ psprintf("xmin " XID_FMT " precedes relation freeze threshold " XID_FMT,
xmin,
- EpochFromFullTransactionId(ctx->relfrozenfxid),
XidFromFullTransactionId(ctx->relfrozenfxid)));
return false;
}
@@ -822,23 +826,20 @@ check_tuple_visibility(HeapCheckContext *ctx)
return false;
case XID_IN_FUTURE:
report_corruption(ctx,
- psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple equals or exceeds next valid transaction ID %u:%u",
+ psprintf("old-style VACUUM FULL transaction ID " XID_FMT " for moved off tuple equals or exceeds next valid transaction ID " XID_FMT,
xvac,
- EpochFromFullTransactionId(ctx->next_fxid),
XidFromFullTransactionId(ctx->next_fxid)));
return false;
case XID_PRECEDES_RELMIN:
report_corruption(ctx,
- psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple precedes relation freeze threshold %u:%u",
+ psprintf("old-style VACUUM FULL transaction ID " XID_FMT " for moved off tuple precedes relation freeze threshold " XID_FMT,
xvac,
- EpochFromFullTransactionId(ctx->relfrozenfxid),
XidFromFullTransactionId(ctx->relfrozenfxid)));
return false;
case XID_PRECEDES_CLUSTERMIN:
report_corruption(ctx,
- psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple precedes oldest valid transaction ID %u:%u",
+ psprintf("old-style VACUUM FULL transaction ID " XID_FMT " for moved off tuple precedes oldest valid transaction ID " XID_FMT,
xvac,
- EpochFromFullTransactionId(ctx->oldest_fxid),
XidFromFullTransactionId(ctx->oldest_fxid)));
return false;
case XID_BOUNDS_OK:
@@ -849,12 +850,12 @@ check_tuple_visibility(HeapCheckContext *ctx)
{
case XID_IS_CURRENT_XID:
report_corruption(ctx,
- psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple matches our current transaction ID",
+ psprintf("old-style VACUUM FULL transaction ID " XID_FMT " for moved off tuple matches our current transaction ID",
xvac));
return false;
case XID_IN_PROGRESS:
report_corruption(ctx,
- psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple appears to be in progress",
+ psprintf("old-style VACUUM FULL transaction ID " XID_FMT " for moved off tuple appears to be in progress",
xvac));
return false;
@@ -891,23 +892,20 @@ check_tuple_visibility(HeapCheckContext *ctx)
return false;
case XID_IN_FUTURE:
report_corruption(ctx,
- psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple equals or exceeds next valid transaction ID %u:%u",
+ psprintf("old-style VACUUM FULL transaction ID " XID_FMT " for moved in tuple equals or exceeds next valid transaction ID " XID_FMT,
xvac,
- EpochFromFullTransactionId(ctx->next_fxid),
XidFromFullTransactionId(ctx->next_fxid)));
return false;
case XID_PRECEDES_RELMIN:
report_corruption(ctx,
- psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple precedes relation freeze threshold %u:%u",
+ psprintf("old-style VACUUM FULL transaction ID " XID_FMT " for moved in tuple precedes relation freeze threshold " XID_FMT,
xvac,
- EpochFromFullTransactionId(ctx->relfrozenfxid),
XidFromFullTransactionId(ctx->relfrozenfxid)));
return false;
case XID_PRECEDES_CLUSTERMIN:
report_corruption(ctx,
- psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple precedes oldest valid transaction ID %u:%u",
+ psprintf("old-style VACUUM FULL transaction ID " XID_FMT " for moved in tuple precedes oldest valid transaction ID " XID_FMT,
xvac,
- EpochFromFullTransactionId(ctx->oldest_fxid),
XidFromFullTransactionId(ctx->oldest_fxid)));
return false;
case XID_BOUNDS_OK:
@@ -918,12 +916,12 @@ check_tuple_visibility(HeapCheckContext *ctx)
{
case XID_IS_CURRENT_XID:
report_corruption(ctx,
- psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple matches our current transaction ID",
+ psprintf("old-style VACUUM FULL transaction ID " XID_FMT " for moved in tuple matches our current transaction ID",
xvac));
return false;
case XID_IN_PROGRESS:
report_corruption(ctx,
- psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple appears to be in progress",
+ psprintf("old-style VACUUM FULL transaction ID " XID_FMT " for moved in tuple appears to be in progress",
xvac));
return false;
@@ -985,7 +983,7 @@ check_tuple_visibility(HeapCheckContext *ctx)
* HEAP_XMAX_IS_LOCKED_ONLY is true, but for now we err on the side of
* avoiding possibly-bogus complaints about missing TOAST entries.
*/
- xmax = HeapTupleHeaderGetRawXmax(tuphdr);
+ xmax = HeapTupleGetRawXmax(tuple);
switch (check_mxid_valid_in_rel(xmax, ctx))
{
case XID_INVALID:
@@ -994,17 +992,17 @@ check_tuple_visibility(HeapCheckContext *ctx)
return true;
case XID_PRECEDES_RELMIN:
report_corruption(ctx,
- psprintf("multitransaction ID %u precedes relation minimum multitransaction ID threshold %u",
+ psprintf("multitransaction ID " XID_FMT " precedes relation minimum multitransaction ID threshold " XID_FMT,
xmax, ctx->relminmxid));
return true;
case XID_PRECEDES_CLUSTERMIN:
report_corruption(ctx,
- psprintf("multitransaction ID %u precedes oldest valid multitransaction ID threshold %u",
+ psprintf("multitransaction ID " XID_FMT " precedes oldest valid multitransaction ID threshold " XID_FMT,
xmax, ctx->oldest_mxact));
return true;
case XID_IN_FUTURE:
report_corruption(ctx,
- psprintf("multitransaction ID %u equals or exceeds next valid multitransaction ID %u",
+ psprintf("multitransaction ID " XID_FMT " equals or exceeds next valid multitransaction ID " XID_FMT,
xmax,
ctx->next_mxact));
return true;
@@ -1042,7 +1040,7 @@ check_tuple_visibility(HeapCheckContext *ctx)
* We already checked above that this multixact is within limits for
* this table. Now check the update xid from this multixact.
*/
- xmax = HeapTupleGetUpdateXid(tuphdr);
+ xmax = HeapTupleGetUpdateXid(tuple);
switch (get_xid_status(xmax, ctx, &xmax_status))
{
case XID_INVALID:
@@ -1052,23 +1050,20 @@ check_tuple_visibility(HeapCheckContext *ctx)
return true;
case XID_IN_FUTURE:
report_corruption(ctx,
- psprintf("update xid %u equals or exceeds next valid transaction ID %u:%u",
+ psprintf("update xid " XID_FMT " equals or exceeds next valid transaction ID " XID_FMT,
xmax,
- EpochFromFullTransactionId(ctx->next_fxid),
XidFromFullTransactionId(ctx->next_fxid)));
return true;
case XID_PRECEDES_RELMIN:
report_corruption(ctx,
- psprintf("update xid %u precedes relation freeze threshold %u:%u",
+ psprintf("update xid " XID_FMT " precedes relation freeze threshold " XID_FMT,
xmax,
- EpochFromFullTransactionId(ctx->relfrozenfxid),
XidFromFullTransactionId(ctx->relfrozenfxid)));
return true;
case XID_PRECEDES_CLUSTERMIN:
report_corruption(ctx,
- psprintf("update xid %u precedes oldest valid transaction ID %u:%u",
+ psprintf("update xid " XID_FMT " precedes oldest valid transaction ID " XID_FMT,
xmax,
- EpochFromFullTransactionId(ctx->oldest_fxid),
XidFromFullTransactionId(ctx->oldest_fxid)));
return true;
case XID_BOUNDS_OK:
@@ -1109,28 +1104,25 @@ check_tuple_visibility(HeapCheckContext *ctx)
}
/* xmax is an XID, not a MXID. Sanity check it. */
- xmax = HeapTupleHeaderGetRawXmax(tuphdr);
+ xmax = HeapTupleGetRawXmax(tuple);
switch (get_xid_status(xmax, ctx, &xmax_status))
{
case XID_IN_FUTURE:
report_corruption(ctx,
- psprintf("xmax %u equals or exceeds next valid transaction ID %u:%u",
+ psprintf("xmax " XID_FMT " equals or exceeds next valid transaction ID " XID_FMT,
xmax,
- EpochFromFullTransactionId(ctx->next_fxid),
XidFromFullTransactionId(ctx->next_fxid)));
return false; /* corrupt */
case XID_PRECEDES_RELMIN:
report_corruption(ctx,
- psprintf("xmax %u precedes relation freeze threshold %u:%u",
+ psprintf("xmax " XID_FMT " precedes relation freeze threshold " XID_FMT,
xmax,
- EpochFromFullTransactionId(ctx->relfrozenfxid),
XidFromFullTransactionId(ctx->relfrozenfxid)));
return false; /* corrupt */
case XID_PRECEDES_CLUSTERMIN:
report_corruption(ctx,
- psprintf("xmax %u precedes oldest valid transaction ID %u:%u",
+ psprintf("xmax " XID_FMT " precedes oldest valid transaction ID " XID_FMT,
xmax,
- EpochFromFullTransactionId(ctx->oldest_fxid),
XidFromFullTransactionId(ctx->oldest_fxid)));
return false; /* corrupt */
case XID_BOUNDS_OK:
@@ -1615,21 +1607,13 @@ check_tuple(HeapCheckContext *ctx)
}
/*
- * Convert a TransactionId into a FullTransactionId using our cached values of
- * the valid transaction ID range. It is the caller's responsibility to have
- * already updated the cached values, if necessary.
+ * With 64xid this function is just wrapper for FullTransactionIdFromXid,
+ * but we left it in order for its calls remain "vanilla" like.
*/
static FullTransactionId
-FullTransactionIdFromXidAndCtx(TransactionId xid, const HeapCheckContext *ctx)
+FullTransactionIdFromXidAndCtx(TransactionId xid, const HeapCheckContext *ctx pg_attribute_unused())
{
- uint32 epoch;
-
- if (!TransactionIdIsNormal(xid))
- return FullTransactionIdFromEpochAndXid(0, xid);
- epoch = EpochFromFullTransactionId(ctx->next_fxid);
- if (xid > ctx->next_xid)
- epoch--;
- return FullTransactionIdFromEpochAndXid(epoch, xid);
+ return FullTransactionIdFromXid(xid);
}
/*
@@ -1754,7 +1738,6 @@ get_xid_status(TransactionId xid, HeapCheckContext *ctx,
* performed the full xid conversion, reconvert.
*/
update_cached_xid_range(ctx);
- fxid = FullTransactionIdFromXidAndCtx(xid, ctx);
}
if (FullTransactionIdPrecedesOrEquals(ctx->next_fxid, fxid))
diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c
index d2510ee6480..e44e192dcef 100644
--- a/contrib/amcheck/verify_nbtree.c
+++ b/contrib/amcheck/verify_nbtree.c
@@ -498,7 +498,7 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
* avoid this.
*/
if (IsolationUsesXactSnapshot() && rel->rd_index->indcheckxmin &&
- !TransactionIdPrecedes(HeapTupleHeaderGetXmin(rel->rd_indextuple->t_data),
+ !TransactionIdPrecedes(HeapTupleGetXmin(rel->rd_indextuple),
snapshot->xmin))
ereport(ERROR,
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
diff --git a/contrib/hstore/hstore_io.c b/contrib/hstore/hstore_io.c
index b3304ff8445..f54741330c5 100644
--- a/contrib/hstore/hstore_io.c
+++ b/contrib/hstore/hstore_io.c
@@ -859,6 +859,7 @@ hstore_from_record(PG_FUNCTION_ARGS)
ItemPointerSetInvalid(&(tuple.t_self));
tuple.t_tableOid = InvalidOid;
tuple.t_data = rec;
+ HeapTupleSetZeroBase(&tuple);
values = (Datum *) palloc(ncolumns * sizeof(Datum));
nulls = (bool *) palloc(ncolumns * sizeof(bool));
@@ -1012,6 +1013,7 @@ hstore_populate_record(PG_FUNCTION_ARGS)
ItemPointerSetInvalid(&(tuple.t_self));
tuple.t_tableOid = InvalidOid;
tuple.t_data = rec;
+ HeapTupleSetZeroBase(&tuple);
}
/*
diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index 5c0736564ab..5ca80c9d766 100644
--- a/contrib/pageinspect/Makefile
+++ b/contrib/pageinspect/Makefile
@@ -13,7 +13,8 @@ OBJS = \
rawpage.o
EXTENSION = pageinspect
-DATA = pageinspect--1.9--1.10.sql pageinspect--1.8--1.9.sql \
+DATA = pageinspect--1.10--1.11.sql \
+ pageinspect--1.9--1.10.sql pageinspect--1.8--1.9.sql \
pageinspect--1.7--1.8.sql pageinspect--1.6--1.7.sql \
pageinspect--1.5.sql pageinspect--1.5--1.6.sql \
pageinspect--1.4--1.5.sql pageinspect--1.3--1.4.sql \
diff --git a/contrib/pageinspect/brinfuncs.c b/contrib/pageinspect/brinfuncs.c
index f1e64a39ef2..b80b9afac4d 100644
--- a/contrib/pageinspect/brinfuncs.c
+++ b/contrib/pageinspect/brinfuncs.c
@@ -45,7 +45,7 @@ Datum
brin_page_type(PG_FUNCTION_ARGS)
{
bytea *raw_page = PG_GETARG_BYTEA_P(0);
- Page page = VARDATA(raw_page);
+ Page page = get_page_from_raw(raw_page);
int raw_page_size;
char *type;
@@ -101,7 +101,7 @@ verify_brin_page(bytea *raw_page, uint16 type, const char *strtype)
errdetail("Expected size %d, got %d",
BLCKSZ, raw_page_size)));
- page = VARDATA(raw_page);
+ page = get_page_from_raw(raw_page);
/* verify the special space says this page is what we want */
if (BrinPageType(page) != type)
@@ -393,15 +393,15 @@ brin_revmap_data(PG_FUNCTION_ARGS)
MemoryContext mctx;
Page page;
- /* minimally verify the page we got */
- page = verify_brin_page(raw_page, BRIN_PAGETYPE_REVMAP, "revmap");
-
/* create a function context for cross-call persistence */
fctx = SRF_FIRSTCALL_INIT();
/* switch to memory context appropriate for multiple function calls */
mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
+ /* minimally verify the page we got */
+ page = verify_brin_page(raw_page, BRIN_PAGETYPE_REVMAP, "revmap");
+
state = palloc(sizeof(*state));
state->tids = ((RevmapContents *) PageGetContents(page))->rm_tids;
state->idx = 0;
diff --git a/contrib/pageinspect/btreefuncs.c b/contrib/pageinspect/btreefuncs.c
index 03debe336ba..21463cf65c4 100644
--- a/contrib/pageinspect/btreefuncs.c
+++ b/contrib/pageinspect/btreefuncs.c
@@ -105,9 +105,14 @@ GetBTPageStatistics(BlockNumber blkno, Buffer buffer, BTPageStat *stat)
stat->page_size = PageGetPageSize(page);
+ stat->btpo_prev = opaque->btpo_prev;
+ stat->btpo_level = opaque->btpo_level;
+
/* page type (flags) */
if (P_ISDELETED(opaque))
{
+ TransactionId safexid;
+
/* We divide deleted pages into leaf ('d') or internal ('D') */
if (P_ISLEAF(opaque) || !P_HAS_FULLXID(opaque))
stat->type = 'd';
@@ -122,16 +127,16 @@ GetBTPageStatistics(BlockNumber blkno, Buffer buffer, BTPageStat *stat)
* called "bpto").
*/
if (P_HAS_FULLXID(opaque))
+ safexid = XidFromFullTransactionId(BTPageGetDeleteXid(page));
+ else
{
- FullTransactionId safexid = BTPageGetDeleteXid(page);
-
- elog(DEBUG2, "deleted page from block %u has safexid %u:%u",
- blkno, EpochFromFullTransactionId(safexid),
- XidFromFullTransactionId(safexid));
+ safexid = BTP_GET_XACT(opaque);
+ stat->btpo_prev = 0;
+ stat->btpo_level = 0;
}
- else
- elog(DEBUG2, "deleted page from block %u has safexid %u",
- blkno, opaque->btpo_level);
+
+ elog(DEBUG2, "deleted page from block %u has safexid " XID_FMT,
+ blkno, safexid);
/* Don't interpret BTDeletedPageData as index tuples */
maxoff = InvalidOffsetNumber;
@@ -146,9 +151,7 @@ GetBTPageStatistics(BlockNumber blkno, Buffer buffer, BTPageStat *stat)
stat->type = 'i';
/* btpage opaque data */
- stat->btpo_prev = opaque->btpo_prev;
stat->btpo_next = opaque->btpo_next;
- stat->btpo_level = opaque->btpo_level;
stat->btpo_flags = opaque->btpo_flags;
stat->btpo_cycleid = opaque->btpo_cycleid;
@@ -613,7 +616,7 @@ bt_page_items_bytea(PG_FUNCTION_ARGS)
uargs = palloc(sizeof(struct user_args));
- uargs->page = VARDATA(raw_page);
+ uargs->page = get_page_from_raw(raw_page);
uargs->offset = FirstOffsetNumber;
diff --git a/contrib/pageinspect/expected/hash_1.out b/contrib/pageinspect/expected/hash_1.out
new file mode 100644
index 00000000000..5e64eb92602
--- /dev/null
+++ b/contrib/pageinspect/expected/hash_1.out
@@ -0,0 +1,166 @@
+CREATE TABLE test_hash (a int, b text);
+INSERT INTO test_hash VALUES (1, 'one');
+CREATE INDEX test_hash_a_idx ON test_hash USING hash (a);
+\x
+SELECT hash_page_type(get_raw_page('test_hash_a_idx', 0));
+-[ RECORD 1 ]--+---------
+hash_page_type | metapage
+
+SELECT hash_page_type(get_raw_page('test_hash_a_idx', 1));
+-[ RECORD 1 ]--+-------
+hash_page_type | bucket
+
+SELECT hash_page_type(get_raw_page('test_hash_a_idx', 2));
+-[ RECORD 1 ]--+-------
+hash_page_type | bucket
+
+SELECT hash_page_type(get_raw_page('test_hash_a_idx', 3));
+-[ RECORD 1 ]--+-------
+hash_page_type | bucket
+
+SELECT hash_page_type(get_raw_page('test_hash_a_idx', 4));
+-[ RECORD 1 ]--+-------
+hash_page_type | bucket
+
+SELECT hash_page_type(get_raw_page('test_hash_a_idx', 5));
+-[ RECORD 1 ]--+-------
+hash_page_type | bitmap
+
+SELECT hash_page_type(get_raw_page('test_hash_a_idx', 6));
+ERROR: block number 6 is out of range for relation "test_hash_a_idx"
+SELECT * FROM hash_bitmap_info('test_hash_a_idx', -1);
+ERROR: invalid block number
+SELECT * FROM hash_bitmap_info('test_hash_a_idx', 0);
+ERROR: invalid overflow block number 0
+SELECT * FROM hash_bitmap_info('test_hash_a_idx', 1);
+ERROR: invalid overflow block number 1
+SELECT * FROM hash_bitmap_info('test_hash_a_idx', 2);
+ERROR: invalid overflow block number 2
+SELECT * FROM hash_bitmap_info('test_hash_a_idx', 3);
+ERROR: invalid overflow block number 3
+SELECT * FROM hash_bitmap_info('test_hash_a_idx', 4);
+ERROR: invalid overflow block number 4
+SELECT * FROM hash_bitmap_info('test_hash_a_idx', 5);
+ERROR: invalid overflow block number 5
+SELECT * FROM hash_bitmap_info('test_hash_a_idx', 6);
+ERROR: block number 6 is out of range for relation "test_hash_a_idx"
+SELECT magic, version, ntuples, bsize, bmsize, bmshift, maxbucket, highmask,
+lowmask, ovflpoint, firstfree, nmaps, procid, spares, mapp FROM
+hash_metapage_info(get_raw_page('test_hash_a_idx', 0));
+-[ RECORD 1 ]--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+magic | 105121344
+version | 4
+ntuples | 1
+bsize | 8156
+bmsize | 4096
+bmshift | 15
+maxbucket | 3
+highmask | 7
+lowmask | 3
+ovflpoint | 2
+firstfree | 0
+nmaps | 1
+procid | 450
+spares | {0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+mapp | {5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+
+SELECT magic, version, ntuples, bsize, bmsize, bmshift, maxbucket, highmask,
+lowmask, ovflpoint, firstfree, nmaps, procid, spares, mapp FROM
+hash_metapage_info(get_raw_page('test_hash_a_idx', 1));
+ERROR: page is not a hash meta page
+SELECT magic, version, ntuples, bsize, bmsize, bmshift, maxbucket, highmask,
+lowmask, ovflpoint, firstfree, nmaps, procid, spares, mapp FROM
+hash_metapage_info(get_raw_page('test_hash_a_idx', 2));
+ERROR: page is not a hash meta page
+SELECT magic, version, ntuples, bsize, bmsize, bmshift, maxbucket, highmask,
+lowmask, ovflpoint, firstfree, nmaps, procid, spares, mapp FROM
+hash_metapage_info(get_raw_page('test_hash_a_idx', 3));
+ERROR: page is not a hash meta page
+SELECT magic, version, ntuples, bsize, bmsize, bmshift, maxbucket, highmask,
+lowmask, ovflpoint, firstfree, nmaps, procid, spares, mapp FROM
+hash_metapage_info(get_raw_page('test_hash_a_idx', 4));
+ERROR: page is not a hash meta page
+SELECT magic, version, ntuples, bsize, bmsize, bmshift, maxbucket, highmask,
+lowmask, ovflpoint, firstfree, nmaps, procid, spares, mapp FROM
+hash_metapage_info(get_raw_page('test_hash_a_idx', 5));
+ERROR: page is not a hash meta page
+SELECT live_items, dead_items, page_size, hasho_prevblkno, hasho_nextblkno,
+hasho_bucket, hasho_flag, hasho_page_id FROM
+hash_page_stats(get_raw_page('test_hash_a_idx', 0));
+ERROR: page is not a hash bucket or overflow page
+SELECT live_items, dead_items, page_size, hasho_prevblkno, hasho_nextblkno,
+hasho_bucket, hasho_flag, hasho_page_id FROM
+hash_page_stats(get_raw_page('test_hash_a_idx', 1));
+-[ RECORD 1 ]---+-----------
+live_items | 0
+dead_items | 0
+page_size | 8192
+hasho_prevblkno | 3
+hasho_nextblkno | 4294967295
+hasho_bucket | 0
+hasho_flag | 2
+hasho_page_id | 65408
+
+SELECT live_items, dead_items, page_size, hasho_prevblkno, hasho_nextblkno,
+hasho_bucket, hasho_flag, hasho_page_id FROM
+hash_page_stats(get_raw_page('test_hash_a_idx', 2));
+-[ RECORD 1 ]---+-----------
+live_items | 0
+dead_items | 0
+page_size | 8192
+hasho_prevblkno | 3
+hasho_nextblkno | 4294967295
+hasho_bucket | 1
+hasho_flag | 2
+hasho_page_id | 65408
+
+SELECT live_items, dead_items, page_size, hasho_prevblkno, hasho_nextblkno,
+hasho_bucket, hasho_flag, hasho_page_id FROM
+hash_page_stats(get_raw_page('test_hash_a_idx', 3));
+-[ RECORD 1 ]---+-----------
+live_items | 1
+dead_items | 0
+page_size | 8192
+hasho_prevblkno | 3
+hasho_nextblkno | 4294967295
+hasho_bucket | 2
+hasho_flag | 2
+hasho_page_id | 65408
+
+SELECT live_items, dead_items, page_size, hasho_prevblkno, hasho_nextblkno,
+hasho_bucket, hasho_flag, hasho_page_id FROM
+hash_page_stats(get_raw_page('test_hash_a_idx', 4));
+-[ RECORD 1 ]---+-----------
+live_items | 0
+dead_items | 0
+page_size | 8192
+hasho_prevblkno | 3
+hasho_nextblkno | 4294967295
+hasho_bucket | 3
+hasho_flag | 2
+hasho_page_id | 65408
+
+SELECT live_items, dead_items, page_size, hasho_prevblkno, hasho_nextblkno,
+hasho_bucket, hasho_flag, hasho_page_id FROM
+hash_page_stats(get_raw_page('test_hash_a_idx', 5));
+ERROR: page is not a hash bucket or overflow page
+SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 0));
+ERROR: page is not a hash bucket or overflow page
+SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 1));
+(0 rows)
+
+SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 2));
+(0 rows)
+
+SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 3));
+-[ RECORD 1 ]----------
+itemoffset | 1
+ctid | (0,1)
+data | 2389907270
+
+SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 4));
+(0 rows)
+
+SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 5));
+ERROR: page is not a hash bucket or overflow page
+DROP TABLE test_hash;
diff --git a/contrib/pageinspect/expected/oldextversions.out b/contrib/pageinspect/expected/oldextversions.out
index f5c4b61bd79..00323d392d6 100644
--- a/contrib/pageinspect/expected/oldextversions.out
+++ b/contrib/pageinspect/expected/oldextversions.out
@@ -40,16 +40,16 @@ SELECT * FROM bt_page_items('test1_a_idx', 1);
-- pagesize in pageinspect >= 1.10.
ALTER EXTENSION pageinspect UPDATE TO '1.9';
\df page_header
- List of functions
- Schema | Name | Result data type | Argument data types | Type
---------+-------------+------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------
- public | page_header | record | page bytea, OUT lsn pg_lsn, OUT checksum smallint, OUT flags smallint, OUT lower smallint, OUT upper smallint, OUT special smallint, OUT pagesize smallint, OUT version smallint, OUT prune_xid xid | func
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------------+------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------
+ public | page_header | record | page bytea, OUT lsn pg_lsn, OUT checksum smallint, OUT flags smallint, OUT lower smallint, OUT upper smallint, OUT special smallint, OUT pagesize smallint, OUT version smallint, OUT xid_base xid, OUT multi_base xid, OUT prune_xid xid | func
(1 row)
SELECT pagesize, version FROM page_header(get_raw_page('test1', 0));
pagesize | version
----------+---------
- 8192 | 4
+ 8192 | 5
(1 row)
DROP TABLE test1;
diff --git a/contrib/pageinspect/expected/page.out b/contrib/pageinspect/expected/page.out
index 4e325ae56dd..804be53c6dc 100644
--- a/contrib/pageinspect/expected/page.out
+++ b/contrib/pageinspect/expected/page.out
@@ -48,7 +48,7 @@ SELECT get_raw_page('test1', 0) = get_raw_page('test1', 'main', 0);
SELECT pagesize, version FROM page_header(get_raw_page('test1', 0));
pagesize | version
----------+---------
- 8192 | 4
+ 8192 | 5
(1 row)
SELECT page_checksum(get_raw_page('test1', 0), 0) IS NOT NULL AS silly_checksum_test;
@@ -69,19 +69,19 @@ SELECT tuple_data_split('test1'::regclass, t_data, t_infomask, t_infomask2, t_bi
SELECT * FROM fsm_page_contents(get_raw_page('test1', 'fsm', 0));
fsm_page_contents
-------------------
- 0: 254 +
- 1: 254 +
- 3: 254 +
- 7: 254 +
- 15: 254 +
- 31: 254 +
- 63: 254 +
- 127: 254 +
- 255: 254 +
- 511: 254 +
- 1023: 254 +
- 2047: 254 +
- 4095: 254 +
+ 0: 253 +
+ 1: 253 +
+ 3: 253 +
+ 7: 253 +
+ 15: 253 +
+ 31: 253 +
+ 63: 253 +
+ 127: 253 +
+ 255: 253 +
+ 511: 253 +
+ 1023: 253 +
+ 2047: 253 +
+ 4095: 253 +
fp_next_slot: 0 +
(1 row)
diff --git a/contrib/pageinspect/fsmfuncs.c b/contrib/pageinspect/fsmfuncs.c
index dadd62aa20a..7aa1a0558f0 100644
--- a/contrib/pageinspect/fsmfuncs.c
+++ b/contrib/pageinspect/fsmfuncs.c
@@ -44,7 +44,7 @@ fsm_page_contents(PG_FUNCTION_ARGS)
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to use raw page functions")));
- fsmpage = (FSMPage) PageGetContents(VARDATA(raw_page));
+ fsmpage = (FSMPage) PageGetContents(get_page_from_raw(raw_page));
initStringInfo(&sinfo);
diff --git a/contrib/pageinspect/heapfuncs.c b/contrib/pageinspect/heapfuncs.c
index 3dd1a9bc2ab..3a22c4794dd 100644
--- a/contrib/pageinspect/heapfuncs.c
+++ b/contrib/pageinspect/heapfuncs.c
@@ -163,7 +163,7 @@ heap_page_items(PG_FUNCTION_ARGS)
inter_call_data->tupd = tupdesc;
inter_call_data->offset = FirstOffsetNumber;
- inter_call_data->page = VARDATA(raw_page);
+ inter_call_data->page = get_page_from_raw(raw_page);
fctx->max_calls = PageGetMaxOffsetNumber(inter_call_data->page);
fctx->user_fctx = inter_call_data;
@@ -211,6 +211,7 @@ heap_page_items(PG_FUNCTION_ARGS)
lp_offset == MAXALIGN(lp_offset) &&
lp_offset + lp_len <= raw_page_size)
{
+ HeapTupleData tup;
HeapTupleHeader tuphdr;
bytea *tuple_data_bytea;
int tuple_data_len;
@@ -218,9 +219,11 @@ heap_page_items(PG_FUNCTION_ARGS)
/* Extract information from the tuple header */
tuphdr = (HeapTupleHeader) PageGetItem(page, id);
+ tup.t_data = tuphdr;
+ HeapTupleCopyBaseFromPage(&tup, page);
- values[4] = UInt32GetDatum(HeapTupleHeaderGetRawXmin(tuphdr));
- values[5] = UInt32GetDatum(HeapTupleHeaderGetRawXmax(tuphdr));
+ values[4] = TransactionIdGetDatum(HeapTupleGetXmin(&tup));
+ values[5] = TransactionIdGetDatum(HeapTupleGetRawXmax(&tup));
/* shared with xvac */
values[6] = UInt32GetDatum(HeapTupleHeaderGetRawCommandId(tuphdr));
values[7] = PointerGetDatum(&tuphdr->t_ctid);
diff --git a/contrib/pageinspect/pageinspect--1.10--1.11.sql b/contrib/pageinspect/pageinspect--1.10--1.11.sql
new file mode 100644
index 00000000000..236f18aa2f8
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.10--1.11.sql
@@ -0,0 +1,145 @@
+/* contrib/pageinspect/pageinspect--1.10--1.11.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION pageinspect UPDATE TO '1.11'" to load this file. \quit
+
+--
+-- gist_page_opaque_info()
+--
+DROP FUNCTION gist_page_opaque_info(bytea);
+CREATE FUNCTION gist_page_opaque_info(IN page bytea,
+ OUT lsn pg_lsn,
+ OUT nsn pg_lsn,
+ OUT rightlink bigint,
+ OUT flags text[])
+AS 'MODULE_PATHNAME', 'gist_page_opaque_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+
+--
+-- gist_page_items_bytea()
+--
+DROP FUNCTION gist_page_items_bytea(bytea);
+CREATE FUNCTION gist_page_items_bytea(IN page bytea,
+ OUT itemoffset smallint,
+ OUT ctid tid,
+ OUT itemlen smallint,
+ OUT dead boolean,
+ OUT key_data bytea)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'gist_page_items_bytea'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- gist_page_items()
+--
+DROP FUNCTION gist_page_items(bytea, regclass);
+CREATE FUNCTION gist_page_items(IN page bytea,
+ IN index_oid regclass,
+ OUT itemoffset smallint,
+ OUT ctid tid,
+ OUT itemlen smallint,
+ OUT dead boolean,
+ OUT keys text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'gist_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- get_raw_page()
+--
+DROP FUNCTION get_raw_page(text, int8);
+DROP FUNCTION IF EXISTS get_raw_page(text, int4);
+CREATE FUNCTION get_raw_page(text, int8)
+RETURNS bytea
+AS 'MODULE_PATHNAME', 'get_raw_page_1_9'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+DROP FUNCTION get_raw_page(text, text, int8);
+DROP FUNCTION IF EXISTS get_raw_page(text, text, int4);
+CREATE FUNCTION get_raw_page(text, text, int8)
+RETURNS bytea
+AS 'MODULE_PATHNAME', 'get_raw_page_fork_1_9'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- page_checksum()
+--
+DROP FUNCTION page_checksum(IN page bytea, IN blkno int8);
+DROP FUNCTION IF EXISTS page_checksum(IN page bytea, IN blkno int4);
+CREATE FUNCTION page_checksum(IN page bytea, IN blkno int8)
+RETURNS smallint
+AS 'MODULE_PATHNAME', 'page_checksum_1_9'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- bt_metap()
+--
+DROP FUNCTION bt_metap(text);
+CREATE FUNCTION bt_metap(IN relname text,
+ OUT magic int4,
+ OUT version int4,
+ OUT root int8,
+ OUT level int8,
+ OUT fastroot int8,
+ OUT fastlevel int8,
+ OUT last_cleanup_num_delpages int8,
+ OUT last_cleanup_num_tuples float8,
+ OUT allequalimage boolean)
+AS 'MODULE_PATHNAME', 'bt_metap'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- bt_page_stats()
+--
+DROP FUNCTION bt_page_stats(text, int8);
+DROP FUNCTION IF EXISTS bt_page_stats(text, int4);
+CREATE FUNCTION bt_page_stats(IN relname text, IN blkno int8,
+ OUT blkno int8,
+ OUT type "char",
+ OUT live_items int4,
+ OUT dead_items int4,
+ OUT avg_item_size int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT btpo_prev int8,
+ OUT btpo_next int8,
+ OUT btpo_level int8,
+ OUT btpo_flags int4)
+AS 'MODULE_PATHNAME', 'bt_page_stats_1_9'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- bt_page_items()
+--
+DROP FUNCTION bt_page_items(text, int8);
+DROP FUNCTION IF EXISTS bt_page_items(text, int4);
+CREATE FUNCTION bt_page_items(IN relname text, IN blkno int8,
+ OUT itemoffset smallint,
+ OUT ctid tid,
+ OUT itemlen smallint,
+ OUT nulls bool,
+ OUT vars bool,
+ OUT data text,
+ OUT dead boolean,
+ OUT htid tid,
+ OUT tids tid[])
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'bt_page_items_1_9'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_page_items()
+--
+DROP FUNCTION brin_page_items(IN page bytea, IN index_oid regclass);
+CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass,
+ OUT itemoffset int,
+ OUT blknum int8,
+ OUT attnum int,
+ OUT allnulls bool,
+ OUT hasnulls bool,
+ OUT placeholder bool,
+ OUT value text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'brin_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect--1.5.sql b/contrib/pageinspect/pageinspect--1.5.sql
index 1e40c3c97e2..fdbd2995a22 100644
--- a/contrib/pageinspect/pageinspect--1.5.sql
+++ b/contrib/pageinspect/pageinspect--1.5.sql
@@ -28,6 +28,8 @@ CREATE FUNCTION page_header(IN page bytea,
OUT special smallint,
OUT pagesize smallint,
OUT version smallint,
+ OUT xid_base xid,
+ OUT multi_base xid,
OUT prune_xid xid)
AS 'MODULE_PATHNAME', 'page_header'
LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control
index 7cdf37913da..f277413dd8c 100644
--- a/contrib/pageinspect/pageinspect.control
+++ b/contrib/pageinspect/pageinspect.control
@@ -1,5 +1,5 @@
# pageinspect extension
comment = 'inspect the contents of database pages at a low level'
-default_version = '1.10'
+default_version = '1.11'
module_pathname = '$libdir/pageinspect'
relocatable = true
diff --git a/contrib/pageinspect/rawpage.c b/contrib/pageinspect/rawpage.c
index 7e41af045f3..5caa9c100a6 100644
--- a/contrib/pageinspect/rawpage.c
+++ b/contrib/pageinspect/rawpage.c
@@ -17,6 +17,7 @@
#include "access/htup_details.h"
#include "access/relation.h"
+#include "commands/sequence.h"
#include "catalog/namespace.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
@@ -252,8 +253,8 @@ page_header(PG_FUNCTION_ARGS)
Datum result;
HeapTuple tuple;
- Datum values[9];
- bool nulls[9];
+ Datum values[11];
+ bool nulls[11];
PageHeader page;
XLogRecPtr lsn;
@@ -274,7 +275,7 @@ page_header(PG_FUNCTION_ARGS)
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("input page too small (%d bytes)", raw_page_size)));
- page = (PageHeader) VARDATA(raw_page);
+ page = (PageHeader) get_page_from_raw(raw_page);
/* Build a tuple descriptor for our result type */
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
@@ -324,12 +325,29 @@ page_header(PG_FUNCTION_ARGS)
}
values[7] = UInt16GetDatum(PageGetPageLayoutVersion(page));
- values[8] = TransactionIdGetDatum(page->pd_prune_xid);
/* Build and return the tuple. */
memset(nulls, 0, sizeof(nulls));
+ if (PageGetSpecialSize(page) == MAXALIGN(sizeof(HeapPageSpecialData)))
+ {
+ HeapPageSpecial pageSpecial = HeapPageGetSpecial(page);
+
+ values[8] = TransactionIdGetDatum(pageSpecial->pd_xid_base);
+ values[9] = TransactionIdGetDatum(pageSpecial->pd_multi_base);
+ values[10] = TransactionIdGetDatum(HeapPageGetPruneXid(page));
+ nulls[8] = false;
+ nulls[9] = false;
+ nulls[10] = false;
+ }
+ else
+ {
+ nulls[8] = true;
+ nulls[9] = true;
+ nulls[10] = true;
+ }
+
tuple = heap_form_tuple(tupdesc, values, nulls);
result = HeapTupleGetDatum(tuple);
diff --git a/contrib/pg_surgery/heap_surgery.c b/contrib/pg_surgery/heap_surgery.c
index 3d8eb0abc29..84c8601f8c4 100644
--- a/contrib/pg_surgery/heap_surgery.c
+++ b/contrib/pg_surgery/heap_surgery.c
@@ -270,11 +270,17 @@ heap_force_common(FunctionCallInfo fcinfo, HeapTupleForceOption heap_force_opt)
else
{
HeapTupleHeader htup;
+ HeapTupleData tuple;
Assert(heap_force_opt == HEAP_FORCE_FREEZE);
htup = (HeapTupleHeader) PageGetItem(page, itemid);
+ tuple.t_data = htup;
+ tuple.t_len = ItemIdGetLength(itemid);
+ tuple.t_tableOid = RelationGetRelid(rel);
+ HeapTupleCopyBaseFromPage(&tuple, page);
+
/*
* Reset all visibility-related fields of the tuple. This
* logic should mimic heap_execute_freeze_tuple(), but we
@@ -282,8 +288,8 @@ heap_force_common(FunctionCallInfo fcinfo, HeapTupleForceOption heap_force_opt)
* potentially-garbled data is left behind.
*/
ItemPointerSet(&htup->t_ctid, blkno, curoff);
- HeapTupleHeaderSetXmin(htup, FrozenTransactionId);
- HeapTupleHeaderSetXmax(htup, InvalidTransactionId);
+ HeapTupleSetXmin(&tuple, FrozenTransactionId);
+ HeapTupleSetXmax(&tuple, InvalidTransactionId);
if (htup->t_infomask & HEAP_MOVED)
{
if (htup->t_infomask & HEAP_MOVED_OFF)
diff --git a/contrib/pg_visibility/expected/pg_visibility.out b/contrib/pg_visibility/expected/pg_visibility.out
index 9de54db2a29..32bfbaa84e0 100644
--- a/contrib/pg_visibility/expected/pg_visibility.out
+++ b/contrib/pg_visibility/expected/pg_visibility.out
@@ -267,6 +267,22 @@ select * from pg_check_frozen('copyfreeze');
--------
(0 rows)
+create table vacuum_test as select 42 i;
+vacuum (disable_page_skipping) vacuum_test;
+-- pg_check_visible() can report false positive due to autovacuum activity.
+-- To workaround this issue, repeat the call. See PGPRO-4776.
+do $$
+declare
+ non_visible_count bigint;
+ i integer;
+begin
+ for i in 1 .. 10 loop
+ if i > 1 then perform pg_sleep(1); end if;
+ select count(*) from pg_check_visible('vacuum_test') into non_visible_count;
+ if non_visible_count = 0 then exit; end if;
+ end loop;
+ if non_visible_count > 0 then raise exception 'The visibility map is corrupt.'; end if;
+end $$;
-- cleanup
drop table test_partitioned;
drop view test_view;
@@ -277,3 +293,4 @@ drop foreign data wrapper dummy;
drop materialized view matview_visibility_test;
drop table regular_table;
drop table copyfreeze;
+drop table vacuum_test;
diff --git a/contrib/pg_visibility/pg_visibility.c b/contrib/pg_visibility/pg_visibility.c
index 44331c6f496..fbd67927a42 100644
--- a/contrib/pg_visibility/pg_visibility.c
+++ b/contrib/pg_visibility/pg_visibility.c
@@ -653,6 +653,7 @@ collect_corrupt_items(Oid relid, bool all_visible, bool all_frozen)
/* Initialize a HeapTupleData structure for checks below. */
ItemPointerSet(&(tuple.t_self), blkno, offnum);
+ HeapTupleCopyBaseFromPage(&tuple, page);
tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid);
tuple.t_len = ItemIdGetLength(itemid);
tuple.t_tableOid = relid;
@@ -762,7 +763,7 @@ tuple_all_visible(HeapTuple tup, TransactionId OldestXmin, Buffer buffer)
* be set here. So just check the xmin.
*/
- xmin = HeapTupleHeaderGetXmin(tup->t_data);
+ xmin = HeapTupleGetXmin(tup);
if (!TransactionIdPrecedes(xmin, OldestXmin))
return false; /* xmin not old enough for all to see */
diff --git a/contrib/pg_visibility/sql/pg_visibility.sql b/contrib/pg_visibility/sql/pg_visibility.sql
index ff3538f9964..42c4fad36ad 100644
--- a/contrib/pg_visibility/sql/pg_visibility.sql
+++ b/contrib/pg_visibility/sql/pg_visibility.sql
@@ -170,6 +170,23 @@ commit;
select * from pg_visibility_map('copyfreeze');
select * from pg_check_frozen('copyfreeze');
+create table vacuum_test as select 42 i;
+vacuum (disable_page_skipping) vacuum_test;
+-- pg_check_visible() can report false positive due to autovacuum activity.
+-- To workaround this issue, repeat the call. See PGPRO-4776.
+do $$
+declare
+ non_visible_count bigint;
+ i integer;
+begin
+ for i in 1 .. 10 loop
+ if i > 1 then perform pg_sleep(1); end if;
+ select count(*) from pg_check_visible('vacuum_test') into non_visible_count;
+ if non_visible_count = 0 then exit; end if;
+ end loop;
+ if non_visible_count > 0 then raise exception 'The visibility map is corrupt.'; end if;
+end $$;
+
-- cleanup
drop table test_partitioned;
drop view test_view;
@@ -180,3 +197,4 @@ drop foreign data wrapper dummy;
drop materialized view matview_visibility_test;
drop table regular_table;
drop table copyfreeze;
+drop table vacuum_test;
diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index d8946dc5107..e26451c9d5a 100644
--- a/contrib/pgrowlocks/pgrowlocks.c
+++ b/contrib/pgrowlocks/pgrowlocks.c
@@ -156,7 +156,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
htsu = HeapTupleSatisfiesUpdate(tuple,
GetCurrentCommandId(false),
hscan->rs_cbuf);
- xmax = HeapTupleHeaderGetRawXmax(tuple->t_data);
+ xmax = HeapTupleGetRawXmax(tuple);
infomask = tuple->t_data->t_infomask;
/*
@@ -168,7 +168,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
PointerGetDatum(&tuple->t_self));
values[Atnum_xmax] = palloc(NCHARS * sizeof(char));
- snprintf(values[Atnum_xmax], NCHARS, "%u", xmax);
+ snprintf(values[Atnum_xmax], NCHARS, XID_FMT, xmax);
if (infomask & HEAP_XMAX_IS_MULTI)
{
MultiXactMember *members;
@@ -209,7 +209,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
strcat(values[Atnum_modes], ",");
strcat(values[Atnum_pids], ",");
}
- snprintf(buf, NCHARS, "%u", members[j].xid);
+ snprintf(buf, NCHARS, XID_FMT, members[j].xid);
strcat(values[Atnum_xids], buf);
switch (members[j].status)
{
@@ -250,7 +250,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
values[Atnum_ismulti] = pstrdup("false");
values[Atnum_xids] = palloc(NCHARS * sizeof(char));
- snprintf(values[Atnum_xids], NCHARS, "{%u}", xmax);
+ snprintf(values[Atnum_xids], NCHARS, "{" XID_FMT "}", xmax);
values[Atnum_modes] = palloc(NCHARS);
if (infomask & HEAP_XMAX_LOCK_ONLY)
diff --git a/contrib/pgstattuple/pgstatapprox.c b/contrib/pgstattuple/pgstatapprox.c
index 15ddc322392..6d9e481013d 100644
--- a/contrib/pgstattuple/pgstatapprox.c
+++ b/contrib/pgstattuple/pgstatapprox.c
@@ -153,6 +153,7 @@ statapprox_heap(Relation rel, output_type *stat)
tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid);
tuple.t_len = ItemIdGetLength(itemid);
tuple.t_tableOid = RelationGetRelid(rel);
+ HeapTupleCopyBaseFromPage(&tuple, page);
/*
* We follow VACUUM's lead in counting INSERT_IN_PROGRESS tuples
diff --git a/contrib/pgstattuple/pgstatindex.c b/contrib/pgstattuple/pgstatindex.c
index 6c4b053dd07..7ed7f1381a2 100644
--- a/contrib/pgstattuple/pgstatindex.c
+++ b/contrib/pgstattuple/pgstatindex.c
@@ -605,7 +605,7 @@ pgstathashindex(PG_FUNCTION_ARGS)
metabuf = _hash_getbuf(rel, HASH_METAPAGE, HASH_READ, LH_META_PAGE);
metap = HashPageGetMeta(BufferGetPage(metabuf));
stats.version = metap->hashm_version;
- stats.space_per_page = metap->hashm_bsize;
+ stats.space_per_page = BLCKSZ - SizeOfPageHeaderData - MAXALIGN(sizeof(HashPageOpaqueData));
_hash_relbuf(rel, metabuf);
/* Get the current relation length */
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 7d6f7d9e3df..1ac9b34577d 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -4447,16 +4447,24 @@ UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3;
UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3;
EXPLAIN (verbose, costs off)
-UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; -- can be pushed down
- QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------
- Update on public.ft2
- Output: c1, c2, c3, c4, c5, c6, c7, c8
- -> Foreign Update on public.ft2
- Remote SQL: UPDATE "S 1"."T 1" SET c2 = (c2 + 400), c3 = (c3 || '_update7') WHERE ((("C 1" % 10) = 7)) RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8
-(4 rows)
+WITH t AS (UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *)
+SELECT * FROM t ORDER BY c1; -- can be pushed down
+ QUERY PLAN
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Sort
+ Output: t.c1, t.c2, t.c3, t.c4, t.c5, t.c6, t.c7, t.c8
+ Sort Key: t.c1
+ CTE t
+ -> Update on public.ft2
+ Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8
+ -> Foreign Update on public.ft2
+ Remote SQL: UPDATE "S 1"."T 1" SET c2 = (c2 + 400), c3 = (c3 || '_update7') WHERE ((("C 1" % 10) = 7)) RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8
+ -> CTE Scan on t
+ Output: t.c1, t.c2, t.c3, t.c4, t.c5, t.c6, t.c7, t.c8
+(10 rows)
-UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *;
+WITH t AS (UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *)
+SELECT * FROM t ORDER BY c1;
c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
------+-----+--------------------+------------------------------+--------------------------+----+------------+-----
7 | 407 | 00007_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo
@@ -4576,16 +4584,24 @@ UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;
EXPLAIN (verbose, costs off)
- DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; -- can be pushed down
- QUERY PLAN
---------------------------------------------------------------------------------------------
- Delete on public.ft2
- Output: c1, c4
- -> Foreign Delete on public.ft2
- Remote SQL: DELETE FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 5)) RETURNING "C 1", c4
-(4 rows)
+ WITH t AS (DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4)
+ SELECT * FROM t ORDER BY c1; -- can be pushed down
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------
+ Sort
+ Output: t.c1, t.c4
+ Sort Key: t.c1
+ CTE t
+ -> Delete on public.ft2
+ Output: ft2.c1, ft2.c4
+ -> Foreign Delete on public.ft2
+ Remote SQL: DELETE FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 5)) RETURNING "C 1", c4
+ -> CTE Scan on t
+ Output: t.c1, t.c4
+(10 rows)
-DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;
+WITH t AS (DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4)
+SELECT * FROM t ORDER BY c1;
c1 | c4
------+------------------------------
5 | Tue Jan 06 00:00:00 1970 PST
@@ -5846,7 +5862,8 @@ INSERT INTO ft2 (c1,c2,c3,c6) VALUES (1218, 818, 'ggg', '(--;') RETURNING *;
1218 | 818 | ggg_trig_update | | | (--; | ft2 |
(1 row)
-UPDATE ft2 SET c2 = c2 + 600 WHERE c1 % 10 = 8 AND c1 < 1200 RETURNING *;
+WITH t AS (UPDATE ft2 SET c2 = c2 + 600 WHERE c1 % 10 = 8 AND c1 < 1200 RETURNING *)
+SELECT * FROM t ORDER BY c1;
c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
------+-----+------------------------+------------------------------+--------------------------+----+------------+-----
8 | 608 | 00008_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index bf3f3d9e26e..cd81fc12c3d 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -4791,8 +4791,8 @@ apply_returning_filter(PgFdwDirectModifyState *dmstate,
* Note: no need to care about tableoid here because it will be
* initialized in ExecProcessReturning().
*/
- HeapTupleHeaderSetXmin(resultTup->t_data, InvalidTransactionId);
- HeapTupleHeaderSetXmax(resultTup->t_data, InvalidTransactionId);
+ HeapTupleSetXmin(resultTup, InvalidTransactionId);
+ HeapTupleSetXmax(resultTup, InvalidTransactionId);
HeapTupleHeaderSetCmin(resultTup->t_data, InvalidTransactionId);
}
@@ -7251,6 +7251,7 @@ make_tuple_from_result_row(PGresult *res,
*/
if (ctid)
tuple->t_self = tuple->t_data->t_ctid = *ctid;
+ HeapTupleSetZeroBase(tuple);
/*
* Stomp on the xmin, xmax, and cmin fields from the tuple created by
@@ -7260,8 +7261,8 @@ make_tuple_from_result_row(PGresult *res,
* assumption. If we don't do this then, for example, the tuple length
* ends up in the xmin field, which isn't what we want.
*/
- HeapTupleHeaderSetXmax(tuple->t_data, InvalidTransactionId);
- HeapTupleHeaderSetXmin(tuple->t_data, InvalidTransactionId);
+ HeapTupleSetXmax(tuple, InvalidTransactionId);
+ HeapTupleSetXmin(tuple, InvalidTransactionId);
HeapTupleHeaderSetCmin(tuple->t_data, InvalidTransactionId);
/* Clean up */
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 9eb673e3693..d610760f35a 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -1243,16 +1243,20 @@ EXPLAIN (verbose, costs off)
UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3; -- can be pushed down
UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3;
EXPLAIN (verbose, costs off)
-UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; -- can be pushed down
-UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *;
+WITH t AS (UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *)
+SELECT * FROM t ORDER BY c1; -- can be pushed down
+WITH t AS (UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *)
+SELECT * FROM t ORDER BY c1;
EXPLAIN (verbose, costs off)
UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; -- can be pushed down
UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;
EXPLAIN (verbose, costs off)
- DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; -- can be pushed down
-DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;
+ WITH t AS (DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4)
+ SELECT * FROM t ORDER BY c1; -- can be pushed down
+WITH t AS (DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4)
+SELECT * FROM t ORDER BY c1;
EXPLAIN (verbose, costs off)
DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; -- can be pushed down
DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
@@ -1359,7 +1363,8 @@ CREATE TRIGGER t1_br_insert BEFORE INSERT OR UPDATE
INSERT INTO ft2 (c1,c2,c3) VALUES (1208, 818, 'fff') RETURNING *;
INSERT INTO ft2 (c1,c2,c3,c6) VALUES (1218, 818, 'ggg', '(--;') RETURNING *;
-UPDATE ft2 SET c2 = c2 + 600 WHERE c1 % 10 = 8 AND c1 < 1200 RETURNING *;
+WITH t AS (UPDATE ft2 SET c2 = c2 + 600 WHERE c1 % 10 = 8 AND c1 < 1200 RETURNING *)
+SELECT * FROM t ORDER BY c1;
-- Test errors thrown on remote side during update
ALTER TABLE "S 1"."T 1" ADD CONSTRAINT c2positive CHECK (c2 >= 0);
diff --git a/contrib/test_decoding/test_decoding.c b/contrib/test_decoding/test_decoding.c
index 42fe91a2f9c..42d83f72c54 100644
--- a/contrib/test_decoding/test_decoding.c
+++ b/contrib/test_decoding/test_decoding.c
@@ -310,7 +310,7 @@ pg_output_begin(LogicalDecodingContext *ctx, TestDecodingData *data, ReorderBuff
{
OutputPluginPrepareWrite(ctx, last_write);
if (data->include_xids)
- appendStringInfo(ctx->out, "BEGIN %u", txn->xid);
+ appendStringInfo(ctx->out, "BEGIN " XID_FMT, txn->xid);
else
appendStringInfoString(ctx->out, "BEGIN");
OutputPluginWrite(ctx, last_write);
@@ -333,7 +333,7 @@ pg_decode_commit_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
OutputPluginPrepareWrite(ctx, true);
if (data->include_xids)
- appendStringInfo(ctx->out, "COMMIT %u", txn->xid);
+ appendStringInfo(ctx->out, "COMMIT " XID_FMT, txn->xid);
else
appendStringInfoString(ctx->out, "COMMIT");
@@ -378,7 +378,7 @@ pg_decode_prepare_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
quote_literal_cstr(txn->gid));
if (data->include_xids)
- appendStringInfo(ctx->out, ", txid %u", txn->xid);
+ appendStringInfo(ctx->out, ", txid " XID_FMT, txn->xid);
if (data->include_timestamp)
appendStringInfo(ctx->out, " (at %s)",
@@ -400,7 +400,7 @@ pg_decode_commit_prepared_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn
quote_literal_cstr(txn->gid));
if (data->include_xids)
- appendStringInfo(ctx->out, ", txid %u", txn->xid);
+ appendStringInfo(ctx->out, ", txid " XID_FMT, txn->xid);
if (data->include_timestamp)
appendStringInfo(ctx->out, " (at %s)",
@@ -424,7 +424,7 @@ pg_decode_rollback_prepared_txn(LogicalDecodingContext *ctx,
quote_literal_cstr(txn->gid));
if (data->include_xids)
- appendStringInfo(ctx->out, ", txid %u", txn->xid);
+ appendStringInfo(ctx->out, ", txid " XID_FMT, txn->xid);
if (data->include_timestamp)
appendStringInfo(ctx->out, " (at %s)",
@@ -773,7 +773,7 @@ pg_output_stream_start(LogicalDecodingContext *ctx, TestDecodingData *data, Reor
{
OutputPluginPrepareWrite(ctx, last_write);
if (data->include_xids)
- appendStringInfo(ctx->out, "opening a streamed block for transaction TXN %u", txn->xid);
+ appendStringInfo(ctx->out, "opening a streamed block for transaction TXN " XID_FMT, txn->xid);
else
appendStringInfoString(ctx->out, "opening a streamed block for transaction");
OutputPluginWrite(ctx, last_write);
@@ -791,7 +791,7 @@ pg_decode_stream_stop(LogicalDecodingContext *ctx,
OutputPluginPrepareWrite(ctx, true);
if (data->include_xids)
- appendStringInfo(ctx->out, "closing a streamed block for transaction TXN %u", txn->xid);
+ appendStringInfo(ctx->out, "closing a streamed block for transaction TXN " XID_FMT, txn->xid);
else
appendStringInfoString(ctx->out, "closing a streamed block for transaction");
OutputPluginWrite(ctx, true);
@@ -825,7 +825,7 @@ pg_decode_stream_abort(LogicalDecodingContext *ctx,
OutputPluginPrepareWrite(ctx, true);
if (data->include_xids)
- appendStringInfo(ctx->out, "aborting streamed (sub)transaction TXN %u", txn->xid);
+ appendStringInfo(ctx->out, "aborting streamed (sub)transaction TXN " XID_FMT, txn->xid);
else
appendStringInfoString(ctx->out, "aborting streamed (sub)transaction");
OutputPluginWrite(ctx, true);
@@ -845,7 +845,7 @@ pg_decode_stream_prepare(LogicalDecodingContext *ctx,
OutputPluginPrepareWrite(ctx, true);
if (data->include_xids)
- appendStringInfo(ctx->out, "preparing streamed transaction TXN %s, txid %u",
+ appendStringInfo(ctx->out, "preparing streamed transaction TXN %s, txid " XID_FMT,
quote_literal_cstr(txn->gid), txn->xid);
else
appendStringInfo(ctx->out, "preparing streamed transaction %s",
@@ -876,7 +876,7 @@ pg_decode_stream_commit(LogicalDecodingContext *ctx,
OutputPluginPrepareWrite(ctx, true);
if (data->include_xids)
- appendStringInfo(ctx->out, "committing streamed transaction TXN %u", txn->xid);
+ appendStringInfo(ctx->out, "committing streamed transaction TXN " XID_FMT, txn->xid);
else
appendStringInfoString(ctx->out, "committing streamed transaction");
@@ -910,7 +910,7 @@ pg_decode_stream_change(LogicalDecodingContext *ctx,
OutputPluginPrepareWrite(ctx, true);
if (data->include_xids)
- appendStringInfo(ctx->out, "streaming change for TXN %u", txn->xid);
+ appendStringInfo(ctx->out, "streaming change for TXN " XID_FMT, txn->xid);
else
appendStringInfoString(ctx->out, "streaming change for transaction");
OutputPluginWrite(ctx, true);
@@ -963,7 +963,7 @@ pg_decode_stream_truncate(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
OutputPluginPrepareWrite(ctx, true);
if (data->include_xids)
- appendStringInfo(ctx->out, "streaming truncate for TXN %u", txn->xid);
+ appendStringInfo(ctx->out, "streaming truncate for TXN " XID_FMT, txn->xid);
else
appendStringInfoString(ctx->out, "streaming truncate for transaction");
OutputPluginWrite(ctx, true);
diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 503cda46eff..a9d013d9fa5 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -640,10 +640,10 @@ heap_getsysattr(HeapTuple tup, int attnum, TupleDesc tupleDesc, bool *isnull)
result = PointerGetDatum(&(tup->t_self));
break;
case MinTransactionIdAttributeNumber:
- result = TransactionIdGetDatum(HeapTupleHeaderGetRawXmin(tup->t_data));
+ result = TransactionIdGetDatum(HeapTupleGetRawXmin(tup));
break;
case MaxTransactionIdAttributeNumber:
- result = TransactionIdGetDatum(HeapTupleHeaderGetRawXmax(tup->t_data));
+ result = TransactionIdGetDatum(HeapTupleGetRawXmax(tup));
break;
case MinCommandIdAttributeNumber:
case MaxCommandIdAttributeNumber:
@@ -688,6 +688,7 @@ heap_copytuple(HeapTuple tuple)
newTuple->t_len = tuple->t_len;
newTuple->t_self = tuple->t_self;
newTuple->t_tableOid = tuple->t_tableOid;
+ HeapTupleCopyBase(newTuple, tuple);
newTuple->t_data = (HeapTupleHeader) ((char *) newTuple + HEAPTUPLESIZE);
memcpy((char *) newTuple->t_data, (char *) tuple->t_data, tuple->t_len);
return newTuple;
@@ -714,6 +715,7 @@ heap_copytuple_with_tuple(HeapTuple src, HeapTuple dest)
dest->t_len = src->t_len;
dest->t_self = src->t_self;
dest->t_tableOid = src->t_tableOid;
+ HeapTupleCopyBase(dest, src);
dest->t_data = (HeapTupleHeader) palloc(src->t_len);
memcpy((char *) dest->t_data, (char *) src->t_data, src->t_len);
}
@@ -1077,6 +1079,7 @@ heap_form_tuple(TupleDesc tupleDescriptor,
tuple->t_len = len;
ItemPointerSetInvalid(&(tuple->t_self));
tuple->t_tableOid = InvalidOid;
+ HeapTupleSetZeroBase(tuple);
HeapTupleHeaderSetDatumLength(td, len);
HeapTupleHeaderSetTypeId(td, tupleDescriptor->tdtypeid);
@@ -1161,6 +1164,7 @@ heap_modify_tuple(HeapTuple tuple,
newTuple->t_data->t_ctid = tuple->t_data->t_ctid;
newTuple->t_self = tuple->t_self;
newTuple->t_tableOid = tuple->t_tableOid;
+ HeapTupleCopyBase(newTuple, tuple);
return newTuple;
}
@@ -1224,6 +1228,7 @@ heap_modify_tuple_by_cols(HeapTuple tuple,
newTuple->t_data->t_ctid = tuple->t_data->t_ctid;
newTuple->t_self = tuple->t_self;
newTuple->t_tableOid = tuple->t_tableOid;
+ HeapTupleCopyBase(newTuple, tuple);
return newTuple;
}
@@ -1464,6 +1469,7 @@ heap_tuple_from_minimal_tuple(MinimalTuple mtup)
result->t_len = len;
ItemPointerSetInvalid(&(result->t_self));
result->t_tableOid = InvalidOid;
+ HeapTupleSetZeroBase(result);
result->t_data = (HeapTupleHeader) ((char *) result + HEAPTUPLESIZE);
memcpy((char *) result->t_data + MINIMAL_TUPLE_OFFSET, mtup, mtup->t_len);
memset(result->t_data, 0, offsetof(HeapTupleHeaderData, t_infomask2));
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index e7062d4da30..4e8553b4033 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -251,58 +251,6 @@ static relopt_int intRelOpts[] =
},
-1, 1, 10000
},
- {
- {
- "autovacuum_freeze_min_age",
- "Minimum age at which VACUUM should freeze a table row, for autovacuum",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- -1, 0, 1000000000
- },
- {
- {
- "autovacuum_multixact_freeze_min_age",
- "Minimum multixact age at which VACUUM should freeze a row multixact's, for autovacuum",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- -1, 0, 1000000000
- },
- {
- {
- "autovacuum_freeze_max_age",
- "Age at which to autovacuum a table to prevent transaction ID wraparound",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- -1, 100000, 2000000000
- },
- {
- {
- "autovacuum_multixact_freeze_max_age",
- "Multixact age at which to autovacuum a table to prevent multixact wraparound",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- -1, 10000, 2000000000
- },
- {
- {
- "autovacuum_freeze_table_age",
- "Age at which VACUUM should perform a full table sweep to freeze row versions",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- }, -1, 0, 2000000000
- },
- {
- {
- "autovacuum_multixact_freeze_table_age",
- "Age of multixact at which VACUUM should perform a full table sweep to freeze row versions",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- }, -1, 0, 2000000000
- },
{
{
"log_autovacuum_min_duration",
@@ -379,6 +327,60 @@ static relopt_int intRelOpts[] =
static relopt_int64 int64RelOpts[] =
{
+ {
+ {
+ "autovacuum_freeze_min_age",
+ "Minimum age at which VACUUM should freeze a table row, for autovacuum",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ INT64CONST(-1), INT64CONST(0), INT64CONST(1000000000)
+ },
+ {
+ {
+ "autovacuum_multixact_freeze_min_age",
+ "Minimum multixact age at which VACUUM should freeze a row multixact's, for autovacuum",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ INT64CONST(-1), INT64CONST(0), INT64CONST(1000000000)
+ },
+ {
+ {
+ "autovacuum_freeze_max_age",
+ "Age at which to autovacuum a table to prevent transaction ID wraparound",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ INT64CONST(-1), INT64CONST(100000), INT64CONST(2000000000)
+ },
+ {
+ {
+ "autovacuum_multixact_freeze_max_age",
+ "Multixact age at which to autovacuum a table to prevent multixact wraparound",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ INT64CONST(-1), INT64CONST(10000), INT64CONST(2000000000)
+ },
+ {
+ {
+ "autovacuum_freeze_table_age",
+ "Age at which VACUUM should perform a full table sweep to freeze row versions",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ INT64CONST(-1), INT64CONST(0), INT64CONST(2000000000)
+ },
+ {
+ {
+ "autovacuum_multixact_freeze_table_age",
+ "Age of multixact at which VACUUM should perform a full table sweep to freeze row versions",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ INT64CONST(-1), INT64CONST(0), INT64CONST(2000000000)
+ },
/* list terminator */
{{NULL}}
};
@@ -1912,17 +1914,17 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, analyze_threshold)},
{"autovacuum_vacuum_cost_limit", RELOPT_TYPE_INT,
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_cost_limit)},
- {"autovacuum_freeze_min_age", RELOPT_TYPE_INT,
+ {"autovacuum_freeze_min_age", RELOPT_TYPE_INT64,
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, freeze_min_age)},
- {"autovacuum_freeze_max_age", RELOPT_TYPE_INT,
+ {"autovacuum_freeze_max_age", RELOPT_TYPE_INT64,
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, freeze_max_age)},
- {"autovacuum_freeze_table_age", RELOPT_TYPE_INT,
+ {"autovacuum_freeze_table_age", RELOPT_TYPE_INT64,
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, freeze_table_age)},
- {"autovacuum_multixact_freeze_min_age", RELOPT_TYPE_INT,
+ {"autovacuum_multixact_freeze_min_age", RELOPT_TYPE_INT64,
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, multixact_freeze_min_age)},
- {"autovacuum_multixact_freeze_max_age", RELOPT_TYPE_INT,
+ {"autovacuum_multixact_freeze_max_age", RELOPT_TYPE_INT64,
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, multixact_freeze_max_age)},
- {"autovacuum_multixact_freeze_table_age", RELOPT_TYPE_INT,
+ {"autovacuum_multixact_freeze_table_age", RELOPT_TYPE_INT64,
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, multixact_freeze_table_age)},
{"log_autovacuum_min_duration", RELOPT_TYPE_INT,
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, log_min_duration)},
diff --git a/src/backend/access/hash/hashvalidate.c b/src/backend/access/hash/hashvalidate.c
index 10bf26ce7c0..83e033b93cc 100644
--- a/src/backend/access/hash/hashvalidate.c
+++ b/src/backend/access/hash/hashvalidate.c
@@ -317,11 +317,10 @@ check_hash_func_signature(Oid funcid, int16 amprocnum, Oid argtype)
* INTERNAL and allowing any such function seems too scary.
*/
if ((funcid == F_HASHINT4 || funcid == F_HASHINT4EXTENDED) &&
- (argtype == DATEOID ||
- argtype == XIDOID || argtype == CIDOID))
+ (argtype == DATEOID || argtype == CIDOID))
/* okay, allowed use of hashint4() */ ;
else if ((funcid == F_HASHINT8 || funcid == F_HASHINT8EXTENDED) &&
- (argtype == XID8OID))
+ (argtype == XID8OID || argtype == XIDOID))
/* okay, allowed use of hashint8() */ ;
else if ((funcid == F_TIMESTAMP_HASH ||
funcid == F_TIMESTAMP_HASH_EXTENDED) &&
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 6ec57f3d8b2..593ccba367c 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -52,6 +52,9 @@
#include "access/xloginsert.h"
#include "access/xlogutils.h"
#include "catalog/catalog.h"
+#include "catalog/index.h"
+#include "catalog/namespace.h"
+#include "commands/vacuum.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "port/atomics.h"
@@ -73,7 +76,7 @@
static HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup,
- TransactionId xid, CommandId cid, int options);
+ CommandId cid, int options);
static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
Buffer newbuf, HeapTuple oldtup,
HeapTuple newtup, HeapTuple old_key_tuple,
@@ -214,6 +217,20 @@ static const int MultiXactStatusLock[MaxMultiXactStatus + 1] =
#define TUPLOCK_from_mxstatus(status) \
(MultiXactStatusLock[(status)])
+ShortTransactionId
+HeapPageSetPruneXidInternal(Page page, TransactionId xid)
+{
+ if (TransactionIdIsNormal(xid) &&
+ /* abuse unsigned underflow */
+ xid - HeapPageGetSpecial(page)->pd_xid_base - FirstNormalTransactionId >
+ MaxShortTransactionId)
+ elog(WARNING, "pd_prune_xid is out of range");
+ ((PageHeader) (page))->pd_prune_xid =
+ NormalTransactionIdToShort(HeapPageGetSpecial(page)->pd_xid_base, xid);
+
+ return ((PageHeader) (page))->pd_prune_xid;
+}
+
/* ----------------------------------------------------------------
* heap support routines
* ----------------------------------------------------------------
@@ -458,6 +475,7 @@ heapgetpage(TableScanDesc sscan, BlockNumber page)
loctup.t_tableOid = RelationGetRelid(scan->rs_base.rs_rd);
loctup.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
loctup.t_len = ItemIdGetLength(lpp);
+ HeapTupleCopyBaseFromPage(&loctup, dp);
ItemPointerSet(&(loctup.t_self), page, lineoff);
if (all_visible)
@@ -674,6 +692,7 @@ heapgettup(HeapScanDesc scan,
tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
tuple->t_len = ItemIdGetLength(lpp);
+ HeapTupleCopyBaseFromPage(tuple, dp);
return;
}
@@ -700,6 +719,7 @@ heapgettup(HeapScanDesc scan,
tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
tuple->t_len = ItemIdGetLength(lpp);
+ HeapTupleCopyBaseFromPage(tuple, dp);
ItemPointerSet(&(tuple->t_self), page, lineoff);
/*
@@ -999,6 +1019,7 @@ heapgettup_pagemode(HeapScanDesc scan,
tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
tuple->t_len = ItemIdGetLength(lpp);
+ HeapTupleCopyBaseFromPage(tuple, dp);
/* check that rs_cindex is in sync */
Assert(scan->rs_cindex < scan->rs_ntuples);
@@ -1021,6 +1042,7 @@ heapgettup_pagemode(HeapScanDesc scan,
tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
tuple->t_len = ItemIdGetLength(lpp);
+ HeapTupleCopyBaseFromPage(tuple, dp);
ItemPointerSet(&(tuple->t_self), page, lineoff);
/*
@@ -1653,6 +1675,7 @@ heap_fetch(Relation relation,
tuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
tuple->t_len = ItemIdGetLength(lp);
tuple->t_tableOid = RelationGetRelid(relation);
+ HeapTupleCopyBaseFromPage(tuple, page);
/*
* check tuple visibility, then release lock
@@ -1661,7 +1684,7 @@ heap_fetch(Relation relation,
if (valid)
PredicateLockTID(relation, &(tuple->t_self), snapshot,
- HeapTupleHeaderGetXmin(tuple->t_data));
+ HeapTupleGetXmin(tuple));
HeapCheckForSerializableConflictOut(valid, relation, tuple, buffer, snapshot);
@@ -1733,6 +1756,9 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
Assert(TransactionIdIsValid(RecentXmin));
Assert(BufferGetBlockNumber(buffer) == blkno);
+ heapTuple->t_self = *tid;
+ HeapTupleCopyBaseFromPage(heapTuple, dp);
+
/* Scan through possible multiple members of HOT-chain */
for (;;)
{
@@ -1768,6 +1794,7 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
heapTuple->t_data = (HeapTupleHeader) PageGetItem(dp, lp);
heapTuple->t_len = ItemIdGetLength(lp);
heapTuple->t_tableOid = RelationGetRelid(relation);
+ HeapTupleCopyBaseFromPage(heapTuple, dp);
ItemPointerSet(&heapTuple->t_self, blkno, offnum);
/*
@@ -1782,7 +1809,7 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
*/
if (TransactionIdIsValid(prev_xmax) &&
!TransactionIdEquals(prev_xmax,
- HeapTupleHeaderGetXmin(heapTuple->t_data)))
+ HeapTupleGetXmin(heapTuple)))
break;
/*
@@ -1803,7 +1830,7 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
{
ItemPointerSetOffsetNumber(tid, offnum);
PredicateLockTID(relation, &heapTuple->t_self, snapshot,
- HeapTupleHeaderGetXmin(heapTuple->t_data));
+ HeapTupleGetXmin(heapTuple));
if (all_dead)
*all_dead = false;
return true;
@@ -1838,7 +1865,7 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
blkno);
offnum = ItemPointerGetOffsetNumber(&heapTuple->t_data->t_ctid);
at_chain_start = false;
- prev_xmax = HeapTupleHeaderGetUpdateXid(heapTuple->t_data);
+ prev_xmax = HeapTupleGetUpdateXidAny(heapTuple);
}
else
break; /* end of chain */
@@ -1925,13 +1952,14 @@ heap_get_latest_tid(TableScanDesc sscan,
tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
tp.t_len = ItemIdGetLength(lp);
tp.t_tableOid = RelationGetRelid(relation);
+ HeapTupleCopyBaseFromPage(&tp, page);
/*
* After following a t_ctid link, we might arrive at an unrelated
* tuple. Check for XMIN match.
*/
if (TransactionIdIsValid(priorXmax) &&
- !TransactionIdEquals(priorXmax, HeapTupleHeaderGetXmin(tp.t_data)))
+ !TransactionIdEquals(priorXmax, HeapTupleGetXmin(&tp)))
{
UnlockReleaseBuffer(buffer);
break;
@@ -1950,7 +1978,7 @@ heap_get_latest_tid(TableScanDesc sscan,
* If there's a valid t_ctid link, follow it, else we're done.
*/
if ((tp.t_data->t_infomask & HEAP_XMAX_INVALID) ||
- HeapTupleHeaderIsOnlyLocked(tp.t_data) ||
+ HeapTupleIsOnlyLocked(&tp) ||
HeapTupleHeaderIndicatesMovedPartitions(tp.t_data) ||
ItemPointerEquals(&tp.t_self, &tp.t_data->t_ctid))
{
@@ -1959,7 +1987,7 @@ heap_get_latest_tid(TableScanDesc sscan,
}
ctid = tp.t_data->t_ctid;
- priorXmax = HeapTupleHeaderGetUpdateXid(tp.t_data);
+ priorXmax = HeapTupleGetUpdateXidAny(&tp);
UnlockReleaseBuffer(buffer);
} /* end of loop */
}
@@ -1984,7 +2012,7 @@ heap_get_latest_tid(TableScanDesc sscan,
static void
UpdateXmaxHintBits(HeapTupleHeader tuple, Buffer buffer, TransactionId xid)
{
- Assert(TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple), xid));
+ Assert(TransactionIdEquals(HeapTupleHeaderGetRawXmax(BufferGetPage(buffer), tuple), xid));
Assert(!(tuple->t_infomask & HEAP_XMAX_IS_MULTI));
if (!(tuple->t_infomask & (HEAP_XMAX_COMMITTED | HEAP_XMAX_INVALID)))
@@ -2076,7 +2104,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
* Note: below this point, heaptup is the data we actually intend to store
* into the relation; tup is the caller's original untoasted data.
*/
- heaptup = heap_prepare_insert(relation, tup, xid, cid, options);
+ heaptup = heap_prepare_insert(relation, tup, cid, options);
/*
* Find buffer to insert this tuple into. If the page is all visible,
@@ -2103,6 +2131,10 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
*/
CheckForSerializableConflictIn(relation, NULL, InvalidBlockNumber);
+ heap_page_prepare_for_xid(relation, buffer, xid, false);
+ HeapTupleCopyBaseFromPage(heaptup, BufferGetPage(buffer));
+ HeapTupleSetXmin(heaptup, xid);
+
/* NO EREPORT(ERROR) from here till changes are logged */
START_CRIT_SECTION();
@@ -2184,6 +2216,10 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
}
XLogBeginInsert();
+ if (info & XLOG_HEAP_INIT_PAGE)
+ {
+ XLogRegisterData((char *) &HeapPageGetSpecial(page)->pd_xid_base, sizeof(TransactionId));
+ }
XLogRegisterData((char *) &xlrec, SizeOfHeapInsert);
xlhdr.t_infomask2 = heaptup->t_data->t_infomask2;
@@ -2238,6 +2274,544 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
}
}
+/*
+ * Find minimum and maximum short transaction ids which occurs in the page.
+ */
+static bool
+heap_page_xid_min_max(Page page, bool multi,
+ ShortTransactionId *min,
+ ShortTransactionId *max)
+{
+ TransactionId xid_base = HeapPageGetSpecial(page)->pd_xid_base;
+ bool found = false;
+ OffsetNumber offnum,
+ maxoff;
+
+ maxoff = PageGetMaxOffsetNumber(page);
+
+ for (offnum = FirstOffsetNumber;
+ offnum <= maxoff;
+ offnum = OffsetNumberNext(offnum))
+ {
+ ItemId itemid;
+ HeapTupleHeader htup;
+
+ itemid = PageGetItemId(page, offnum);
+
+ if (!ItemIdIsNormal(itemid))
+ continue;
+
+ htup = (HeapTupleHeader) PageGetItem(page, itemid);
+
+ if (!multi)
+ {
+ if (!HeapTupleHeaderXminFrozen(htup) &&
+ TransactionIdIsNormal(htup->t_choice.t_heap.t_xmin))
+ {
+ Assert(htup->t_choice.t_heap.t_xmin >= FirstNormalTransactionId);
+ Assert(htup->t_choice.t_heap.t_xmin <= MaxShortTransactionId);
+
+ if (!found)
+ {
+ *min = *max = htup->t_choice.t_heap.t_xmin;
+ found = true;
+ }
+ else
+ {
+ *min = Min(*min, htup->t_choice.t_heap.t_xmin);
+ *max = Max(*max, htup->t_choice.t_heap.t_xmin);
+ }
+ }
+
+ if (htup->t_infomask & HEAP_XMAX_IS_MULTI)
+ {
+ if (!(htup->t_infomask & (HEAP_XMAX_INVALID | HEAP_XMAX_LOCK_ONLY)))
+ {
+ TransactionId update_xid =
+ MultiXactIdGetUpdateXid(HeapTupleHeaderGetRawXmax(page, htup),
+ htup->t_infomask);
+ ShortTransactionId xid = NormalTransactionIdToShort(xid_base, update_xid);
+
+ Assert(xid >= FirstNormalTransactionId);
+ Assert(xid <= MaxShortTransactionId);
+
+ if (!found)
+ {
+ *min = *max = xid;
+ found = true;
+ }
+ else
+ {
+ *min = Min(*min, xid);
+ *max = Max(*max, xid);
+ }
+ }
+ }
+ else if (!(htup->t_infomask & HEAP_XMAX_INVALID) &&
+ TransactionIdIsNormal(htup->t_choice.t_heap.t_xmax))
+ {
+ Assert(htup->t_choice.t_heap.t_xmax >= FirstNormalTransactionId);
+ Assert(htup->t_choice.t_heap.t_xmax <= MaxShortTransactionId);
+
+ if (!found)
+ {
+ *min = *max = htup->t_choice.t_heap.t_xmax;
+ found = true;
+ }
+ else
+ {
+ *min = Min(*min, htup->t_choice.t_heap.t_xmax);
+ *max = Max(*max, htup->t_choice.t_heap.t_xmax);
+ }
+ }
+ }
+ else
+ {
+ if (TransactionIdIsNormal(htup->t_choice.t_heap.t_xmax) &&
+ (htup->t_infomask & HEAP_XMAX_IS_MULTI))
+ {
+ Assert(htup->t_choice.t_heap.t_xmax >= FirstNormalTransactionId);
+ Assert(htup->t_choice.t_heap.t_xmax <= MaxShortTransactionId);
+
+ if (!found)
+ {
+ *min = *max = htup->t_choice.t_heap.t_xmax;
+ found = true;
+ }
+ else
+ {
+ *min = Min(*min, htup->t_choice.t_heap.t_xmax);
+ *max = Max(*max, htup->t_choice.t_heap.t_xmax);
+ }
+ }
+ }
+ }
+ return found;
+}
+
+/*
+ * Shift xid base in the page. WAL-logged if buffer is specified.
+ */
+static void
+heap_page_shift_base(Relation relation, Buffer buffer, Page page, bool multi, int64 delta)
+{
+ HeapPageSpecial pageSpecial = HeapPageGetSpecial(page);
+ OffsetNumber offnum,
+ maxoff;
+
+ START_CRIT_SECTION();
+
+ /* Iterate over page items */
+ maxoff = PageGetMaxOffsetNumber(page);
+ for (offnum = FirstOffsetNumber;
+ offnum <= maxoff;
+ offnum = OffsetNumberNext(offnum))
+ {
+ ItemId itemid;
+ HeapTupleHeader htup;
+
+ itemid = PageGetItemId(page, offnum);
+
+ if (!ItemIdIsNormal(itemid))
+ continue;
+
+ htup = (HeapTupleHeader) PageGetItem(page, itemid);
+
+ /* Apply xid shift to heap tuple */
+ if (!multi)
+ {
+ if (!HeapTupleHeaderXminFrozen(htup) &&
+ TransactionIdIsNormal(htup->t_choice.t_heap.t_xmin))
+ {
+ Assert(htup->t_choice.t_heap.t_xmin - delta >= FirstNormalTransactionId);
+ Assert(htup->t_choice.t_heap.t_xmin - delta <= MaxShortTransactionId);
+ htup->t_choice.t_heap.t_xmin -= delta;
+ }
+
+ if (TransactionIdIsNormal(htup->t_choice.t_heap.t_xmax) &&
+ !(htup->t_infomask & HEAP_XMAX_IS_MULTI))
+ {
+ Assert(htup->t_choice.t_heap.t_xmax - delta >= FirstNormalTransactionId);
+ Assert(htup->t_choice.t_heap.t_xmax - delta <= MaxShortTransactionId);
+ htup->t_choice.t_heap.t_xmax -= delta;
+ }
+ }
+ else
+ {
+ if (TransactionIdIsNormal(htup->t_choice.t_heap.t_xmax) &&
+ (htup->t_infomask & HEAP_XMAX_IS_MULTI))
+ {
+ Assert(htup->t_choice.t_heap.t_xmax - delta >= FirstNormalTransactionId);
+ Assert(htup->t_choice.t_heap.t_xmax - delta <= MaxShortTransactionId);
+ htup->t_choice.t_heap.t_xmax -= delta;
+ }
+ }
+ }
+
+ /* Apply xid shift to base as well */
+ if (!multi)
+ pageSpecial->pd_xid_base += delta;
+ else
+ pageSpecial->pd_multi_base += delta;
+
+ if (BufferIsValid(buffer))
+ MarkBufferDirty(buffer);
+
+ /* Write WAL record if needed */
+ if (relation && RelationNeedsWAL(relation))
+ {
+ XLogRecPtr recptr;
+ xl_heap_base_shift xlrec;
+
+ xlrec.multi = multi;
+ xlrec.delta = delta;
+
+ XLogBeginInsert();
+ XLogRegisterData((char *) &xlrec, SizeOfHeapBaseShift);
+
+ XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
+
+ recptr = XLogInsert(RM_HEAP3_ID, XLOG_HEAP3_BASE_SHIFT);
+
+ PageSetLSN(page, recptr);
+ }
+
+ END_CRIT_SECTION();
+}
+
+/*
+ * Freeze xids in the single heap page. Useful when we can't fit new xid even
+ * with base shift.
+ */
+static void
+freeze_single_heap_page(Relation relation, Buffer buffer)
+{
+ Page page = BufferGetPage(buffer);
+ OffsetNumber offnum,
+ maxoff;
+ HeapTupleData tuple;
+ int nfrozen = 0;
+ xl_heap_freeze_tuple *frozen;
+ TransactionId OldestXmin,
+ FreezeXid;
+ MultiXactId MultiXactCutoff;
+ GlobalVisState *vistest;
+
+ vacuum_set_xid_limits(relation, 0, 0, 0, 0,
+ &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
+ NULL);
+
+ vistest = GlobalVisTestFor(relation);
+
+ heap_page_prune(relation, buffer, vistest,
+ InvalidTransactionId, 0,
+ false, &offnum, false);
+
+ /*
+ * Now scan the page to collect vacuumable items and check for tuples
+ * requiring freezing.
+ */
+ maxoff = PageGetMaxOffsetNumber(page);
+ frozen = palloc(sizeof(xl_heap_freeze_tuple) * MaxHeapTuplesPerPage);
+
+ /*
+ * Note: If you change anything in the loop below, also look at
+ * heap_page_is_all_visible to see if that needs to be changed.
+ */
+ for (offnum = FirstOffsetNumber;
+ offnum <= maxoff;
+ offnum = OffsetNumberNext(offnum))
+ {
+ ItemId itemid;
+ bool tuple_totally_frozen;
+
+ itemid = PageGetItemId(page, offnum);
+
+ if (!ItemIdIsNormal(itemid))
+ continue;
+
+ Assert(ItemIdIsNormal(itemid));
+
+ tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid);
+ tuple.t_len = ItemIdGetLength(itemid);
+ tuple.t_tableOid = RelationGetRelid(relation);
+ HeapTupleCopyBaseFromPage(&tuple, page);
+
+ /*
+ * Each non-removable tuple must be checked to see if it needs
+ * freezing. Note we already have exclusive buffer lock.
+ */
+ if (heap_prepare_freeze_tuple(&tuple,
+ relation->rd_rel->relfrozenxid,
+ relation->rd_rel->relminmxid,
+ FreezeXid, MultiXactCutoff,
+ &frozen[nfrozen], &tuple_totally_frozen))
+ frozen[nfrozen++].offset = offnum;
+ } /* scan along page */
+
+ /*
+ * If we froze any tuples, mark the buffer dirty, and write a WAL
+ * record recording the changes. We must log the changes to be
+ * crash-safe against future truncation of CLOG.
+ */
+ if (nfrozen > 0)
+ {
+ int i;
+
+ START_CRIT_SECTION();
+
+ MarkBufferDirty(buffer);
+
+ /* execute collected freezes */
+ for (i = 0; i < nfrozen; i++)
+ {
+ ItemId itemid;
+ HeapTupleHeader htup;
+
+ itemid = PageGetItemId(page, frozen[i].offset);
+ htup = (HeapTupleHeader) PageGetItem(page, itemid);
+
+ heap_execute_freeze_tuple_page(page, htup, &frozen[i]);
+ }
+
+ /* Now WAL-log freezing if necessary */
+ if (RelationNeedsWAL(relation))
+ {
+ XLogRecPtr recptr;
+
+ recptr = log_heap_freeze(relation, buffer, FreezeXid,
+ frozen, nfrozen);
+ PageSetLSN(page, recptr);
+ }
+
+ END_CRIT_SECTION();
+ }
+
+ return;
+}
+
+/*
+ * Ensure that given xid fits base of given page.
+ */
+bool
+heap_page_prepare_for_xid(Relation relation, Buffer buffer,
+ TransactionId xid, bool multi)
+{
+ Page page = BufferGetPage(buffer);
+ HeapPageSpecial pageSpecial = HeapPageGetSpecial(page);
+ TransactionId base;
+ bool found;
+ ShortTransactionId min,
+ max;
+ int i;
+
+ /* "Double xmax" page format doesn't require any preparation */
+ if (HeapPageIsDoubleXmax(page))
+ return false;
+
+ if (!TransactionIdIsNormal(xid))
+ return false;
+
+ for (i = 0; i < 2; i++)
+ {
+ base = multi ? pageSpecial->pd_multi_base : pageSpecial->pd_xid_base;
+
+ /* Can we already store this xid? */
+ if (xid >= base + FirstNormalTransactionId && xid <= base + MaxShortTransactionId)
+ return false;
+
+ /* Find minimum and maximum xids in the page */
+ found = heap_page_xid_min_max(page, multi, &min, &max);
+
+ /* No items on the page? */
+ if (!found)
+ {
+ int64 delta;
+
+ delta = (int64) (xid - FirstNormalTransactionId) - (int64) base;
+
+ if (xid < base + delta + FirstNormalTransactionId ||
+ xid > base + delta + MaxShortTransactionId)
+ {
+ elog(FATAL, "Fatal xid base calculation error: "
+ "xid = " XID_FMT ", "
+ "base = " XID_FMT ", "
+ "min = %u, "
+ "max = %u, "
+ "delta = " INT64_FORMAT,
+ xid, base, min, max, delta
+ );
+ }
+
+ Assert(xid >= base + delta + FirstNormalTransactionId);
+ Assert(xid <= base + delta + MaxShortTransactionId);
+
+ heap_page_shift_base(relation, buffer, page, multi, delta);
+
+ base = multi ? pageSpecial->pd_multi_base : pageSpecial->pd_xid_base;
+ Assert(xid >= base + FirstNormalTransactionId);
+ Assert(xid <= base + MaxShortTransactionId);
+ return false;
+ }
+
+ /* Can we just shift base on the page */
+ if (xid < base + FirstNormalTransactionId)
+ {
+ int64 freeDelta = MaxShortTransactionId - max,
+ requiredDelta = (base + FirstNormalTransactionId) - xid;
+
+ /* Shouldn't consider setting base less than 0 */
+ freeDelta = Min(freeDelta, base);
+
+ if (requiredDelta <= freeDelta)
+ {
+ int64 delta = - (freeDelta + requiredDelta) / 2;
+
+ if (xid < base + delta + FirstNormalTransactionId ||
+ xid > base + delta + MaxShortTransactionId)
+ {
+ elog(FATAL, "Fatal xid base calculation error: "
+ "xid = " XID_FMT ", "
+ "base = " XID_FMT ", "
+ "min = %u, "
+ "max = %u, "
+ "freeDelta = " INT64_FORMAT ", "
+ "requiredDelta = " INT64_FORMAT ", "
+ "delta = " INT64_FORMAT,
+ xid, base, min, max, freeDelta, requiredDelta, delta
+ );
+ }
+
+ Assert(xid >= base + delta + FirstNormalTransactionId);
+ Assert(xid <= base + delta + MaxShortTransactionId);
+
+ heap_page_shift_base(relation, buffer, page, multi, delta);
+
+ base = multi ? pageSpecial->pd_multi_base : pageSpecial->pd_xid_base;
+ Assert(xid >= base + FirstNormalTransactionId);
+ Assert(xid <= base + MaxShortTransactionId);
+ return true;
+ }
+ }
+ else
+ {
+ int64 freeDelta = min - FirstNormalTransactionId,
+ requiredDelta = xid - (base + MaxShortTransactionId);
+
+ Assert(xid > base + MaxShortTransactionId);
+
+ if (requiredDelta <= freeDelta)
+ {
+ int64 delta = (freeDelta + requiredDelta) / 2;
+
+ if (xid < base + delta + FirstNormalTransactionId ||
+ xid > base + delta + MaxShortTransactionId)
+ {
+ elog(FATAL, "Fatal xid base calculation error: "
+ "xid = " XID_FMT ", "
+ "base = " XID_FMT ", "
+ "min = %u, "
+ "max = %u, "
+ "freeDelta = " INT64_FORMAT ", "
+ "requiredDelta = " INT64_FORMAT ", "
+ "delta = " INT64_FORMAT,
+ xid, base, min, max, freeDelta, requiredDelta, delta
+ );
+ }
+
+ Assert(xid >= base + delta + FirstNormalTransactionId);
+ Assert(xid <= base + delta + MaxShortTransactionId);
+
+ heap_page_shift_base(relation, buffer, page, multi, delta);
+
+ base = multi ? pageSpecial->pd_multi_base : pageSpecial->pd_xid_base;
+ Assert(xid >= base + FirstNormalTransactionId);
+ Assert(xid <= base + MaxShortTransactionId);
+ return true;
+ }
+ }
+
+ if (i == 1)
+ {
+ break;
+ }
+
+ /* Have to try freeing the page... */
+ freeze_single_heap_page(relation, buffer);
+ }
+
+ elog(ERROR, "Can't fit xid into page.");
+ return false;
+}
+
+/*
+ * Ensure that given xid fits base of given page.
+ */
+bool
+rewrite_page_prepare_for_xid(Page page, TransactionId xid, bool multi)
+{
+ HeapPageSpecial pageSpecial = HeapPageGetSpecial(page);
+ TransactionId base;
+ bool found;
+ ShortTransactionId min,
+ max;
+
+ if (!TransactionIdIsNormal(xid))
+ return false;
+
+ if (!multi)
+ base = pageSpecial->pd_xid_base;
+ else
+ base = pageSpecial->pd_multi_base;
+
+ /* Can we already store this xid? */
+ if (xid >= base + FirstNormalTransactionId && xid <= base + MaxShortTransactionId)
+ return false;
+
+ /* Find minimum and maximum xids in the page */
+ found = heap_page_xid_min_max(page, multi, &min, &max);
+
+ /* No items on the page? */
+ if (!found)
+ {
+ if (!multi)
+ pageSpecial->pd_xid_base = xid - FirstNormalTransactionId;
+ else
+ pageSpecial->pd_multi_base = xid - FirstNormalTransactionId;
+ return false;
+ }
+
+ /* Can we just shift base on the page */
+ if (xid < base + FirstNormalTransactionId)
+ {
+ int64 freeDelta = MaxShortTransactionId - max,
+ requiredDelta = (base + FirstNormalTransactionId) - xid;
+
+ if (requiredDelta <= freeDelta)
+ {
+ heap_page_shift_base(NULL, InvalidBuffer,
+ page, multi, - (freeDelta + requiredDelta) / 2);
+ return true;
+ }
+ }
+ else
+ {
+ int64 freeDelta = min - FirstNormalTransactionId,
+ requiredDelta = xid - (base + MaxShortTransactionId);
+
+ if (requiredDelta <= freeDelta)
+ {
+ heap_page_shift_base(NULL, InvalidBuffer,
+ page, multi, (freeDelta + requiredDelta) / 2);
+ return true;
+ }
+ }
+
+ elog(ERROR, "Can't fit xid into page.");
+ return false;
+}
+
+
/*
* Subroutine for heap_insert(). Prepares a tuple for insertion. This sets the
* tuple header fields and toasts the tuple if necessary. Returns a toasted
@@ -2245,7 +2819,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
* that in any case, the header fields are also set in the original tuple.
*/
static HeapTuple
-heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
+heap_prepare_insert(Relation relation, HeapTuple tup,
CommandId cid, int options)
{
/*
@@ -2262,12 +2836,12 @@ heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
tup->t_data->t_infomask &= ~(HEAP_XACT_MASK);
tup->t_data->t_infomask2 &= ~(HEAP2_XACT_MASK);
tup->t_data->t_infomask |= HEAP_XMAX_INVALID;
- HeapTupleHeaderSetXmin(tup->t_data, xid);
+ HeapTupleSetXmin(tup, InvalidTransactionId);
if (options & HEAP_INSERT_FROZEN)
HeapTupleHeaderSetXminFrozen(tup->t_data);
HeapTupleHeaderSetCmin(tup->t_data, cid);
- HeapTupleHeaderSetXmax(tup->t_data, 0); /* for cleanliness */
+ HeapTupleSetXmax(tup, 0); /* for cleanliness */
tup->t_tableOid = RelationGetRelid(relation);
/*
@@ -2330,8 +2904,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
tuple = ExecFetchSlotHeapTuple(slots[i], true, NULL);
slots[i]->tts_tableOid = RelationGetRelid(relation);
tuple->t_tableOid = slots[i]->tts_tableOid;
- heaptuples[i] = heap_prepare_insert(relation, tuple, xid, cid,
- options);
+ heaptuples[i] = heap_prepare_insert(relation, tuple, cid, options);
}
/*
@@ -2387,6 +2960,8 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
if (starting_with_empty_page && (options & HEAP_INSERT_FROZEN))
all_frozen_set = true;
+ heap_page_prepare_for_xid(relation, buffer, xid, false);
+
/* NO EREPORT(ERROR) from here till changes are logged */
START_CRIT_SECTION();
@@ -2394,6 +2969,8 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
* RelationGetBufferForTuple has ensured that the first tuple fits.
* Put that on the page, and then as many other tuples as fit.
*/
+ HeapTupleCopyBaseFromPage(heaptuples[ndone], BufferGetPage(buffer));
+ HeapTupleSetXmin(heaptuples[ndone], xid);
RelationPutHeapTuple(relation, buffer, heaptuples[ndone], false);
/*
@@ -2410,6 +2987,8 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
if (PageGetHeapFreeSpace(page) < MAXALIGN(heaptup->t_len) + saveFreeSpace)
break;
+ HeapTupleCopyBaseFromPage(heaptup, BufferGetPage(buffer));
+ HeapTupleSetXmin(heaptup, xid);
RelationPutHeapTuple(relation, buffer, heaptup, false);
/*
@@ -2545,6 +3124,10 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
bufflags |= REGBUF_KEEP_DATA;
XLogBeginInsert();
+ if (info & XLOG_HEAP_INIT_PAGE)
+ {
+ XLogRegisterData((char *) &HeapPageGetSpecial(page)->pd_xid_base, sizeof(TransactionId));
+ }
XLogRegisterData((char *) xlrec, tupledata - scratch.data);
XLogRegisterBuffer(0, buffer, REGBUF_STANDARD | bufflags);
@@ -2765,6 +3348,7 @@ heap_delete(Relation relation, ItemPointer tid,
tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
tp.t_len = ItemIdGetLength(lp);
tp.t_self = *tid;
+ HeapTupleCopyBaseFromPage(&tp, page);
l1:
result = HeapTupleSatisfiesUpdate(&tp, cid, buffer);
@@ -2782,7 +3366,7 @@ l1:
uint16 infomask;
/* must copy state data before unlocking buffer */
- xwait = HeapTupleHeaderGetRawXmax(tp.t_data);
+ xwait = HeapTupleGetRawXmax(&tp);
infomask = tp.t_data->t_infomask;
/*
@@ -2821,13 +3405,16 @@ l1:
NULL);
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+ /* Copy possibly updated xid base after relocking */
+ HeapTupleCopyBaseFromPage(&tp, page);
+
/*
* If xwait had just locked the tuple then some other xact
* could update this tuple before we get to this point. Check
* for xmax change, and start over if so.
*/
if (xmax_infomask_changed(tp.t_data->t_infomask, infomask) ||
- !TransactionIdEquals(HeapTupleHeaderGetRawXmax(tp.t_data),
+ !TransactionIdEquals(HeapTupleGetRawXmax(&tp),
xwait))
goto l1;
}
@@ -2854,13 +3441,16 @@ l1:
XactLockTableWait(xwait, relation, &(tp.t_self), XLTW_Delete);
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+ /* Copy possibly updated xid base after relocking */
+ HeapTupleCopyBaseFromPage(&tp, page);
+
/*
* xwait is done, but if xwait had just locked the tuple then some
* other xact could update this tuple before we get to this point.
* Check for xmax change, and start over if so.
*/
if (xmax_infomask_changed(tp.t_data->t_infomask, infomask) ||
- !TransactionIdEquals(HeapTupleHeaderGetRawXmax(tp.t_data),
+ !TransactionIdEquals(HeapTupleGetRawXmax(&tp),
xwait))
goto l1;
@@ -2874,7 +3464,7 @@ l1:
*/
if ((tp.t_data->t_infomask & HEAP_XMAX_INVALID) ||
HEAP_XMAX_IS_LOCKED_ONLY(tp.t_data->t_infomask) ||
- HeapTupleHeaderIsOnlyLocked(tp.t_data))
+ HeapTupleIsOnlyLocked(&tp))
result = TM_Ok;
else if (!ItemPointerEquals(&tp.t_self, &tp.t_data->t_ctid))
result = TM_Updated;
@@ -2899,9 +3489,9 @@ l1:
Assert(result != TM_Updated ||
!ItemPointerEquals(&tp.t_self, &tp.t_data->t_ctid));
tmfd->ctid = tp.t_data->t_ctid;
- tmfd->xmax = HeapTupleHeaderGetUpdateXid(tp.t_data);
+ tmfd->xmax = HeapTupleGetUpdateXidAny(&tp);
if (result == TM_SelfModified)
- tmfd->cmax = HeapTupleHeaderGetCmax(tp.t_data);
+ tmfd->cmax = HeapTupleGetCmax(&tp);
else
tmfd->cmax = InvalidCommandId;
UnlockReleaseBuffer(buffer);
@@ -2924,7 +3514,7 @@ l1:
CheckForSerializableConflictIn(relation, tid, BufferGetBlockNumber(buffer));
/* replace cid with a combo CID if necessary */
- HeapTupleHeaderAdjustCmax(tp.t_data, &cid, &iscombo);
+ HeapTupleHeaderAdjustCmax(&tp, &cid, &iscombo);
/*
* Compute replica identity tuple before entering the critical section so
@@ -2942,11 +3532,15 @@ l1:
*/
MultiXactIdSetOldestMember();
- compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(tp.t_data),
+ compute_new_xmax_infomask(HeapTupleGetRawXmax(&tp),
tp.t_data->t_infomask, tp.t_data->t_infomask2,
xid, LockTupleExclusive, true,
&new_xmax, &new_infomask, &new_infomask2);
+ heap_page_prepare_for_xid(relation, buffer, new_xmax,
+ (new_infomask & HEAP_XMAX_IS_MULTI) ? true : false);
+ HeapTupleCopyBaseFromPage(&tp, page);
+
START_CRIT_SECTION();
/*
@@ -2972,7 +3566,7 @@ l1:
tp.t_data->t_infomask |= new_infomask;
tp.t_data->t_infomask2 |= new_infomask2;
HeapTupleHeaderClearHotUpdated(tp.t_data);
- HeapTupleHeaderSetXmax(tp.t_data, new_xmax);
+ HeapTupleSetXmax(&tp, new_xmax);
HeapTupleHeaderSetCmax(tp.t_data, cid, iscombo);
/* Make sure there is no forward chain link in t_ctid */
tp.t_data->t_ctid = tp.t_self;
@@ -3166,7 +3760,8 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
HeapTuple heaptup;
HeapTuple old_key_tuple = NULL;
bool old_key_copied = false;
- Page page;
+ Page page,
+ newpage;
BlockNumber block;
MultiXactStatus mxact_status;
Buffer buffer,
@@ -3258,9 +3853,11 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
oldtup.t_len = ItemIdGetLength(lp);
oldtup.t_self = *otid;
+ HeapTupleCopyBaseFromPage(&oldtup, page);
/* the new tuple is ready, except for this: */
newtup->t_tableOid = RelationGetRelid(relation);
+ HeapTupleCopyBaseFromPage(newtup, page);
/* Determine columns modified by the update. */
modified_attrs = HeapDetermineModifiedColumns(relation, interesting_attrs,
@@ -3343,7 +3940,7 @@ l2:
*/
/* must copy state data before unlocking buffer */
- xwait = HeapTupleHeaderGetRawXmax(oldtup.t_data);
+ xwait = HeapTupleGetRawXmax(&oldtup);
infomask = oldtup.t_data->t_infomask;
/*
@@ -3394,6 +3991,7 @@ l2:
checked_lockers = true;
locker_remains = remain != 0;
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(&oldtup, page);
/*
* If xwait had just locked the tuple then some other xact
@@ -3402,7 +4000,7 @@ l2:
*/
if (xmax_infomask_changed(oldtup.t_data->t_infomask,
infomask) ||
- !TransactionIdEquals(HeapTupleHeaderGetRawXmax(oldtup.t_data),
+ !TransactionIdEquals(HeapTupleGetRawXmax(&oldtup),
xwait))
goto l2;
}
@@ -3428,7 +4026,7 @@ l2:
* subxact aborts.
*/
if (!HEAP_XMAX_IS_LOCKED_ONLY(oldtup.t_data->t_infomask))
- update_xact = HeapTupleGetUpdateXid(oldtup.t_data);
+ update_xact = HeapTupleGetUpdateXid(&oldtup);
else
update_xact = InvalidTransactionId;
@@ -3476,6 +4074,8 @@ l2:
checked_lockers = true;
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(&oldtup, page);
+
/*
* xwait is done, but if xwait had just locked the tuple then some
* other xact could update this tuple before we get to this point.
@@ -3483,7 +4083,7 @@ l2:
*/
if (xmax_infomask_changed(oldtup.t_data->t_infomask, infomask) ||
!TransactionIdEquals(xwait,
- HeapTupleHeaderGetRawXmax(oldtup.t_data)))
+ HeapTupleGetRawXmax(&oldtup)))
goto l2;
/* Otherwise check if it committed or aborted */
@@ -3520,9 +4120,9 @@ l2:
Assert(result != TM_Updated ||
!ItemPointerEquals(&oldtup.t_self, &oldtup.t_data->t_ctid));
tmfd->ctid = oldtup.t_data->t_ctid;
- tmfd->xmax = HeapTupleHeaderGetUpdateXid(oldtup.t_data);
+ tmfd->xmax = HeapTupleGetUpdateXidAny(&oldtup);
if (result == TM_SelfModified)
- tmfd->cmax = HeapTupleHeaderGetCmax(oldtup.t_data);
+ tmfd->cmax = HeapTupleGetCmax(&oldtup);
else
tmfd->cmax = InvalidCommandId;
UnlockReleaseBuffer(buffer);
@@ -3552,6 +4152,7 @@ l2:
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
visibilitymap_pin(relation, block, &vmbuffer);
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(&oldtup, page);
goto l2;
}
@@ -3561,7 +4162,7 @@ l2:
* If the tuple we're updating is locked, we need to preserve the locking
* info in the old tuple's Xmax. Prepare a new Xmax value for this.
*/
- compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup.t_data),
+ compute_new_xmax_infomask(HeapTupleGetRawXmax(&oldtup),
oldtup.t_data->t_infomask,
oldtup.t_data->t_infomask2,
xid, *lockmode, true,
@@ -3580,7 +4181,7 @@ l2:
(checked_lockers && !locker_remains))
xmax_new_tuple = InvalidTransactionId;
else
- xmax_new_tuple = HeapTupleHeaderGetRawXmax(oldtup.t_data);
+ xmax_new_tuple = HeapTupleGetRawXmax(&oldtup);
if (!TransactionIdIsValid(xmax_new_tuple))
{
@@ -3613,17 +4214,15 @@ l2:
*/
newtup->t_data->t_infomask &= ~(HEAP_XACT_MASK);
newtup->t_data->t_infomask2 &= ~(HEAP2_XACT_MASK);
- HeapTupleHeaderSetXmin(newtup->t_data, xid);
HeapTupleHeaderSetCmin(newtup->t_data, cid);
newtup->t_data->t_infomask |= HEAP_UPDATED | infomask_new_tuple;
newtup->t_data->t_infomask2 |= infomask2_new_tuple;
- HeapTupleHeaderSetXmax(newtup->t_data, xmax_new_tuple);
/*
* Replace cid with a combo CID if necessary. Note that we already put
* the plain cid into the new tuple.
*/
- HeapTupleHeaderAdjustCmax(oldtup.t_data, &cid, &iscombo);
+ HeapTupleHeaderAdjustCmax(&oldtup, &cid, &iscombo);
/*
* If the toaster needs to be activated, OR if the new tuple will not fit
@@ -3653,7 +4252,7 @@ l2:
newtupsize = MAXALIGN(newtup->t_len);
- if (need_toast || newtupsize > pagefree)
+ if (need_toast || newtupsize > pagefree || HeapPageIsDoubleXmax(page))
{
TransactionId xmax_lock_old_tuple;
uint16 infomask_lock_old_tuple,
@@ -3678,7 +4277,7 @@ l2:
* updating, because the potentially created multixact would otherwise
* be wrong.
*/
- compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup.t_data),
+ compute_new_xmax_infomask(HeapTupleGetRawXmax(&oldtup),
oldtup.t_data->t_infomask,
oldtup.t_data->t_infomask2,
xid, *lockmode, false,
@@ -3687,6 +4286,10 @@ l2:
Assert(HEAP_XMAX_IS_LOCKED_ONLY(infomask_lock_old_tuple));
+ heap_page_prepare_for_xid(relation, buffer, xmax_lock_old_tuple,
+ (infomask_lock_old_tuple & HEAP_XMAX_IS_MULTI) ? true : false);
+ HeapTupleCopyBaseFromPage(&oldtup, page);
+
START_CRIT_SECTION();
/* Clear obsolete visibility flags ... */
@@ -3695,9 +4298,9 @@ l2:
HeapTupleClearHotUpdated(&oldtup);
/* ... and store info about transaction updating this tuple */
Assert(TransactionIdIsValid(xmax_lock_old_tuple));
- HeapTupleHeaderSetXmax(oldtup.t_data, xmax_lock_old_tuple);
oldtup.t_data->t_infomask |= infomask_lock_old_tuple;
oldtup.t_data->t_infomask2 |= infomask2_lock_old_tuple;
+ HeapTupleSetXmax(&oldtup, xmax_lock_old_tuple);
HeapTupleHeaderSetCmax(oldtup.t_data, cid, iscombo);
/* temporarily make it look not-updated, but locked */
@@ -3780,7 +4383,11 @@ l2:
*/
for (;;)
{
- if (newtupsize > pagefree)
+ /*
+ * We can't fit new tuple to "double xmax" page, since it's impossible to
+ * set xmin there.
+ */
+ if (newtupsize > pagefree || HeapPageIsDoubleXmax(page))
{
/* It doesn't fit, must use RelationGetBufferForTuple. */
newbuf = RelationGetBufferForTuple(relation, heaptup->t_len,
@@ -3813,6 +4420,9 @@ l2:
break;
}
}
+
+ /* Copy possibly updated xid base to old tuple after relocking */
+ HeapTupleCopyBaseFromPage(&oldtup, page);
}
else
{
@@ -3870,6 +4480,33 @@ l2:
bms_overlap(modified_attrs, id_attrs),
&old_key_copied);
+ newpage = BufferGetPage(newbuf);
+
+ /*
+ * Prepare pages for the current xid, that witten to the new tuple's
+ * Xmax and old page's pd_prune_xid.
+ */
+ heap_page_prepare_for_xid(relation, buffer, xid, false);
+ if (newbuf != buffer)
+ heap_page_prepare_for_xid(relation, newbuf, xid, false);
+
+ /* Prepare pages for tuple's Xmax */
+ heap_page_prepare_for_xid(relation, buffer, xmax_old_tuple,
+ (infomask_old_tuple & HEAP_XMAX_IS_MULTI) ? true : false);
+ heap_page_prepare_for_xid(relation, newbuf, xmax_new_tuple,
+ (heaptup->t_data->t_infomask & HEAP_XMAX_IS_MULTI) ? true : false);
+
+ /* Copy possibly updated Xid bases to the both tuples. */
+ HeapTupleCopyBaseFromPage(&oldtup, page);
+ HeapTupleCopyBaseFromPage(heaptup, newpage);
+
+ /*
+ * Set new tuple's Xmin/Xmax, old tuple's Xmin/Xmax were already
+ * shifted.
+ */
+ HeapTupleSetXmin(heaptup, xid);
+ HeapTupleSetXmax(heaptup, xmax_new_tuple);
+
/* NO EREPORT(ERROR) from here till changes are logged */
START_CRIT_SECTION();
@@ -3912,9 +4549,9 @@ l2:
oldtup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
/* ... and store info about transaction updating this tuple */
Assert(TransactionIdIsValid(xmax_old_tuple));
- HeapTupleHeaderSetXmax(oldtup.t_data, xmax_old_tuple);
oldtup.t_data->t_infomask |= infomask_old_tuple;
oldtup.t_data->t_infomask2 |= infomask2_old_tuple;
+ HeapTupleSetXmax(&oldtup, xmax_old_tuple);
HeapTupleHeaderSetCmax(oldtup.t_data, cid, iscombo);
/* record address of new tuple in t_ctid of old one */
@@ -3969,6 +4606,18 @@ l2:
END_CRIT_SECTION();
+ if (newtup != heaptup)
+ {
+ /*
+ * Set new tuple's Xmin/Xmax only after both xid base preparations.
+ * Old tuple's Xmin/Xmax were already shifted because old tuple is
+ * on the page.
+ */
+ HeapTupleCopyBase(newtup, heaptup);
+ HeapTupleSetXmin(newtup, xid);
+ HeapTupleSetXmax(newtup, xmax_new_tuple);
+ }
+
if (newbuf != buffer)
LockBuffer(newbuf, BUFFER_LOCK_UNLOCK);
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
@@ -4267,6 +4916,7 @@ heap_lock_tuple(Relation relation, HeapTuple tuple,
tuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
tuple->t_len = ItemIdGetLength(lp);
tuple->t_tableOid = RelationGetRelid(relation);
+ HeapTupleCopyBaseFromPage(tuple, page);
l3:
result = HeapTupleSatisfiesUpdate(tuple, cid, *buffer);
@@ -4293,7 +4943,7 @@ l3:
ItemPointerData t_ctid;
/* must copy state data before unlocking buffer */
- xwait = HeapTupleHeaderGetRawXmax(tuple->t_data);
+ xwait = HeapTupleGetRawXmax(tuple);
infomask = tuple->t_data->t_infomask;
infomask2 = tuple->t_data->t_infomask2;
ItemPointerCopy(&tuple->t_data->t_ctid, &t_ctid);
@@ -4451,11 +5101,13 @@ l3:
result = res;
/* recovery code expects to have buffer lock held */
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(tuple, page);
goto failed;
}
}
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(tuple, page);
/*
* Make sure it's still an appropriate lock, else start over.
@@ -4464,7 +5116,7 @@ l3:
* now need to follow the update chain to lock the new
* versions.
*/
- if (!HeapTupleHeaderIsOnlyLocked(tuple->t_data) &&
+ if (!HeapTupleIsOnlyLocked(tuple) &&
((tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED) ||
!updated))
goto l3;
@@ -4491,6 +5143,7 @@ l3:
!HEAP_XMAX_IS_EXCL_LOCKED(infomask))
{
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(tuple, page);
/*
* Make sure it's still an appropriate lock, else start over.
@@ -4519,8 +5172,10 @@ l3:
* meantime, start over.
*/
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(tuple, page);
+
if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
- !TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple->t_data),
+ !TransactionIdEquals(HeapTupleGetRawXmax(tuple),
xwait))
goto l3;
@@ -4531,10 +5186,11 @@ l3:
else if (HEAP_XMAX_IS_KEYSHR_LOCKED(infomask))
{
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(tuple, page);
/* if the xmax changed in the meantime, start over */
if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
- !TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple->t_data),
+ !TransactionIdEquals(HeapTupleGetRawXmax(tuple),
xwait))
goto l3;
/* otherwise, we're good */
@@ -4559,8 +5215,10 @@ l3:
{
/* ... but if the xmax changed in the meantime, start over */
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(tuple, page);
+
if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
- !TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple->t_data),
+ !TransactionIdEquals(HeapTupleGetRawXmax(tuple),
xwait))
goto l3;
Assert(HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_data->t_infomask));
@@ -4581,6 +5239,7 @@ l3:
if (require_sleep && (result == TM_Updated || result == TM_Deleted))
{
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(tuple, page);
goto failed;
}
else if (require_sleep)
@@ -4606,6 +5265,7 @@ l3:
result = TM_WouldBlock;
/* recovery code expects to have buffer lock held */
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(tuple, page);
goto failed;
}
@@ -4632,6 +5292,7 @@ l3:
result = TM_WouldBlock;
/* recovery code expects to have buffer lock held */
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(tuple, page);
goto failed;
}
break;
@@ -4672,6 +5333,7 @@ l3:
result = TM_WouldBlock;
/* recovery code expects to have buffer lock held */
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(tuple, page);
goto failed;
}
break;
@@ -4698,11 +5360,13 @@ l3:
result = res;
/* recovery code expects to have buffer lock held */
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(tuple, page);
goto failed;
}
}
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(tuple, page);
/*
* xwait is done, but if xwait had just locked the tuple then some
@@ -4710,7 +5374,7 @@ l3:
* Check for xmax change, and start over if so.
*/
if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
- !TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple->t_data),
+ !TransactionIdEquals(HeapTupleGetRawXmax(tuple),
xwait))
goto l3;
@@ -4738,7 +5402,7 @@ l3:
if (!require_sleep ||
(tuple->t_data->t_infomask & HEAP_XMAX_INVALID) ||
HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_data->t_infomask) ||
- HeapTupleHeaderIsOnlyLocked(tuple->t_data))
+ HeapTupleIsOnlyLocked(tuple))
result = TM_Ok;
else if (!ItemPointerEquals(&tuple->t_self, &tuple->t_data->t_ctid))
result = TM_Updated;
@@ -4764,9 +5428,9 @@ failed:
Assert(result != TM_Updated ||
!ItemPointerEquals(&tuple->t_self, &tuple->t_data->t_ctid));
tmfd->ctid = tuple->t_data->t_ctid;
- tmfd->xmax = HeapTupleHeaderGetUpdateXid(tuple->t_data);
+ tmfd->xmax = HeapTupleGetUpdateXidAny(tuple);
if (result == TM_SelfModified)
- tmfd->cmax = HeapTupleHeaderGetCmax(tuple->t_data);
+ tmfd->cmax = HeapTupleGetCmax(tuple);
else
tmfd->cmax = InvalidCommandId;
goto out_locked;
@@ -4786,10 +5450,11 @@ failed:
LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
visibilitymap_pin(relation, block, &vmbuffer);
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(tuple, page);
goto l3;
}
- xmax = HeapTupleHeaderGetRawXmax(tuple->t_data);
+ xmax = HeapTupleGetRawXmax(tuple);
old_infomask = tuple->t_data->t_infomask;
/*
@@ -4811,6 +5476,10 @@ failed:
GetCurrentTransactionId(), mode, false,
&xid, &new_infomask, &new_infomask2);
+ heap_page_prepare_for_xid(relation, *buffer, xid,
+ (new_infomask & HEAP_XMAX_IS_MULTI) ? true : false);
+ HeapTupleCopyBaseFromPage(tuple, page);
+
START_CRIT_SECTION();
/*
@@ -4829,7 +5498,7 @@ failed:
tuple->t_data->t_infomask2 |= new_infomask2;
if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
HeapTupleHeaderClearHotUpdated(tuple->t_data);
- HeapTupleHeaderSetXmax(tuple->t_data, xid);
+ HeapTupleSetXmax(tuple, xid);
/*
* Make sure there is no forward chain link in t_ctid. Note that in the
@@ -5145,7 +5814,7 @@ l5:
* TransactionIdIsInProgress() should have returned false. We
* assume it's no longer locked in this case.
*/
- elog(WARNING, "LOCK_ONLY found for Xid in progress %u", xmax);
+ elog(WARNING, "LOCK_ONLY found for Xid in progress " XID_FMT, xmax);
old_infomask |= HEAP_XMAX_INVALID;
old_infomask &= ~HEAP_XMAX_LOCK_ONLY;
goto l5;
@@ -5422,12 +6091,18 @@ l4:
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
}
+ /*
+ * Copy xid base after buffer relocking, it could have changed
+ * since heap_fetch().
+ */
+ HeapTupleCopyBaseFromPage(&mytup, BufferGetPage(buf));
+
/*
* Check the tuple XMIN against prior XMAX, if any. If we reached the
* end of the chain, we're done, so return success.
*/
if (TransactionIdIsValid(priorXmax) &&
- !TransactionIdEquals(HeapTupleHeaderGetXmin(mytup.t_data),
+ !TransactionIdEquals(HeapTupleGetXmin(&mytup),
priorXmax))
{
result = TM_Ok;
@@ -5439,7 +6114,7 @@ l4:
* (sub)transaction, then we already locked the last live one in the
* chain, thus we're done, so return success.
*/
- if (TransactionIdDidAbort(HeapTupleHeaderGetXmin(mytup.t_data)))
+ if (TransactionIdDidAbort(HeapTupleGetXmin(&mytup)))
{
result = TM_Ok;
goto out_locked;
@@ -5447,7 +6122,7 @@ l4:
old_infomask = mytup.t_data->t_infomask;
old_infomask2 = mytup.t_data->t_infomask2;
- xmax = HeapTupleHeaderGetRawXmax(mytup.t_data);
+ xmax = HeapTupleGetRawXmax(&mytup);
/*
* If this tuple version has been updated or locked by some concurrent
@@ -5460,7 +6135,7 @@ l4:
TransactionId rawxmax;
bool needwait;
- rawxmax = HeapTupleHeaderGetRawXmax(mytup.t_data);
+ rawxmax = HeapTupleGetRawXmax(&mytup);
if (old_infomask & HEAP_XMAX_IS_MULTI)
{
int nmembers;
@@ -5601,14 +6276,18 @@ l4:
VISIBILITYMAP_ALL_FROZEN))
cleared_all_frozen = true;
+ heap_page_prepare_for_xid(rel, buf, new_xmax,
+ (new_infomask & HEAP_XMAX_IS_MULTI) ? true : false);
+ HeapTupleCopyBaseFromPage(&mytup, BufferGetPage(buf));
+
START_CRIT_SECTION();
/* ... and set them */
- HeapTupleHeaderSetXmax(mytup.t_data, new_xmax);
mytup.t_data->t_infomask &= ~HEAP_XMAX_BITS;
mytup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
mytup.t_data->t_infomask |= new_infomask;
mytup.t_data->t_infomask2 |= new_infomask2;
+ HeapTupleSetXmax(&mytup, new_xmax);
MarkBufferDirty(buf);
@@ -5642,14 +6321,14 @@ next:
if (mytup.t_data->t_infomask & HEAP_XMAX_INVALID ||
HeapTupleHeaderIndicatesMovedPartitions(mytup.t_data) ||
ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_ctid) ||
- HeapTupleHeaderIsOnlyLocked(mytup.t_data))
+ HeapTupleIsOnlyLocked(&mytup))
{
result = TM_Ok;
goto out_locked;
}
/* tail recursion */
- priorXmax = HeapTupleHeaderGetUpdateXid(mytup.t_data);
+ priorXmax = HeapTupleGetUpdateXidAny(&mytup);
ItemPointerCopy(&(mytup.t_data->t_ctid), &tupid);
UnlockReleaseBuffer(buf);
}
@@ -5856,12 +6535,13 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
tp.t_len = ItemIdGetLength(lp);
tp.t_self = *tid;
+ HeapTupleCopyBaseFromPage(&tp, page);
/*
* Sanity check that the tuple really is a speculatively inserted tuple,
* inserted by us.
*/
- if (tp.t_data->t_choice.t_heap.t_xmin != xid)
+ if (HeapTupleGetRawXmin(&tp) != xid)
elog(ERROR, "attempted to kill a tuple inserted by another transaction");
if (!(IsToastRelation(relation) || HeapTupleHeaderIsSpeculative(tp.t_data)))
elog(ERROR, "attempted to kill a non-speculative tuple");
@@ -5890,6 +6570,8 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
prune_xid = relation->rd_rel->relfrozenxid;
else
prune_xid = TransactionXmin;
+ Assert(TransactionIdIsValid(prune_xid));
+ heap_page_prepare_for_xid(relation, buffer, prune_xid, false);
PageSetPrunable(page, prune_xid);
/* store transaction information of xact deleting the tuple */
@@ -5900,8 +6582,10 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
* Set the tuple header xmin to InvalidTransactionId. This makes the
* tuple immediately invisible everyone. (In particular, to any
* transactions waiting on the speculative token, woken up later.)
+ * Don't need to reload xid base from page because InvalidTransactionId
+ * doesn't require xid base to be valid.
*/
- HeapTupleHeaderSetXmin(tp.t_data, InvalidTransactionId);
+ HeapTupleSetXmin(&tp, InvalidTransactionId);
/* Clear the speculative insertion token too */
tp.t_data->t_ctid = tp.t_self;
@@ -6120,7 +6804,7 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
else if (MultiXactIdPrecedes(multi, relminmxid))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
- errmsg_internal("found multixact %u from before relminmxid %u",
+ errmsg_internal("found multixact " XID_FMT " from before relminmxid " XID_FMT,
multi, relminmxid)));
else if (MultiXactIdPrecedes(multi, cutoff_multi))
{
@@ -6134,7 +6818,7 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
HEAP_XMAX_IS_LOCKED_ONLY(t_infomask)))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
- errmsg_internal("multixact %u from before cutoff %u found to be still running",
+ errmsg_internal("multixact " XID_FMT " from before cutoff " XID_FMT " found to be still running",
multi, cutoff_multi)));
if (HEAP_XMAX_IS_LOCKED_ONLY(t_infomask))
@@ -6153,7 +6837,7 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
if (TransactionIdPrecedes(xid, relfrozenxid))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
- errmsg_internal("found update xid %u from before relfrozenxid %u",
+ errmsg_internal("found update xid " XID_FMT " from before relfrozenxid " XID_FMT,
xid, relfrozenxid)));
/*
@@ -6165,7 +6849,7 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
if (TransactionIdDidCommit(xid))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
- errmsg_internal("cannot freeze committed update xid %u", xid)));
+ errmsg_internal("cannot freeze committed update xid " XID_FMT, xid)));
*flags |= FRM_INVALIDATE_XMAX;
xid = InvalidTransactionId; /* not strictly necessary */
}
@@ -6223,7 +6907,7 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
* to keep.
*/
nnewmembers = 0;
- newmembers = palloc(sizeof(MultiXactMember) * nmembers);
+ newmembers = palloc0(sizeof(MultiXactMember) * nmembers);
has_lockers = false;
update_xid = InvalidTransactionId;
update_committed = false;
@@ -6241,7 +6925,7 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
if (TransactionIdPrecedes(xid, relfrozenxid))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
- errmsg_internal("found update xid %u from before relfrozenxid %u",
+ errmsg_internal("found update xid " XID_FMT " from before relfrozenxid " XID_FMT,
xid, relfrozenxid)));
/*
@@ -6291,7 +6975,7 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
TransactionIdPrecedes(update_xid, cutoff_xid))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
- errmsg_internal("found update xid %u from before xid cutoff %u",
+ errmsg_internal("found update xid " XID_FMT " from before xid cutoff " XID_FMT,
update_xid, cutoff_xid)));
/*
@@ -6389,7 +7073,7 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
* recovery. We really need to remove old xids.
*/
bool
-heap_prepare_freeze_tuple(HeapTupleHeader tuple,
+heap_prepare_freeze_tuple(HeapTuple htup,
TransactionId relfrozenxid, TransactionId relminmxid,
TransactionId cutoff_xid, TransactionId cutoff_multi,
xl_heap_freeze_tuple *frz, bool *totally_frozen_p)
@@ -6399,11 +7083,12 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple,
bool xmin_frozen;
bool freeze_xmax;
TransactionId xid;
+ HeapTupleHeader tuple = htup->t_data;
frz->frzflags = 0;
frz->t_infomask2 = tuple->t_infomask2;
frz->t_infomask = tuple->t_infomask;
- frz->xmax = HeapTupleHeaderGetRawXmax(tuple);
+ frz->xmax = HeapTupleGetRawXmax(htup);
/*
* Process xmin. xmin_frozen has two slightly different meanings: in the
@@ -6413,7 +7098,7 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple,
* otherwise). In both cases we're allowed to set totally_frozen, as far
* as xmin is concerned.
*/
- xid = HeapTupleHeaderGetXmin(tuple);
+ xid = HeapTupleGetXmin(htup);
if (!TransactionIdIsNormal(xid))
xmin_frozen = true;
else
@@ -6421,7 +7106,7 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple,
if (TransactionIdPrecedes(xid, relfrozenxid))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
- errmsg_internal("found xmin %u from before relfrozenxid %u",
+ errmsg_internal("found xmin " XID_FMT " from before relfrozenxid " XID_FMT,
xid, relfrozenxid)));
xmin_frozen = TransactionIdPrecedes(xid, cutoff_xid);
@@ -6430,7 +7115,7 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple,
if (!TransactionIdDidCommit(xid))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
- errmsg_internal("uncommitted xmin %u from before xid cutoff %u needs to be frozen",
+ errmsg_internal("uncommitted xmin " XID_FMT " from before xid cutoff " XID_FMT " needs to be frozen",
xid, cutoff_xid)));
frz->t_infomask |= HEAP_XMIN_FROZEN;
@@ -6447,7 +7132,7 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple,
*
* Make sure to keep heap_tuple_needs_freeze in sync with this.
*/
- xid = HeapTupleHeaderGetRawXmax(tuple);
+ xid = HeapTupleGetRawXmax(htup);
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
@@ -6502,7 +7187,7 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple,
if (TransactionIdPrecedes(xid, relfrozenxid))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
- errmsg_internal("found xmax %u from before relfrozenxid %u",
+ errmsg_internal("found xmax " XID_FMT " from before relfrozenxid " XID_FMT,
xid, relfrozenxid)));
if (TransactionIdPrecedes(xid, cutoff_xid))
@@ -6517,7 +7202,7 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple,
TransactionIdDidCommit(xid))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
- errmsg_internal("cannot freeze committed xmax %u",
+ errmsg_internal("cannot freeze committed xmax " XID_FMT,
xid)));
freeze_xmax = true;
}
@@ -6525,7 +7210,7 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple,
freeze_xmax = false;
}
else if ((tuple->t_infomask & HEAP_XMAX_INVALID) ||
- !TransactionIdIsValid(HeapTupleHeaderGetRawXmax(tuple)))
+ !TransactionIdIsValid(HeapTupleGetRawXmax(htup)))
{
freeze_xmax = false;
xmax_already_frozen = true;
@@ -6533,7 +7218,7 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple,
else
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
- errmsg_internal("found xmax %u (infomask 0x%04x) not frozen, not multi, not normal",
+ errmsg_internal("found xmax " XID_FMT " (infomask 0x%04x) not frozen, not multi, not normal",
xid, tuple->t_infomask)));
if (freeze_xmax)
@@ -6618,18 +7303,35 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple,
* NB: All code in here must be safe to execute during crash recovery!
*/
void
-heap_execute_freeze_tuple(HeapTupleHeader tuple, xl_heap_freeze_tuple *frz)
+heap_execute_freeze_tuple(HeapTuple htup, xl_heap_freeze_tuple *frz)
{
- HeapTupleHeaderSetXmax(tuple, frz->xmax);
+ HeapTupleHeader tuple = htup->t_data;
+
+ tuple->t_infomask = frz->t_infomask;
+ tuple->t_infomask2 = frz->t_infomask2;
+
+ HeapTupleSetXmax(htup, frz->xmax);
if (frz->frzflags & XLH_FREEZE_XVAC)
HeapTupleHeaderSetXvac(tuple, FrozenTransactionId);
if (frz->frzflags & XLH_INVALID_XVAC)
HeapTupleHeaderSetXvac(tuple, InvalidTransactionId);
+}
+void
+heap_execute_freeze_tuple_page(Page page, HeapTupleHeader tuple, xl_heap_freeze_tuple *frz)
+{
tuple->t_infomask = frz->t_infomask;
tuple->t_infomask2 = frz->t_infomask2;
+
+ HeapTupleHeaderSetXmax(page, tuple, frz->xmax);
+
+ if (frz->frzflags & XLH_FREEZE_XVAC)
+ HeapTupleHeaderSetXvac(tuple, FrozenTransactionId);
+
+ if (frz->frzflags & XLH_INVALID_XVAC)
+ HeapTupleHeaderSetXvac(tuple, InvalidTransactionId);
}
/*
@@ -6639,7 +7341,7 @@ heap_execute_freeze_tuple(HeapTupleHeader tuple, xl_heap_freeze_tuple *frz)
* Useful for callers like CLUSTER that perform their own WAL logging.
*/
bool
-heap_freeze_tuple(HeapTupleHeader tuple,
+heap_freeze_tuple(HeapTuple tuple,
TransactionId relfrozenxid, TransactionId relminmxid,
TransactionId cutoff_xid, TransactionId cutoff_multi)
{
@@ -6803,10 +7505,10 @@ MultiXactIdGetUpdateXid(TransactionId xmax, uint16 t_infomask)
* checking the hint bits.
*/
TransactionId
-HeapTupleGetUpdateXid(HeapTupleHeader tuple)
+HeapTupleGetUpdateXid(HeapTuple tuple)
{
- return MultiXactIdGetUpdateXid(HeapTupleHeaderGetRawXmax(tuple),
- tuple->t_infomask);
+ return MultiXactIdGetUpdateXid(HeapTupleGetRawXmax(tuple),
+ tuple->t_data->t_infomask);
}
/*
@@ -7037,12 +7739,16 @@ bool
heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple)
{
TransactionId xid;
+ HeapTupleData htup;
+
+ htup.t_data = tuple;
+ HeapTupleSetZeroBase(&htup);
/*
* If xmin is a normal transaction ID, this tuple is definitely not
* frozen.
*/
- xid = HeapTupleHeaderGetXmin(tuple);
+ xid = HeapTupleGetXmin(&htup);
if (TransactionIdIsNormal(xid))
return true;
@@ -7053,13 +7759,13 @@ heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple)
{
MultiXactId multi;
- multi = HeapTupleHeaderGetRawXmax(tuple);
+ multi = HeapTupleGetRawXmax(&htup);
if (MultiXactIdIsValid(multi))
return true;
}
else
{
- xid = HeapTupleHeaderGetRawXmax(tuple);
+ xid = HeapTupleGetRawXmax(&htup);
if (TransactionIdIsNormal(xid))
return true;
}
@@ -7087,12 +7793,13 @@ heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple)
* on a standby.
*/
bool
-heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
+heap_tuple_needs_freeze(HeapTuple htup, TransactionId cutoff_xid,
MultiXactId cutoff_multi, Buffer buf)
{
TransactionId xid;
+ HeapTupleHeader tuple = htup->t_data;
- xid = HeapTupleHeaderGetXmin(tuple);
+ xid = HeapTupleGetXmin(htup);
if (TransactionIdIsNormal(xid) &&
TransactionIdPrecedes(xid, cutoff_xid))
return true;
@@ -7106,7 +7813,7 @@ heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
{
MultiXactId multi;
- multi = HeapTupleHeaderGetRawXmax(tuple);
+ multi = HeapTupleGetRawXmax(htup);
if (!MultiXactIdIsValid(multi))
{
/* no xmax set, ignore */
@@ -7141,7 +7848,7 @@ heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
}
else
{
- xid = HeapTupleHeaderGetRawXmax(tuple);
+ xid = HeapTupleGetRawXmax(htup);
if (TransactionIdIsNormal(xid) &&
TransactionIdPrecedes(xid, cutoff_xid))
return true;
@@ -7166,14 +7873,14 @@ heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
* with queries.
*/
void
-HeapTupleHeaderAdvanceLatestRemovedXid(HeapTupleHeader tuple,
+HeapTupleHeaderAdvanceLatestRemovedXid(HeapTuple tuple,
TransactionId *latestRemovedXid)
{
- TransactionId xmin = HeapTupleHeaderGetXmin(tuple);
- TransactionId xmax = HeapTupleHeaderGetUpdateXid(tuple);
- TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+ TransactionId xmin = HeapTupleGetXmin(tuple);
+ TransactionId xmax = HeapTupleGetUpdateXidAny(tuple);
+ TransactionId xvac = HeapTupleHeaderGetXvac(tuple->t_data);
- if (tuple->t_infomask & HEAP_MOVED)
+ if (tuple->t_data->t_infomask & HEAP_MOVED)
{
if (TransactionIdPrecedes(*latestRemovedXid, xvac))
*latestRemovedXid = xvac;
@@ -7185,8 +7892,8 @@ HeapTupleHeaderAdvanceLatestRemovedXid(HeapTupleHeader tuple,
*
* Look for a committed hint bit, or if no xmin bit is set, check clog.
*/
- if (HeapTupleHeaderXminCommitted(tuple) ||
- (!HeapTupleHeaderXminInvalid(tuple) && TransactionIdDidCommit(xmin)))
+ if (HeapTupleHeaderXminCommitted(tuple->t_data) ||
+ (!HeapTupleHeaderXminInvalid(tuple->t_data) && TransactionIdDidCommit(xmin)))
{
if (xmax != xmin &&
TransactionIdFollows(xmax, *latestRemovedXid))
@@ -7536,7 +8243,7 @@ heap_index_delete_tuples(Relation rel, TM_IndexDeleteOp *delstate)
for (;;)
{
ItemId lp;
- HeapTupleHeader htup;
+ HeapTupleData htup;
/* Sanity check (pure paranoia) */
if (offnum < FirstOffsetNumber)
@@ -7573,16 +8280,18 @@ heap_index_delete_tuples(Relation rel, TM_IndexDeleteOp *delstate)
if (!ItemIdIsNormal(lp))
break;
- htup = (HeapTupleHeader) PageGetItem(page, lp);
+ htup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+ htup.t_len = ItemIdGetLength(lp);
+ HeapTupleCopyBaseFromPage(&htup, page);
/*
* Check the tuple XMIN against prior XMAX, if any
*/
if (TransactionIdIsValid(priorXmax) &&
- !TransactionIdEquals(HeapTupleHeaderGetXmin(htup), priorXmax))
+ !TransactionIdEquals(HeapTupleGetXmin(&htup), priorXmax))
break;
- HeapTupleHeaderAdvanceLatestRemovedXid(htup, &latestRemovedXid);
+ HeapTupleHeaderAdvanceLatestRemovedXid(&htup, &latestRemovedXid);
/*
* If the tuple is not HOT-updated, then we are at the end of this
@@ -7590,13 +8299,13 @@ heap_index_delete_tuples(Relation rel, TM_IndexDeleteOp *delstate)
* chain (they get their own index entries) -- just move on to
* next htid from index AM caller.
*/
- if (!HeapTupleHeaderIsHotUpdated(htup))
+ if (!HeapTupleHeaderIsHotUpdated(htup.t_data))
break;
/* Advance to next HOT chain member */
- Assert(ItemPointerGetBlockNumber(&htup->t_ctid) == blkno);
- offnum = ItemPointerGetOffsetNumber(&htup->t_ctid);
- priorXmax = HeapTupleHeaderGetUpdateXid(htup);
+ Assert(ItemPointerGetBlockNumber(&htup.t_data->t_ctid) == blkno);
+ offnum = ItemPointerGetOffsetNumber(&htup.t_data->t_ctid);
+ priorXmax = HeapTupleGetUpdateXidAny(&htup);
}
/* Enable further/final shrinking of deltids for caller */
@@ -8176,13 +8885,13 @@ log_heap_update(Relation reln, Buffer oldbuf,
/* Prepare WAL data for the old page */
xlrec.old_offnum = ItemPointerGetOffsetNumber(&oldtup->t_self);
- xlrec.old_xmax = HeapTupleHeaderGetRawXmax(oldtup->t_data);
+ xlrec.old_xmax = HeapTupleGetRawXmax(oldtup);
xlrec.old_infobits_set = compute_infobits(oldtup->t_data->t_infomask,
oldtup->t_data->t_infomask2);
/* Prepare WAL data for the new page */
xlrec.new_offnum = ItemPointerGetOffsetNumber(&newtup->t_self);
- xlrec.new_xmax = HeapTupleHeaderGetRawXmax(newtup->t_data);
+ xlrec.new_xmax = HeapTupleGetRawXmax(newtup);
bufflags = REGBUF_STANDARD;
if (init)
@@ -8194,6 +8903,10 @@ log_heap_update(Relation reln, Buffer oldbuf,
if (oldbuf != newbuf)
XLogRegisterBuffer(1, oldbuf, REGBUF_STANDARD);
+ if (info & XLOG_HEAP_INIT_PAGE)
+ {
+ XLogRegisterData((char *) &HeapPageGetSpecial(page)->pd_xid_base, sizeof(TransactionId));
+ }
XLogRegisterData((char *) &xlrec, SizeOfHeapUpdate);
/*
@@ -8306,8 +9019,8 @@ log_heap_new_cid(Relation relation, HeapTuple tup)
{
Assert(!(hdr->t_infomask & HEAP_XMAX_INVALID));
Assert(!HeapTupleHeaderXminInvalid(hdr));
- xlrec.cmin = HeapTupleHeaderGetCmin(hdr);
- xlrec.cmax = HeapTupleHeaderGetCmax(hdr);
+ xlrec.cmin = HeapTupleGetCmin(tup);
+ xlrec.cmax = HeapTupleGetCmax(tup);
xlrec.combocid = HeapTupleHeaderGetRawCommandId(hdr);
}
/* No combo CID, so only cmin or cmax can be set by this TX */
@@ -8510,7 +9223,8 @@ heap_xlog_prune(XLogReaderState *record)
heap_page_prune_execute(buffer,
redirected, nredirected,
nowdead, ndead,
- nowunused, nunused);
+ nowunused, nunused,
+ true);
/*
* Note: we don't worry about updating the page's prunability hints.
@@ -8802,7 +9516,7 @@ heap_xlog_freeze_page(XLogReaderState *record)
lp = PageGetItemId(page, xlrec_tp->offset); /* offsets are one-based */
tuple = (HeapTupleHeader) PageGetItem(page, lp);
- heap_execute_freeze_tuple(tuple, xlrec_tp);
+ heap_execute_freeze_tuple_page(page, tuple, xlrec_tp);
}
PageSetLSN(page, lsn);
@@ -8889,9 +9603,9 @@ heap_xlog_delete(XLogReaderState *record)
fix_infomask_from_infobits(xlrec->infobits_set,
&htup->t_infomask, &htup->t_infomask2);
if (!(xlrec->flags & XLH_DELETE_IS_SUPER))
- HeapTupleHeaderSetXmax(htup, xlrec->xmax);
+ HeapTupleHeaderSetXmax(page, htup, xlrec->xmax);
else
- HeapTupleHeaderSetXmin(htup, InvalidTransactionId);
+ HeapTupleHeaderSetXmin(page, htup, InvalidTransactionId);
HeapTupleHeaderSetCmax(htup, FirstCommandId, false);
/* Mark the page as a candidate for pruning */
@@ -8916,7 +9630,7 @@ static void
heap_xlog_insert(XLogReaderState *record)
{
XLogRecPtr lsn = record->EndRecPtr;
- xl_heap_insert *xlrec = (xl_heap_insert *) XLogRecGetData(record);
+ xl_heap_insert *xlrec;
Buffer buffer;
Page page;
union
@@ -8932,6 +9646,17 @@ heap_xlog_insert(XLogReaderState *record)
BlockNumber blkno;
ItemPointerData target_tid;
XLogRedoAction action;
+ bool isinit = (XLogRecGetInfo(record) & XLOG_HEAP_INIT_PAGE) != 0;
+ TransactionId pd_xid_base = InvalidTransactionId;
+ Pointer rec_data = (Pointer) XLogRecGetData(record);
+
+ if (isinit)
+ {
+ pd_xid_base = *((TransactionId *) rec_data);
+ rec_data += sizeof(TransactionId);
+ }
+
+ xlrec = (xl_heap_insert *) rec_data;
XLogRecGetBlockTag(record, 0, &target_node, NULL, &blkno);
ItemPointerSetBlockNumber(&target_tid, blkno);
@@ -8956,11 +9681,12 @@ heap_xlog_insert(XLogReaderState *record)
* If we inserted the first and only tuple on the page, re-initialize the
* page from scratch.
*/
- if (XLogRecGetInfo(record) & XLOG_HEAP_INIT_PAGE)
+ if (isinit)
{
buffer = XLogInitBufferForRedo(record, 0);
page = BufferGetPage(buffer);
- PageInit(page, BufferGetPageSize(buffer), 0);
+ PageInit(page, BufferGetPageSize(buffer), sizeof(HeapPageSpecialData));
+ HeapPageGetSpecial(page)->pd_xid_base = pd_xid_base;
action = BLK_NEEDS_REDO;
}
else
@@ -8992,7 +9718,7 @@ heap_xlog_insert(XLogReaderState *record)
htup->t_infomask2 = xlhdr.t_infomask2;
htup->t_infomask = xlhdr.t_infomask;
htup->t_hoff = xlhdr.t_hoff;
- HeapTupleHeaderSetXmin(htup, XLogRecGetXid(record));
+ HeapTupleHeaderSetXmin(page, htup, XLogRecGetXid(record));
HeapTupleHeaderSetCmin(htup, FirstCommandId);
htup->t_ctid = target_tid;
@@ -9052,12 +9778,19 @@ heap_xlog_multi_insert(XLogReaderState *record)
int i;
bool isinit = (XLogRecGetInfo(record) & XLOG_HEAP_INIT_PAGE) != 0;
XLogRedoAction action;
+ TransactionId pd_xid_base = InvalidTransactionId;
+ Pointer rec_data = (Pointer) XLogRecGetData(record);
/*
* Insertion doesn't overwrite MVCC data, so no conflict processing is
* required.
*/
- xlrec = (xl_heap_multi_insert *) XLogRecGetData(record);
+ if (isinit)
+ {
+ pd_xid_base = *((TransactionId *) rec_data);
+ rec_data += sizeof(TransactionId);
+ }
+ xlrec = (xl_heap_multi_insert *) rec_data;
XLogRecGetBlockTag(record, 0, &rnode, NULL, &blkno);
@@ -9084,7 +9817,8 @@ heap_xlog_multi_insert(XLogReaderState *record)
{
buffer = XLogInitBufferForRedo(record, 0);
page = BufferGetPage(buffer);
- PageInit(page, BufferGetPageSize(buffer), 0);
+ PageInit(page, BufferGetPageSize(buffer), sizeof(HeapPageSpecialData));
+ HeapPageGetSpecial(page)->pd_xid_base = pd_xid_base;
action = BLK_NEEDS_REDO;
}
else
@@ -9135,7 +9869,7 @@ heap_xlog_multi_insert(XLogReaderState *record)
htup->t_infomask2 = xlhdr->t_infomask2;
htup->t_infomask = xlhdr->t_infomask;
htup->t_hoff = xlhdr->t_hoff;
- HeapTupleHeaderSetXmin(htup, XLogRecGetXid(record));
+ HeapTupleHeaderSetXmin(page, htup, XLogRecGetXid(record));
HeapTupleHeaderSetCmin(htup, FirstCommandId);
ItemPointerSetBlockNumber(&htup->t_ctid, blkno);
ItemPointerSetOffsetNumber(&htup->t_ctid, offnum);
@@ -9183,7 +9917,7 @@ static void
heap_xlog_update(XLogReaderState *record, bool hot_update)
{
XLogRecPtr lsn = record->EndRecPtr;
- xl_heap_update *xlrec = (xl_heap_update *) XLogRecGetData(record);
+ xl_heap_update *xlrec;
RelFileNode rnode;
BlockNumber oldblk;
BlockNumber newblk;
@@ -9208,6 +9942,17 @@ heap_xlog_update(XLogReaderState *record, bool hot_update)
Size freespace = 0;
XLogRedoAction oldaction;
XLogRedoAction newaction;
+ bool isinit = (XLogRecGetInfo(record) & XLOG_HEAP_INIT_PAGE) != 0;
+ TransactionId pd_xid_base = InvalidTransactionId;
+ Pointer rec_data = (Pointer) XLogRecGetData(record);
+
+ if (isinit)
+ {
+ pd_xid_base = *((TransactionId *) rec_data);
+ rec_data += sizeof(TransactionId);
+ }
+
+ xlrec = (xl_heap_update *) rec_data;
/* initialize to keep the compiler quiet */
oldtup.t_data = NULL;
@@ -9266,6 +10011,7 @@ heap_xlog_update(XLogReaderState *record, bool hot_update)
oldtup.t_data = htup;
oldtup.t_len = ItemIdGetLength(lp);
+ HeapTupleCopyBaseFromPage(&oldtup, page);
htup->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
htup->t_infomask2 &= ~HEAP_KEYS_UPDATED;
@@ -9275,7 +10021,7 @@ heap_xlog_update(XLogReaderState *record, bool hot_update)
HeapTupleHeaderClearHotUpdated(htup);
fix_infomask_from_infobits(xlrec->old_infobits_set, &htup->t_infomask,
&htup->t_infomask2);
- HeapTupleHeaderSetXmax(htup, xlrec->old_xmax);
+ HeapTupleHeaderSetXmax(page, htup, xlrec->old_xmax);
HeapTupleHeaderSetCmax(htup, FirstCommandId, false);
/* Set forward chain link in t_ctid */
htup->t_ctid = newtid;
@@ -9298,11 +10044,12 @@ heap_xlog_update(XLogReaderState *record, bool hot_update)
nbuffer = obuffer;
newaction = oldaction;
}
- else if (XLogRecGetInfo(record) & XLOG_HEAP_INIT_PAGE)
+ else if (isinit)
{
nbuffer = XLogInitBufferForRedo(record, 0);
page = (Page) BufferGetPage(nbuffer);
- PageInit(page, BufferGetPageSize(nbuffer), 0);
+ PageInit(page, BufferGetPageSize(nbuffer), sizeof(HeapPageSpecialData));
+ HeapPageGetSpecial(page)->pd_xid_base = pd_xid_base;
newaction = BLK_NEEDS_REDO;
}
else
@@ -9408,9 +10155,9 @@ heap_xlog_update(XLogReaderState *record, bool hot_update)
htup->t_infomask = xlhdr.t_infomask;
htup->t_hoff = xlhdr.t_hoff;
- HeapTupleHeaderSetXmin(htup, XLogRecGetXid(record));
+ HeapTupleHeaderSetXmin(page, htup, XLogRecGetXid(record));
HeapTupleHeaderSetCmin(htup, FirstCommandId);
- HeapTupleHeaderSetXmax(htup, xlrec->new_xmax);
+ HeapTupleHeaderSetXmax(page, htup, xlrec->new_xmax);
/* Make sure there is no forward chain link in t_ctid */
htup->t_ctid = newtid;
@@ -9549,7 +10296,7 @@ heap_xlog_lock(XLogReaderState *record)
BufferGetBlockNumber(buffer),
offnum);
}
- HeapTupleHeaderSetXmax(htup, xlrec->locking_xid);
+ HeapTupleHeaderSetXmax(page, htup, xlrec->locking_xid);
HeapTupleHeaderSetCmax(htup, FirstCommandId, false);
PageSetLSN(page, lsn);
MarkBufferDirty(buffer);
@@ -9609,7 +10356,7 @@ heap_xlog_lock_updated(XLogReaderState *record)
htup->t_infomask2 &= ~HEAP_KEYS_UPDATED;
fix_infomask_from_infobits(xlrec->infobits_set, &htup->t_infomask,
&htup->t_infomask2);
- HeapTupleHeaderSetXmax(htup, xlrec->xmax);
+ HeapTupleHeaderSetXmax(page, htup, xlrec->xmax);
PageSetLSN(page, lsn);
MarkBufferDirty(buffer);
@@ -9659,6 +10406,31 @@ heap_xlog_inplace(XLogReaderState *record)
UnlockReleaseBuffer(buffer);
}
+static void
+heap_xlog_base_shift(XLogReaderState *record)
+{
+ XLogRecPtr lsn = record->EndRecPtr;
+ xl_heap_base_shift *xlrec = (xl_heap_base_shift *) XLogRecGetData(record);
+ Buffer buffer;
+ Page page;
+ BlockNumber blkno;
+ RelFileNode target_node;
+
+ XLogRecGetBlockTag(record, 0, &target_node, NULL, &blkno);
+
+ if (XLogReadBufferForRedo(record, 0, &buffer) == BLK_NEEDS_REDO)
+ {
+ page = BufferGetPage(buffer);
+ heap_page_shift_base(NULL, InvalidBuffer, page, xlrec->multi, xlrec->delta);
+ PageSetLSN(page, lsn);
+ MarkBufferDirty(buffer);
+ }
+
+ if (BufferIsValid(buffer))
+ UnlockReleaseBuffer(buffer);
+}
+
+
void
heap_redo(XLogReaderState *record)
{
@@ -9745,6 +10517,21 @@ heap2_redo(XLogReaderState *record)
}
}
+void
+heap3_redo(XLogReaderState *record)
+{
+ uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+
+ switch (info & XLOG_HEAP_OPMASK)
+ {
+ case XLOG_HEAP3_BASE_SHIFT:
+ heap_xlog_base_shift(record);
+ break;
+ default:
+ elog(PANIC, "heap3_redo: unknown op code %u", info);
+ }
+}
+
/*
* Mask a heap page before performing consistency checks on it.
*/
@@ -9757,6 +10544,10 @@ heap_mask(char *pagedata, BlockNumber blkno)
mask_page_lsn_and_checksum(page);
mask_page_hint_bits(page);
+
+ /* Ignore prune_xid (it's like a hint-bit) */
+ HeapPageSetPruneXid(page, InvalidTransactionId);
+
mask_unused_space(page);
for (off = 1; off <= PageGetMaxOffsetNumber(page); off++)
@@ -9872,14 +10663,14 @@ HeapCheckForSerializableConflictOut(bool visible, Relation relation,
case HEAPTUPLE_LIVE:
if (visible)
return;
- xid = HeapTupleHeaderGetXmin(tuple->t_data);
+ xid = HeapTupleGetXmin(tuple);
break;
case HEAPTUPLE_RECENTLY_DEAD:
case HEAPTUPLE_DELETE_IN_PROGRESS:
if (visible)
- xid = HeapTupleHeaderGetUpdateXid(tuple->t_data);
+ xid = HeapTupleGetUpdateXidAny(tuple);
else
- xid = HeapTupleHeaderGetXmin(tuple->t_data);
+ xid = HeapTupleGetXmin(tuple);
if (TransactionIdPrecedes(xid, TransactionXmin))
{
@@ -9889,7 +10680,7 @@ HeapCheckForSerializableConflictOut(bool visible, Relation relation,
}
break;
case HEAPTUPLE_INSERT_IN_PROGRESS:
- xid = HeapTupleHeaderGetXmin(tuple->t_data);
+ xid = HeapTupleGetXmin(tuple);
break;
case HEAPTUPLE_DEAD:
Assert(!visible);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 39ef8a0b77d..93e153773a1 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -413,7 +413,7 @@ tuple_lock_retry:
* changes in an existing tuple, except to invalid or
* frozen, and neither of those can match priorXmax.)
*/
- if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple->t_data),
+ if (!TransactionIdEquals(HeapTupleGetXmin(tuple),
priorXmax))
{
ReleaseBuffer(buffer);
@@ -424,7 +424,7 @@ tuple_lock_retry:
if (TransactionIdIsValid(SnapshotDirty.xmin))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
- errmsg_internal("t_xmin %u is uncommitted in tuple (%u,%u) to be updated in table \"%s\"",
+ errmsg_internal("t_xmin " XID_FMT " is uncommitted in tuple (%u,%u) to be updated in table \"%s\"",
SnapshotDirty.xmin,
ItemPointerGetBlockNumber(&tuple->t_self),
ItemPointerGetOffsetNumber(&tuple->t_self),
@@ -473,7 +473,7 @@ tuple_lock_retry:
* variable instead of doing HeapTupleHeaderGetXmin again.
*/
if (TransactionIdIsCurrentTransactionId(priorXmax) &&
- HeapTupleHeaderGetCmin(tuple->t_data) >= cid)
+ HeapTupleGetCmin(tuple) >= cid)
{
tmfd->xmax = priorXmax;
@@ -481,7 +481,7 @@ tuple_lock_retry:
* Cmin is the problematic value, so store that. See
* above.
*/
- tmfd->cmax = HeapTupleHeaderGetCmin(tuple->t_data);
+ tmfd->cmax = HeapTupleGetCmin(tuple);
ReleaseBuffer(buffer);
return TM_SelfModified;
}
@@ -506,7 +506,7 @@ tuple_lock_retry:
/*
* As above, if xmin isn't what we're expecting, do nothing.
*/
- if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple->t_data),
+ if (!TransactionIdEquals(HeapTupleGetXmin(tuple),
priorXmax))
{
if (BufferIsValid(buffer))
@@ -539,7 +539,7 @@ tuple_lock_retry:
/* updated, so look at the updated row */
*tid = tuple->t_data->t_ctid;
/* updated row should have xmin matching this xmax */
- priorXmax = HeapTupleHeaderGetUpdateXid(tuple->t_data);
+ priorXmax = HeapTupleGetUpdateXidAny(tuple);
if (BufferIsValid(buffer))
ReleaseBuffer(buffer);
/* loop back to fetch next in chain */
@@ -860,7 +860,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
* case we had better copy it.
*/
if (!is_system_catalog &&
- !TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple->t_data)))
+ !TransactionIdIsCurrentTransactionId(HeapTupleGetXmin(tuple)))
elog(WARNING, "concurrent insert in progress within table \"%s\"",
RelationGetRelationName(OldHeap));
/* treat as live */
@@ -872,7 +872,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
* Similar situation to INSERT_IN_PROGRESS case.
*/
if (!is_system_catalog &&
- !TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple->t_data)))
+ !TransactionIdIsCurrentTransactionId(HeapTupleGetUpdateXidAny(tuple)))
elog(WARNING, "concurrent delete in progress within table \"%s\"",
RelationGetRelationName(OldHeap));
/* treat as recently dead */
@@ -1057,6 +1057,7 @@ heapam_scan_analyze_next_tuple(TableScanDesc scan, TransactionId OldestXmin,
targtuple->t_tableOid = RelationGetRelid(scan->rs_rd);
targtuple->t_data = (HeapTupleHeader) PageGetItem(targpage, itemid);
targtuple->t_len = ItemIdGetLength(itemid);
+ HeapTupleCopyBaseFromPage(targtuple, targpage);
switch (HeapTupleSatisfiesVacuum(targtuple, OldestXmin,
hscan->rs_cbuf))
@@ -1092,7 +1093,7 @@ heapam_scan_analyze_next_tuple(TableScanDesc scan, TransactionId OldestXmin,
* numbers we send to the stats collector to make this come
* out right.)
*/
- if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(targtuple->t_data)))
+ if (TransactionIdIsCurrentTransactionId(HeapTupleGetXmin(targtuple)))
{
sample_it = true;
*liverows += 1;
@@ -1123,7 +1124,7 @@ heapam_scan_analyze_next_tuple(TableScanDesc scan, TransactionId OldestXmin,
* but not the post-image. We also get sane results if the
* concurrent transaction never commits.
*/
- if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(targtuple->t_data)))
+ if (TransactionIdIsCurrentTransactionId(HeapTupleGetUpdateXidAny(targtuple)))
*deadrows += 1;
else
{
@@ -1465,7 +1466,7 @@ heapam_index_build_range_scan(Relation heapRelation,
* before commit there. Give a warning if neither case
* applies.
*/
- xwait = HeapTupleHeaderGetXmin(heapTuple->t_data);
+ xwait = HeapTupleGetXmin(heapTuple);
if (!TransactionIdIsCurrentTransactionId(xwait))
{
if (!is_system_catalog)
@@ -1524,7 +1525,7 @@ heapam_index_build_range_scan(Relation heapRelation,
break;
}
- xwait = HeapTupleHeaderGetUpdateXid(heapTuple->t_data);
+ xwait = HeapTupleGetUpdateXidAny(heapTuple);
if (!TransactionIdIsCurrentTransactionId(xwait))
{
if (!is_system_catalog)
@@ -2202,13 +2203,14 @@ heapam_scan_bitmap_next_block(TableScanDesc scan,
loctup.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lp);
loctup.t_len = ItemIdGetLength(lp);
loctup.t_tableOid = scan->rs_rd->rd_id;
+ HeapTupleCopyBaseFromPage(&loctup, dp);
ItemPointerSet(&loctup.t_self, page, offnum);
valid = HeapTupleSatisfiesVisibility(&loctup, snapshot, buffer);
if (valid)
{
hscan->rs_vistuples[ntup++] = offnum;
PredicateLockTID(scan->rs_rd, &loctup.t_self, snapshot,
- HeapTupleHeaderGetXmin(loctup.t_data));
+ HeapTupleGetXmin(&loctup));
}
HeapCheckForSerializableConflictOut(valid, scan->rs_rd, &loctup,
buffer, snapshot);
@@ -2247,6 +2249,7 @@ heapam_scan_bitmap_next_tuple(TableScanDesc scan,
hscan->rs_ctup.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lp);
hscan->rs_ctup.t_len = ItemIdGetLength(lp);
hscan->rs_ctup.t_tableOid = scan->rs_rd->rd_id;
+ HeapTupleCopyBaseFromPage(&hscan->rs_ctup, dp);
ItemPointerSet(&hscan->rs_ctup.t_self, hscan->rs_cblock, targoffset);
pgstat_count_heap_fetch(scan->rs_rd);
@@ -2387,6 +2390,7 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
tuple->t_data = (HeapTupleHeader) PageGetItem(page, itemid);
tuple->t_len = ItemIdGetLength(itemid);
+ HeapTupleCopyBaseFromPage(tuple, page);
ItemPointerSet(&(tuple->t_self), blockno, tupoffset);
diff --git a/src/backend/access/heap/heapam_visibility.c b/src/backend/access/heap/heapam_visibility.c
index ceadac70d57..5adfb9d12c9 100644
--- a/src/backend/access/heap/heapam_visibility.c
+++ b/src/backend/access/heap/heapam_visibility.c
@@ -217,7 +217,7 @@ HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
}
}
}
- else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+ else if (TransactionIdIsCurrentTransactionId(HeapTupleGetRawXmin(htup)))
{
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
return true;
@@ -229,7 +229,7 @@ HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
{
TransactionId xmax;
- xmax = HeapTupleGetUpdateXid(tuple);
+ xmax = HeapTupleGetUpdateXid(htup);
/* not LOCKED_ONLY, so it has to have an xmax */
Assert(TransactionIdIsValid(xmax));
@@ -241,7 +241,7 @@ HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
return false;
}
- if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+ if (!TransactionIdIsCurrentTransactionId(HeapTupleGetRawXmax(htup)))
{
/* deleting subtransaction must have aborted */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
@@ -251,11 +251,11 @@ HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
return false;
}
- else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+ else if (TransactionIdIsInProgress(HeapTupleGetRawXmin(htup)))
return false;
- else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+ else if (TransactionIdDidCommit(HeapTupleGetRawXmin(htup)))
SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
- HeapTupleHeaderGetRawXmin(tuple));
+ HeapTupleGetRawXmin(htup));
else
{
/* it must have aborted or crashed */
@@ -284,7 +284,7 @@ HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
return true;
- xmax = HeapTupleGetUpdateXid(tuple);
+ xmax = HeapTupleGetUpdateXid(htup);
/* not LOCKED_ONLY, so it has to have an xmax */
Assert(TransactionIdIsValid(xmax));
@@ -299,17 +299,17 @@ HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
return true;
}
- if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+ if (TransactionIdIsCurrentTransactionId(HeapTupleGetRawXmax(htup)))
{
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
return true;
return false;
}
- if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+ if (TransactionIdIsInProgress(HeapTupleGetRawXmax(htup)))
return true;
- if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+ if (!TransactionIdDidCommit(HeapTupleGetRawXmax(htup)))
{
/* it must have aborted or crashed */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
@@ -327,7 +327,7 @@ HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
}
SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
- HeapTupleHeaderGetRawXmax(tuple));
+ HeapTupleGetRawXmax(htup));
return false;
}
@@ -416,7 +416,7 @@ HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot,
* is canceled by super-deleting the tuple. This also applies to
* TOAST tuples created during speculative insertion.
*/
- else if (!TransactionIdIsValid(HeapTupleHeaderGetXmin(tuple)))
+ else if (!TransactionIdIsValid(HeapTupleGetXmin(htup)))
return false;
}
@@ -506,9 +506,9 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
}
}
}
- else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+ else if (TransactionIdIsCurrentTransactionId(HeapTupleGetRawXmin(htup)))
{
- if (HeapTupleHeaderGetCmin(tuple) >= curcid)
+ if (HeapTupleGetCmin(htup) >= curcid)
return TM_Invisible; /* inserted after scan started */
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
@@ -518,7 +518,7 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
{
TransactionId xmax;
- xmax = HeapTupleHeaderGetRawXmax(tuple);
+ xmax = HeapTupleGetRawXmax(htup);
/*
* Careful here: even though this tuple was created by our own
@@ -549,7 +549,7 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
{
TransactionId xmax;
- xmax = HeapTupleGetUpdateXid(tuple);
+ xmax = HeapTupleGetUpdateXid(htup);
/* not LOCKED_ONLY, so it has to have an xmax */
Assert(TransactionIdIsValid(xmax));
@@ -557,21 +557,21 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
/* deleting subtransaction must have aborted */
if (!TransactionIdIsCurrentTransactionId(xmax))
{
- if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple),
+ if (MultiXactIdIsRunning(HeapTupleGetRawXmax(htup),
false))
return TM_BeingModified;
return TM_Ok;
}
else
{
- if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+ if (HeapTupleGetCmax(htup) >= curcid)
return TM_SelfModified; /* updated after scan started */
else
return TM_Invisible; /* updated before scan started */
}
}
- if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+ if (!TransactionIdIsCurrentTransactionId(HeapTupleGetRawXmax(htup)))
{
/* deleting subtransaction must have aborted */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
@@ -579,16 +579,16 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
return TM_Ok;
}
- if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+ if (HeapTupleGetCmax(htup) >= curcid)
return TM_SelfModified; /* updated after scan started */
else
return TM_Invisible; /* updated before scan started */
}
- else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+ else if (TransactionIdIsInProgress(HeapTupleGetRawXmin(htup)))
return TM_Invisible;
- else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+ else if (TransactionIdDidCommit(HeapTupleGetRawXmin(htup)))
SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
- HeapTupleHeaderGetRawXmin(tuple));
+ HeapTupleGetRawXmin(htup));
else
{
/* it must have aborted or crashed */
@@ -622,17 +622,17 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
{
- if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), true))
+ if (MultiXactIdIsRunning(HeapTupleGetRawXmax(htup), true))
return TM_BeingModified;
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
return TM_Ok;
}
- xmax = HeapTupleGetUpdateXid(tuple);
+ xmax = HeapTupleGetUpdateXid(htup);
if (!TransactionIdIsValid(xmax))
{
- if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+ if (MultiXactIdIsRunning(HeapTupleGetRawXmax(htup), false))
return TM_BeingModified;
}
@@ -641,13 +641,13 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
if (TransactionIdIsCurrentTransactionId(xmax))
{
- if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+ if (HeapTupleGetCmax(htup) >= curcid)
return TM_SelfModified; /* updated after scan started */
else
return TM_Invisible; /* updated before scan started */
}
- if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+ if (MultiXactIdIsRunning(HeapTupleGetRawXmax(htup), false))
return TM_BeingModified;
if (TransactionIdDidCommit(xmax))
@@ -663,7 +663,7 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
* what about the other members?
*/
- if (!MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+ if (!MultiXactIdIsRunning(HeapTupleGetRawXmax(htup), false))
{
/*
* There's no member, even just a locker, alive anymore, so we can
@@ -680,20 +680,20 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
}
}
- if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+ if (TransactionIdIsCurrentTransactionId(HeapTupleGetRawXmax(htup)))
{
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
return TM_BeingModified;
- if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+ if (HeapTupleGetCmax(htup) >= curcid)
return TM_SelfModified; /* updated after scan started */
else
return TM_Invisible; /* updated before scan started */
}
- if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+ if (TransactionIdIsInProgress(HeapTupleGetRawXmax(htup)))
return TM_BeingModified;
- if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+ if (!TransactionIdDidCommit(HeapTupleGetRawXmax(htup)))
{
/* it must have aborted or crashed */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
@@ -711,7 +711,7 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
}
SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
- HeapTupleHeaderGetRawXmax(tuple));
+ HeapTupleGetRawXmax(htup));
if (!ItemPointerEquals(&htup->t_self, &tuple->t_ctid))
return TM_Updated; /* updated by other */
else
@@ -794,7 +794,7 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
}
}
}
- else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+ else if (TransactionIdIsCurrentTransactionId(HeapTupleGetRawXmin(htup)))
{
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
return true;
@@ -806,7 +806,7 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
{
TransactionId xmax;
- xmax = HeapTupleGetUpdateXid(tuple);
+ xmax = HeapTupleGetUpdateXid(htup);
/* not LOCKED_ONLY, so it has to have an xmax */
Assert(TransactionIdIsValid(xmax));
@@ -818,7 +818,7 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
return false;
}
- if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+ if (!TransactionIdIsCurrentTransactionId(HeapTupleGetRawXmax(htup)))
{
/* deleting subtransaction must have aborted */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
@@ -828,7 +828,7 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
return false;
}
- else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+ else if (TransactionIdIsInProgress(HeapTupleGetRawXmin(htup)))
{
/*
* Return the speculative token to caller. Caller can worry about
@@ -844,13 +844,13 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
Assert(snapshot->speculativeToken != 0);
}
- snapshot->xmin = HeapTupleHeaderGetRawXmin(tuple);
+ snapshot->xmin = HeapTupleGetRawXmin(htup);
/* XXX shouldn't we fall through to look at xmax? */
return true; /* in insertion by other */
}
- else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+ else if (TransactionIdDidCommit(HeapTupleGetRawXmin(htup)))
SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
- HeapTupleHeaderGetRawXmin(tuple));
+ HeapTupleGetRawXmin(htup));
else
{
/* it must have aborted or crashed */
@@ -879,7 +879,7 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
return true;
- xmax = HeapTupleGetUpdateXid(tuple);
+ xmax = HeapTupleGetUpdateXid(htup);
/* not LOCKED_ONLY, so it has to have an xmax */
Assert(TransactionIdIsValid(xmax));
@@ -897,21 +897,21 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
return true;
}
- if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+ if (TransactionIdIsCurrentTransactionId(HeapTupleGetRawXmax(htup)))
{
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
return true;
return false;
}
- if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+ if (TransactionIdIsInProgress(HeapTupleGetRawXmax(htup)))
{
if (!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
- snapshot->xmax = HeapTupleHeaderGetRawXmax(tuple);
+ snapshot->xmax = HeapTupleGetRawXmax(htup);
return true;
}
- if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+ if (!TransactionIdDidCommit(HeapTupleGetRawXmax(htup)))
{
/* it must have aborted or crashed */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
@@ -929,7 +929,7 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
}
SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
- HeapTupleHeaderGetRawXmax(tuple));
+ HeapTupleGetRawXmax(htup));
return false; /* updated by other */
}
@@ -1008,9 +1008,9 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
}
}
}
- else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+ else if (TransactionIdIsCurrentTransactionId(HeapTupleGetRawXmin(htup)))
{
- if (HeapTupleHeaderGetCmin(tuple) >= snapshot->curcid)
+ if (HeapTupleGetCmin(htup) >= snapshot->curcid)
return false; /* inserted after scan started */
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
@@ -1023,7 +1023,7 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
{
TransactionId xmax;
- xmax = HeapTupleGetUpdateXid(tuple);
+ xmax = HeapTupleGetUpdateXid(htup);
/* not LOCKED_ONLY, so it has to have an xmax */
Assert(TransactionIdIsValid(xmax));
@@ -1031,13 +1031,13 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
/* updating subtransaction must have aborted */
if (!TransactionIdIsCurrentTransactionId(xmax))
return true;
- else if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+ else if (HeapTupleGetCmax(htup) >= snapshot->curcid)
return true; /* updated after scan started */
else
return false; /* updated before scan started */
}
- if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+ if (!TransactionIdIsCurrentTransactionId(HeapTupleGetRawXmax(htup)))
{
/* deleting subtransaction must have aborted */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
@@ -1045,16 +1045,16 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
return true;
}
- if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+ if (HeapTupleGetCmax(htup) >= snapshot->curcid)
return true; /* deleted after scan started */
else
return false; /* deleted before scan started */
}
- else if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
+ else if (XidInMVCCSnapshot(HeapTupleGetRawXmin(htup), snapshot))
return false;
- else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+ else if (TransactionIdDidCommit(HeapTupleGetRawXmin(htup)))
SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
- HeapTupleHeaderGetRawXmin(tuple));
+ HeapTupleGetRawXmin(htup));
else
{
/* it must have aborted or crashed */
@@ -1067,7 +1067,7 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
{
/* xmin is committed, but maybe not according to our snapshot */
if (!HeapTupleHeaderXminFrozen(tuple) &&
- XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
+ XidInMVCCSnapshot(HeapTupleGetRawXmin(htup), snapshot))
return false; /* treat as still in progress */
}
@@ -1086,14 +1086,14 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
/* already checked above */
Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
- xmax = HeapTupleGetUpdateXid(tuple);
+ xmax = HeapTupleGetUpdateXid(htup);
/* not LOCKED_ONLY, so it has to have an xmax */
Assert(TransactionIdIsValid(xmax));
if (TransactionIdIsCurrentTransactionId(xmax))
{
- if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+ if (HeapTupleGetCmax(htup) >= snapshot->curcid)
return true; /* deleted after scan started */
else
return false; /* deleted before scan started */
@@ -1108,18 +1108,18 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
{
- if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+ if (TransactionIdIsCurrentTransactionId(HeapTupleGetRawXmax(htup)))
{
- if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+ if (HeapTupleGetCmax(htup) >= snapshot->curcid)
return true; /* deleted after scan started */
else
return false; /* deleted before scan started */
}
- if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
+ if (XidInMVCCSnapshot(HeapTupleGetRawXmax(htup), snapshot))
return true;
- if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+ if (!TransactionIdDidCommit(HeapTupleGetRawXmax(htup)))
{
/* it must have aborted or crashed */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
@@ -1129,12 +1129,12 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
/* xmax transaction committed */
SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
- HeapTupleHeaderGetRawXmax(tuple));
+ HeapTupleGetRawXmax(htup));
}
else
{
/* xmax is committed, but maybe not according to our snapshot */
- if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
+ if (XidInMVCCSnapshot(HeapTupleGetRawXmax(htup), snapshot))
return true; /* treat as still in progress */
}
@@ -1249,21 +1249,21 @@ HeapTupleSatisfiesVacuumHorizon(HeapTuple htup, Buffer buffer, TransactionId *de
return HEAPTUPLE_DEAD;
}
}
- else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+ else if (TransactionIdIsCurrentTransactionId(HeapTupleGetRawXmin(htup)))
{
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
return HEAPTUPLE_INSERT_IN_PROGRESS;
/* only locked? run infomask-only check first, for performance */
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask) ||
- HeapTupleHeaderIsOnlyLocked(tuple))
+ HeapTupleIsOnlyLocked(htup))
return HEAPTUPLE_INSERT_IN_PROGRESS;
/* inserted and then deleted by same xact */
- if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple)))
+ if (TransactionIdIsCurrentTransactionId(HeapTupleGetUpdateXidAny(htup)))
return HEAPTUPLE_DELETE_IN_PROGRESS;
/* deleting subtransaction must have aborted */
return HEAPTUPLE_INSERT_IN_PROGRESS;
}
- else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+ else if (TransactionIdIsInProgress(HeapTupleGetRawXmin(htup)))
{
/*
* It'd be possible to discern between INSERT/DELETE in progress
@@ -1275,9 +1275,9 @@ HeapTupleSatisfiesVacuumHorizon(HeapTuple htup, Buffer buffer, TransactionId *de
*/
return HEAPTUPLE_INSERT_IN_PROGRESS;
}
- else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+ else if (TransactionIdDidCommit(HeapTupleGetRawXmin(htup)))
SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
- HeapTupleHeaderGetRawXmin(tuple));
+ HeapTupleGetRawXmin(htup));
else
{
/*
@@ -1319,14 +1319,14 @@ HeapTupleSatisfiesVacuumHorizon(HeapTuple htup, Buffer buffer, TransactionId *de
* possibly be running; otherwise have to check.
*/
if (!HEAP_LOCKED_UPGRADED(tuple->t_infomask) &&
- MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple),
+ MultiXactIdIsRunning(HeapTupleGetRawXmax(htup),
true))
return HEAPTUPLE_LIVE;
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
}
else
{
- if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+ if (TransactionIdIsInProgress(HeapTupleGetRawXmax(htup)))
return HEAPTUPLE_LIVE;
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
InvalidTransactionId);
@@ -1344,7 +1344,7 @@ HeapTupleSatisfiesVacuumHorizon(HeapTuple htup, Buffer buffer, TransactionId *de
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
- TransactionId xmax = HeapTupleGetUpdateXid(tuple);
+ TransactionId xmax = HeapTupleGetUpdateXid(htup);
/* already checked above */
Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
@@ -1367,7 +1367,7 @@ HeapTupleSatisfiesVacuumHorizon(HeapTuple htup, Buffer buffer, TransactionId *de
*dead_after = xmax;
return HEAPTUPLE_RECENTLY_DEAD;
}
- else if (!MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+ else if (!MultiXactIdIsRunning(HeapTupleGetRawXmax(htup), false))
{
/*
* Not in Progress, Not Committed, so either Aborted or crashed.
@@ -1381,11 +1381,11 @@ HeapTupleSatisfiesVacuumHorizon(HeapTuple htup, Buffer buffer, TransactionId *de
if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
{
- if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+ if (TransactionIdIsInProgress(HeapTupleGetRawXmax(htup)))
return HEAPTUPLE_DELETE_IN_PROGRESS;
- else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+ else if (TransactionIdDidCommit(HeapTupleGetRawXmax(htup)))
SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
- HeapTupleHeaderGetRawXmax(tuple));
+ HeapTupleGetRawXmax(htup));
else
{
/*
@@ -1407,7 +1407,7 @@ HeapTupleSatisfiesVacuumHorizon(HeapTuple htup, Buffer buffer, TransactionId *de
* Deleter committed, allow caller to check if it was recent enough that
* some open transactions could still see the tuple.
*/
- *dead_after = HeapTupleHeaderGetRawXmax(tuple);
+ *dead_after = HeapTupleGetRawXmax(htup);
return HEAPTUPLE_RECENTLY_DEAD;
}
@@ -1503,7 +1503,7 @@ HeapTupleIsSurelyDead(HeapTuple htup, GlobalVisState *vistest)
/* Deleter committed, so tuple is dead if the XID is old enough. */
return GlobalVisTestIsRemovableXid(vistest,
- HeapTupleHeaderGetRawXmax(tuple));
+ HeapTupleGetRawXmax(htup));
}
/*
@@ -1516,8 +1516,9 @@ HeapTupleIsSurelyDead(HeapTuple htup, GlobalVisState *vistest)
* at the top of this file.
*/
bool
-HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple)
+HeapTupleIsOnlyLocked(HeapTuple htup)
{
+ HeapTupleHeader tuple = htup->t_data;
TransactionId xmax;
/* if there's no valid Xmax, then there's obviously no update either */
@@ -1528,7 +1529,7 @@ HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple)
return true;
/* invalid xmax means no update */
- if (!TransactionIdIsValid(HeapTupleHeaderGetRawXmax(tuple)))
+ if (!TransactionIdIsValid(HeapTupleGetRawXmax(htup)))
return true;
/*
@@ -1539,7 +1540,7 @@ HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple)
return false;
/* ... but if it's a multi, then perhaps the updating Xid aborted. */
- xmax = HeapTupleGetUpdateXid(tuple);
+ xmax = HeapTupleGetUpdateXid(htup);
/* not LOCKED_ONLY, so it has to have an xmax */
Assert(TransactionIdIsValid(xmax));
@@ -1587,8 +1588,8 @@ HeapTupleSatisfiesHistoricMVCC(HeapTuple htup, Snapshot snapshot,
Buffer buffer)
{
HeapTupleHeader tuple = htup->t_data;
- TransactionId xmin = HeapTupleHeaderGetXmin(tuple);
- TransactionId xmax = HeapTupleHeaderGetRawXmax(tuple);
+ TransactionId xmin = HeapTupleGetXmin(htup);
+ TransactionId xmax = HeapTupleGetRawXmax(htup);
Assert(ItemPointerIsValid(&htup->t_self));
Assert(htup->t_tableOid != InvalidOid);
@@ -1688,7 +1689,7 @@ HeapTupleSatisfiesHistoricMVCC(HeapTuple htup, Snapshot snapshot,
*/
else if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
- xmax = HeapTupleGetUpdateXid(tuple);
+ xmax = HeapTupleGetUpdateXid(htup);
}
/* check if it's one of our txids, toplevel is also in there */
diff --git a/src/backend/access/heap/heaptoast.c b/src/backend/access/heap/heaptoast.c
index 1575a81b01b..252e57cc1de 100644
--- a/src/backend/access/heap/heaptoast.c
+++ b/src/backend/access/heap/heaptoast.c
@@ -307,6 +307,7 @@ heap_toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
result_tuple->t_len = new_tuple_len;
result_tuple->t_self = newtup->t_self;
result_tuple->t_tableOid = newtup->t_tableOid;
+ HeapTupleCopyBase(result_tuple, newtup);
new_data = (HeapTupleHeader) ((char *) result_tuple + HEAPTUPLESIZE);
result_tuple->t_data = new_data;
@@ -395,6 +396,7 @@ toast_flatten_tuple(HeapTuple tup, TupleDesc tupleDesc)
*/
new_tuple->t_self = tup->t_self;
new_tuple->t_tableOid = tup->t_tableOid;
+ HeapTupleCopyBase(new_tuple, tup);
new_tuple->t_data->t_choice = tup->t_data->t_choice;
new_tuple->t_data->t_ctid = tup->t_data->t_ctid;
@@ -467,6 +469,7 @@ toast_flatten_tuple_to_datum(HeapTupleHeader tup,
ItemPointerSetInvalid(&(tmptup.t_self));
tmptup.t_tableOid = InvalidOid;
tmptup.t_data = tup;
+ HeapTupleSetZeroBase(&tmptup);
/*
* Break down the tuple into fields.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index ae2e2ce37a5..5668004d598 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -232,6 +232,9 @@ RelationAddExtraBlocks(Relation relation, BulkInsertState bistate)
BufferGetBlockNumber(buffer),
RelationGetRelationName(relation));
+ PageInit(page, BufferGetPageSize(buffer), sizeof(HeapPageSpecialData));
+ HeapPageGetSpecial(page)->pd_xid_base = RecentXmin - FirstNormalTransactionId;
+
/*
* Add the page to the FSM without initializing. If we were to
* initialize here, the page would potentially get flushed out to disk
@@ -243,7 +246,7 @@ RelationAddExtraBlocks(Relation relation, BulkInsertState bistate)
/* we'll need this info below */
blockNum = BufferGetBlockNumber(buffer);
- freespace = BufferGetPageSize(buffer) - SizeOfPageHeaderData;
+ freespace = BufferGetPageSize(buffer) - SizeOfPageHeaderData - MAXALIGN(sizeof(HeapPageSpecialData));
UnlockReleaseBuffer(buffer);
@@ -514,6 +517,9 @@ loop:
/*
* Now we can check to see if there's enough free space here. If so,
* we're done.
+ *
+ * "Double xmax" page is not suitable for any new tuple, since xmin
+ * can't be set there.
*/
page = BufferGetPage(buffer);
@@ -525,12 +531,13 @@ loop:
*/
if (PageIsNew(page))
{
- PageInit(page, BufferGetPageSize(buffer), 0);
+ PageInit(page, BufferGetPageSize(buffer), sizeof(HeapPageSpecialData));
MarkBufferDirty(buffer);
}
pageFreeSpace = PageGetHeapFreeSpace(page);
- if (targetFreeSpace <= pageFreeSpace)
+ if (targetFreeSpace <= pageFreeSpace &&
+ !HeapPageIsDoubleXmax(page))
{
/* use this page as future insert target, too */
RelationSetTargetBlock(relation, targetBlock);
@@ -635,7 +642,8 @@ loop:
BufferGetBlockNumber(buffer),
RelationGetRelationName(relation));
- PageInit(page, BufferGetPageSize(buffer), 0);
+ PageInit(page, BufferGetPageSize(buffer), sizeof(HeapPageSpecialData));
+ HeapPageGetSpecial(page)->pd_xid_base = RecentXmin - FirstNormalTransactionId;
MarkBufferDirty(buffer);
/*
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index b3e2eec52fa..ab84855a7c2 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -135,7 +135,7 @@ heap_page_prune_opt(Relation relation, Buffer buffer)
* determining the appropriate horizon is a waste if there's no prune_xid
* (i.e. no updates/deletes left potentially dead tuples around).
*/
- prune_xid = ((PageHeader) page)->pd_prune_xid;
+ prune_xid = HeapPageGetPruneXidNoAssert(page);
if (!TransactionIdIsValid(prune_xid))
return;
@@ -206,7 +206,7 @@ heap_page_prune_opt(Relation relation, Buffer buffer)
nnewlpdead;
ndeleted = heap_page_prune(relation, buffer, vistest, limited_xmin,
- limited_ts, &nnewlpdead, NULL);
+ limited_ts, &nnewlpdead, NULL, true);
/*
* Report the number of tuples reclaimed to pgstats. This is
@@ -265,7 +265,8 @@ heap_page_prune(Relation relation, Buffer buffer,
TransactionId old_snap_xmin,
TimestampTz old_snap_ts,
int *nnewlpdead,
- OffsetNumber *off_loc)
+ OffsetNumber *off_loc,
+ bool repairFragmentation)
{
int ndeleted = 0;
Page page = BufferGetPage(buffer);
@@ -336,6 +337,7 @@ heap_page_prune(Relation relation, Buffer buffer,
htup = (HeapTupleHeader) PageGetItem(page, itemid);
tup.t_data = htup;
tup.t_len = ItemIdGetLength(itemid);
+ HeapTupleCopyBaseFromPage(&tup, page);
ItemPointerSet(&(tup.t_self), blockno, offnum);
/*
@@ -390,13 +392,15 @@ heap_page_prune(Relation relation, Buffer buffer,
heap_page_prune_execute(buffer,
prstate.redirected, prstate.nredirected,
prstate.nowdead, prstate.ndead,
- prstate.nowunused, prstate.nunused);
+ prstate.nowunused, prstate.nunused,
+ repairFragmentation);
/*
* Update the page's pd_prune_xid field to either zero, or the lowest
* XID of any soon-prunable tuple.
*/
- ((PageHeader) page)->pd_prune_xid = prstate.new_prune_xid;
+ if (XidFitsPage(page, prstate.new_prune_xid))
+ HeapPageSetPruneXid(page, prstate.new_prune_xid);
/*
* Also clear the "page is full" flag, since there's no point in
@@ -458,10 +462,10 @@ heap_page_prune(Relation relation, Buffer buffer,
* point in repeating the prune/defrag process until something else
* happens to the page.
*/
- if (((PageHeader) page)->pd_prune_xid != prstate.new_prune_xid ||
+ if (HeapPageGetPruneXid(page) != prstate.new_prune_xid ||
PageIsFull(page))
{
- ((PageHeader) page)->pd_prune_xid = prstate.new_prune_xid;
+ HeapPageSetPruneXid(page, prstate.new_prune_xid);
PageClearFull(page);
MarkBufferDirtyHint(buffer, true);
}
@@ -598,6 +602,10 @@ heap_prune_chain(Buffer buffer, OffsetNumber rootoffnum, PruneState *prstate)
OffsetNumber chainitems[MaxHeapTuplesPerPage];
int nchain = 0,
i;
+ HeapTupleData tup;
+
+ tup.t_tableOid = RelationGetRelid(prstate->rel);
+ HeapTupleCopyBaseFromPage(&tup, dp);
rootlp = PageGetItemId(dp, rootoffnum);
@@ -609,6 +617,10 @@ heap_prune_chain(Buffer buffer, OffsetNumber rootoffnum, PruneState *prstate)
Assert(prstate->htsv[rootoffnum] != -1);
htup = (HeapTupleHeader) PageGetItem(dp, rootlp);
+ tup.t_data = htup;
+ tup.t_len = ItemIdGetLength(rootlp);
+ ItemPointerSet(&(tup.t_self), BufferGetBlockNumber(buffer), rootoffnum);
+
if (HeapTupleHeaderIsHeapOnly(htup))
{
/*
@@ -633,7 +645,7 @@ heap_prune_chain(Buffer buffer, OffsetNumber rootoffnum, PruneState *prstate)
!HeapTupleHeaderIsHotUpdated(htup))
{
heap_prune_record_unused(prstate, rootoffnum);
- HeapTupleHeaderAdvanceLatestRemovedXid(htup,
+ HeapTupleHeaderAdvanceLatestRemovedXid(&tup,
&prstate->latestRemovedXid);
ndeleted++;
}
@@ -700,11 +712,16 @@ heap_prune_chain(Buffer buffer, OffsetNumber rootoffnum, PruneState *prstate)
Assert(prstate->htsv[offnum] != -1);
htup = (HeapTupleHeader) PageGetItem(dp, lp);
+ tup.t_data = htup;
+ tup.t_len = ItemIdGetLength(lp);
+ HeapTupleCopyBaseFromPage(&tup, dp);
+ ItemPointerSet(&(tup.t_self), BufferGetBlockNumber(buffer), offnum);
+
/*
* Check the tuple XMIN against prior XMAX, if any
*/
if (TransactionIdIsValid(priorXmax) &&
- !TransactionIdEquals(HeapTupleHeaderGetXmin(htup), priorXmax))
+ !TransactionIdEquals(HeapTupleGetXmin(&tup), priorXmax))
break;
/*
@@ -731,7 +748,7 @@ heap_prune_chain(Buffer buffer, OffsetNumber rootoffnum, PruneState *prstate)
* that the page is reconsidered for pruning in future.
*/
heap_prune_record_prunable(prstate,
- HeapTupleHeaderGetUpdateXid(htup));
+ HeapTupleGetUpdateXidAny(&tup));
break;
case HEAPTUPLE_DELETE_IN_PROGRESS:
@@ -741,7 +758,7 @@ heap_prune_chain(Buffer buffer, OffsetNumber rootoffnum, PruneState *prstate)
* that the page is reconsidered for pruning in future.
*/
heap_prune_record_prunable(prstate,
- HeapTupleHeaderGetUpdateXid(htup));
+ HeapTupleGetUpdateXidAny(&tup));
break;
case HEAPTUPLE_LIVE:
@@ -770,7 +787,7 @@ heap_prune_chain(Buffer buffer, OffsetNumber rootoffnum, PruneState *prstate)
if (tupdead)
{
latestdead = offnum;
- HeapTupleHeaderAdvanceLatestRemovedXid(htup,
+ HeapTupleHeaderAdvanceLatestRemovedXid(&tup,
&prstate->latestRemovedXid);
}
else if (!recent_dead)
@@ -792,7 +809,7 @@ heap_prune_chain(Buffer buffer, OffsetNumber rootoffnum, PruneState *prstate)
Assert(ItemPointerGetBlockNumber(&htup->t_ctid) ==
BufferGetBlockNumber(buffer));
offnum = ItemPointerGetOffsetNumber(&htup->t_ctid);
- priorXmax = HeapTupleHeaderGetUpdateXid(htup);
+ priorXmax = HeapTupleGetUpdateXidAny(&tup);
}
/*
@@ -909,7 +926,8 @@ void
heap_page_prune_execute(Buffer buffer,
OffsetNumber *redirected, int nredirected,
OffsetNumber *nowdead, int ndead,
- OffsetNumber *nowunused, int nunused)
+ OffsetNumber *nowunused, int nunused,
+ bool repairFragmentation)
{
Page page = (Page) BufferGetPage(buffer);
OffsetNumber *offnum;
@@ -1033,7 +1051,8 @@ heap_page_prune_execute(Buffer buffer,
* Finally, repair any fragmentation, and update the page's hint bit about
* whether it has free pointers.
*/
- PageRepairFragmentation(page);
+ if (repairFragmentation)
+ PageRepairFragmentation(page);
/*
* Now that the page has been modified, assert that redirect items still
@@ -1120,6 +1139,7 @@ heap_get_root_tuples(Page page, OffsetNumber *root_offsets)
HeapTupleHeader htup;
OffsetNumber nextoffnum;
TransactionId priorXmax;
+ HeapTupleData tup;
/* skip unused and dead items */
if (!ItemIdIsUsed(lp) || ItemIdIsDead(lp))
@@ -1128,6 +1148,8 @@ heap_get_root_tuples(Page page, OffsetNumber *root_offsets)
if (ItemIdIsNormal(lp))
{
htup = (HeapTupleHeader) PageGetItem(page, lp);
+ tup.t_data = htup;
+ HeapTupleCopyBaseFromPage(&tup, page);
/*
* Check if this tuple is part of a HOT-chain rooted at some other
@@ -1149,7 +1171,7 @@ heap_get_root_tuples(Page page, OffsetNumber *root_offsets)
/* Set up to scan the HOT-chain */
nextoffnum = ItemPointerGetOffsetNumber(&htup->t_ctid);
- priorXmax = HeapTupleHeaderGetUpdateXid(htup);
+ priorXmax = HeapTupleGetUpdateXidAny(&tup);
}
else
{
@@ -1188,9 +1210,11 @@ heap_get_root_tuples(Page page, OffsetNumber *root_offsets)
break;
htup = (HeapTupleHeader) PageGetItem(page, lp);
+ tup.t_data = htup;
+ HeapTupleCopyBaseFromPage(&tup, page);
if (TransactionIdIsValid(priorXmax) &&
- !TransactionIdEquals(priorXmax, HeapTupleHeaderGetXmin(htup)))
+ !TransactionIdEquals(priorXmax, HeapTupleGetXmin(&tup)))
break;
/* Remember the root line pointer for this item */
@@ -1204,7 +1228,7 @@ heap_get_root_tuples(Page page, OffsetNumber *root_offsets)
Assert(!HeapTupleHeaderIndicatesMovedPartitions(htup));
nextoffnum = ItemPointerGetOffsetNumber(&htup->t_ctid);
- priorXmax = HeapTupleHeaderGetUpdateXid(htup);
+ priorXmax = HeapTupleGetUpdateXidAny(&tup);
}
}
}
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index aa265edf601..64e69cc3a75 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -379,6 +379,7 @@ rewrite_heap_tuple(RewriteState state,
&old_tuple->t_data->t_choice.t_heap,
sizeof(HeapTupleFields));
+ HeapTupleCopyBase(new_tuple, old_tuple);
new_tuple->t_data->t_infomask &= ~HEAP_XACT_MASK;
new_tuple->t_data->t_infomask2 &= ~HEAP2_XACT_MASK;
new_tuple->t_data->t_infomask |=
@@ -388,7 +389,7 @@ rewrite_heap_tuple(RewriteState state,
* While we have our hands on the tuple, we may as well freeze any
* eligible xmin or xmax, so that future VACUUM effort can be saved.
*/
- heap_freeze_tuple(new_tuple->t_data,
+ heap_freeze_tuple(new_tuple,
state->rs_old_rel->rd_rel->relfrozenxid,
state->rs_old_rel->rd_rel->relminmxid,
state->rs_freeze_xid,
@@ -404,7 +405,7 @@ rewrite_heap_tuple(RewriteState state,
* If the tuple has been updated, check the old-to-new mapping hash table.
*/
if (!((old_tuple->t_data->t_infomask & HEAP_XMAX_INVALID) ||
- HeapTupleHeaderIsOnlyLocked(old_tuple->t_data)) &&
+ HeapTupleIsOnlyLocked(old_tuple)) &&
!HeapTupleHeaderIndicatesMovedPartitions(old_tuple->t_data) &&
!(ItemPointerEquals(&(old_tuple->t_self),
&(old_tuple->t_data->t_ctid))))
@@ -412,7 +413,7 @@ rewrite_heap_tuple(RewriteState state,
OldToNewMapping mapping;
memset(&hashkey, 0, sizeof(hashkey));
- hashkey.xmin = HeapTupleHeaderGetUpdateXid(old_tuple->t_data);
+ hashkey.xmin = HeapTupleGetUpdateXidAny(old_tuple);
hashkey.tid = old_tuple->t_data->t_ctid;
mapping = (OldToNewMapping)
@@ -485,7 +486,7 @@ rewrite_heap_tuple(RewriteState state,
* RECENTLY_DEAD if and only if the xmin is not before OldestXmin.
*/
if ((new_tuple->t_data->t_infomask & HEAP_UPDATED) &&
- !TransactionIdPrecedes(HeapTupleHeaderGetXmin(new_tuple->t_data),
+ !TransactionIdPrecedes(HeapTupleGetXmin(new_tuple),
state->rs_oldest_xmin))
{
/*
@@ -494,7 +495,7 @@ rewrite_heap_tuple(RewriteState state,
UnresolvedTup unresolved;
memset(&hashkey, 0, sizeof(hashkey));
- hashkey.xmin = HeapTupleHeaderGetXmin(new_tuple->t_data);
+ hashkey.xmin = HeapTupleGetXmin(new_tuple);
hashkey.tid = old_tid;
unresolved = hash_search(state->rs_unresolved_tups, &hashkey,
@@ -582,7 +583,7 @@ rewrite_heap_dead_tuple(RewriteState state, HeapTuple old_tuple)
bool found;
memset(&hashkey, 0, sizeof(hashkey));
- hashkey.xmin = HeapTupleHeaderGetXmin(old_tuple->t_data);
+ hashkey.xmin = HeapTupleGetXmin(old_tuple);
hashkey.tid = old_tuple->t_self;
unresolved = hash_search(state->rs_unresolved_tups, &hashkey,
@@ -618,6 +619,7 @@ raw_heap_insert(RewriteState state, HeapTuple tup)
Size len;
OffsetNumber newoff;
HeapTuple heaptup;
+ TransactionId xmin, xmax;
/*
* If the new tuple is too big for storage or contains already toasted
@@ -703,10 +705,20 @@ raw_heap_insert(RewriteState state, HeapTuple tup)
if (!state->rs_buffer_valid)
{
/* Initialize a new empty page */
- PageInit(page, BLCKSZ, 0);
+ PageInit(page, BLCKSZ, sizeof(HeapPageSpecialData));
state->rs_buffer_valid = true;
}
+ rewrite_page_prepare_for_xid(page, HeapTupleGetXmin(heaptup), false);
+ rewrite_page_prepare_for_xid(page, HeapTupleGetRawXmax(heaptup),
+ (heaptup->t_data->t_infomask & HEAP_XMAX_IS_MULTI) ? true : false);
+
+ xmin = HeapTupleGetXmin(heaptup);
+ xmax = HeapTupleGetRawXmax(heaptup);
+ HeapTupleCopyBaseFromPage(heaptup, page);
+ HeapTupleSetXmin(heaptup, xmin);
+ HeapTupleSetXmax(heaptup, xmax);
+
/* And now we can insert the tuple into the page */
newoff = PageAddItem(page, (Item) heaptup->t_data, heaptup->t_len,
InvalidOffsetNumber, false, true);
@@ -999,7 +1011,10 @@ logical_rewrite_log_mapping(RewriteState state, TransactionId xid,
"pg_logical/mappings/" LOGICAL_REWRITE_FORMAT,
dboid, relid,
LSN_FORMAT_ARGS(state->rs_begin_lsn),
- xid, GetCurrentTransactionId());
+ (uint32) (xid >> 32),
+ (uint32) xid,
+ (uint32) (GetCurrentTransactionId() >> 32),
+ (uint32) GetCurrentTransactionId());
dlist_init(&src->mappings);
src->num_mappings = 0;
@@ -1048,9 +1063,9 @@ logical_rewrite_heap_tuple(RewriteState state, ItemPointerData old_tid,
if (!state->rs_logical_rewrite)
return;
- xmin = HeapTupleHeaderGetXmin(new_tuple->t_data);
+ xmin = HeapTupleGetXmin(new_tuple);
/* use *GetUpdateXid to correctly deal with multixacts */
- xmax = HeapTupleHeaderGetUpdateXid(new_tuple->t_data);
+ xmax = HeapTupleGetUpdateXidAny(new_tuple);
/*
* Log the mapping iff the tuple has been created recently.
@@ -1121,7 +1136,10 @@ heap_xlog_logical_rewrite(XLogReaderState *r)
"pg_logical/mappings/" LOGICAL_REWRITE_FORMAT,
xlrec->mapped_db, xlrec->mapped_rel,
LSN_FORMAT_ARGS(xlrec->start_lsn),
- xlrec->mapped_xid, XLogRecGetXid(r));
+ (uint32) (xlrec->mapped_xid >> 32),
+ (uint32) xlrec->mapped_xid,
+ (uint32) (XLogRecGetXid(r) >> 32),
+ (uint32) XLogRecGetXid(r));
fd = OpenTransientFile(path,
O_CREAT | O_WRONLY | PG_BINARY);
@@ -1217,10 +1235,12 @@ CheckPointLogicalRewriteHeap(void)
Oid dboid;
Oid relid;
XLogRecPtr lsn;
- TransactionId rewrite_xid;
- TransactionId create_xid;
- uint32 hi,
- lo;
+ uint32 lsn_hi,
+ lsn_lo,
+ rewrite_xid_hi,
+ rewrite_xid_lo,
+ create_xid_hi,
+ create_xid_lo;
if (strcmp(mapping_de->d_name, ".") == 0 ||
strcmp(mapping_de->d_name, "..") == 0)
@@ -1235,10 +1255,12 @@ CheckPointLogicalRewriteHeap(void)
continue;
if (sscanf(mapping_de->d_name, LOGICAL_REWRITE_FORMAT,
- &dboid, &relid, &hi, &lo, &rewrite_xid, &create_xid) != 6)
+ &dboid, &relid, &lsn_hi, &lsn_lo,
+ &rewrite_xid_hi, &rewrite_xid_lo,
+ &create_xid_hi, &create_xid_lo) != 8)
elog(ERROR, "could not parse filename \"%s\"", mapping_de->d_name);
- lsn = ((uint64) hi) << 32 | lo;
+ lsn = ((uint64) lsn_hi) << 32 | lsn_lo;
if (lsn < cutoff || cutoff == InvalidXLogRecPtr)
{
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 1749cc2a476..797866dcfdb 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -253,7 +253,6 @@ static int lazy_vacuum_heap_page(LVRelState *vacrel, BlockNumber blkno,
Buffer buffer, int index, Buffer *vmbuffer);
static bool lazy_check_needs_freeze(Buffer buf, bool *hastup,
LVRelState *vacrel);
-static bool lazy_check_wraparound_failsafe(LVRelState *vacrel);
static void lazy_cleanup_all_indexes(LVRelState *vacrel);
static IndexBulkDeleteResult *lazy_vacuum_one_index(Relation indrel,
IndexBulkDeleteResult *istat,
@@ -615,11 +614,11 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
vacrel->pinskipped_pages,
vacrel->frozenskipped_pages);
appendStringInfo(&buf,
- _("tuples: %lld removed, %lld remain, %lld are dead but not yet removable, oldest xmin: %u\n"),
+ _("tuples: %lld removed, %lld remain, %lld are dead but not yet removable, oldest xmin: "),
(long long) vacrel->tuples_deleted,
(long long) vacrel->new_rel_tuples,
- (long long) vacrel->new_dead_tuples,
- OldestXmin);
+ (long long) vacrel->new_dead_tuples);
+ appendStringInfo(&buf, XID_FMT "\n", OldestXmin);
orig_rel_pages = vacrel->rel_pages + vacrel->pages_removed;
if (orig_rel_pages > 0)
{
@@ -781,13 +780,6 @@ lazy_scan_heap(LVRelState *vacrel, VacuumParams *params, bool aggressive)
vacrel->indstats = (IndexBulkDeleteResult **)
palloc0(vacrel->nindexes * sizeof(IndexBulkDeleteResult *));
- /*
- * Do failsafe precheck before calling dead_items_alloc. This ensures
- * that parallel VACUUM won't be attempted when relfrozenxid is already
- * dangerously old.
- */
- lazy_check_wraparound_failsafe(vacrel);
-
/*
* Allocate the space for dead_items. Note that this handles parallel
* VACUUM initialization as part of allocating shared memory space used
@@ -980,7 +972,6 @@ lazy_scan_heap(LVRelState *vacrel, VacuumParams *params, bool aggressive)
*/
if (blkno - next_failsafe_block >= FAILSAFE_EVERY_PAGES)
{
- lazy_check_wraparound_failsafe(vacrel);
next_failsafe_block = blkno;
}
@@ -1141,7 +1132,9 @@ lazy_scan_heap(LVRelState *vacrel, VacuumParams *params, bool aggressive)
if (GetRecordedFreeSpace(vacrel->rel, blkno) == 0)
{
- Size freespace = BLCKSZ - SizeOfPageHeaderData;
+ Size freespace = BufferGetPageSize(buf)
+ - SizeOfPageHeaderData
+ - sizeof(HeapPageSpecialData);
RecordPageWithFreeSpace(vacrel->rel, blkno, freespace);
}
@@ -1513,7 +1506,7 @@ retry:
*/
tuples_deleted = heap_page_prune(rel, buf, vistest,
InvalidTransactionId, 0, &nnewlpdead,
- &vacrel->offnum);
+ &vacrel->offnum, true);
/*
* Now scan the page to collect LP_DEAD items and check for tuples
@@ -1580,6 +1573,7 @@ retry:
tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid);
tuple.t_len = ItemIdGetLength(itemid);
tuple.t_tableOid = RelationGetRelid(rel);
+ HeapTupleCopyBaseFromPage(&tuple, page);
/*
* DEAD tuples are almost always pruned into LP_DEAD line pointers by
@@ -1643,7 +1637,7 @@ retry:
* The inserter definitely committed. But is it old enough
* that everyone sees it as committed?
*/
- xmin = HeapTupleHeaderGetXmin(tuple.t_data);
+ xmin = HeapTupleGetXmin(&tuple);
if (!TransactionIdPrecedes(xmin, vacrel->OldestXmin))
{
prunestate->all_visible = false;
@@ -1700,7 +1694,7 @@ retry:
*/
num_tuples++;
prunestate->hastup = true;
- if (heap_prepare_freeze_tuple(tuple.t_data,
+ if (heap_prepare_freeze_tuple(&tuple,
vacrel->relfrozenxid,
vacrel->relminmxid,
vacrel->FreezeLimit,
@@ -1748,15 +1742,19 @@ retry:
MarkBufferDirty(buf);
+ tuple.t_tableOid = RelationGetRelid(vacrel->rel);
+ HeapTupleCopyBaseFromPage(&tuple, page);
+
/* execute collected freezes */
for (int i = 0; i < nfrozen; i++)
{
- HeapTupleHeader htup;
-
itemid = PageGetItemId(page, frozen[i].offset);
- htup = (HeapTupleHeader) PageGetItem(page, itemid);
- heap_execute_freeze_tuple(htup, &frozen[i]);
+ ItemPointerSet(&tuple.t_self, blkno, frozen[i].offset);
+ tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid);
+ tuple.t_len = ItemIdGetLength(itemid);
+
+ heap_execute_freeze_tuple(&tuple, &frozen[i]);
}
/* Now WAL-log freezing if necessary */
@@ -1992,13 +1990,6 @@ lazy_vacuum_all_indexes(LVRelState *vacrel)
Assert(TransactionIdIsNormal(vacrel->relfrozenxid));
Assert(MultiXactIdIsValid(vacrel->relminmxid));
- /* Precheck for XID wraparound emergencies */
- if (lazy_check_wraparound_failsafe(vacrel))
- {
- /* Wraparound emergency -- don't even start an index scan */
- return false;
- }
-
/* Report that we are now vacuuming indexes */
pgstat_progress_update_param(PROGRESS_VACUUM_PHASE,
PROGRESS_VACUUM_PHASE_VACUUM_INDEX);
@@ -2013,13 +2004,6 @@ lazy_vacuum_all_indexes(LVRelState *vacrel)
vacrel->indstats[idx] =
lazy_vacuum_one_index(indrel, istat, vacrel->old_live_tuples,
vacrel);
-
- if (lazy_check_wraparound_failsafe(vacrel))
- {
- /* Wraparound emergency -- end current index scan */
- allindexes = false;
- break;
- }
}
}
else
@@ -2027,13 +2011,6 @@ lazy_vacuum_all_indexes(LVRelState *vacrel)
/* Outsource everything to parallel variant */
parallel_vacuum_bulkdel_all_indexes(vacrel->pvs, vacrel->old_live_tuples,
vacrel->num_index_scans);
-
- /*
- * Do a postcheck to consider applying wraparound failsafe now. Note
- * that parallel VACUUM only gets the precheck and this postcheck.
- */
- if (lazy_check_wraparound_failsafe(vacrel))
- allindexes = false;
}
/*
@@ -2318,6 +2295,7 @@ lazy_check_needs_freeze(Buffer buf, bool *hastup, LVRelState *vacrel)
offnum = OffsetNumberNext(offnum))
{
ItemId itemid;
+ HeapTupleData htup;
/*
* Set the offset number so that we can display it along with any
@@ -2335,8 +2313,10 @@ lazy_check_needs_freeze(Buffer buf, bool *hastup, LVRelState *vacrel)
continue;
tupleheader = (HeapTupleHeader) PageGetItem(page, itemid);
+ htup.t_data = tupleheader;
+ HeapTupleCopyBaseFromPage(&htup, page);
- if (heap_tuple_needs_freeze(tupleheader, vacrel->FreezeLimit,
+ if (heap_tuple_needs_freeze(&htup, vacrel->FreezeLimit,
vacrel->MultiXactCutoff, buf))
break;
} /* scan along page */
@@ -2347,55 +2327,6 @@ lazy_check_needs_freeze(Buffer buf, bool *hastup, LVRelState *vacrel)
return (offnum <= maxoff);
}
-/*
- * Trigger the failsafe to avoid wraparound failure when vacrel table has a
- * relfrozenxid and/or relminmxid that is dangerously far in the past.
- * Triggering the failsafe makes the ongoing VACUUM bypass any further index
- * vacuuming and heap vacuuming. Truncating the heap is also bypassed.
- *
- * Any remaining work (work that VACUUM cannot just bypass) is typically sped
- * up when the failsafe triggers. VACUUM stops applying any cost-based delay
- * that it started out with.
- *
- * Returns true when failsafe has been triggered.
- */
-static bool
-lazy_check_wraparound_failsafe(LVRelState *vacrel)
-{
- /* Don't warn more than once per VACUUM */
- if (vacrel->failsafe_active)
- return true;
-
- if (unlikely(vacuum_xid_failsafe_check(vacrel->relfrozenxid,
- vacrel->relminmxid)))
- {
- vacrel->failsafe_active = true;
-
- /* Disable index vacuuming, index cleanup, and heap rel truncation */
- vacrel->do_index_vacuuming = false;
- vacrel->do_index_cleanup = false;
- vacrel->do_rel_truncate = false;
-
- ereport(WARNING,
- (errmsg("bypassing nonessential maintenance of table \"%s.%s.%s\" as a failsafe after %d index scans",
- get_database_name(MyDatabaseId),
- vacrel->relnamespace,
- vacrel->relname,
- vacrel->num_index_scans),
- errdetail("The table's relfrozenxid or relminmxid is too far in the past."),
- errhint("Consider increasing configuration parameter \"maintenance_work_mem\" or \"autovacuum_work_mem\".\n"
- "You might also need to consider other ways for VACUUM to keep up with the allocation of transaction IDs.")));
-
- /* Stop applying cost limits from this point on */
- VacuumCostActive = false;
- VacuumCostBalance = 0;
-
- return true;
- }
-
- return false;
-}
-
/*
* lazy_cleanup_all_indexes() -- cleanup all indexes of relation.
*/
@@ -3016,6 +2947,7 @@ heap_page_is_all_visible(LVRelState *vacrel, Buffer buf,
tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid);
tuple.t_len = ItemIdGetLength(itemid);
tuple.t_tableOid = RelationGetRelid(vacrel->rel);
+ HeapTupleCopyBaseFromPage(&tuple, page);
switch (HeapTupleSatisfiesVacuum(&tuple, vacrel->OldestXmin, buf))
{
@@ -3035,7 +2967,7 @@ heap_page_is_all_visible(LVRelState *vacrel, Buffer buf,
* The inserter definitely committed. But is it old enough
* that everyone sees it as committed?
*/
- xmin = HeapTupleHeaderGetXmin(tuple.t_data);
+ xmin = HeapTupleGetXmin(&tuple);
if (!TransactionIdPrecedes(xmin, vacrel->OldestXmin))
{
all_visible = false;
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 6b5f01e1d07..75840325bf3 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -546,6 +546,7 @@ _bt_getroot(Relation rel, int access)
rootblkno = rootopaque->btpo_next;
}
+ /* Note: can't check btpo_level on deleted pages */
if (rootopaque->btpo_level != rootlevel)
elog(ERROR, "root page %u of index \"%s\" has level %u, expected %u",
rootblkno, RelationGetRelationName(rel),
@@ -649,6 +650,7 @@ _bt_gettrueroot(Relation rel)
rootblkno = rootopaque->btpo_next;
}
+ /* Note: can't check btpo_level on deleted pages */
if (rootopaque->btpo_level != rootlevel)
elog(ERROR, "root page %u of index \"%s\" has level %u, expected %u",
rootblkno, RelationGetRelationName(rel),
diff --git a/src/backend/access/nbtree/nbtsplitloc.c b/src/backend/access/nbtree/nbtsplitloc.c
index c46594e1a20..c0bcff5755f 100644
--- a/src/backend/access/nbtree/nbtsplitloc.c
+++ b/src/backend/access/nbtree/nbtsplitloc.c
@@ -141,6 +141,7 @@ _bt_findsplitloc(Relation rel,
olddataitemstoleft,
perfectpenalty,
leaffillfactor;
+ int maxTupleEnd PG_USED_FOR_ASSERTS_ONLY;
FindSplitData state;
FindSplitStrat strategy;
ItemId itemid;
@@ -154,6 +155,7 @@ _bt_findsplitloc(Relation rel,
opaque = (BTPageOpaque) PageGetSpecialPointer(origpage);
maxoff = PageGetMaxOffsetNumber(origpage);
+ maxTupleEnd = ItemIdGetTupleEnd(PageGetItemId(origpage, P_HIKEY));
/* Total free space available on a btree page, after fixed overhead */
leftspace = rightspace =
@@ -215,6 +217,15 @@ _bt_findsplitloc(Relation rel,
itemid = PageGetItemId(origpage, offnum);
itemsz = MAXALIGN(ItemIdGetLength(itemid)) + sizeof(ItemIdData);
+#ifdef USE_ASSERT_CHECKING
+ /* Ending of rightmost tuple on a page can be shifted relative to
+ * left boundary of BTPageOpaqueData due to conversion from EE96,
+ * which used different BTPageOpaqueData layout. It is only checked
+ * in the assert below.
+ */
+ if (maxTupleEnd < ItemIdGetTupleEnd(itemid))
+ maxTupleEnd = ItemIdGetTupleEnd(itemid);
+#endif
/*
* When item offset number is not newitemoff, neither side of the
* split can be newitem. Record a split after the previous data item
@@ -249,7 +260,7 @@ _bt_findsplitloc(Relation rel,
* (Though only when it's possible that newitem will end up alone on new
* right page.)
*/
- Assert(olddataitemstoleft == olddataitemstotal);
+ Assert(olddataitemstoleft + ((PageHeader)origpage)->pd_special - maxTupleEnd == olddataitemstotal);
if (newitemoff > maxoff)
_bt_recsplitloc(&state, newitemoff, false, olddataitemstotal, 0);
diff --git a/src/backend/access/nbtree/nbtxlog.c b/src/backend/access/nbtree/nbtxlog.c
index 611f412ba8a..d8294b429b0 100644
--- a/src/backend/access/nbtree/nbtxlog.c
+++ b/src/backend/access/nbtree/nbtxlog.c
@@ -15,6 +15,8 @@
#include "postgres.h"
#include "access/bufmask.h"
+#include "access/heapam_xlog.h"
+#include "access/htup_details.h"
#include "access/nbtree.h"
#include "access/nbtxlog.h"
#include "access/transam.h"
diff --git a/src/backend/access/rmgrdesc/clogdesc.c b/src/backend/access/rmgrdesc/clogdesc.c
index 87513732be6..e950dd0bdc4 100644
--- a/src/backend/access/rmgrdesc/clogdesc.c
+++ b/src/backend/access/rmgrdesc/clogdesc.c
@@ -25,17 +25,17 @@ clog_desc(StringInfo buf, XLogReaderState *record)
if (info == CLOG_ZEROPAGE)
{
- int pageno;
+ int64 pageno;
- memcpy(&pageno, rec, sizeof(int));
- appendStringInfo(buf, "page %d", pageno);
+ memcpy(&pageno, rec, sizeof(int64));
+ appendStringInfo(buf, "page " INT64_FORMAT, pageno);
}
else if (info == CLOG_TRUNCATE)
{
xl_clog_truncate xlrec;
memcpy(&xlrec, rec, sizeof(xl_clog_truncate));
- appendStringInfo(buf, "page %d; oldestXact %u",
+ appendStringInfo(buf, "page " INT64_FORMAT "; oldestXact " XID_FMT,
xlrec.pageno, xlrec.oldestXact);
}
}
diff --git a/src/backend/access/rmgrdesc/committsdesc.c b/src/backend/access/rmgrdesc/committsdesc.c
index 3a65538bb0a..904e13a882f 100644
--- a/src/backend/access/rmgrdesc/committsdesc.c
+++ b/src/backend/access/rmgrdesc/committsdesc.c
@@ -26,16 +26,16 @@ commit_ts_desc(StringInfo buf, XLogReaderState *record)
if (info == COMMIT_TS_ZEROPAGE)
{
- int pageno;
+ int64 pageno;
- memcpy(&pageno, rec, sizeof(int));
- appendStringInfo(buf, "%d", pageno);
+ memcpy(&pageno, rec, sizeof(int64));
+ appendStringInfo(buf, INT64_FORMAT, pageno);
}
else if (info == COMMIT_TS_TRUNCATE)
{
xl_commit_ts_truncate *trunc = (xl_commit_ts_truncate *) rec;
- appendStringInfo(buf, "pageno %d, oldestXid %u",
+ appendStringInfo(buf, "pageno " INT64_FORMAT ", oldestXid " XID_FMT,
trunc->pageno, trunc->oldestXid);
}
}
diff --git a/src/backend/access/rmgrdesc/gistdesc.c b/src/backend/access/rmgrdesc/gistdesc.c
index 9cab4fa1580..0b1a7c6ad8b 100644
--- a/src/backend/access/rmgrdesc/gistdesc.c
+++ b/src/backend/access/rmgrdesc/gistdesc.c
@@ -26,17 +26,16 @@ out_gistxlogPageUpdate(StringInfo buf, gistxlogPageUpdate *xlrec)
static void
out_gistxlogPageReuse(StringInfo buf, gistxlogPageReuse *xlrec)
{
- appendStringInfo(buf, "rel %u/%u/%u; blk %u; latestRemovedXid %u:%u",
+ appendStringInfo(buf, "rel %u/%u/%u; blk %u; latestRemovedXid " XID_FMT,
xlrec->node.spcNode, xlrec->node.dbNode,
xlrec->node.relNode, xlrec->block,
- EpochFromFullTransactionId(xlrec->latestRemovedFullXid),
XidFromFullTransactionId(xlrec->latestRemovedFullXid));
}
static void
out_gistxlogDelete(StringInfo buf, gistxlogDelete *xlrec)
{
- appendStringInfo(buf, "delete: latestRemovedXid %u, nitems: %u",
+ appendStringInfo(buf, "delete: latestRemovedXid " XID_FMT ", nitems: %u",
xlrec->latestRemovedXid, xlrec->ntodelete);
}
@@ -51,8 +50,7 @@ out_gistxlogPageSplit(StringInfo buf, gistxlogPageSplit *xlrec)
static void
out_gistxlogPageDelete(StringInfo buf, gistxlogPageDelete *xlrec)
{
- appendStringInfo(buf, "deleteXid %u:%u; downlink %u",
- EpochFromFullTransactionId(xlrec->deleteXid),
+ appendStringInfo(buf, "deleteXid " XID_FMT "; downlink %u",
XidFromFullTransactionId(xlrec->deleteXid),
xlrec->downlinkOffset);
}
diff --git a/src/backend/access/rmgrdesc/hashdesc.c b/src/backend/access/rmgrdesc/hashdesc.c
index ef443bdb16c..d9f0c40fbfe 100644
--- a/src/backend/access/rmgrdesc/hashdesc.c
+++ b/src/backend/access/rmgrdesc/hashdesc.c
@@ -113,7 +113,7 @@ hash_desc(StringInfo buf, XLogReaderState *record)
{
xl_hash_vacuum_one_page *xlrec = (xl_hash_vacuum_one_page *) rec;
- appendStringInfo(buf, "ntuples %d, latestRemovedXid %u",
+ appendStringInfo(buf, "ntuples %d, latestRemovedXid " XID_FMT,
xlrec->ntuples,
xlrec->latestRemovedXid);
break;
diff --git a/src/backend/access/rmgrdesc/heapdesc.c b/src/backend/access/rmgrdesc/heapdesc.c
index 6238085d65e..12140ba3702 100644
--- a/src/backend/access/rmgrdesc/heapdesc.c
+++ b/src/backend/access/rmgrdesc/heapdesc.c
@@ -58,12 +58,12 @@ heap_desc(StringInfo buf, XLogReaderState *record)
{
xl_heap_update *xlrec = (xl_heap_update *) rec;
- appendStringInfo(buf, "off %u xmax %u flags 0x%02X ",
+ appendStringInfo(buf, "off %u xmax " XID_FMT " flags 0x%02X ",
xlrec->old_offnum,
xlrec->old_xmax,
xlrec->flags);
out_infobits(buf, xlrec->old_infobits_set);
- appendStringInfo(buf, "; new off %u xmax %u",
+ appendStringInfo(buf, "; new off %u xmax " XID_FMT,
xlrec->new_offnum,
xlrec->new_xmax);
}
@@ -71,12 +71,12 @@ heap_desc(StringInfo buf, XLogReaderState *record)
{
xl_heap_update *xlrec = (xl_heap_update *) rec;
- appendStringInfo(buf, "off %u xmax %u flags 0x%02X ",
+ appendStringInfo(buf, "off %u xmax " XID_FMT " flags 0x%02X ",
xlrec->old_offnum,
xlrec->old_xmax,
xlrec->flags);
out_infobits(buf, xlrec->old_infobits_set);
- appendStringInfo(buf, "; new off %u xmax %u",
+ appendStringInfo(buf, "; new off %u xmax " XID_FMT,
xlrec->new_offnum,
xlrec->new_xmax);
}
@@ -103,7 +103,7 @@ heap_desc(StringInfo buf, XLogReaderState *record)
{
xl_heap_lock *xlrec = (xl_heap_lock *) rec;
- appendStringInfo(buf, "off %u: xid %u: flags 0x%02X ",
+ appendStringInfo(buf, "off %u: xid " XID_FMT ": flags 0x%02X ",
xlrec->offnum, xlrec->locking_xid, xlrec->flags);
out_infobits(buf, xlrec->infobits_set);
}
@@ -125,7 +125,7 @@ heap2_desc(StringInfo buf, XLogReaderState *record)
{
xl_heap_prune *xlrec = (xl_heap_prune *) rec;
- appendStringInfo(buf, "latestRemovedXid %u nredirected %u ndead %u",
+ appendStringInfo(buf, "latestRemovedXid " XID_FMT " nredirected %u ndead %u",
xlrec->latestRemovedXid,
xlrec->nredirected,
xlrec->ndead);
@@ -140,14 +140,14 @@ heap2_desc(StringInfo buf, XLogReaderState *record)
{
xl_heap_freeze_page *xlrec = (xl_heap_freeze_page *) rec;
- appendStringInfo(buf, "cutoff xid %u ntuples %u",
+ appendStringInfo(buf, "cutoff xid " XID_FMT " ntuples %u",
xlrec->cutoff_xid, xlrec->ntuples);
}
else if (info == XLOG_HEAP2_VISIBLE)
{
xl_heap_visible *xlrec = (xl_heap_visible *) rec;
- appendStringInfo(buf, "cutoff xid %u flags 0x%02X",
+ appendStringInfo(buf, "cutoff xid " XID_FMT " flags 0x%02X",
xlrec->cutoff_xid, xlrec->flags);
}
else if (info == XLOG_HEAP2_MULTI_INSERT)
@@ -161,7 +161,7 @@ heap2_desc(StringInfo buf, XLogReaderState *record)
{
xl_heap_lock_updated *xlrec = (xl_heap_lock_updated *) rec;
- appendStringInfo(buf, "off %u: xmax %u: flags 0x%02X ",
+ appendStringInfo(buf, "off %u: xmax " XID_FMT ": flags 0x%02X ",
xlrec->offnum, xlrec->xmax, xlrec->flags);
out_infobits(buf, xlrec->infobits_set);
}
@@ -180,6 +180,23 @@ heap2_desc(StringInfo buf, XLogReaderState *record)
}
}
+void
+heap3_desc(StringInfo buf, XLogReaderState *record)
+{
+ char *rec = XLogRecGetData(record);
+ uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+
+ info &= XLOG_HEAP_OPMASK;
+ if (info == XLOG_HEAP3_BASE_SHIFT)
+ {
+ xl_heap_base_shift *xlrec = (xl_heap_base_shift *) rec;
+
+ appendStringInfo(buf, "%s delta " INT64_FORMAT " ",
+ xlrec->multi ? "MultiXactId" : "XactId",
+ xlrec->delta);
+ }
+}
+
const char *
heap_identify(uint8 info)
{
@@ -263,3 +280,18 @@ heap2_identify(uint8 info)
return id;
}
+
+const char *
+heap3_identify(uint8 info)
+{
+ const char *id = NULL;
+
+ switch (info & ~XLR_INFO_MASK)
+ {
+ case XLOG_HEAP3_BASE_SHIFT:
+ id = "BASE_SHIFT";
+ break;
+ }
+
+ return id;
+}
diff --git a/src/backend/access/rmgrdesc/mxactdesc.c b/src/backend/access/rmgrdesc/mxactdesc.c
index 7076be2b3f2..fba52ac9f45 100644
--- a/src/backend/access/rmgrdesc/mxactdesc.c
+++ b/src/backend/access/rmgrdesc/mxactdesc.c
@@ -19,7 +19,7 @@
static void
out_member(StringInfo buf, MultiXactMember *member)
{
- appendStringInfo(buf, "%u ", member->xid);
+ appendStringInfo(buf, XID_FMT " ", member->xid);
switch (member->status)
{
case MultiXactStatusForKeyShare:
@@ -55,18 +55,18 @@ multixact_desc(StringInfo buf, XLogReaderState *record)
if (info == XLOG_MULTIXACT_ZERO_OFF_PAGE ||
info == XLOG_MULTIXACT_ZERO_MEM_PAGE)
{
- int pageno;
+ int64 pageno;
memcpy(&pageno, rec, sizeof(int));
- appendStringInfo(buf, "%d", pageno);
+ appendStringInfo(buf, INT64_FORMAT, pageno);
}
else if (info == XLOG_MULTIXACT_CREATE_ID)
{
xl_multixact_create *xlrec = (xl_multixact_create *) rec;
int i;
- appendStringInfo(buf, "%u offset %u nmembers %d: ", xlrec->mid,
- xlrec->moff, xlrec->nmembers);
+ appendStringInfo(buf, XID_FMT " offset " INT64_FORMAT " nmembers %d: ",
+ xlrec->mid, xlrec->moff, xlrec->nmembers);
for (i = 0; i < xlrec->nmembers; i++)
out_member(buf, &xlrec->members[i]);
}
@@ -74,7 +74,7 @@ multixact_desc(StringInfo buf, XLogReaderState *record)
{
xl_multixact_truncate *xlrec = (xl_multixact_truncate *) rec;
- appendStringInfo(buf, "offsets [%u, %u), members [%u, %u)",
+ appendStringInfo(buf, "offsets [" XID_FMT ", " XID_FMT "), members [" INT64_FORMAT ", " INT64_FORMAT ")",
xlrec->startTruncOff, xlrec->endTruncOff,
xlrec->startTruncMemb, xlrec->endTruncMemb);
}
diff --git a/src/backend/access/rmgrdesc/nbtdesc.c b/src/backend/access/rmgrdesc/nbtdesc.c
index dfbbf4ebaf8..8aea94b8731 100644
--- a/src/backend/access/rmgrdesc/nbtdesc.c
+++ b/src/backend/access/rmgrdesc/nbtdesc.c
@@ -63,7 +63,7 @@ btree_desc(StringInfo buf, XLogReaderState *record)
{
xl_btree_delete *xlrec = (xl_btree_delete *) rec;
- appendStringInfo(buf, "latestRemovedXid %u; ndeleted %u; nupdated %u",
+ appendStringInfo(buf, "latestRemovedXid " XID_FMT "; ndeleted %u; nupdated %u",
xlrec->latestRemovedXid, xlrec->ndeleted, xlrec->nupdated);
break;
}
@@ -80,9 +80,8 @@ btree_desc(StringInfo buf, XLogReaderState *record)
{
xl_btree_unlink_page *xlrec = (xl_btree_unlink_page *) rec;
- appendStringInfo(buf, "left %u; right %u; level %u; safexid %u:%u; ",
+ appendStringInfo(buf, "left %u; right %u; level %u; safexid " XID_FMT "; ",
xlrec->leftsib, xlrec->rightsib, xlrec->level,
- EpochFromFullTransactionId(xlrec->safexid),
XidFromFullTransactionId(xlrec->safexid));
appendStringInfo(buf, "leafleft %u; leafright %u; leaftopparent %u",
xlrec->leafleftsib, xlrec->leafrightsib,
@@ -100,10 +99,9 @@ btree_desc(StringInfo buf, XLogReaderState *record)
{
xl_btree_reuse_page *xlrec = (xl_btree_reuse_page *) rec;
- appendStringInfo(buf, "rel %u/%u/%u; latestRemovedXid %u:%u",
+ appendStringInfo(buf, "rel %u/%u/%u; latestRemovedXid " XID_FMT,
xlrec->node.spcNode, xlrec->node.dbNode,
xlrec->node.relNode,
- EpochFromFullTransactionId(xlrec->latestRemovedFullXid),
XidFromFullTransactionId(xlrec->latestRemovedFullXid));
break;
}
diff --git a/src/backend/access/rmgrdesc/spgdesc.c b/src/backend/access/rmgrdesc/spgdesc.c
index d5d921a42a1..a094ac738b4 100644
--- a/src/backend/access/rmgrdesc/spgdesc.c
+++ b/src/backend/access/rmgrdesc/spgdesc.c
@@ -118,7 +118,7 @@ spg_desc(StringInfo buf, XLogReaderState *record)
{
spgxlogVacuumRedirect *xlrec = (spgxlogVacuumRedirect *) rec;
- appendStringInfo(buf, "ntoplaceholder: %u, firstplaceholder: %u, newestredirectxid: %u",
+ appendStringInfo(buf, "ntoplaceholder: %u, firstplaceholder: %u, newestredirectxid: " XID_FMT,
xlrec->nToPlaceholder,
xlrec->firstPlaceholder,
xlrec->newestRedirectXid);
diff --git a/src/backend/access/rmgrdesc/standbydesc.c b/src/backend/access/rmgrdesc/standbydesc.c
index 2dba39e349c..57fa4b729ed 100644
--- a/src/backend/access/rmgrdesc/standbydesc.c
+++ b/src/backend/access/rmgrdesc/standbydesc.c
@@ -21,7 +21,7 @@ standby_desc_running_xacts(StringInfo buf, xl_running_xacts *xlrec)
{
int i;
- appendStringInfo(buf, "nextXid %u latestCompletedXid %u oldestRunningXid %u",
+ appendStringInfo(buf, "nextXid " XID_FMT " latestCompletedXid " XID_FMT " oldestRunningXid " XID_FMT,
xlrec->nextXid,
xlrec->latestCompletedXid,
xlrec->oldestRunningXid);
@@ -29,7 +29,7 @@ standby_desc_running_xacts(StringInfo buf, xl_running_xacts *xlrec)
{
appendStringInfo(buf, "; %d xacts:", xlrec->xcnt);
for (i = 0; i < xlrec->xcnt; i++)
- appendStringInfo(buf, " %u", xlrec->xids[i]);
+ appendStringInfo(buf, " " XID_FMT, xlrec->xids[i]);
}
if (xlrec->subxid_overflow)
@@ -48,7 +48,7 @@ standby_desc(StringInfo buf, XLogReaderState *record)
int i;
for (i = 0; i < xlrec->nlocks; i++)
- appendStringInfo(buf, "xid %u db %u rel %u ",
+ appendStringInfo(buf, "xid " XID_FMT " db %u rel %u ",
xlrec->locks[i].xid, xlrec->locks[i].dbOid,
xlrec->locks[i].relOid);
}
diff --git a/src/backend/access/rmgrdesc/xactdesc.c b/src/backend/access/rmgrdesc/xactdesc.c
index 025d556f6ce..d15de291850 100644
--- a/src/backend/access/rmgrdesc/xactdesc.c
+++ b/src/backend/access/rmgrdesc/xactdesc.c
@@ -99,7 +99,8 @@ ParseCommitRecord(uint8 info, xl_xact_commit *xlrec, xl_xact_parsed_commit *pars
{
xl_xact_twophase *xl_twophase = (xl_xact_twophase *) data;
- parsed->twophase_xid = xl_twophase->xid;
+ parsed->twophase_xid =
+ ((uint64) xl_twophase->xid_hi << 32) | xl_twophase->xid_lo;
data += sizeof(xl_xact_twophase);
@@ -183,7 +184,8 @@ ParseAbortRecord(uint8 info, xl_xact_abort *xlrec, xl_xact_parsed_abort *parsed)
{
xl_xact_twophase *xl_twophase = (xl_xact_twophase *) data;
- parsed->twophase_xid = xl_twophase->xid;
+ parsed->twophase_xid =
+ ((uint64) xl_twophase->xid_hi << 32) | xl_twophase->xid_lo;
data += sizeof(xl_xact_twophase);
@@ -276,7 +278,7 @@ xact_desc_subxacts(StringInfo buf, int nsubxacts, TransactionId *subxacts)
{
appendStringInfoString(buf, "; subxacts:");
for (i = 0; i < nsubxacts; i++)
- appendStringInfo(buf, " %u", subxacts[i]);
+ appendStringInfo(buf, " " XID_FMT, subxacts[i]);
}
}
@@ -289,7 +291,7 @@ xact_desc_commit(StringInfo buf, uint8 info, xl_xact_commit *xlrec, RepOriginId
/* If this is a prepared xact, show the xid of the original xact */
if (TransactionIdIsValid(parsed.twophase_xid))
- appendStringInfo(buf, "%u: ", parsed.twophase_xid);
+ appendStringInfo(buf, XID_FMT ": ", parsed.twophase_xid);
appendStringInfoString(buf, timestamptz_to_str(xlrec->xact_time));
@@ -324,7 +326,7 @@ xact_desc_abort(StringInfo buf, uint8 info, xl_xact_abort *xlrec, RepOriginId or
/* If this is a prepared xact, show the xid of the original xact */
if (TransactionIdIsValid(parsed.twophase_xid))
- appendStringInfo(buf, "%u: ", parsed.twophase_xid);
+ appendStringInfo(buf, XID_FMT ": ", parsed.twophase_xid);
appendStringInfoString(buf, timestamptz_to_str(xlrec->xact_time));
@@ -377,7 +379,7 @@ xact_desc_assignment(StringInfo buf, xl_xact_assignment *xlrec)
appendStringInfoString(buf, "subxacts:");
for (i = 0; i < xlrec->nsubxacts; i++)
- appendStringInfo(buf, " %u", xlrec->xsub[i]);
+ appendStringInfo(buf, " " XID_FMT, xlrec->xsub[i]);
}
void
@@ -416,7 +418,7 @@ xact_desc(StringInfo buf, XLogReaderState *record)
* interested in the top-level xid that issued the record and which
* xids are being reported here.
*/
- appendStringInfo(buf, "xtop %u: ", xlrec->xtop);
+ appendStringInfo(buf, "xtop " XID_FMT ": ", xlrec->xtop);
xact_desc_assignment(buf, xlrec);
}
else if (info == XLOG_XACT_INVALIDATIONS)
diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c
index e7452af6790..ee897a96c75 100644
--- a/src/backend/access/rmgrdesc/xlogdesc.c
+++ b/src/backend/access/rmgrdesc/xlogdesc.c
@@ -45,15 +45,14 @@ xlog_desc(StringInfo buf, XLogReaderState *record)
CheckPoint *checkpoint = (CheckPoint *) rec;
appendStringInfo(buf, "redo %X/%X; "
- "tli %u; prev tli %u; fpw %s; xid %u:%u; oid %u; multi %u; offset %u; "
- "oldest xid %u in DB %u; oldest multi %u in DB %u; "
- "oldest/newest commit timestamp xid: %u/%u; "
- "oldest running xid %u; %s",
+ "tli %u; prev tli %u; fpw %s; xid " XID_FMT "; oid %u; multi " XID_FMT "; offset " INT64_FORMAT "; "
+ "oldest xid " XID_FMT " in DB %u; oldest multi " XID_FMT " in DB %u; "
+ "oldest/newest commit timestamp xid: " XID_FMT "/" XID_FMT "; "
+ "oldest running xid " XID_FMT "; %s",
LSN_FORMAT_ARGS(checkpoint->redo),
checkpoint->ThisTimeLineID,
checkpoint->PrevTimeLineID,
checkpoint->fullPageWrites ? "true" : "false",
- EpochFromFullTransactionId(checkpoint->nextXid),
XidFromFullTransactionId(checkpoint->nextXid),
checkpoint->nextOid,
checkpoint->nextMulti,
diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c
index de787c3d375..6a3dee57385 100644
--- a/src/backend/access/transam/clog.c
+++ b/src/backend/access/transam/clog.c
@@ -89,24 +89,24 @@ static SlruCtlData XactCtlData;
#define XactCtl (&XactCtlData)
-static int ZeroCLOGPage(int pageno, bool writeXlog);
-static bool CLOGPagePrecedes(int page1, int page2);
-static void WriteZeroPageXlogRec(int pageno);
-static void WriteTruncateXlogRec(int pageno, TransactionId oldestXact,
+static int ZeroCLOGPage(int64 pageno, bool writeXlog);
+static bool CLOGPagePrecedes(int64 page1, int64 page2);
+static void WriteZeroPageXlogRec(int64 pageno);
+static void WriteTruncateXlogRec(int64 pageno, TransactionId oldestXact,
Oid oldestXactDb);
static void TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
TransactionId *subxids, XidStatus status,
- XLogRecPtr lsn, int pageno,
+ XLogRecPtr lsn, int64 pageno,
bool all_xact_same_page);
static void TransactionIdSetStatusBit(TransactionId xid, XidStatus status,
XLogRecPtr lsn, int slotno);
static void set_status_by_pages(int nsubxids, TransactionId *subxids,
XidStatus status, XLogRecPtr lsn);
static bool TransactionGroupUpdateXidStatus(TransactionId xid,
- XidStatus status, XLogRecPtr lsn, int pageno);
+ XidStatus status, XLogRecPtr lsn, int64 pageno);
static void TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids,
TransactionId *subxids, XidStatus status,
- XLogRecPtr lsn, int pageno);
+ XLogRecPtr lsn, int64 pageno);
/*
@@ -164,7 +164,7 @@ void
TransactionIdSetTreeStatus(TransactionId xid, int nsubxids,
TransactionId *subxids, XidStatus status, XLogRecPtr lsn)
{
- int pageno = TransactionIdToPage(xid); /* get page of parent */
+ int64 pageno = TransactionIdToPage(xid); /* get page of parent */
int i;
Assert(status == TRANSACTION_STATUS_COMMITTED ||
@@ -238,7 +238,7 @@ static void
set_status_by_pages(int nsubxids, TransactionId *subxids,
XidStatus status, XLogRecPtr lsn)
{
- int pageno = TransactionIdToPage(subxids[0]);
+ int64 pageno = TransactionIdToPage(subxids[0]);
int offset = 0;
int i = 0;
@@ -247,7 +247,7 @@ set_status_by_pages(int nsubxids, TransactionId *subxids,
while (i < nsubxids)
{
int num_on_page = 0;
- int nextpageno;
+ int64 nextpageno;
do
{
@@ -273,7 +273,7 @@ set_status_by_pages(int nsubxids, TransactionId *subxids,
static void
TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
TransactionId *subxids, XidStatus status,
- XLogRecPtr lsn, int pageno,
+ XLogRecPtr lsn, int64 pageno,
bool all_xact_same_page)
{
/* Can't use group update when PGPROC overflows. */
@@ -338,7 +338,7 @@ TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
static void
TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids,
TransactionId *subxids, XidStatus status,
- XLogRecPtr lsn, int pageno)
+ XLogRecPtr lsn, int64 pageno)
{
int slotno;
int i;
@@ -412,7 +412,7 @@ TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids,
*/
static bool
TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
- XLogRecPtr lsn, int pageno)
+ XLogRecPtr lsn, int64 pageno)
{
volatile PROC_HDR *procglobal = ProcGlobal;
PGPROC *proc = MyProc;
@@ -638,7 +638,7 @@ TransactionIdSetStatusBit(TransactionId xid, XidStatus status, XLogRecPtr lsn, i
XidStatus
TransactionIdGetStatus(TransactionId xid, XLogRecPtr *lsn)
{
- int pageno = TransactionIdToPage(xid);
+ int64 pageno = TransactionIdToPage(xid);
int byteno = TransactionIdToByte(xid);
int bshift = TransactionIdToBIndex(xid) * CLOG_BITS_PER_XACT;
int slotno;
@@ -712,6 +712,9 @@ void
BootStrapCLOG(void)
{
int slotno;
+ int64 pageno;
+
+ pageno = TransactionIdToPage(XidFromFullTransactionId(ShmemVariableCache->nextXid));
LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
@@ -722,6 +725,16 @@ BootStrapCLOG(void)
SimpleLruWritePage(XactCtl, slotno);
Assert(!XactCtl->shared->page_dirty[slotno]);
+ if (pageno != 0)
+ {
+ /* Create and zero the first page of the commit log */
+ slotno = ZeroCLOGPage(pageno, false);
+
+ /* Make sure it's written out */
+ SimpleLruWritePage(XactCtl, slotno);
+ Assert(!XactCtl->shared->page_dirty[slotno]);
+ }
+
LWLockRelease(XactSLRULock);
}
@@ -735,7 +748,7 @@ BootStrapCLOG(void)
* Control lock must be held at entry, and will be held at exit.
*/
static int
-ZeroCLOGPage(int pageno, bool writeXlog)
+ZeroCLOGPage(int64 pageno, bool writeXlog)
{
int slotno;
@@ -755,7 +768,7 @@ void
StartupCLOG(void)
{
TransactionId xid = XidFromFullTransactionId(ShmemVariableCache->nextXid);
- int pageno = TransactionIdToPage(xid);
+ int64 pageno = TransactionIdToPage(xid);
LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
@@ -774,7 +787,7 @@ void
TrimCLOG(void)
{
TransactionId xid = XidFromFullTransactionId(ShmemVariableCache->nextXid);
- int pageno = TransactionIdToPage(xid);
+ int64 pageno = TransactionIdToPage(xid);
LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
@@ -839,7 +852,7 @@ CheckPointCLOG(void)
void
ExtendCLOG(TransactionId newestXact)
{
- int pageno;
+ int64 pageno;
/*
* No work except at first XID of a page. But beware: just after
@@ -878,7 +891,7 @@ ExtendCLOG(TransactionId newestXact)
void
TruncateCLOG(TransactionId oldestXact, Oid oldestxid_datoid)
{
- int cutoffPage;
+ int64 cutoffPage;
/*
* The cutoff point is the start of the segment containing oldestXact. We
@@ -911,49 +924,26 @@ TruncateCLOG(TransactionId oldestXact, Oid oldestxid_datoid)
SimpleLruTruncate(XactCtl, cutoffPage);
}
-
/*
* Decide whether a CLOG page number is "older" for truncation purposes.
*
- * We need to use comparison of TransactionIds here in order to do the right
- * thing with wraparound XID arithmetic. However, TransactionIdPrecedes()
- * would get weird about permanent xact IDs. So, offset both such that xid1,
- * xid2, and xid2 + CLOG_XACTS_PER_PAGE - 1 are all normal XIDs; this offset
- * is relevant to page 0 and to the page preceding page 0.
- *
- * The page containing oldestXact-2^31 is the important edge case. The
- * portion of that page equaling or following oldestXact-2^31 is expendable,
- * but the portion preceding oldestXact-2^31 is not. When oldestXact-2^31 is
- * the first XID of a page and segment, the entire page and segment is
- * expendable, and we could truncate the segment. Recognizing that case would
- * require making oldestXact, not just the page containing oldestXact,
- * available to this callback. The benefit would be rare and small, so we
- * don't optimize that edge case.
+ * With 64xid this function is just "<", but we left it as a function in order
+ * for its calls remain "vanilla" like.
*/
static bool
-CLOGPagePrecedes(int page1, int page2)
+CLOGPagePrecedes(int64 page1, int64 page2)
{
- TransactionId xid1;
- TransactionId xid2;
-
- xid1 = ((TransactionId) page1) * CLOG_XACTS_PER_PAGE;
- xid1 += FirstNormalTransactionId + 1;
- xid2 = ((TransactionId) page2) * CLOG_XACTS_PER_PAGE;
- xid2 += FirstNormalTransactionId + 1;
-
- return (TransactionIdPrecedes(xid1, xid2) &&
- TransactionIdPrecedes(xid1, xid2 + CLOG_XACTS_PER_PAGE - 1));
+ return page1 < page2;
}
-
/*
* Write a ZEROPAGE xlog record
*/
static void
-WriteZeroPageXlogRec(int pageno)
+WriteZeroPageXlogRec(int64 pageno)
{
XLogBeginInsert();
- XLogRegisterData((char *) (&pageno), sizeof(int));
+ XLogRegisterData((char *) (&pageno), sizeof(int64));
(void) XLogInsert(RM_CLOG_ID, CLOG_ZEROPAGE);
}
@@ -964,7 +954,7 @@ WriteZeroPageXlogRec(int pageno)
* in TruncateCLOG().
*/
static void
-WriteTruncateXlogRec(int pageno, TransactionId oldestXact, Oid oldestXactDb)
+WriteTruncateXlogRec(int64 pageno, TransactionId oldestXact, Oid oldestXactDb)
{
XLogRecPtr recptr;
xl_clog_truncate xlrec;
@@ -992,10 +982,10 @@ clog_redo(XLogReaderState *record)
if (info == CLOG_ZEROPAGE)
{
- int pageno;
+ int64 pageno;
int slotno;
- memcpy(&pageno, XLogRecGetData(record), sizeof(int));
+ memcpy(&pageno, XLogRecGetData(record), sizeof(int64));
LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
diff --git a/src/backend/access/transam/commit_ts.c b/src/backend/access/transam/commit_ts.c
index 9419a5ca416..f1d551577c7 100644
--- a/src/backend/access/transam/commit_ts.c
+++ b/src/backend/access/transam/commit_ts.c
@@ -105,16 +105,16 @@ bool track_commit_timestamp;
static void SetXidCommitTsInPage(TransactionId xid, int nsubxids,
TransactionId *subxids, TimestampTz ts,
- RepOriginId nodeid, int pageno);
+ RepOriginId nodeid, int64 pageno);
static void TransactionIdSetCommitTs(TransactionId xid, TimestampTz ts,
RepOriginId nodeid, int slotno);
static void error_commit_ts_disabled(void);
-static int ZeroCommitTsPage(int pageno, bool writeXlog);
-static bool CommitTsPagePrecedes(int page1, int page2);
+static int ZeroCommitTsPage(int64 pageno, bool writeXlog);
+static bool CommitTsPagePrecedes(int64 page1, int64 page2);
static void ActivateCommitTs(void);
static void DeactivateCommitTs(void);
-static void WriteZeroPageXlogRec(int pageno);
-static void WriteTruncateXlogRec(int pageno, TransactionId oldestXid);
+static void WriteZeroPageXlogRec(int64 pageno);
+static void WriteTruncateXlogRec(int64 pageno, TransactionId oldestXid);
/*
* TransactionTreeSetCommitTsData
@@ -172,7 +172,7 @@ TransactionTreeSetCommitTsData(TransactionId xid, int nsubxids,
i = 0;
for (;;)
{
- int pageno = TransactionIdToCTsPage(headxid);
+ int64 pageno = TransactionIdToCTsPage(headxid);
int j;
for (j = i; j < nsubxids; j++)
@@ -216,7 +216,7 @@ TransactionTreeSetCommitTsData(TransactionId xid, int nsubxids,
static void
SetXidCommitTsInPage(TransactionId xid, int nsubxids,
TransactionId *subxids, TimestampTz ts,
- RepOriginId nodeid, int pageno)
+ RepOriginId nodeid, int64 pageno)
{
int slotno;
int i;
@@ -268,7 +268,7 @@ bool
TransactionIdGetCommitTsData(TransactionId xid, TimestampTz *ts,
RepOriginId *nodeid)
{
- int pageno = TransactionIdToCTsPage(xid);
+ int64 pageno = TransactionIdToCTsPage(xid);
int entryno = TransactionIdToCTsEntry(xid);
int slotno;
CommitTimestampEntry entry;
@@ -278,7 +278,7 @@ TransactionIdGetCommitTsData(TransactionId xid, TimestampTz *ts,
if (!TransactionIdIsValid(xid))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("cannot retrieve commit timestamp for transaction %u", xid)));
+ errmsg("cannot retrieve commit timestamp for transaction %" PRIu64, xid)));
else if (!TransactionIdIsNormal(xid))
{
/* frozen and bootstrap xids are always committed far in the past */
@@ -589,7 +589,7 @@ BootStrapCommitTs(void)
* Control lock must be held at entry, and will be held at exit.
*/
static int
-ZeroCommitTsPage(int pageno, bool writeXlog)
+ZeroCommitTsPage(int64 pageno, bool writeXlog)
{
int slotno;
@@ -682,7 +682,7 @@ static void
ActivateCommitTs(void)
{
TransactionId xid;
- int pageno;
+ int64 pageno;
/* If we've done this already, there's nothing to do */
LWLockAcquire(CommitTsLock, LW_EXCLUSIVE);
@@ -815,7 +815,7 @@ CheckPointCommitTs(void)
void
ExtendCommitTs(TransactionId newestXact)
{
- int pageno;
+ int64 pageno;
/*
* Nothing to do if module not enabled. Note we do an unlocked read of
@@ -853,7 +853,7 @@ ExtendCommitTs(TransactionId newestXact)
void
TruncateCommitTs(TransactionId oldestXact)
{
- int cutoffPage;
+ int64 cutoffPage;
/*
* The cutoff point is the start of the segment containing oldestXact. We
@@ -918,49 +918,23 @@ AdvanceOldestCommitTsXid(TransactionId oldestXact)
* Decide whether a commitTS page number is "older" for truncation purposes.
* Analogous to CLOGPagePrecedes().
*
- * At default BLCKSZ, (1 << 31) % COMMIT_TS_XACTS_PER_PAGE == 128. This
- * introduces differences compared to CLOG and the other SLRUs having (1 <<
- * 31) % per_page == 0. This function never tests exactly
- * TransactionIdPrecedes(x-2^31, x). When the system reaches xidStopLimit,
- * there are two possible counts of page boundaries between oldestXact and the
- * latest XID assigned, depending on whether oldestXact is within the first
- * 128 entries of its page. Since this function doesn't know the location of
- * oldestXact within page2, it returns false for one page that actually is
- * expendable. This is a wider (yet still negligible) version of the
- * truncation opportunity that CLOGPagePrecedes() cannot recognize.
- *
- * For the sake of a worked example, number entries with decimal values such
- * that page1==1 entries range from 1.0 to 1.999. Let N+0.15 be the number of
- * pages that 2^31 entries will span (N is an integer). If oldestXact=N+2.1,
- * then the final safe XID assignment leaves newestXact=1.95. We keep page 2,
- * because entry=2.85 is the border that toggles whether entries precede the
- * last entry of the oldestXact page. While page 2 is expendable at
- * oldestXact=N+2.1, it would be precious at oldestXact=N+2.9.
+ * With 64xid this function is just "<", but we left it as a function in order
+ * for its calls remain "vanilla" like.
*/
static bool
-CommitTsPagePrecedes(int page1, int page2)
+CommitTsPagePrecedes(int64 page1, int64 page2)
{
- TransactionId xid1;
- TransactionId xid2;
-
- xid1 = ((TransactionId) page1) * COMMIT_TS_XACTS_PER_PAGE;
- xid1 += FirstNormalTransactionId + 1;
- xid2 = ((TransactionId) page2) * COMMIT_TS_XACTS_PER_PAGE;
- xid2 += FirstNormalTransactionId + 1;
-
- return (TransactionIdPrecedes(xid1, xid2) &&
- TransactionIdPrecedes(xid1, xid2 + COMMIT_TS_XACTS_PER_PAGE - 1));
+ return page1 < page2;
}
-
/*
* Write a ZEROPAGE xlog record
*/
static void
-WriteZeroPageXlogRec(int pageno)
+WriteZeroPageXlogRec(int64 pageno)
{
XLogBeginInsert();
- XLogRegisterData((char *) (&pageno), sizeof(int));
+ XLogRegisterData((char *) (&pageno), sizeof(int64));
(void) XLogInsert(RM_COMMIT_TS_ID, COMMIT_TS_ZEROPAGE);
}
@@ -968,7 +942,7 @@ WriteZeroPageXlogRec(int pageno)
* Write a TRUNCATE xlog record
*/
static void
-WriteTruncateXlogRec(int pageno, TransactionId oldestXid)
+WriteTruncateXlogRec(int64 pageno, TransactionId oldestXid)
{
xl_commit_ts_truncate xlrec;
@@ -993,10 +967,10 @@ commit_ts_redo(XLogReaderState *record)
if (info == COMMIT_TS_ZEROPAGE)
{
- int pageno;
+ int64 pageno;
int slotno;
- memcpy(&pageno, XLogRecGetData(record), sizeof(int));
+ memcpy(&pageno, XLogRecGetData(record), sizeof(int64));
LWLockAcquire(CommitTsSLRULock, LW_EXCLUSIVE);
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 806f2e43ba9..e44a8b85453 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -112,15 +112,15 @@
((xid) / (MultiXactOffset) MULTIXACT_OFFSETS_PER_PAGE)
#define MultiXactIdToOffsetEntry(xid) \
((xid) % (MultiXactOffset) MULTIXACT_OFFSETS_PER_PAGE)
-#define MultiXactIdToOffsetSegment(xid) (MultiXactIdToOffsetPage(xid) / SLRU_PAGES_PER_SEGMENT)
+#define MultiXactIdToOffsetSegment(xid) ((uint64)(MultiXactIdToOffsetPage(xid) / SLRU_PAGES_PER_SEGMENT))
/*
* The situation for members is a bit more complex: we store one byte of
* additional flag bits for each TransactionId. To do this without getting
- * into alignment issues, we store four bytes of flags, and then the
- * corresponding 4 Xids. Each such 5-word (20-byte) set we call a "group", and
- * are stored as a whole in pages. Thus, with 8kB BLCKSZ, we keep 409 groups
- * per page. This wastes 12 bytes per page, but that's OK -- simplicity (and
+ * into alignment issues, we store eight bytes of flags, and then the
+ * corresponding 8 Xids. Each such 9-word (72-byte) set we call a "group", and
+ * are stored as a whole in pages. Thus, with 8kB BLCKSZ, we keep 113 groups
+ * per page. This wastes 56 bytes per page, but that's OK -- simplicity (and
* performance) trumps space efficiency here.
*
* Note that the "offset" macros work with byte offset, not array indexes, so
@@ -132,7 +132,7 @@
#define MXACT_MEMBER_XACT_BITMASK ((1 << MXACT_MEMBER_BITS_PER_XACT) - 1)
/* how many full bytes of flags are there in a group? */
-#define MULTIXACT_FLAGBYTES_PER_GROUP 4
+#define MULTIXACT_FLAGBYTES_PER_GROUP 8
#define MULTIXACT_MEMBERS_PER_MEMBERGROUP \
(MULTIXACT_FLAGBYTES_PER_GROUP * MXACT_MEMBER_FLAGS_PER_BYTE)
/* size in bytes of a complete group */
@@ -142,22 +142,9 @@
#define MULTIXACT_MEMBERS_PER_PAGE \
(MULTIXACT_MEMBERGROUPS_PER_PAGE * MULTIXACT_MEMBERS_PER_MEMBERGROUP)
-/*
- * Because the number of items per page is not a divisor of the last item
- * number (member 0xFFFFFFFF), the last segment does not use the maximum number
- * of pages, and moreover the last used page therein does not use the same
- * number of items as previous pages. (Another way to say it is that the
- * 0xFFFFFFFF member is somewhere in the middle of the last page, so the page
- * has some empty space after that item.)
- *
- * This constant is the number of members in the last page of the last segment.
- */
-#define MAX_MEMBERS_IN_LAST_MEMBERS_PAGE \
- ((uint32) ((0xFFFFFFFF % MULTIXACT_MEMBERS_PER_PAGE) + 1))
-
/* page in which a member is to be found */
#define MXOffsetToMemberPage(xid) ((xid) / (TransactionId) MULTIXACT_MEMBERS_PER_PAGE)
-#define MXOffsetToMemberSegment(xid) (MXOffsetToMemberPage(xid) / SLRU_PAGES_PER_SEGMENT)
+#define MXOffsetToMemberSegment(xid) ((uint64)(MXOffsetToMemberPage(xid) / SLRU_PAGES_PER_SEGMENT))
/* Location (byte offset within page) of flag word for a given member */
#define MXOffsetToFlagsOffset(xid) \
@@ -216,22 +203,8 @@ typedef struct MultiXactStateData
MultiXactId oldestMultiXactId;
Oid oldestMultiXactDB;
- /*
- * Oldest multixact offset that is potentially referenced by a multixact
- * referenced by a relation. We don't always know this value, so there's
- * a flag here to indicate whether or not we currently do.
- */
- MultiXactOffset oldestOffset;
- bool oldestOffsetKnown;
-
/* support for anti-wraparound measures */
MultiXactId multiVacLimit;
- MultiXactId multiWarnLimit;
- MultiXactId multiStopLimit;
- MultiXactId multiWrapLimit;
-
- /* support for members anti-wraparound measures */
- MultiXactOffset offsetStopLimit; /* known if oldestOffsetKnown */
/*
* Per-backend data starts here. We have two arrays stored in the area
@@ -353,19 +326,16 @@ static void mXactCachePut(MultiXactId multi, int nmembers,
static char *mxstatus_to_string(MultiXactStatus status);
/* management of SLRU infrastructure */
-static int ZeroMultiXactOffsetPage(int pageno, bool writeXlog);
-static int ZeroMultiXactMemberPage(int pageno, bool writeXlog);
-static bool MultiXactOffsetPagePrecedes(int page1, int page2);
-static bool MultiXactMemberPagePrecedes(int page1, int page2);
+static int ZeroMultiXactOffsetPage(int64 pageno, bool writeXlog);
+static int ZeroMultiXactMemberPage(int64 pageno, bool writeXlog);
+static bool MultiXactOffsetPagePrecedes(int64 page1, int64 page2);
+static bool MultiXactMemberPagePrecedes(int64 page1, int64 page2);
static bool MultiXactOffsetPrecedes(MultiXactOffset offset1,
MultiXactOffset offset2);
static void ExtendMultiXactOffset(MultiXactId multi);
static void ExtendMultiXactMember(MultiXactOffset offset, int nmembers);
-static bool MultiXactOffsetWouldWrap(MultiXactOffset boundary,
- MultiXactOffset start, uint32 distance);
-static bool SetOffsetVacuumLimit(bool is_startup);
static bool find_multixact_start(MultiXactId multi, MultiXactOffset *result);
-static void WriteMZeroPageXlogRec(int pageno, uint8 info);
+static void WriteMZeroPageXlogRec(int64 pageno, uint8 info);
static void WriteMTruncateXlogRec(Oid oldestMultiDB,
MultiXactId startTruncOff,
MultiXactId endTruncOff,
@@ -397,6 +367,9 @@ MultiXactIdCreate(TransactionId xid1, MultiXactStatus status1,
/* MultiXactIdSetOldestMember() must have been called already. */
Assert(MultiXactIdIsValid(OldestMemberMXactId[MyBackendId]));
+ /* memset members array because with 64-bit xids it has a padding hole */
+ MemSet(members, 0, sizeof(members));
+
/*
* Note: unlike MultiXactIdExpand, we don't bother to check that both XIDs
* are still running. In typical usage, xid2 will be our own XID and the
@@ -451,7 +424,7 @@ MultiXactIdExpand(MultiXactId multi, TransactionId xid, MultiXactStatus status)
/* MultiXactIdSetOldestMember() must have been called already. */
Assert(MultiXactIdIsValid(OldestMemberMXactId[MyBackendId]));
- debug_elog5(DEBUG2, "Expand: received multi %u, xid %u status %s",
+ debug_elog5(DEBUG2, "Expand: received multi " XID_FMT ", xid " XID_FMT " status %s",
multi, xid, mxstatus_to_string(status));
/*
@@ -476,7 +449,7 @@ MultiXactIdExpand(MultiXactId multi, TransactionId xid, MultiXactStatus status)
member.status = status;
newMulti = MultiXactIdCreateFromMembers(1, &member);
- debug_elog4(DEBUG2, "Expand: %u has no members, create singleton %u",
+ debug_elog4(DEBUG2, "Expand: " XID_FMT " has no members, create singleton " XID_FMT,
multi, newMulti);
return newMulti;
}
@@ -490,7 +463,7 @@ MultiXactIdExpand(MultiXactId multi, TransactionId xid, MultiXactStatus status)
if (TransactionIdEquals(members[i].xid, xid) &&
(members[i].status == status))
{
- debug_elog4(DEBUG2, "Expand: %u is already a member of %u",
+ debug_elog4(DEBUG2, "Expand: " XID_FMT " is already a member of " XID_FMT,
xid, multi);
pfree(members);
return multi;
@@ -511,7 +484,7 @@ MultiXactIdExpand(MultiXactId multi, TransactionId xid, MultiXactStatus status)
* end of the loop.
*/
newMembers = (MultiXactMember *)
- palloc(sizeof(MultiXactMember) * (nmembers + 1));
+ palloc0(sizeof(MultiXactMember) * (nmembers + 1));
for (i = 0, j = 0; i < nmembers; i++)
{
@@ -526,12 +499,13 @@ MultiXactIdExpand(MultiXactId multi, TransactionId xid, MultiXactStatus status)
newMembers[j].xid = xid;
newMembers[j++].status = status;
+
newMulti = MultiXactIdCreateFromMembers(j, newMembers);
pfree(members);
pfree(newMembers);
- debug_elog3(DEBUG2, "Expand: returning new multi %u", newMulti);
+ debug_elog3(DEBUG2, "Expand: returning new multi " XID_FMT, newMulti);
return newMulti;
}
@@ -554,7 +528,7 @@ MultiXactIdIsRunning(MultiXactId multi, bool isLockOnly)
int nmembers;
int i;
- debug_elog3(DEBUG2, "IsRunning %u?", multi);
+ debug_elog3(DEBUG2, "IsRunning " XID_FMT "?", multi);
/*
* "false" here means we assume our callers have checked that the given
@@ -594,7 +568,7 @@ MultiXactIdIsRunning(MultiXactId multi, bool isLockOnly)
{
if (TransactionIdIsInProgress(members[i].xid))
{
- debug_elog4(DEBUG2, "IsRunning: member %d (%u) is running",
+ debug_elog4(DEBUG2, "IsRunning: member %d (" XID_FMT ") is running",
i, members[i].xid);
pfree(members);
return true;
@@ -603,7 +577,7 @@ MultiXactIdIsRunning(MultiXactId multi, bool isLockOnly)
pfree(members);
- debug_elog3(DEBUG2, "IsRunning: %u is not running", multi);
+ debug_elog3(DEBUG2, "IsRunning: " XID_FMT " is not running", multi);
return false;
}
@@ -657,7 +631,7 @@ MultiXactIdSetOldestMember(void)
LWLockRelease(MultiXactGenLock);
- debug_elog4(DEBUG2, "MultiXact: setting OldestMember[%d] = %u",
+ debug_elog4(DEBUG2, "MultiXact: setting OldestMember[%d] = " XID_FMT,
MyBackendId, nextMXact);
}
}
@@ -710,7 +684,7 @@ MultiXactIdSetOldestVisible(void)
LWLockRelease(MultiXactGenLock);
- debug_elog4(DEBUG2, "MultiXact: setting OldestVisible[%d] = %u",
+ debug_elog4(DEBUG2, "MultiXact: setting OldestVisible[%d] = " XID_FMT,
MyBackendId, oldestMXact);
}
}
@@ -729,9 +703,6 @@ ReadNextMultiXactId(void)
mxid = MultiXactState->nextMXact;
LWLockRelease(MultiXactGenLock);
- if (mxid < FirstMultiXactId)
- mxid = FirstMultiXactId;
-
return mxid;
}
@@ -862,8 +833,8 @@ static void
RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset,
int nmembers, MultiXactMember *members)
{
- int pageno;
- int prev_pageno;
+ int64 pageno;
+ int64 prev_pageno;
int entryno;
int slotno;
MultiXactOffset *offptr;
@@ -899,8 +870,8 @@ RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset,
for (i = 0; i < nmembers; i++, offset++)
{
TransactionId *memberptr;
- uint32 *flagsptr;
- uint32 flagsval;
+ uint64 *flagsptr;
+ uint64 flagsval;
int bshift;
int flagsoff;
int memberoff;
@@ -921,14 +892,15 @@ RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset,
memberptr = (TransactionId *)
(MultiXactMemberCtl->shared->page_buffer[slotno] + memberoff);
+
*memberptr = members[i].xid;
- flagsptr = (uint32 *)
+ flagsptr = (uint64 *)
(MultiXactMemberCtl->shared->page_buffer[slotno] + flagsoff);
flagsval = *flagsptr;
- flagsval &= ~(((1 << MXACT_MEMBER_BITS_PER_XACT) - 1) << bshift);
- flagsval |= (members[i].status << bshift);
+ flagsval &= ~((uint64)((1 << MXACT_MEMBER_BITS_PER_XACT) - 1) << bshift);
+ flagsval |= ((uint64)members[i].status << bshift);
*flagsptr = flagsval;
MultiXactMemberCtl->shared->page_dirty[slotno] = true;
@@ -981,8 +953,6 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
* If we're past multiVacLimit or the safe threshold for member storage
* space, or we don't know what the safe threshold for member storage is,
* start trying to force autovacuum cycles.
- * If we're past multiWarnLimit, start issuing warnings.
- * If we're past multiStopLimit, refuse to create new MultiXactIds.
*
* Note these are pretty much the same protections in GetNewTransactionId.
*----------
@@ -996,41 +966,9 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
* possibility of deadlock while doing get_database_name(). First,
* copy all the shared values we'll need in this path.
*/
- MultiXactId multiWarnLimit = MultiXactState->multiWarnLimit;
- MultiXactId multiStopLimit = MultiXactState->multiStopLimit;
- MultiXactId multiWrapLimit = MultiXactState->multiWrapLimit;
- Oid oldest_datoid = MultiXactState->oldestMultiXactDB;
LWLockRelease(MultiXactGenLock);
- if (IsUnderPostmaster &&
- !MultiXactIdPrecedes(result, multiStopLimit))
- {
- char *oldest_datname = get_database_name(oldest_datoid);
-
- /*
- * Immediately kick autovacuum into action as we're already in
- * ERROR territory.
- */
- SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
-
- /* complain even if that DB has disappeared */
- if (oldest_datname)
- ereport(ERROR,
- (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg("database is not accepting commands that generate new MultiXactIds to avoid wraparound data loss in database \"%s\"",
- oldest_datname),
- errhint("Execute a database-wide VACUUM in that database.\n"
- "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
- else
- ereport(ERROR,
- (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg("database is not accepting commands that generate new MultiXactIds to avoid wraparound data loss in database with OID %u",
- oldest_datoid),
- errhint("Execute a database-wide VACUUM in that database.\n"
- "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
- }
-
/*
* To avoid swamping the postmaster with signals, we issue the autovac
* request only once per 64K multis generated. This still gives
@@ -1039,36 +977,9 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
if (IsUnderPostmaster && (result % 65536) == 0)
SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
- if (!MultiXactIdPrecedes(result, multiWarnLimit))
- {
- char *oldest_datname = get_database_name(oldest_datoid);
-
- /* complain even if that DB has disappeared */
- if (oldest_datname)
- ereport(WARNING,
- (errmsg_plural("database \"%s\" must be vacuumed before %u more MultiXactId is used",
- "database \"%s\" must be vacuumed before %u more MultiXactIds are used",
- multiWrapLimit - result,
- oldest_datname,
- multiWrapLimit - result),
- errhint("Execute a database-wide VACUUM in that database.\n"
- "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
- else
- ereport(WARNING,
- (errmsg_plural("database with OID %u must be vacuumed before %u more MultiXactId is used",
- "database with OID %u must be vacuumed before %u more MultiXactIds are used",
- multiWrapLimit - result,
- oldest_datoid,
- multiWrapLimit - result),
- errhint("Execute a database-wide VACUUM in that database.\n"
- "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
- }
-
/* Re-acquire lock and start over */
LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE);
result = MultiXactState->nextMXact;
- if (result < FirstMultiXactId)
- result = FirstMultiXactId;
}
/* Make sure there is room for the MXID in the file. */
@@ -1088,78 +999,6 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
else
*offset = nextOffset;
- /*----------
- * Protect against overrun of the members space as well, with the
- * following rules:
- *
- * If we're past offsetStopLimit, refuse to generate more multis.
- * If we're close to offsetStopLimit, emit a warning.
- *
- * Arbitrarily, we start emitting warnings when we're 20 segments or less
- * from offsetStopLimit.
- *
- * Note we haven't updated the shared state yet, so if we fail at this
- * point, the multixact ID we grabbed can still be used by the next guy.
- *
- * Note that there is no point in forcing autovacuum runs here: the
- * multixact freeze settings would have to be reduced for that to have any
- * effect.
- *----------
- */
-#define OFFSET_WARN_SEGMENTS 20
- if (MultiXactState->oldestOffsetKnown &&
- MultiXactOffsetWouldWrap(MultiXactState->offsetStopLimit, nextOffset,
- nmembers))
- {
- /* see comment in the corresponding offsets wraparound case */
- SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
-
- ereport(ERROR,
- (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg("multixact \"members\" limit exceeded"),
- errdetail_plural("This command would create a multixact with %u members, but the remaining space is only enough for %u member.",
- "This command would create a multixact with %u members, but the remaining space is only enough for %u members.",
- MultiXactState->offsetStopLimit - nextOffset - 1,
- nmembers,
- MultiXactState->offsetStopLimit - nextOffset - 1),
- errhint("Execute a database-wide VACUUM in database with OID %u with reduced vacuum_multixact_freeze_min_age and vacuum_multixact_freeze_table_age settings.",
- MultiXactState->oldestMultiXactDB)));
- }
-
- /*
- * Check whether we should kick autovacuum into action, to prevent members
- * wraparound. NB we use a much larger window to trigger autovacuum than
- * just the warning limit. The warning is just a measure of last resort -
- * this is in line with GetNewTransactionId's behaviour.
- */
- if (!MultiXactState->oldestOffsetKnown ||
- (MultiXactState->nextOffset - MultiXactState->oldestOffset
- > MULTIXACT_MEMBER_SAFE_THRESHOLD))
- {
- /*
- * To avoid swamping the postmaster with signals, we issue the autovac
- * request only when crossing a segment boundary. With default
- * compilation settings that's roughly after 50k members. This still
- * gives plenty of chances before we get into real trouble.
- */
- if ((MXOffsetToMemberPage(nextOffset) / SLRU_PAGES_PER_SEGMENT) !=
- (MXOffsetToMemberPage(nextOffset + nmembers) / SLRU_PAGES_PER_SEGMENT))
- SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
- }
-
- if (MultiXactState->oldestOffsetKnown &&
- MultiXactOffsetWouldWrap(MultiXactState->offsetStopLimit,
- nextOffset,
- nmembers + MULTIXACT_MEMBERS_PER_PAGE * SLRU_PAGES_PER_SEGMENT * OFFSET_WARN_SEGMENTS))
- ereport(WARNING,
- (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg_plural("database with OID %u must be vacuumed before %d more multixact member is used",
- "database with OID %u must be vacuumed before %d more multixact members are used",
- MultiXactState->offsetStopLimit - nextOffset + nmembers,
- MultiXactState->oldestMultiXactDB,
- MultiXactState->offsetStopLimit - nextOffset + nmembers),
- errhint("Execute a database-wide VACUUM in that database with reduced vacuum_multixact_freeze_min_age and vacuum_multixact_freeze_table_age settings.")));
-
ExtendMultiXactMember(nextOffset, nmembers);
/*
@@ -1188,7 +1027,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
LWLockRelease(MultiXactGenLock);
- debug_elog4(DEBUG2, "GetNew: returning %u offset %u", result, *offset);
+ debug_elog4(DEBUG2, "GetNew: returning " XID_FMT " offset " INT64_FORMAT, result, *offset);
return result;
}
@@ -1223,8 +1062,8 @@ int
GetMultiXactIdMembers(MultiXactId multi, MultiXactMember **members,
bool from_pgupgrade, bool onlyLock)
{
- int pageno;
- int prev_pageno;
+ int64 pageno;
+ int64 prev_pageno;
int entryno;
int slotno;
MultiXactOffset *offptr;
@@ -1232,13 +1071,12 @@ GetMultiXactIdMembers(MultiXactId multi, MultiXactMember **members,
int length;
int truelength;
int i;
- MultiXactId oldestMXact;
MultiXactId nextMXact;
MultiXactId tmpMXact;
MultiXactOffset nextOffset;
MultiXactMember *ptr;
- debug_elog3(DEBUG2, "GetMembers: asked for %u", multi);
+ debug_elog3(DEBUG2, "GetMembers: asked for " XID_FMT, multi);
if (!MultiXactIdIsValid(multi) || from_pgupgrade)
{
@@ -1288,24 +1126,11 @@ GetMultiXactIdMembers(MultiXactId multi, MultiXactMember **members,
*/
LWLockAcquire(MultiXactGenLock, LW_SHARED);
- oldestMXact = MultiXactState->oldestMultiXactId;
nextMXact = MultiXactState->nextMXact;
nextOffset = MultiXactState->nextOffset;
LWLockRelease(MultiXactGenLock);
- if (MultiXactIdPrecedes(multi, oldestMXact))
- ereport(ERROR,
- (errcode(ERRCODE_INTERNAL_ERROR),
- errmsg("MultiXactId %u does no longer exist -- apparent wraparound",
- multi)));
-
- if (!MultiXactIdPrecedes(multi, nextMXact))
- ereport(ERROR,
- (errcode(ERRCODE_INTERNAL_ERROR),
- errmsg("MultiXactId %u has not been created yet -- apparent wraparound",
- multi)));
-
/*
* Find out the offset at which we need to start reading MultiXactMembers
* and the number of members in the multixact. We determine the latter as
@@ -1350,7 +1175,9 @@ retry:
offptr += entryno;
offset = *offptr;
- Assert(offset != 0);
+ if (offset == 0)
+ ereport(ERROR,
+ (errmsg("found invalid zero offset in multixact " XID_FMT, multi)));
/*
* Use the same increment rule as GetNewMultiXactId(), that is, don't
@@ -1367,10 +1194,6 @@ retry:
{
MultiXactOffset nextMXOffset;
- /* handle wraparound if needed */
- if (tmpMXact < FirstMultiXactId)
- tmpMXact = FirstMultiXactId;
-
prev_pageno = pageno;
pageno = MultiXactIdToOffsetPage(tmpMXact);
@@ -1397,7 +1220,7 @@ retry:
LWLockRelease(MultiXactOffsetSLRULock);
- ptr = (MultiXactMember *) palloc(length * sizeof(MultiXactMember));
+ ptr = (MultiXactMember *) palloc0(length * sizeof(MultiXactMember));
/* Now get the members themselves. */
LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);
@@ -1407,7 +1230,7 @@ retry:
for (i = 0; i < length; i++, offset++)
{
TransactionId *xactptr;
- uint32 *flagsptr;
+ uint64 *flagsptr;
int flagsoff;
int bshift;
int memberoff;
@@ -1433,7 +1256,7 @@ retry:
flagsoff = MXOffsetToFlagsOffset(offset);
bshift = MXOffsetToFlagsBitShift(offset);
- flagsptr = (uint32 *) (MultiXactMemberCtl->shared->page_buffer[slotno] + flagsoff);
+ flagsptr = (uint64 *) (MultiXactMemberCtl->shared->page_buffer[slotno] + flagsoff);
ptr[truelength].xid = *xactptr;
ptr[truelength].status = (*flagsptr >> bshift) & MXACT_MEMBER_XACT_BITMASK;
@@ -1517,7 +1340,7 @@ mXactCacheGetBySet(int nmembers, MultiXactMember *members)
*/
if (memcmp(members, entry->members, nmembers * sizeof(MultiXactMember)) == 0)
{
- debug_elog3(DEBUG2, "CacheGet: found %u", entry->multi);
+ debug_elog3(DEBUG2, "CacheGet: found " XID_FMT, entry->multi);
dlist_move_head(&MXactCache, iter.cur);
return entry->multi;
}
@@ -1540,7 +1363,7 @@ mXactCacheGetById(MultiXactId multi, MultiXactMember **members)
{
dlist_iter iter;
- debug_elog3(DEBUG2, "CacheGet: looking for %u", multi);
+ debug_elog3(DEBUG2, "CacheGet: looking for " XID_FMT, multi);
dlist_foreach(iter, &MXactCache)
{
@@ -1621,7 +1444,7 @@ mXactCachePut(MultiXactId multi, int nmembers, MultiXactMember *members)
MXactCacheMembers--;
entry = dlist_container(mXactCacheEnt, node, node);
- debug_elog3(DEBUG2, "CachePut: pruning cached multi %u",
+ debug_elog3(DEBUG2, "CachePut: pruning cached multi " XID_FMT,
entry->multi);
pfree(entry);
@@ -1663,11 +1486,11 @@ mxid_to_string(MultiXactId multi, int nmembers, MultiXactMember *members)
initStringInfo(&buf);
- appendStringInfo(&buf, "%u %d[%u (%s)", multi, nmembers, members[0].xid,
+ appendStringInfo(&buf, XID_FMT " %d[" XID_FMT " (%s)", multi, nmembers, members[0].xid,
mxstatus_to_string(members[0].status));
for (i = 1; i < nmembers; i++)
- appendStringInfo(&buf, ", %u (%s)", members[i].xid,
+ appendStringInfo(&buf, ", " XID_FMT " (%s)", members[i].xid,
mxstatus_to_string(members[i].status));
appendStringInfoChar(&buf, ']');
@@ -1894,6 +1717,8 @@ void
BootStrapMultiXact(void)
{
int slotno;
+ int64 multiOffsetPageno;
+ int64 multiMemberPageno;
LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
@@ -1904,6 +1729,17 @@ BootStrapMultiXact(void)
SimpleLruWritePage(MultiXactOffsetCtl, slotno);
Assert(!MultiXactOffsetCtl->shared->page_dirty[slotno]);
+ multiOffsetPageno = MultiXactIdToOffsetPage(MultiXactState->nextMXact - 1);
+ if (multiOffsetPageno != 0)
+ {
+ /* Create and zero the first page of the offsets log */
+ slotno = ZeroMultiXactOffsetPage(multiOffsetPageno, false);
+
+ /* Make sure it's written out */
+ SimpleLruWritePage(MultiXactOffsetCtl, slotno);
+ Assert(!MultiXactOffsetCtl->shared->page_dirty[slotno]);
+ }
+
LWLockRelease(MultiXactOffsetSLRULock);
LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);
@@ -1915,7 +1751,32 @@ BootStrapMultiXact(void)
SimpleLruWritePage(MultiXactMemberCtl, slotno);
Assert(!MultiXactMemberCtl->shared->page_dirty[slotno]);
+ multiMemberPageno = MXOffsetToMemberPage(MultiXactState->nextOffset - 1);
+ if (multiMemberPageno != 0)
+ {
+ /* Create and zero the first page of the members log */
+ slotno = ZeroMultiXactMemberPage(multiMemberPageno, false);
+
+ /* Make sure it's written out */
+ SimpleLruWritePage(MultiXactMemberCtl, slotno);
+ Assert(!MultiXactMemberCtl->shared->page_dirty[slotno]);
+ }
+
LWLockRelease(MultiXactMemberSLRULock);
+
+ /*
+ * If we're starting not from zero offset, initilize dummy multixact to
+ * evade too long loop in PerformMembersTruncation().
+ */
+ if (MultiXactState->nextOffset > 0 && MultiXactState->nextMXact > 0)
+ {
+ RecordNewMultiXact(FirstMultiXactId,
+ MultiXactState->nextOffset,
+ 0, NULL);
+ RecordNewMultiXact(MultiXactState->nextMXact - 1,
+ MultiXactState->nextOffset,
+ 0, NULL);
+ }
}
/*
@@ -1928,7 +1789,7 @@ BootStrapMultiXact(void)
* Control lock must be held at entry, and will be held at exit.
*/
static int
-ZeroMultiXactOffsetPage(int pageno, bool writeXlog)
+ZeroMultiXactOffsetPage(int64 pageno, bool writeXlog)
{
int slotno;
@@ -1944,7 +1805,7 @@ ZeroMultiXactOffsetPage(int pageno, bool writeXlog)
* Ditto, for MultiXactMember
*/
static int
-ZeroMultiXactMemberPage(int pageno, bool writeXlog)
+ZeroMultiXactMemberPage(int64 pageno, bool writeXlog)
{
int slotno;
@@ -1974,7 +1835,7 @@ ZeroMultiXactMemberPage(int pageno, bool writeXlog)
static void
MaybeExtendOffsetSlru(void)
{
- int pageno;
+ int64 pageno;
pageno = MultiXactIdToOffsetPage(MultiXactState->nextMXact);
@@ -2009,7 +1870,7 @@ StartupMultiXact(void)
{
MultiXactId multi = MultiXactState->nextMXact;
MultiXactOffset offset = MultiXactState->nextOffset;
- int pageno;
+ int64 pageno;
/*
* Initialize offset's idea of the latest page number.
@@ -2034,7 +1895,7 @@ TrimMultiXact(void)
MultiXactOffset offset;
MultiXactId oldestMXact;
Oid oldestMXactDB;
- int pageno;
+ int64 pageno;
int entryno;
int flagsoff;
@@ -2144,7 +2005,7 @@ MultiXactGetCheckptMulti(bool is_shutdown,
LWLockRelease(MultiXactGenLock);
debug_elog6(DEBUG2,
- "MultiXact: checkpoint is nextMulti %u, nextOffset %u, oldestMulti %u in DB %u",
+ "MultiXact: checkpoint is nextMulti " XID_FMT ", nextOffset " INT64_FORMAT ", oldestMulti " XID_FMT " in DB %u",
*nextMulti, *nextMultiOffset, *oldestMulti, *oldestMultiDB);
}
@@ -2179,7 +2040,7 @@ void
MultiXactSetNextMXact(MultiXactId nextMulti,
MultiXactOffset nextMultiOffset)
{
- debug_elog4(DEBUG2, "MultiXact: setting next multi to %u offset %u",
+ debug_elog4(DEBUG2, "MultiXact: setting next multi to " XID_FMT " offset " INT64_FORMAT,
nextMulti, nextMultiOffset);
LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE);
MultiXactState->nextMXact = nextMulti;
@@ -2214,47 +2075,9 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid,
bool is_startup)
{
MultiXactId multiVacLimit;
- MultiXactId multiWarnLimit;
- MultiXactId multiStopLimit;
- MultiXactId multiWrapLimit;
- MultiXactId curMulti;
- bool needs_offset_vacuum;
Assert(MultiXactIdIsValid(oldest_datminmxid));
- /*
- * We pretend that a wrap will happen halfway through the multixact ID
- * space, but that's not really true, because multixacts wrap differently
- * from transaction IDs. Note that, separately from any concern about
- * multixact IDs wrapping, we must ensure that multixact members do not
- * wrap. Limits for that are set in SetOffsetVacuumLimit, not here.
- */
- multiWrapLimit = oldest_datminmxid + (MaxMultiXactId >> 1);
- if (multiWrapLimit < FirstMultiXactId)
- multiWrapLimit += FirstMultiXactId;
-
- /*
- * We'll refuse to continue assigning MultiXactIds once we get within 3M
- * multi of data loss. See SetTransactionIdLimit.
- */
- multiStopLimit = multiWrapLimit - 3000000;
- if (multiStopLimit < FirstMultiXactId)
- multiStopLimit -= FirstMultiXactId;
-
- /*
- * We'll start complaining loudly when we get within 40M multis of data
- * loss. This is kind of arbitrary, but if you let your gas gauge get
- * down to 2% of full, would you be looking for the next gas station? We
- * need to be fairly liberal about this number because there are lots of
- * scenarios where most transactions are done by automatic clients that
- * won't pay attention to warnings. (No, we're not gonna make this
- * configurable. If you know enough to configure it, you know enough to
- * not get in this kind of trouble in the first place.)
- */
- multiWarnLimit = multiWrapLimit - 40000000;
- if (multiWarnLimit < FirstMultiXactId)
- multiWarnLimit -= FirstMultiXactId;
-
/*
* We'll start trying to force autovacuums when oldest_datminmxid gets to
* be more than autovacuum_multixact_freeze_max_age mxids old.
@@ -2264,25 +2087,14 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid,
* its value. See SetTransactionIdLimit.
*/
multiVacLimit = oldest_datminmxid + autovacuum_multixact_freeze_max_age;
- if (multiVacLimit < FirstMultiXactId)
- multiVacLimit += FirstMultiXactId;
/* Grab lock for just long enough to set the new limit values */
LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE);
MultiXactState->oldestMultiXactId = oldest_datminmxid;
MultiXactState->oldestMultiXactDB = oldest_datoid;
MultiXactState->multiVacLimit = multiVacLimit;
- MultiXactState->multiWarnLimit = multiWarnLimit;
- MultiXactState->multiStopLimit = multiStopLimit;
- MultiXactState->multiWrapLimit = multiWrapLimit;
- curMulti = MultiXactState->nextMXact;
LWLockRelease(MultiXactGenLock);
- /* Log the info */
- ereport(DEBUG1,
- (errmsg_internal("MultiXactId wrap limit is %u, limited by database with OID %u",
- multiWrapLimit, oldest_datoid)));
-
/*
* Computing the actual limits is only possible once the data directory is
* in a consistent state. There's no need to compute the limits while
@@ -2294,59 +2106,6 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid,
return;
Assert(!InRecovery);
-
- /* Set limits for offset vacuum. */
- needs_offset_vacuum = SetOffsetVacuumLimit(is_startup);
-
- /*
- * If past the autovacuum force point, immediately signal an autovac
- * request. The reason for this is that autovac only processes one
- * database per invocation. Once it's finished cleaning up the oldest
- * database, it'll call here, and we'll signal the postmaster to start
- * another iteration immediately if there are still any old databases.
- */
- if ((MultiXactIdPrecedes(multiVacLimit, curMulti) ||
- needs_offset_vacuum) && IsUnderPostmaster)
- SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
-
- /* Give an immediate warning if past the wrap warn point */
- if (MultiXactIdPrecedes(multiWarnLimit, curMulti))
- {
- char *oldest_datname;
-
- /*
- * We can be called when not inside a transaction, for example during
- * StartupXLOG(). In such a case we cannot do database access, so we
- * must just report the oldest DB's OID.
- *
- * Note: it's also possible that get_database_name fails and returns
- * NULL, for example because the database just got dropped. We'll
- * still warn, even though the warning might now be unnecessary.
- */
- if (IsTransactionState())
- oldest_datname = get_database_name(oldest_datoid);
- else
- oldest_datname = NULL;
-
- if (oldest_datname)
- ereport(WARNING,
- (errmsg_plural("database \"%s\" must be vacuumed before %u more MultiXactId is used",
- "database \"%s\" must be vacuumed before %u more MultiXactIds are used",
- multiWrapLimit - curMulti,
- oldest_datname,
- multiWrapLimit - curMulti),
- errhint("To avoid a database shutdown, execute a database-wide VACUUM in that database.\n"
- "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
- else
- ereport(WARNING,
- (errmsg_plural("database with OID %u must be vacuumed before %u more MultiXactId is used",
- "database with OID %u must be vacuumed before %u more MultiXactIds are used",
- multiWrapLimit - curMulti,
- oldest_datoid,
- multiWrapLimit - curMulti),
- errhint("To avoid a database shutdown, execute a database-wide VACUUM in that database.\n"
- "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
- }
}
/*
@@ -2365,12 +2124,12 @@ MultiXactAdvanceNextMXact(MultiXactId minMulti,
LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE);
if (MultiXactIdPrecedes(MultiXactState->nextMXact, minMulti))
{
- debug_elog3(DEBUG2, "MultiXact: setting next multi to %u", minMulti);
+ debug_elog3(DEBUG2, "MultiXact: setting next multi to " XID_FMT, minMulti);
MultiXactState->nextMXact = minMulti;
}
if (MultiXactOffsetPrecedes(MultiXactState->nextOffset, minMultiOffset))
{
- debug_elog3(DEBUG2, "MultiXact: setting next offset to %u",
+ debug_elog3(DEBUG2, "MultiXact: setting next offset to " INT64_FORMAT,
minMultiOffset);
MultiXactState->nextOffset = minMultiOffset;
}
@@ -2403,7 +2162,7 @@ MultiXactAdvanceOldest(MultiXactId oldestMulti, Oid oldestMultiDB)
static void
ExtendMultiXactOffset(MultiXactId multi)
{
- int pageno;
+ int64 pageno;
/*
* No work except at first MultiXactId of a page. But beware: just after
@@ -2452,7 +2211,7 @@ ExtendMultiXactMember(MultiXactOffset offset, int nmembers)
flagsbit = MXOffsetToFlagsBitShift(offset);
if (flagsoff == 0 && flagsbit == 0)
{
- int pageno;
+ int64 pageno;
pageno = MXOffsetToMemberPage(offset);
@@ -2464,23 +2223,7 @@ ExtendMultiXactMember(MultiXactOffset offset, int nmembers)
LWLockRelease(MultiXactMemberSLRULock);
}
- /*
- * Compute the number of items till end of current page. Careful: if
- * addition of unsigned ints wraps around, we're at the last page of
- * the last segment; since that page holds a different number of items
- * than other pages, we need to do it differently.
- */
- if (offset + MAX_MEMBERS_IN_LAST_MEMBERS_PAGE < offset)
- {
- /*
- * This is the last page of the last segment; we can compute the
- * number of items left to allocate in it without modulo
- * arithmetic.
- */
- difference = MaxMultiXactOffset - offset + 1;
- }
- else
- difference = MULTIXACT_MEMBERS_PER_PAGE - offset % MULTIXACT_MEMBERS_PER_PAGE;
+ difference = MULTIXACT_MEMBERS_PER_PAGE - offset % MULTIXACT_MEMBERS_PER_PAGE;
/*
* Advance to next page, taking care to properly handle the wraparound
@@ -2544,183 +2287,6 @@ GetOldestMultiXactId(void)
return oldestMXact;
}
-/*
- * Determine how aggressively we need to vacuum in order to prevent member
- * wraparound.
- *
- * To do so determine what's the oldest member offset and install the limit
- * info in MultiXactState, where it can be used to prevent overrun of old data
- * in the members SLRU area.
- *
- * The return value is true if emergency autovacuum is required and false
- * otherwise.
- */
-static bool
-SetOffsetVacuumLimit(bool is_startup)
-{
- MultiXactId oldestMultiXactId;
- MultiXactId nextMXact;
- MultiXactOffset oldestOffset = 0; /* placate compiler */
- MultiXactOffset prevOldestOffset;
- MultiXactOffset nextOffset;
- bool oldestOffsetKnown = false;
- bool prevOldestOffsetKnown;
- MultiXactOffset offsetStopLimit = 0;
- MultiXactOffset prevOffsetStopLimit;
-
- /*
- * NB: Have to prevent concurrent truncation, we might otherwise try to
- * lookup an oldestMulti that's concurrently getting truncated away.
- */
- LWLockAcquire(MultiXactTruncationLock, LW_SHARED);
-
- /* Read relevant fields from shared memory. */
- LWLockAcquire(MultiXactGenLock, LW_SHARED);
- oldestMultiXactId = MultiXactState->oldestMultiXactId;
- nextMXact = MultiXactState->nextMXact;
- nextOffset = MultiXactState->nextOffset;
- prevOldestOffsetKnown = MultiXactState->oldestOffsetKnown;
- prevOldestOffset = MultiXactState->oldestOffset;
- prevOffsetStopLimit = MultiXactState->offsetStopLimit;
- Assert(MultiXactState->finishedStartup);
- LWLockRelease(MultiXactGenLock);
-
- /*
- * Determine the offset of the oldest multixact. Normally, we can read
- * the offset from the multixact itself, but there's an important special
- * case: if there are no multixacts in existence at all, oldestMXact
- * obviously can't point to one. It will instead point to the multixact
- * ID that will be assigned the next time one is needed.
- */
- if (oldestMultiXactId == nextMXact)
- {
- /*
- * When the next multixact gets created, it will be stored at the next
- * offset.
- */
- oldestOffset = nextOffset;
- oldestOffsetKnown = true;
- }
- else
- {
- /*
- * Figure out where the oldest existing multixact's offsets are
- * stored. Due to bugs in early release of PostgreSQL 9.3.X and 9.4.X,
- * the supposedly-earliest multixact might not really exist. We are
- * careful not to fail in that case.
- */
- oldestOffsetKnown =
- find_multixact_start(oldestMultiXactId, &oldestOffset);
-
- if (oldestOffsetKnown)
- ereport(DEBUG1,
- (errmsg_internal("oldest MultiXactId member is at offset %u",
- oldestOffset)));
- else
- ereport(LOG,
- (errmsg("MultiXact member wraparound protections are disabled because oldest checkpointed MultiXact %u does not exist on disk",
- oldestMultiXactId)));
- }
-
- LWLockRelease(MultiXactTruncationLock);
-
- /*
- * If we can, compute limits (and install them MultiXactState) to prevent
- * overrun of old data in the members SLRU area. We can only do so if the
- * oldest offset is known though.
- */
- if (oldestOffsetKnown)
- {
- /* move back to start of the corresponding segment */
- offsetStopLimit = oldestOffset - (oldestOffset %
- (MULTIXACT_MEMBERS_PER_PAGE * SLRU_PAGES_PER_SEGMENT));
-
- /* always leave one segment before the wraparound point */
- offsetStopLimit -= (MULTIXACT_MEMBERS_PER_PAGE * SLRU_PAGES_PER_SEGMENT);
-
- if (!prevOldestOffsetKnown && !is_startup)
- ereport(LOG,
- (errmsg("MultiXact member wraparound protections are now enabled")));
-
- ereport(DEBUG1,
- (errmsg_internal("MultiXact member stop limit is now %u based on MultiXact %u",
- offsetStopLimit, oldestMultiXactId)));
- }
- else if (prevOldestOffsetKnown)
- {
- /*
- * If we failed to get the oldest offset this time, but we have a
- * value from a previous pass through this function, use the old
- * values rather than automatically forcing an emergency autovacuum
- * cycle again.
- */
- oldestOffset = prevOldestOffset;
- oldestOffsetKnown = true;
- offsetStopLimit = prevOffsetStopLimit;
- }
-
- /* Install the computed values */
- LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE);
- MultiXactState->oldestOffset = oldestOffset;
- MultiXactState->oldestOffsetKnown = oldestOffsetKnown;
- MultiXactState->offsetStopLimit = offsetStopLimit;
- LWLockRelease(MultiXactGenLock);
-
- /*
- * Do we need an emergency autovacuum? If we're not sure, assume yes.
- */
- return !oldestOffsetKnown ||
- (nextOffset - oldestOffset > MULTIXACT_MEMBER_SAFE_THRESHOLD);
-}
-
-/*
- * Return whether adding "distance" to "start" would move past "boundary".
- *
- * We use this to determine whether the addition is "wrapping around" the
- * boundary point, hence the name. The reason we don't want to use the regular
- * 2^31-modulo arithmetic here is that we want to be able to use the whole of
- * the 2^32-1 space here, allowing for more multixacts than would fit
- * otherwise.
- */
-static bool
-MultiXactOffsetWouldWrap(MultiXactOffset boundary, MultiXactOffset start,
- uint32 distance)
-{
- MultiXactOffset finish;
-
- /*
- * Note that offset number 0 is not used (see GetMultiXactIdMembers), so
- * if the addition wraps around the UINT_MAX boundary, skip that value.
- */
- finish = start + distance;
- if (finish < start)
- finish++;
-
- /*-----------------------------------------------------------------------
- * When the boundary is numerically greater than the starting point, any
- * value numerically between the two is not wrapped:
- *
- * <----S----B---->
- * [---) = F wrapped past B (and UINT_MAX)
- * [---) = F not wrapped
- * [----] = F wrapped past B
- *
- * When the boundary is numerically less than the starting point (i.e. the
- * UINT_MAX wraparound occurs somewhere in between) then all values in
- * between are wrapped:
- *
- * <----B----S---->
- * [---) = F not wrapped past B (but wrapped past UINT_MAX)
- * [---) = F wrapped past B (and UINT_MAX)
- * [----] = F not wrapped
- *-----------------------------------------------------------------------
- */
- if (start < boundary)
- return finish >= boundary || finish < start;
- else
- return finish >= boundary && finish < start;
-}
-
/*
* Find the starting offset of the given MultiXactId.
*
@@ -2734,7 +2300,7 @@ static bool
find_multixact_start(MultiXactId multi, MultiXactOffset *result)
{
MultiXactOffset offset;
- int pageno;
+ int64 pageno;
int entryno;
int slotno;
MultiXactOffset *offptr;
@@ -2764,97 +2330,6 @@ find_multixact_start(MultiXactId multi, MultiXactOffset *result)
return true;
}
-/*
- * Determine how many multixacts, and how many multixact members, currently
- * exist. Return false if unable to determine.
- */
-static bool
-ReadMultiXactCounts(uint32 *multixacts, MultiXactOffset *members)
-{
- MultiXactOffset nextOffset;
- MultiXactOffset oldestOffset;
- MultiXactId oldestMultiXactId;
- MultiXactId nextMultiXactId;
- bool oldestOffsetKnown;
-
- LWLockAcquire(MultiXactGenLock, LW_SHARED);
- nextOffset = MultiXactState->nextOffset;
- oldestMultiXactId = MultiXactState->oldestMultiXactId;
- nextMultiXactId = MultiXactState->nextMXact;
- oldestOffset = MultiXactState->oldestOffset;
- oldestOffsetKnown = MultiXactState->oldestOffsetKnown;
- LWLockRelease(MultiXactGenLock);
-
- if (!oldestOffsetKnown)
- return false;
-
- *members = nextOffset - oldestOffset;
- *multixacts = nextMultiXactId - oldestMultiXactId;
- return true;
-}
-
-/*
- * Multixact members can be removed once the multixacts that refer to them
- * are older than every datminmxid. autovacuum_multixact_freeze_max_age and
- * vacuum_multixact_freeze_table_age work together to make sure we never have
- * too many multixacts; we hope that, at least under normal circumstances,
- * this will also be sufficient to keep us from using too many offsets.
- * However, if the average multixact has many members, we might exhaust the
- * members space while still using few enough members that these limits fail
- * to trigger full table scans for relminmxid advancement. At that point,
- * we'd have no choice but to start failing multixact-creating operations
- * with an error.
- *
- * To prevent that, if more than a threshold portion of the members space is
- * used, we effectively reduce autovacuum_multixact_freeze_max_age and
- * to a value just less than the number of multixacts in use. We hope that
- * this will quickly trigger autovacuuming on the table or tables with the
- * oldest relminmxid, thus allowing datminmxid values to advance and removing
- * some members.
- *
- * As the fraction of the member space currently in use grows, we become
- * more aggressive in clamping this value. That not only causes autovacuum
- * to ramp up, but also makes any manual vacuums the user issues more
- * aggressive. This happens because vacuum_set_xid_limits() clamps the
- * freeze table and the minimum freeze age based on the effective
- * autovacuum_multixact_freeze_max_age this function returns. In the worst
- * case, we'll claim the freeze_max_age to zero, and every vacuum of any
- * table will try to freeze every multixact.
- *
- * It's possible that these thresholds should be user-tunable, but for now
- * we keep it simple.
- */
-int
-MultiXactMemberFreezeThreshold(void)
-{
- MultiXactOffset members;
- uint32 multixacts;
- uint32 victim_multixacts;
- double fraction;
-
- /* If we can't determine member space utilization, assume the worst. */
- if (!ReadMultiXactCounts(&multixacts, &members))
- return 0;
-
- /* If member space utilization is low, no special action is required. */
- if (members <= MULTIXACT_MEMBER_SAFE_THRESHOLD)
- return autovacuum_multixact_freeze_max_age;
-
- /*
- * Compute a target for relminmxid advancement. The number of multixacts
- * we try to eliminate from the system is based on how far we are past
- * MULTIXACT_MEMBER_SAFE_THRESHOLD.
- */
- fraction = (double) (members - MULTIXACT_MEMBER_SAFE_THRESHOLD) /
- (MULTIXACT_MEMBER_DANGER_THRESHOLD - MULTIXACT_MEMBER_SAFE_THRESHOLD);
- victim_multixacts = multixacts * fraction;
-
- /* fraction could be > 1.0, but lowest possible freeze age is zero */
- if (victim_multixacts > multixacts)
- return 0;
- return multixacts - victim_multixacts;
-}
-
typedef struct mxtruncinfo
{
int earliestExistingPage;
@@ -2865,7 +2340,7 @@ typedef struct mxtruncinfo
* This callback determines the earliest existing page number.
*/
static bool
-SlruScanDirCbFindEarliest(SlruCtl ctl, char *filename, int segpage, void *data)
+SlruScanDirCbFindEarliest(SlruCtl ctl, char *filename, int64 segpage, void *data)
{
mxtruncinfo *trunc = (mxtruncinfo *) data;
@@ -2881,35 +2356,12 @@ SlruScanDirCbFindEarliest(SlruCtl ctl, char *filename, int segpage, void *data)
/*
* Delete members segments [oldest, newOldest)
- *
- * The members SLRU can, in contrast to the offsets one, be filled to almost
- * the full range at once. This means SimpleLruTruncate() can't trivially be
- * used - instead the to-be-deleted range is computed using the offsets
- * SLRU. C.f. TruncateMultiXact().
*/
static void
PerformMembersTruncation(MultiXactOffset oldestOffset, MultiXactOffset newOldestOffset)
{
- const int maxsegment = MXOffsetToMemberSegment(MaxMultiXactOffset);
- int startsegment = MXOffsetToMemberSegment(oldestOffset);
- int endsegment = MXOffsetToMemberSegment(newOldestOffset);
- int segment = startsegment;
-
- /*
- * Delete all the segments but the last one. The last segment can still
- * contain, possibly partially, valid data.
- */
- while (segment != endsegment)
- {
- elog(DEBUG2, "truncating multixact members segment %x", segment);
- SlruDeleteSegment(MultiXactMemberCtl, segment);
-
- /* move to next segment, handling wraparound correctly */
- if (segment == maxsegment)
- segment = 0;
- else
- segment += 1;
- }
+ SimpleLruTruncate(MultiXactMemberCtl,
+ MXOffsetToMemberPage(newOldestOffset));
}
/*
@@ -3027,7 +2479,7 @@ TruncateMultiXact(MultiXactId newOldestMulti, Oid newOldestMultiDB)
else if (!find_multixact_start(oldestMulti, &oldestOffset))
{
ereport(LOG,
- (errmsg("oldest MultiXact %u not found, earliest MultiXact %u, skipping truncation",
+ (errmsg("oldest MultiXact %" PRIu64 " not found, earliest MultiXact %" PRIu64 ", skipping truncation",
oldestMulti, earliest)));
LWLockRelease(MultiXactTruncationLock);
return;
@@ -3045,21 +2497,27 @@ TruncateMultiXact(MultiXactId newOldestMulti, Oid newOldestMultiDB)
else if (!find_multixact_start(newOldestMulti, &newOldestOffset))
{
ereport(LOG,
- (errmsg("cannot truncate up to MultiXact %u because it does not exist on disk, skipping truncation",
+ (errmsg("cannot truncate up to MultiXact %" PRIu64 " because it does not exist on disk, skipping truncation",
newOldestMulti)));
LWLockRelease(MultiXactTruncationLock);
return;
}
elog(DEBUG1, "performing multixact truncation: "
- "offsets [%u, %u), offsets segments [%x, %x), "
- "members [%u, %u), members segments [%x, %x)",
+ "offsets [" XID_FMT ", " XID_FMT "), "
+ "offsets segments [%04x%08x, %04x%08x), "
+ "members [" INT64_FORMAT ", " INT64_FORMAT "), "
+ "members segments [%04x%08x, %04x%08x)",
oldestMulti, newOldestMulti,
- MultiXactIdToOffsetSegment(oldestMulti),
- MultiXactIdToOffsetSegment(newOldestMulti),
+ (uint32) (MultiXactIdToOffsetSegment(oldestMulti) >> 32),
+ (uint32) MultiXactIdToOffsetSegment(oldestMulti),
+ (uint32) (MultiXactIdToOffsetSegment(newOldestMulti) >> 32),
+ (uint32) MultiXactIdToOffsetSegment(newOldestMulti),
oldestOffset, newOldestOffset,
- MXOffsetToMemberSegment(oldestOffset),
- MXOffsetToMemberSegment(newOldestOffset));
+ (uint32) (MXOffsetToMemberSegment(oldestOffset) >> 32),
+ (uint32) MXOffsetToMemberSegment(oldestOffset),
+ (uint32) (MXOffsetToMemberSegment(newOldestOffset) >> 32),
+ (uint32) MXOffsetToMemberSegment(newOldestOffset));
/*
* Do truncation, and the WAL logging of the truncation, in a critical
@@ -3112,81 +2570,40 @@ TruncateMultiXact(MultiXactId newOldestMulti, Oid newOldestMultiDB)
* Decide whether a MultiXactOffset page number is "older" for truncation
* purposes. Analogous to CLOGPagePrecedes().
*
- * Offsetting the values is optional, because MultiXactIdPrecedes() has
- * translational symmetry.
+ * With 64xid this function is just "<", but we left it as a function in order
+ * for its calls remain "vanilla" like.
*/
static bool
-MultiXactOffsetPagePrecedes(int page1, int page2)
+MultiXactOffsetPagePrecedes(int64 page1, int64 page2)
{
- MultiXactId multi1;
- MultiXactId multi2;
-
- multi1 = ((MultiXactId) page1) * MULTIXACT_OFFSETS_PER_PAGE;
- multi1 += FirstMultiXactId + 1;
- multi2 = ((MultiXactId) page2) * MULTIXACT_OFFSETS_PER_PAGE;
- multi2 += FirstMultiXactId + 1;
-
- return (MultiXactIdPrecedes(multi1, multi2) &&
- MultiXactIdPrecedes(multi1,
- multi2 + MULTIXACT_OFFSETS_PER_PAGE - 1));
+ return page1 < page2;
}
/*
- * Decide whether a MultiXactMember page number is "older" for truncation
- * purposes. There is no "invalid offset number" so use the numbers verbatim.
- */
-static bool
-MultiXactMemberPagePrecedes(int page1, int page2)
-{
- MultiXactOffset offset1;
- MultiXactOffset offset2;
-
- offset1 = ((MultiXactOffset) page1) * MULTIXACT_MEMBERS_PER_PAGE;
- offset2 = ((MultiXactOffset) page2) * MULTIXACT_MEMBERS_PER_PAGE;
-
- return (MultiXactOffsetPrecedes(offset1, offset2) &&
- MultiXactOffsetPrecedes(offset1,
- offset2 + MULTIXACT_MEMBERS_PER_PAGE - 1));
-}
-
-/*
- * Decide which of two MultiXactIds is earlier.
+ * Decide which of two offsets is earlier.
*
- * XXX do we need to do something special for InvalidMultiXactId?
- * (Doesn't look like it.)
+ * With 64xid this function is just "<", but we left it as a function in order
+ * for its calls remain "vanilla" like.
*/
-bool
-MultiXactIdPrecedes(MultiXactId multi1, MultiXactId multi2)
+static bool
+MultiXactOffsetPrecedes(MultiXactOffset offset1, MultiXactOffset offset2)
{
- int32 diff = (int32) (multi1 - multi2);
+ int64 diff = (int64) (offset1 - offset2);
return (diff < 0);
}
/*
- * MultiXactIdPrecedesOrEquals -- is multi1 logically <= multi2?
+ * Decide whether a MultiXactMember page number is "older" for truncation
+ * purposes. There is no "invalid offset number" so use the numbers verbatim.
*
- * XXX do we need to do something special for InvalidMultiXactId?
- * (Doesn't look like it.)
- */
-bool
-MultiXactIdPrecedesOrEquals(MultiXactId multi1, MultiXactId multi2)
-{
- int32 diff = (int32) (multi1 - multi2);
-
- return (diff <= 0);
-}
-
-
-/*
- * Decide which of two offsets is earlier.
+ * With 64xid this function is just "<", but we left it as a function in order
+ * for its calls remain "vanilla" like.
*/
static bool
-MultiXactOffsetPrecedes(MultiXactOffset offset1, MultiXactOffset offset2)
+MultiXactMemberPagePrecedes(int64 page1, int64 page2)
{
- int32 diff = (int32) (offset1 - offset2);
-
- return (diff < 0);
+ return page1 < page2;
}
/*
@@ -3194,10 +2611,10 @@ MultiXactOffsetPrecedes(MultiXactOffset offset1, MultiXactOffset offset2)
* OFFSETs page (info shows which)
*/
static void
-WriteMZeroPageXlogRec(int pageno, uint8 info)
+WriteMZeroPageXlogRec(int64 pageno, uint8 info)
{
XLogBeginInsert();
- XLogRegisterData((char *) (&pageno), sizeof(int));
+ XLogRegisterData((char *) (&pageno), sizeof(int64));
(void) XLogInsert(RM_MULTIXACT_ID, info);
}
@@ -3242,10 +2659,10 @@ multixact_redo(XLogReaderState *record)
if (info == XLOG_MULTIXACT_ZERO_OFF_PAGE)
{
- int pageno;
+ int64 pageno;
int slotno;
- memcpy(&pageno, XLogRecGetData(record), sizeof(int));
+ memcpy(&pageno, XLogRecGetData(record), sizeof(int64));
LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
@@ -3257,10 +2674,10 @@ multixact_redo(XLogReaderState *record)
}
else if (info == XLOG_MULTIXACT_ZERO_MEM_PAGE)
{
- int pageno;
+ int64 pageno;
int slotno;
- memcpy(&pageno, XLogRecGetData(record), sizeof(int));
+ memcpy(&pageno, XLogRecGetData(record), sizeof(int64));
LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);
@@ -3302,20 +2719,26 @@ multixact_redo(XLogReaderState *record)
else if (info == XLOG_MULTIXACT_TRUNCATE_ID)
{
xl_multixact_truncate xlrec;
- int pageno;
+ int64 pageno;
memcpy(&xlrec, XLogRecGetData(record),
SizeOfMultiXactTruncate);
elog(DEBUG1, "replaying multixact truncation: "
- "offsets [%u, %u), offsets segments [%x, %x), "
- "members [%u, %u), members segments [%x, %x)",
+ "offsets [" XID_FMT ", " XID_FMT "), "
+ "offsets segments [%04x%08x, %04x%08x), "
+ "members [" INT64_FORMAT ", " INT64_FORMAT "), "
+ "members segments [%04x%08x, %04x%08x)",
xlrec.startTruncOff, xlrec.endTruncOff,
- MultiXactIdToOffsetSegment(xlrec.startTruncOff),
- MultiXactIdToOffsetSegment(xlrec.endTruncOff),
+ (uint32) (MultiXactIdToOffsetSegment(xlrec.startTruncOff) >> 32),
+ (uint32) MultiXactIdToOffsetSegment(xlrec.startTruncOff),
+ (uint32) (MultiXactIdToOffsetSegment(xlrec.endTruncOff) >> 32),
+ (uint32) MultiXactIdToOffsetSegment(xlrec.endTruncOff),
xlrec.startTruncMemb, xlrec.endTruncMemb,
- MXOffsetToMemberSegment(xlrec.startTruncMemb),
- MXOffsetToMemberSegment(xlrec.endTruncMemb));
+ (uint32) (MXOffsetToMemberSegment(xlrec.startTruncMemb) >> 32),
+ (uint32) MXOffsetToMemberSegment(xlrec.startTruncMemb),
+ (uint32) (MXOffsetToMemberSegment(xlrec.endTruncMemb) >> 32),
+ (uint32) MXOffsetToMemberSegment(xlrec.endTruncMemb));
/* should not be required, but more than cheap enough */
LWLockAcquire(MultiXactTruncationLock, LW_EXCLUSIVE);
@@ -3359,7 +2782,7 @@ pg_get_multixact_members(PG_FUNCTION_ARGS)
if (mxid < FirstMultiXactId)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid MultiXactId: %u", mxid)));
+ errmsg("invalid MultiXactId: %" PRIu64, mxid)));
if (SRF_IS_FIRSTCALL())
{
@@ -3395,7 +2818,7 @@ pg_get_multixact_members(PG_FUNCTION_ARGS)
HeapTuple tuple;
char *values[2];
- values[0] = psprintf("%u", multi->members[multi->iter].xid);
+ values[0] = psprintf(XID_FMT, multi->members[multi->iter].xid);
values[1] = mxstatus_to_string(multi->members[multi->iter].status);
tuple = BuildTupleFromCStrings(funccxt->attinmeta, values);
diff --git a/src/backend/access/transam/slru.c b/src/backend/access/transam/slru.c
index 30a476ed5dc..b442a4dc228 100644
--- a/src/backend/access/transam/slru.c
+++ b/src/backend/access/transam/slru.c
@@ -59,9 +59,11 @@
#include "pgstat.h"
#include "storage/fd.h"
#include "storage/shmem.h"
+#include "utils/builtins.h"
#define SlruFileName(ctl, path, seg) \
- snprintf(path, MAXPGPATH, "%s/%04X", (ctl)->Dir, seg)
+ snprintf(path, MAXPGPATH, "%s/%04X%08X", (ctl)->Dir, \
+ (uint32) ((seg) >> 32), (uint32) ((seg) & (int64)0xFFFFFFFF))
/*
* During SimpleLruWriteAll(), we will usually not need to write more than one
@@ -75,7 +77,7 @@ typedef struct SlruWriteAllData
{
int num_files; /* # files actually open */
int fd[MAX_WRITEALL_BUFFERS]; /* their FD's */
- int segno[MAX_WRITEALL_BUFFERS]; /* their log seg#s */
+ int64 segno[MAX_WRITEALL_BUFFERS]; /* their log seg#s */
} SlruWriteAllData;
typedef struct SlruWriteAllData *SlruWriteAll;
@@ -138,15 +140,16 @@ static int slru_errno;
static void SimpleLruZeroLSNs(SlruCtl ctl, int slotno);
static void SimpleLruWaitIO(SlruCtl ctl, int slotno);
static void SlruInternalWritePage(SlruCtl ctl, int slotno, SlruWriteAll fdata);
-static bool SlruPhysicalReadPage(SlruCtl ctl, int pageno, int slotno);
-static bool SlruPhysicalWritePage(SlruCtl ctl, int pageno, int slotno,
+static bool SlruPhysicalReadPage(SlruCtl ctl, int64 pageno, int slotno);
+static bool SlruPhysicalWritePage(SlruCtl ctl, int64 pageno, int slotno,
SlruWriteAll fdata);
-static void SlruReportIOError(SlruCtl ctl, int pageno, TransactionId xid);
-static int SlruSelectLRUPage(SlruCtl ctl, int pageno);
+static void SlruReportIOError(SlruCtl ctl, int64 pageno, TransactionId xid);
+static int SlruSelectLRUPage(SlruCtl ctl, int64 pageno);
static bool SlruScanDirCbDeleteCutoff(SlruCtl ctl, char *filename,
- int segpage, void *data);
-static void SlruInternalDeleteSegment(SlruCtl ctl, int segno);
+ int64 segpage, void *data);
+static void SlruInternalDeleteSegment(SlruCtl ctl, int64 segno);
+
/*
* Initialization of shared memory
@@ -162,7 +165,7 @@ SimpleLruShmemSize(int nslots, int nlsns)
sz += MAXALIGN(nslots * sizeof(char *)); /* page_buffer[] */
sz += MAXALIGN(nslots * sizeof(SlruPageStatus)); /* page_status[] */
sz += MAXALIGN(nslots * sizeof(bool)); /* page_dirty[] */
- sz += MAXALIGN(nslots * sizeof(int)); /* page_number[] */
+ sz += MAXALIGN(nslots * sizeof(int64)); /* page_number[] */
sz += MAXALIGN(nslots * sizeof(int)); /* page_lru_count[] */
sz += MAXALIGN(nslots * sizeof(LWLockPadded)); /* buffer_locks[] */
@@ -225,8 +228,8 @@ SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
offset += MAXALIGN(nslots * sizeof(SlruPageStatus));
shared->page_dirty = (bool *) (ptr + offset);
offset += MAXALIGN(nslots * sizeof(bool));
- shared->page_number = (int *) (ptr + offset);
- offset += MAXALIGN(nslots * sizeof(int));
+ shared->page_number = (int64 *) (ptr + offset);
+ offset += MAXALIGN(nslots * sizeof(int64));
shared->page_lru_count = (int *) (ptr + offset);
offset += MAXALIGN(nslots * sizeof(int));
@@ -277,7 +280,7 @@ SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
* Control lock must be held at entry, and will be held at exit.
*/
int
-SimpleLruZeroPage(SlruCtl ctl, int pageno)
+SimpleLruZeroPage(SlruCtl ctl, int64 pageno)
{
SlruShared shared = ctl->shared;
int slotno;
@@ -392,7 +395,7 @@ SimpleLruWaitIO(SlruCtl ctl, int slotno)
* Control lock must be held at entry, and will be held at exit.
*/
int
-SimpleLruReadPage(SlruCtl ctl, int pageno, bool write_ok,
+SimpleLruReadPage(SlruCtl ctl, int64 pageno, bool write_ok,
TransactionId xid)
{
SlruShared shared = ctl->shared;
@@ -492,7 +495,7 @@ SimpleLruReadPage(SlruCtl ctl, int pageno, bool write_ok,
* It is unspecified whether the lock will be shared or exclusive.
*/
int
-SimpleLruReadPage_ReadOnly(SlruCtl ctl, int pageno, TransactionId xid)
+SimpleLruReadPage_ReadOnly(SlruCtl ctl, int64 pageno, TransactionId xid)
{
SlruShared shared = ctl->shared;
int slotno;
@@ -539,7 +542,7 @@ static void
SlruInternalWritePage(SlruCtl ctl, int slotno, SlruWriteAll fdata)
{
SlruShared shared = ctl->shared;
- int pageno = shared->page_number[slotno];
+ int64 pageno = shared->page_number[slotno];
bool ok;
/* If a write is in progress, wait for it to finish */
@@ -623,11 +626,11 @@ SimpleLruWritePage(SlruCtl ctl, int slotno)
* large enough to contain the given page.
*/
bool
-SimpleLruDoesPhysicalPageExist(SlruCtl ctl, int pageno)
+SimpleLruDoesPhysicalPageExist(SlruCtl ctl, int64 pageno)
{
- int segno = pageno / SLRU_PAGES_PER_SEGMENT;
- int rpageno = pageno % SLRU_PAGES_PER_SEGMENT;
- int offset = rpageno * BLCKSZ;
+ int64 segno = pageno / SLRU_PAGES_PER_SEGMENT;
+ int64 rpageno = pageno % SLRU_PAGES_PER_SEGMENT;
+ int64 offset = rpageno * BLCKSZ;
char path[MAXPGPATH];
int fd;
bool result;
@@ -681,11 +684,11 @@ SimpleLruDoesPhysicalPageExist(SlruCtl ctl, int pageno)
* read/write operations. We could cache one virtual file pointer ...
*/
static bool
-SlruPhysicalReadPage(SlruCtl ctl, int pageno, int slotno)
+SlruPhysicalReadPage(SlruCtl ctl, int64 pageno, int slotno)
{
SlruShared shared = ctl->shared;
- int segno = pageno / SLRU_PAGES_PER_SEGMENT;
- int rpageno = pageno % SLRU_PAGES_PER_SEGMENT;
+ int64 segno = pageno / SLRU_PAGES_PER_SEGMENT;
+ int64 rpageno = pageno % SLRU_PAGES_PER_SEGMENT;
off_t offset = rpageno * BLCKSZ;
char path[MAXPGPATH];
int fd;
@@ -753,11 +756,11 @@ SlruPhysicalReadPage(SlruCtl ctl, int pageno, int slotno)
* SimpleLruWriteAll.
*/
static bool
-SlruPhysicalWritePage(SlruCtl ctl, int pageno, int slotno, SlruWriteAll fdata)
+SlruPhysicalWritePage(SlruCtl ctl, int64 pageno, int slotno, SlruWriteAll fdata)
{
SlruShared shared = ctl->shared;
- int segno = pageno / SLRU_PAGES_PER_SEGMENT;
- int rpageno = pageno % SLRU_PAGES_PER_SEGMENT;
+ int64 segno = pageno / SLRU_PAGES_PER_SEGMENT;
+ int64 rpageno = pageno % SLRU_PAGES_PER_SEGMENT;
off_t offset = rpageno * BLCKSZ;
char path[MAXPGPATH];
int fd = -1;
@@ -928,11 +931,11 @@ SlruPhysicalWritePage(SlruCtl ctl, int pageno, int slotno, SlruWriteAll fdata)
* SlruPhysicalWritePage. Call this after cleaning up shared-memory state.
*/
static void
-SlruReportIOError(SlruCtl ctl, int pageno, TransactionId xid)
+SlruReportIOError(SlruCtl ctl, int64 pageno, TransactionId xid)
{
- int segno = pageno / SLRU_PAGES_PER_SEGMENT;
- int rpageno = pageno % SLRU_PAGES_PER_SEGMENT;
- int offset = rpageno * BLCKSZ;
+ int64 segno = pageno / SLRU_PAGES_PER_SEGMENT;
+ int64 rpageno = pageno % SLRU_PAGES_PER_SEGMENT;
+ int64 offset = rpageno * BLCKSZ;
char path[MAXPGPATH];
SlruFileName(ctl, path, segno);
@@ -942,52 +945,52 @@ SlruReportIOError(SlruCtl ctl, int pageno, TransactionId xid)
case SLRU_OPEN_FAILED:
ereport(ERROR,
(errcode_for_file_access(),
- errmsg("could not access status of transaction %u", xid),
+ errmsg("could not access status of transaction %" PRIu64, xid),
errdetail("Could not open file \"%s\": %m.", path)));
break;
case SLRU_SEEK_FAILED:
ereport(ERROR,
(errcode_for_file_access(),
- errmsg("could not access status of transaction %u", xid),
- errdetail("Could not seek in file \"%s\" to offset %u: %m.",
+ errmsg("could not access status of transaction %" PRIu64, xid),
+ errdetail("Could not seek in file \"%s\" to offset %" PRIu64 ": %m.",
path, offset)));
break;
case SLRU_READ_FAILED:
if (errno)
ereport(ERROR,
(errcode_for_file_access(),
- errmsg("could not access status of transaction %u", xid),
- errdetail("Could not read from file \"%s\" at offset %u: %m.",
+ errmsg("could not access status of transaction %" PRIu64, xid),
+ errdetail("Could not read from file \"%s\" at offset %" PRIu64 ": %m.",
path, offset)));
else
ereport(ERROR,
- (errmsg("could not access status of transaction %u", xid),
- errdetail("Could not read from file \"%s\" at offset %u: read too few bytes.", path, offset)));
+ (errmsg("could not access status of transaction %" PRIu64, xid),
+ errdetail("Could not read from file \"%s\" at offset %" PRIu64 ": read too few bytes.", path, offset)));
break;
case SLRU_WRITE_FAILED:
if (errno)
ereport(ERROR,
(errcode_for_file_access(),
- errmsg("could not access status of transaction %u", xid),
- errdetail("Could not write to file \"%s\" at offset %u: %m.",
+ errmsg("could not access status of transaction %" PRIu64, xid),
+ errdetail("Could not write to file \"%s\" at offset %" PRIu64 ": %m.",
path, offset)));
else
ereport(ERROR,
- (errmsg("could not access status of transaction %u", xid),
- errdetail("Could not write to file \"%s\" at offset %u: wrote too few bytes.",
+ (errmsg("could not access status of transaction %" PRIu64, xid),
+ errdetail("Could not write to file \"%s\" at offset %" PRIu64 ": wrote too few bytes.",
path, offset)));
break;
case SLRU_FSYNC_FAILED:
ereport(data_sync_elevel(ERROR),
(errcode_for_file_access(),
- errmsg("could not access status of transaction %u", xid),
+ errmsg("could not access status of transaction %" PRIu64, xid),
errdetail("Could not fsync file \"%s\": %m.",
path)));
break;
case SLRU_CLOSE_FAILED:
ereport(ERROR,
(errcode_for_file_access(),
- errmsg("could not access status of transaction %u", xid),
+ errmsg("could not access status of transaction %" PRIu64, xid),
errdetail("Could not close file \"%s\": %m.",
path)));
break;
@@ -1013,7 +1016,7 @@ SlruReportIOError(SlruCtl ctl, int pageno, TransactionId xid)
* Control lock must be held at entry, and will be held at exit.
*/
static int
-SlruSelectLRUPage(SlruCtl ctl, int pageno)
+SlruSelectLRUPage(SlruCtl ctl, int64 pageno)
{
SlruShared shared = ctl->shared;
@@ -1024,10 +1027,10 @@ SlruSelectLRUPage(SlruCtl ctl, int pageno)
int cur_count;
int bestvalidslot = 0; /* keep compiler quiet */
int best_valid_delta = -1;
- int best_valid_page_number = 0; /* keep compiler quiet */
+ int64 best_valid_page_number = 0; /* keep compiler quiet */
int bestinvalidslot = 0; /* keep compiler quiet */
int best_invalid_delta = -1;
- int best_invalid_page_number = 0; /* keep compiler quiet */
+ int64 best_invalid_page_number = 0; /* keep compiler quiet */
/* See if page already has a buffer assigned */
for (slotno = 0; slotno < shared->num_slots; slotno++)
@@ -1068,7 +1071,7 @@ SlruSelectLRUPage(SlruCtl ctl, int pageno)
for (slotno = 0; slotno < shared->num_slots; slotno++)
{
int this_delta;
- int this_page_number;
+ int64 this_page_number;
if (shared->page_status[slotno] == SLRU_PAGE_EMPTY)
return slotno;
@@ -1158,7 +1161,7 @@ SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied)
SlruShared shared = ctl->shared;
SlruWriteAllData fdata;
int slotno;
- int pageno = 0;
+ int64 pageno = 0;
int i;
bool ok;
@@ -1223,7 +1226,7 @@ SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied)
* after it has accrued freshly-written data.
*/
void
-SimpleLruTruncate(SlruCtl ctl, int cutoffPage)
+SimpleLruTruncate(SlruCtl ctl, int64 cutoffPage)
{
SlruShared shared = ctl->shared;
int slotno;
@@ -1301,7 +1304,7 @@ restart:;
* they either can't yet contain anything, or have already been cleaned out.
*/
static void
-SlruInternalDeleteSegment(SlruCtl ctl, int segno)
+SlruInternalDeleteSegment(SlruCtl ctl, int64 segno)
{
char path[MAXPGPATH];
@@ -1324,7 +1327,7 @@ SlruInternalDeleteSegment(SlruCtl ctl, int segno)
* Delete an individual SLRU segment, identified by the segment number.
*/
void
-SlruDeleteSegment(SlruCtl ctl, int segno)
+SlruDeleteSegment(SlruCtl ctl, int64 segno)
{
SlruShared shared = ctl->shared;
int slotno;
@@ -1399,6 +1402,7 @@ SlruMayDeleteSegment(SlruCtl ctl, int segpage, int cutoffPage)
}
#ifdef USE_ASSERT_CHECKING
+#if 0 /* XXX remove unit tests */
static void
SlruPagePrecedesTestOffset(SlruCtl ctl, int per_page, uint32 offset)
{
@@ -1471,6 +1475,7 @@ SlruPagePrecedesTestOffset(SlruCtl ctl, int per_page, uint32 offset)
newestPage % SLRU_PAGES_PER_SEGMENT),
oldestPage));
}
+#endif
/*
* Unit-test a PagePrecedes function.
@@ -1484,10 +1489,12 @@ SlruPagePrecedesTestOffset(SlruCtl ctl, int per_page, uint32 offset)
void
SlruPagePrecedesUnitTests(SlruCtl ctl, int per_page)
{
+#if 0 /* XXX remove unit tests */
/* Test first, middle and last entries of a page. */
SlruPagePrecedesTestOffset(ctl, per_page, 0);
SlruPagePrecedesTestOffset(ctl, per_page, per_page / 2);
SlruPagePrecedesTestOffset(ctl, per_page, per_page - 1);
+#endif
}
#endif
@@ -1497,9 +1504,9 @@ SlruPagePrecedesUnitTests(SlruCtl ctl, int per_page)
* one containing the page passed as "data".
*/
bool
-SlruScanDirCbReportPresence(SlruCtl ctl, char *filename, int segpage, void *data)
+SlruScanDirCbReportPresence(SlruCtl ctl, char *filename, int64 segpage, void *data)
{
- int cutoffPage = *(int *) data;
+ int64 cutoffPage = *(int64 *) data;
if (SlruMayDeleteSegment(ctl, segpage, cutoffPage))
return true; /* found one; don't iterate any more */
@@ -1512,9 +1519,9 @@ SlruScanDirCbReportPresence(SlruCtl ctl, char *filename, int segpage, void *data
* This callback deletes segments prior to the one passed in as "data".
*/
static bool
-SlruScanDirCbDeleteCutoff(SlruCtl ctl, char *filename, int segpage, void *data)
+SlruScanDirCbDeleteCutoff(SlruCtl ctl, char *filename, int64 segpage, void *data)
{
- int cutoffPage = *(int *) data;
+ int64 cutoffPage = *(int64 *) data;
if (SlruMayDeleteSegment(ctl, segpage, cutoffPage))
SlruInternalDeleteSegment(ctl, segpage / SLRU_PAGES_PER_SEGMENT);
@@ -1527,7 +1534,7 @@ SlruScanDirCbDeleteCutoff(SlruCtl ctl, char *filename, int segpage, void *data)
* This callback deletes all segments.
*/
bool
-SlruScanDirCbDeleteAll(SlruCtl ctl, char *filename, int segpage, void *data)
+SlruScanDirCbDeleteAll(SlruCtl ctl, char *filename, int64 segpage, void *data)
{
SlruInternalDeleteSegment(ctl, segpage / SLRU_PAGES_PER_SEGMENT);
@@ -1555,8 +1562,8 @@ SlruScanDirectory(SlruCtl ctl, SlruScanCallback callback, void *data)
bool retval = false;
DIR *cldir;
struct dirent *clde;
- int segno;
- int segpage;
+ int64 segno;
+ int64 segpage;
cldir = AllocateDir(ctl->Dir);
while ((clde = ReadDir(cldir, ctl->Dir)) != NULL)
@@ -1565,10 +1572,10 @@ SlruScanDirectory(SlruCtl ctl, SlruScanCallback callback, void *data)
len = strlen(clde->d_name);
- if ((len == 4 || len == 5 || len == 6) &&
+ if ((len == 12 || len == 13 || len == 14) &&
strspn(clde->d_name, "0123456789ABCDEF") == len)
{
- segno = (int) strtol(clde->d_name, NULL, 16);
+ segno = pg_strtouint64(clde->d_name, NULL, 16);
segpage = segno * SLRU_PAGES_PER_SEGMENT;
elog(DEBUG2, "SlruScanDirectory invoking callback on %s/%s",
diff --git a/src/backend/access/transam/subtrans.c b/src/backend/access/transam/subtrans.c
index 66d35481552..2f60fcb79c7 100644
--- a/src/backend/access/transam/subtrans.c
+++ b/src/backend/access/transam/subtrans.c
@@ -63,8 +63,8 @@ static SlruCtlData SubTransCtlData;
#define SubTransCtl (&SubTransCtlData)
-static int ZeroSUBTRANSPage(int pageno);
-static bool SubTransPagePrecedes(int page1, int page2);
+static bool SubTransPagePrecedes(int64 page1, int64 page2);
+static int ZeroSUBTRANSPage(int64 pageno);
/*
@@ -73,7 +73,7 @@ static bool SubTransPagePrecedes(int page1, int page2);
void
SubTransSetParent(TransactionId xid, TransactionId parent)
{
- int pageno = TransactionIdToPage(xid);
+ int64 pageno = TransactionIdToPage(xid);
int entryno = TransactionIdToEntry(xid);
int slotno;
TransactionId *ptr;
@@ -108,7 +108,7 @@ SubTransSetParent(TransactionId xid, TransactionId parent)
TransactionId
SubTransGetParent(TransactionId xid)
{
- int pageno = TransactionIdToPage(xid);
+ int64 pageno = TransactionIdToPage(xid);
int entryno = TransactionIdToEntry(xid);
int slotno;
TransactionId *ptr;
@@ -168,7 +168,7 @@ SubTransGetTopmostTransaction(TransactionId xid)
* structure that could lead to an infinite loop, so exit.
*/
if (!TransactionIdPrecedes(parentXid, previousXid))
- elog(ERROR, "pg_subtrans contains invalid entry: xid %u points to parent xid %u",
+ elog(ERROR, "pg_subtrans contains invalid entry: xid " XID_FMT " points to parent xid " XID_FMT,
previousXid, parentXid);
}
@@ -211,11 +211,14 @@ void
BootStrapSUBTRANS(void)
{
int slotno;
+ int64 pageno;
+
+ pageno = TransactionIdToPage(XidFromFullTransactionId(ShmemVariableCache->nextXid));
LWLockAcquire(SubtransSLRULock, LW_EXCLUSIVE);
/* Create and zero the first page of the subtrans log */
- slotno = ZeroSUBTRANSPage(0);
+ slotno = ZeroSUBTRANSPage(pageno);
/* Make sure it's written out */
SimpleLruWritePage(SubTransCtl, slotno);
@@ -233,7 +236,7 @@ BootStrapSUBTRANS(void)
* Control lock must be held at entry, and will be held at exit.
*/
static int
-ZeroSUBTRANSPage(int pageno)
+ZeroSUBTRANSPage(int64 pageno)
{
return SimpleLruZeroPage(SubTransCtl, pageno);
}
@@ -249,8 +252,8 @@ void
StartupSUBTRANS(TransactionId oldestActiveXID)
{
FullTransactionId nextXid;
- int startPage;
- int endPage;
+ int64 startPage;
+ int64 endPage;
/*
* Since we don't expect pg_subtrans to be valid across crashes, we
@@ -268,9 +271,6 @@ StartupSUBTRANS(TransactionId oldestActiveXID)
{
(void) ZeroSUBTRANSPage(startPage);
startPage++;
- /* must account for wraparound */
- if (startPage > TransactionIdToPage(MaxTransactionId))
- startPage = 0;
}
(void) ZeroSUBTRANSPage(startPage);
@@ -307,7 +307,7 @@ CheckPointSUBTRANS(void)
void
ExtendSUBTRANS(TransactionId newestXact)
{
- int pageno;
+ int64 pageno;
/*
* No work except at first XID of a page. But beware: just after
@@ -337,7 +337,7 @@ ExtendSUBTRANS(TransactionId newestXact)
void
TruncateSUBTRANS(TransactionId oldestXact)
{
- int cutoffPage;
+ int64 cutoffPage;
/*
* The cutoff point is the start of the segment containing oldestXact. We
@@ -347,6 +347,13 @@ TruncateSUBTRANS(TransactionId oldestXact)
* a page and oldestXact == next XID. In that case, if we didn't subtract
* one, we'd trigger SimpleLruTruncate's wraparound detection.
*/
+
+ if (oldestXact <= FirstNormalTransactionId)
+ {
+ SimpleLruTruncate(SubTransCtl, 0);
+ return;
+ }
+
TransactionIdRetreat(oldestXact);
cutoffPage = TransactionIdToPage(oldestXact);
@@ -357,18 +364,12 @@ TruncateSUBTRANS(TransactionId oldestXact)
/*
* Decide whether a SUBTRANS page number is "older" for truncation purposes.
* Analogous to CLOGPagePrecedes().
+ *
+ * With 64xid this function is just "<", but we left it as a function in order
+ * for its calls remain "vanilla" like.
*/
static bool
-SubTransPagePrecedes(int page1, int page2)
+SubTransPagePrecedes(int64 page1, int64 page2)
{
- TransactionId xid1;
- TransactionId xid2;
-
- xid1 = ((TransactionId) page1) * SUBTRANS_XACTS_PER_PAGE;
- xid1 += FirstNormalTransactionId + 1;
- xid2 = ((TransactionId) page2) * SUBTRANS_XACTS_PER_PAGE;
- xid2 += FirstNormalTransactionId + 1;
-
- return (TransactionIdPrecedes(xid1, xid2) &&
- TransactionIdPrecedes(xid1, xid2 + SUBTRANS_XACTS_PER_PAGE - 1));
+ return page1 < page2;
}
diff --git a/src/backend/access/transam/transam.c b/src/backend/access/transam/transam.c
index dbc5f884e88..a588b615b06 100644
--- a/src/backend/access/transam/transam.c
+++ b/src/backend/access/transam/transam.c
@@ -157,7 +157,7 @@ TransactionIdDidCommit(TransactionId transactionId)
parentXid = SubTransGetParent(transactionId);
if (!TransactionIdIsValid(parentXid))
{
- elog(WARNING, "no pg_subtrans entry for subcommitted XID %u",
+ elog(WARNING, "no pg_subtrans entry for subcommitted XID " XID_FMT,
transactionId);
return false;
}
@@ -206,7 +206,7 @@ TransactionIdDidAbort(TransactionId transactionId)
if (!TransactionIdIsValid(parentXid))
{
/* see notes in TransactionIdDidCommit */
- elog(WARNING, "no pg_subtrans entry for subcommitted XID %u",
+ elog(WARNING, "no pg_subtrans entry for subcommitted XID " XID_FMT,
transactionId);
return true;
}
@@ -293,70 +293,6 @@ TransactionIdAbortTree(TransactionId xid, int nxids, TransactionId *xids)
TRANSACTION_STATUS_ABORTED, InvalidXLogRecPtr);
}
-/*
- * TransactionIdPrecedes --- is id1 logically < id2?
- */
-bool
-TransactionIdPrecedes(TransactionId id1, TransactionId id2)
-{
- /*
- * If either ID is a permanent XID then we can just do unsigned
- * comparison. If both are normal, do a modulo-2^32 comparison.
- */
- int32 diff;
-
- if (!TransactionIdIsNormal(id1) || !TransactionIdIsNormal(id2))
- return (id1 < id2);
-
- diff = (int32) (id1 - id2);
- return (diff < 0);
-}
-
-/*
- * TransactionIdPrecedesOrEquals --- is id1 logically <= id2?
- */
-bool
-TransactionIdPrecedesOrEquals(TransactionId id1, TransactionId id2)
-{
- int32 diff;
-
- if (!TransactionIdIsNormal(id1) || !TransactionIdIsNormal(id2))
- return (id1 <= id2);
-
- diff = (int32) (id1 - id2);
- return (diff <= 0);
-}
-
-/*
- * TransactionIdFollows --- is id1 logically > id2?
- */
-bool
-TransactionIdFollows(TransactionId id1, TransactionId id2)
-{
- int32 diff;
-
- if (!TransactionIdIsNormal(id1) || !TransactionIdIsNormal(id2))
- return (id1 > id2);
-
- diff = (int32) (id1 - id2);
- return (diff > 0);
-}
-
-/*
- * TransactionIdFollowsOrEquals --- is id1 logically >= id2?
- */
-bool
-TransactionIdFollowsOrEquals(TransactionId id1, TransactionId id2)
-{
- int32 diff;
-
- if (!TransactionIdIsNormal(id1) || !TransactionIdIsNormal(id2))
- return (id1 >= id2);
-
- diff = (int32) (id1 - id2);
- return (diff >= 0);
-}
-
/*
* TransactionIdLatest --- get latest XID among a main xact and its children
diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c
index 271a3146dbb..0c70795eea9 100644
--- a/src/backend/access/transam/twophase.c
+++ b/src/backend/access/transam/twophase.c
@@ -848,7 +848,7 @@ TwoPhaseGetGXact(TransactionId xid, bool lock_held)
LWLockRelease(TwoPhaseStateLock);
if (result == NULL) /* should not happen */
- elog(ERROR, "failed to find GlobalTransaction for xid %u", xid);
+ elog(ERROR, "failed to find GlobalTransaction for xid " XID_FMT, xid);
cached_xid = xid;
cached_gxact = result;
@@ -940,7 +940,7 @@ TwoPhaseGetDummyProc(TransactionId xid, bool lock_held)
/************************************************************************/
#define TwoPhaseFilePath(path, xid) \
- snprintf(path, MAXPGPATH, TWOPHASE_DIR "/%08X", xid)
+ snprintf(path, MAXPGPATH, TWOPHASE_DIR "/%08X%08X", (uint32)(xid >> 32), (uint32)xid)
/*
* 2PC state file format:
@@ -1847,13 +1847,13 @@ restoreTwoPhaseData(void)
cldir = AllocateDir(TWOPHASE_DIR);
while ((clde = ReadDir(cldir, TWOPHASE_DIR)) != NULL)
{
- if (strlen(clde->d_name) == 8 &&
- strspn(clde->d_name, "0123456789ABCDEF") == 8)
+ if (strlen(clde->d_name) == 16 &&
+ strspn(clde->d_name, "0123456789ABCDEF") == 16)
{
TransactionId xid;
char *buf;
- xid = (TransactionId) strtoul(clde->d_name, NULL, 16);
+ xid = (TransactionId) pg_strtouint64(clde->d_name, NULL, 16);
buf = ProcessTwoPhaseBuffer(xid, InvalidXLogRecPtr,
true, false, false);
@@ -2055,7 +2055,7 @@ RecoverPreparedTransactions(void)
continue;
ereport(LOG,
- (errmsg("recovering prepared transaction %u from shared memory", xid)));
+ (errmsg("recovering prepared transaction %" PRIu64 " from shared memory", xid)));
hdr = (TwoPhaseFileHeader *) buf;
Assert(TransactionIdEquals(hdr->xid, xid));
@@ -2146,14 +2146,14 @@ ProcessTwoPhaseBuffer(TransactionId xid,
if (fromdisk)
{
ereport(WARNING,
- (errmsg("removing stale two-phase state file for transaction %u",
+ (errmsg("removing stale two-phase state file for transaction \"%" PRIu64 "\"",
xid)));
RemoveTwoPhaseFile(xid, true);
}
else
{
ereport(WARNING,
- (errmsg("removing stale two-phase state from memory for transaction %u",
+ (errmsg("removing stale two-phase state from memory for transaction \"%" PRIu64 "\"",
xid)));
PrepareRedoRemove(xid, true);
}
@@ -2166,14 +2166,14 @@ ProcessTwoPhaseBuffer(TransactionId xid,
if (fromdisk)
{
ereport(WARNING,
- (errmsg("removing future two-phase state file for transaction %u",
+ (errmsg("removing future two-phase state file for transaction \"%" PRIu64 "\"",
xid)));
RemoveTwoPhaseFile(xid, true);
}
else
{
ereport(WARNING,
- (errmsg("removing future two-phase state from memory for transaction %u",
+ (errmsg("removing future two-phase state from memory for transaction \"%" PRIu64 "\"",
xid)));
PrepareRedoRemove(xid, true);
}
@@ -2182,7 +2182,6 @@ ProcessTwoPhaseBuffer(TransactionId xid,
if (fromdisk)
{
- /* Read and validate file */
buf = ReadTwoPhaseFile(xid, false);
}
else
@@ -2198,12 +2197,12 @@ ProcessTwoPhaseBuffer(TransactionId xid,
if (fromdisk)
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
- errmsg("corrupted two-phase state file for transaction %u",
+ errmsg("corrupted two-phase state file for transaction %" PRIu64,
xid)));
else
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
- errmsg("corrupted two-phase state in memory for transaction %u",
+ errmsg("corrupted two-phase state in memory for transaction %" PRIu64,
xid)));
}
@@ -2359,7 +2358,7 @@ RecordTransactionAbortPrepared(TransactionId xid,
* RecordTransactionCommitPrepared ...
*/
if (TransactionIdDidCommit(xid))
- elog(PANIC, "cannot abort transaction %u, it was already committed",
+ elog(PANIC, "cannot abort transaction " XID_FMT ", it was already committed",
xid);
START_CRIT_SECTION();
@@ -2466,7 +2465,7 @@ PrepareRedoAdd(char *buf, XLogRecPtr start_lsn,
false /* backward */ , false /* WAL */ );
}
- elog(DEBUG2, "added 2PC data in shared memory for transaction %u", gxact->xid);
+ elog(DEBUG2, "added 2PC data in shared memory for transaction " XID_FMT, gxact->xid);
}
/*
@@ -2509,7 +2508,7 @@ PrepareRedoRemove(TransactionId xid, bool giveWarning)
/*
* And now we can clean up any files we may have left.
*/
- elog(DEBUG2, "removing 2PC data for transaction %u", xid);
+ elog(DEBUG2, "removing 2PC data for transaction " XID_FMT, xid);
if (gxact->ondisk)
RemoveTwoPhaseFile(xid, giveWarning);
RemoveGXact(gxact);
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index 748120a0125..0a89721b004 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -68,7 +68,7 @@ GetNewTransactionId(bool isSubXact)
Assert(!isSubXact);
MyProc->xid = BootstrapTransactionId;
ProcGlobal->xids[MyProc->pgxactoff] = BootstrapTransactionId;
- return FullTransactionIdFromEpochAndXid(0, BootstrapTransactionId);
+ return FullTransactionIdFromXid(BootstrapTransactionId);
}
/* safety check, we should never get this far in a HS standby */
@@ -102,11 +102,6 @@ GetNewTransactionId(bool isSubXact)
* possibility of deadlock while doing get_database_name(). First,
* copy all the shared values we'll need in this path.
*/
- TransactionId xidWarnLimit = ShmemVariableCache->xidWarnLimit;
- TransactionId xidStopLimit = ShmemVariableCache->xidStopLimit;
- TransactionId xidWrapLimit = ShmemVariableCache->xidWrapLimit;
- Oid oldest_datoid = ShmemVariableCache->oldestXidDB;
-
LWLockRelease(XidGenLock);
/*
@@ -117,48 +112,6 @@ GetNewTransactionId(bool isSubXact)
if (IsUnderPostmaster && (xid % 65536) == 0)
SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
- if (IsUnderPostmaster &&
- TransactionIdFollowsOrEquals(xid, xidStopLimit))
- {
- char *oldest_datname = get_database_name(oldest_datoid);
-
- /* complain even if that DB has disappeared */
- if (oldest_datname)
- ereport(ERROR,
- (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg("database is not accepting commands to avoid wraparound data loss in database \"%s\"",
- oldest_datname),
- errhint("Stop the postmaster and vacuum that database in single-user mode.\n"
- "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
- else
- ereport(ERROR,
- (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg("database is not accepting commands to avoid wraparound data loss in database with OID %u",
- oldest_datoid),
- errhint("Stop the postmaster and vacuum that database in single-user mode.\n"
- "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
- }
- else if (TransactionIdFollowsOrEquals(xid, xidWarnLimit))
- {
- char *oldest_datname = get_database_name(oldest_datoid);
-
- /* complain even if that DB has disappeared */
- if (oldest_datname)
- ereport(WARNING,
- (errmsg("database \"%s\" must be vacuumed within %u transactions",
- oldest_datname,
- xidWrapLimit - xid),
- errhint("To avoid a database shutdown, execute a database-wide VACUUM in that database.\n"
- "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
- else
- ereport(WARNING,
- (errmsg("database with OID %u must be vacuumed within %u transactions",
- oldest_datoid,
- xidWrapLimit - xid),
- errhint("To avoid a database shutdown, execute a database-wide VACUUM in that database.\n"
- "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
- }
-
/* Re-acquire lock and start over */
LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
full_xid = ShmemVariableCache->nextXid;
@@ -270,7 +223,7 @@ ReadNextFullTransactionId(void)
}
/*
- * Advance nextXid to the value after a given xid. The epoch is inferred.
+ * Advance nextXid to the value after a given xid.
* This must only be called during recovery or from two-phase start-up code.
*/
void
@@ -278,7 +231,6 @@ AdvanceNextFullTransactionIdPastXid(TransactionId xid)
{
FullTransactionId newNextFullXid;
TransactionId next_xid;
- uint32 epoch;
/*
* It is safe to read nextXid without a lock, because this is only called
@@ -292,19 +244,9 @@ AdvanceNextFullTransactionIdPastXid(TransactionId xid)
if (!TransactionIdFollowsOrEquals(xid, next_xid))
return;
- /*
- * Compute the FullTransactionId that comes after the given xid. To do
- * this, we preserve the existing epoch, but detect when we've wrapped
- * into a new epoch. This is necessary because WAL records and 2PC state
- * currently contain 32 bit xids. The wrap logic is safe in those cases
- * because the span of active xids cannot exceed one epoch at any given
- * point in the WAL stream.
- */
+ /* Compute the FullTransactionId that comes after the given xid. */
TransactionIdAdvance(xid);
- epoch = EpochFromFullTransactionId(ShmemVariableCache->nextXid);
- if (unlikely(xid < next_xid))
- ++epoch;
- newNextFullXid = FullTransactionIdFromEpochAndXid(epoch, xid);
+ newNextFullXid = FullTransactionIdFromXid(xid);
/*
* We still need to take a lock to modify the value when there are
@@ -345,54 +287,10 @@ void
SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
{
TransactionId xidVacLimit;
- TransactionId xidWarnLimit;
- TransactionId xidStopLimit;
- TransactionId xidWrapLimit;
TransactionId curXid;
Assert(TransactionIdIsNormal(oldest_datfrozenxid));
- /*
- * The place where we actually get into deep trouble is halfway around
- * from the oldest potentially-existing XID. (This calculation is
- * probably off by one or two counts, because the special XIDs reduce the
- * size of the loop a little bit. But we throw in plenty of slop below,
- * so it doesn't matter.)
- */
- xidWrapLimit = oldest_datfrozenxid + (MaxTransactionId >> 1);
- if (xidWrapLimit < FirstNormalTransactionId)
- xidWrapLimit += FirstNormalTransactionId;
-
- /*
- * We'll refuse to continue assigning XIDs in interactive mode once we get
- * within 3M transactions of data loss. This leaves lots of room for the
- * DBA to fool around fixing things in a standalone backend, while not
- * being significant compared to total XID space. (VACUUM requires an XID
- * if it truncates at wal_level!=minimal. "VACUUM (ANALYZE)", which a DBA
- * might do by reflex, assigns an XID. Hence, we had better be sure
- * there's lots of XIDs left...) Also, at default BLCKSZ, this leaves two
- * completely-idle segments. In the event of edge-case bugs involving
- * page or segment arithmetic, idle segments render the bugs unreachable
- * outside of single-user mode.
- */
- xidStopLimit = xidWrapLimit - 3000000;
- if (xidStopLimit < FirstNormalTransactionId)
- xidStopLimit -= FirstNormalTransactionId;
-
- /*
- * We'll start complaining loudly when we get within 40M transactions of
- * data loss. This is kind of arbitrary, but if you let your gas gauge
- * get down to 2% of full, would you be looking for the next gas station?
- * We need to be fairly liberal about this number because there are lots
- * of scenarios where most transactions are done by automatic clients that
- * won't pay attention to warnings. (No, we're not gonna make this
- * configurable. If you know enough to configure it, you know enough to
- * not get in this kind of trouble in the first place.)
- */
- xidWarnLimit = xidWrapLimit - 40000000;
- if (xidWarnLimit < FirstNormalTransactionId)
- xidWarnLimit -= FirstNormalTransactionId;
-
/*
* We'll start trying to force autovacuums when oldest_datfrozenxid gets
* to be more than autovacuum_freeze_max_age transactions old.
@@ -416,18 +314,10 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
ShmemVariableCache->oldestXid = oldest_datfrozenxid;
ShmemVariableCache->xidVacLimit = xidVacLimit;
- ShmemVariableCache->xidWarnLimit = xidWarnLimit;
- ShmemVariableCache->xidStopLimit = xidStopLimit;
- ShmemVariableCache->xidWrapLimit = xidWrapLimit;
ShmemVariableCache->oldestXidDB = oldest_datoid;
curXid = XidFromFullTransactionId(ShmemVariableCache->nextXid);
LWLockRelease(XidGenLock);
- /* Log the info */
- ereport(DEBUG1,
- (errmsg_internal("transaction ID wrap limit is %u, limited by database with OID %u",
- xidWrapLimit, oldest_datoid)));
-
/*
* If past the autovacuum force point, immediately signal an autovac
* request. The reason for this is that autovac only processes one
@@ -438,41 +328,6 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
if (TransactionIdFollowsOrEquals(curXid, xidVacLimit) &&
IsUnderPostmaster && !InRecovery)
SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
-
- /* Give an immediate warning if past the wrap warn point */
- if (TransactionIdFollowsOrEquals(curXid, xidWarnLimit) && !InRecovery)
- {
- char *oldest_datname;
-
- /*
- * We can be called when not inside a transaction, for example during
- * StartupXLOG(). In such a case we cannot do database access, so we
- * must just report the oldest DB's OID.
- *
- * Note: it's also possible that get_database_name fails and returns
- * NULL, for example because the database just got dropped. We'll
- * still warn, even though the warning might now be unnecessary.
- */
- if (IsTransactionState())
- oldest_datname = get_database_name(oldest_datoid);
- else
- oldest_datname = NULL;
-
- if (oldest_datname)
- ereport(WARNING,
- (errmsg("database \"%s\" must be vacuumed within %u transactions",
- oldest_datname,
- xidWrapLimit - curXid),
- errhint("To avoid a database shutdown, execute a database-wide VACUUM in that database.\n"
- "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
- else
- ereport(WARNING,
- (errmsg("database with OID %u must be vacuumed within %u transactions",
- oldest_datoid,
- xidWrapLimit - curXid),
- errhint("To avoid a database shutdown, execute a database-wide VACUUM in that database.\n"
- "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
- }
}
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index c9516e03fae..e271493aae2 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -1726,7 +1726,7 @@ RecordTransactionAbort(bool isSubXact)
* Check that we haven't aborted halfway through RecordTransactionCommit.
*/
if (TransactionIdDidCommit(xid))
- elog(PANIC, "cannot abort transaction %u, it was already committed",
+ elog(PANIC, "cannot abort transaction " XID_FMT ", it was already committed",
xid);
/* Fetch the data we need for the abort record */
@@ -5436,22 +5436,22 @@ ShowTransactionStateRec(const char *str, TransactionState s)
{
int i;
- appendStringInfo(&buf, ", children: %u", s->childXids[0]);
+ appendStringInfo(&buf, ", children: " XID_FMT, s->childXids[0]);
for (i = 1; i < s->nChildXids; i++)
- appendStringInfo(&buf, " %u", s->childXids[i]);
+ appendStringInfo(&buf, " " XID_FMT, s->childXids[i]);
}
if (s->parent)
ShowTransactionStateRec(str, s->parent);
ereport(DEBUG5,
- (errmsg_internal("%s(%d) name: %s; blockState: %s; state: %s, xid/subid/cid: %u/%u/%u%s%s",
+ (errmsg_internal("%s(%d) name: %s; blockState: %s; state: %s, xid/subid/cid: " XID_FMT "/" XID_FMT "/%u%s%s",
str, s->nestingLevel,
PointerIsValid(s->name) ? s->name : "unnamed",
BlockStateAsString(s->blockState),
TransStateAsString(s->state),
- (unsigned int) XidFromFullTransactionId(s->fullTransactionId),
- (unsigned int) s->subTransactionId,
+ XidFromFullTransactionId(s->fullTransactionId),
+ s->subTransactionId,
(unsigned int) currentCommandId,
currentCommandIdUsed ? " (used)" : "",
buf.data)));
@@ -5634,6 +5634,17 @@ XactLogCommitRecord(TimestampTz commit_time,
xl_subxacts.nsubxacts = nsubxacts;
}
+ if (TransactionIdIsValid(twophase_xid))
+ {
+ xl_xinfo.xinfo |= XACT_XINFO_HAS_TWOPHASE;
+ xl_twophase.xid_lo = (uint32)(twophase_xid & 0xFFFFFFFF);
+ xl_twophase.xid_hi = (uint32)(twophase_xid >> 32);
+ Assert(twophase_gid != NULL);
+
+ if (XLogLogicalInfoActive())
+ xl_xinfo.xinfo |= XACT_XINFO_HAS_GID;
+ }
+
if (nrels > 0)
{
xl_xinfo.xinfo |= XACT_XINFO_HAS_RELFILENODES;
@@ -5647,16 +5658,6 @@ XactLogCommitRecord(TimestampTz commit_time,
xl_invals.nmsgs = nmsgs;
}
- if (TransactionIdIsValid(twophase_xid))
- {
- xl_xinfo.xinfo |= XACT_XINFO_HAS_TWOPHASE;
- xl_twophase.xid = twophase_xid;
- Assert(twophase_gid != NULL);
-
- if (XLogLogicalInfoActive())
- xl_xinfo.xinfo |= XACT_XINFO_HAS_GID;
- }
-
/* dump transaction origin information */
if (replorigin_session_origin != InvalidRepOriginId)
{
@@ -5767,23 +5768,24 @@ XactLogAbortRecord(TimestampTz abort_time,
xl_subxacts.nsubxacts = nsubxacts;
}
- if (nrels > 0)
- {
- xl_xinfo.xinfo |= XACT_XINFO_HAS_RELFILENODES;
- xl_relfilenodes.nrels = nrels;
- info |= XLR_SPECIAL_REL_UPDATE;
- }
-
if (TransactionIdIsValid(twophase_xid))
{
xl_xinfo.xinfo |= XACT_XINFO_HAS_TWOPHASE;
- xl_twophase.xid = twophase_xid;
+ xl_twophase.xid_lo = (uint32)(twophase_xid & 0xFFFFFFFF);
+ xl_twophase.xid_hi = (uint32)(twophase_xid >> 32);
Assert(twophase_gid != NULL);
if (XLogLogicalInfoActive())
xl_xinfo.xinfo |= XACT_XINFO_HAS_GID;
}
+ if (nrels > 0)
+ {
+ xl_xinfo.xinfo |= XACT_XINFO_HAS_RELFILENODES;
+ xl_relfilenodes.nrels = nrels;
+ info |= XLR_SPECIAL_REL_UPDATE;
+ }
+
if (TransactionIdIsValid(twophase_xid) && XLogLogicalInfoActive())
{
xl_xinfo.xinfo |= XACT_XINFO_HAS_DBINFO;
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index c9d4cbf3ff5..e46de79d313 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -116,6 +116,10 @@ int wal_retrieve_retry_interval = 5000;
int max_slot_wal_keep_size_mb = -1;
bool track_wal_io_timing = false;
+TransactionId start_xid = 0;
+MultiXactId start_mx_id = 0;
+MultiXactOffset start_mx_offset = 0;
+
#ifdef WAL_DEBUG
bool XLOG_DEBUG = false;
#endif
@@ -5395,13 +5399,13 @@ BootStrapXLOG(void)
checkPoint.PrevTimeLineID = BootstrapTimeLineID;
checkPoint.fullPageWrites = fullPageWrites;
checkPoint.nextXid =
- FullTransactionIdFromEpochAndXid(0, FirstNormalTransactionId);
+ FullTransactionIdFromXid(Max(FirstNormalTransactionId + 1, start_xid));
checkPoint.nextOid = FirstGenbkiObjectId;
- checkPoint.nextMulti = FirstMultiXactId;
- checkPoint.nextMultiOffset = 0;
- checkPoint.oldestXid = FirstNormalTransactionId;
+ checkPoint.nextMulti = Max(FirstMultiXactId + 1, start_mx_id);
+ checkPoint.nextMultiOffset = start_mx_offset;
+ checkPoint.oldestXid = XidFromFullTransactionId(checkPoint.nextXid) - 1;
checkPoint.oldestXidDB = TemplateDbOid;
- checkPoint.oldestMulti = FirstMultiXactId;
+ checkPoint.oldestMulti = checkPoint.nextMulti - 1;
checkPoint.oldestMultiDB = TemplateDbOid;
checkPoint.oldestCommitTsXid = InvalidTransactionId;
checkPoint.newestCommitTsXid = InvalidTransactionId;
@@ -6048,14 +6052,14 @@ recoveryStopsBefore(XLogReaderState *record)
if (isCommit)
{
ereport(LOG,
- (errmsg("recovery stopping before commit of transaction %u, time %s",
+ (errmsg("recovery stopping before commit of transaction %" PRIu64 ", time %s",
recoveryStopXid,
timestamptz_to_str(recoveryStopTime))));
}
else
{
ereport(LOG,
- (errmsg("recovery stopping before abort of transaction %u, time %s",
+ (errmsg("recovery stopping before abort of transaction %" PRIu64 ", time %s",
recoveryStopXid,
timestamptz_to_str(recoveryStopTime))));
}
@@ -6193,7 +6197,7 @@ recoveryStopsAfter(XLogReaderState *record)
xact_info == XLOG_XACT_COMMIT_PREPARED)
{
ereport(LOG,
- (errmsg("recovery stopping after commit of transaction %u, time %s",
+ (errmsg("recovery stopping after commit of transaction %" PRIu64 ", time %s",
recoveryStopXid,
timestamptz_to_str(recoveryStopTime))));
}
@@ -6201,7 +6205,7 @@ recoveryStopsAfter(XLogReaderState *record)
xact_info == XLOG_XACT_ABORT_PREPARED)
{
ereport(LOG,
- (errmsg("recovery stopping after abort of transaction %u, time %s",
+ (errmsg("recovery stopping after abort of transaction %" PRIu64 ", time %s",
recoveryStopXid,
timestamptz_to_str(recoveryStopTime))));
}
@@ -6237,7 +6241,7 @@ getRecoveryStopReason(void)
if (recoveryTarget == RECOVERY_TARGET_XID)
snprintf(reason, sizeof(reason),
- "%s transaction %u",
+ "%s transaction " XID_FMT,
recoveryStopAfter ? "after" : "before",
recoveryStopXid);
else if (recoveryTarget == RECOVERY_TARGET_TIME)
@@ -6832,7 +6836,7 @@ StartupXLOG(void)
(errmsg("entering standby mode")));
else if (recoveryTarget == RECOVERY_TARGET_XID)
ereport(LOG,
- (errmsg("starting point-in-time recovery to XID %u",
+ (errmsg("starting point-in-time recovery to XID %" PRIu64,
recoveryTargetXid)));
else if (recoveryTarget == RECOVERY_TARGET_TIME)
ereport(LOG,
@@ -7129,16 +7133,16 @@ StartupXLOG(void)
U64FromFullTransactionId(checkPoint.nextXid),
checkPoint.nextOid)));
ereport(DEBUG1,
- (errmsg_internal("next MultiXactId: %u; next MultiXactOffset: %u",
+ (errmsg_internal("next MultiXactId: " XID_FMT "; next MultiXactOffset: " INT64_FORMAT,
checkPoint.nextMulti, checkPoint.nextMultiOffset)));
ereport(DEBUG1,
- (errmsg_internal("oldest unfrozen transaction ID: %u, in database %u",
+ (errmsg_internal("oldest unfrozen transaction ID: " XID_FMT ", in database %u",
checkPoint.oldestXid, checkPoint.oldestXidDB)));
ereport(DEBUG1,
- (errmsg_internal("oldest MultiXactId: %u, in database %u",
+ (errmsg_internal("oldest MultiXactId: " XID_FMT ", in database %u",
checkPoint.oldestMulti, checkPoint.oldestMultiDB)));
ereport(DEBUG1,
- (errmsg_internal("commit timestamp Xid oldest/newest: %u/%u",
+ (errmsg_internal("commit timestamp Xid oldest/newest: " XID_FMT "/" XID_FMT,
checkPoint.oldestCommitTsXid,
checkPoint.newestCommitTsXid)));
if (!TransactionIdIsNormal(XidFromFullTransactionId(checkPoint.nextXid)))
@@ -9394,7 +9398,7 @@ CreateCheckPoint(int flags)
UpdateControlFile();
LWLockRelease(ControlFileLock);
- /* Update shared-memory copy of checkpoint XID/epoch */
+ /* Update shared-memory copy of checkpoint XID/base */
SpinLockAcquire(&XLogCtl->info_lck);
XLogCtl->ckptFullXid = checkPoint.nextXid;
SpinLockRelease(&XLogCtl->info_lck);
@@ -10411,7 +10415,7 @@ xlog_redo(XLogReaderState *record)
ControlFile->checkPointCopy.nextXid = checkPoint.nextXid;
LWLockRelease(ControlFileLock);
- /* Update shared-memory copy of checkpoint XID/epoch */
+ /* Update shared-memory copy of checkpoint XID/base */
SpinLockAcquire(&XLogCtl->info_lck);
XLogCtl->ckptFullXid = checkPoint.nextXid;
SpinLockRelease(&XLogCtl->info_lck);
@@ -10470,7 +10474,7 @@ xlog_redo(XLogReaderState *record)
ControlFile->checkPointCopy.nextXid = checkPoint.nextXid;
LWLockRelease(ControlFileLock);
- /* Update shared-memory copy of checkpoint XID/epoch */
+ /* Update shared-memory copy of checkpoint XID/base */
SpinLockAcquire(&XLogCtl->info_lck);
XLogCtl->ckptFullXid = checkPoint.nextXid;
SpinLockRelease(&XLogCtl->info_lck);
diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c
index 35029cf97d6..abf7613002a 100644
--- a/src/backend/access/transam/xlogreader.c
+++ b/src/backend/access/transam/xlogreader.c
@@ -1656,37 +1656,3 @@ RestoreBlockImage(XLogReaderState *record, uint8 block_id, char *page)
return true;
}
-
-#ifndef FRONTEND
-
-/*
- * Extract the FullTransactionId from a WAL record.
- */
-FullTransactionId
-XLogRecGetFullXid(XLogReaderState *record)
-{
- TransactionId xid,
- next_xid;
- uint32 epoch;
-
- /*
- * This function is only safe during replay, because it depends on the
- * replay state. See AdvanceNextFullTransactionIdPastXid() for more.
- */
- Assert(AmStartupProcess() || !IsUnderPostmaster);
-
- xid = XLogRecGetXid(record);
- next_xid = XidFromFullTransactionId(ShmemVariableCache->nextXid);
- epoch = EpochFromFullTransactionId(ShmemVariableCache->nextXid);
-
- /*
- * If xid is numerically greater than next_xid, it has to be from the last
- * epoch.
- */
- if (unlikely(xid > next_xid))
- --epoch;
-
- return FullTransactionIdFromEpochAndXid(epoch, xid);
-}
-
-#endif
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 9fa8fdd4cf3..2e3efc1fc07 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -120,7 +120,7 @@ static const struct typinfo TypInfo[] = {
F_OIDIN, F_OIDOUT},
{"tid", TIDOID, 0, 6, false, TYPALIGN_SHORT, TYPSTORAGE_PLAIN, InvalidOid,
F_TIDIN, F_TIDOUT},
- {"xid", XIDOID, 0, 4, true, TYPALIGN_INT, TYPSTORAGE_PLAIN, InvalidOid,
+ {"xid", XIDOID, 0, 8, FLOAT8PASSBYVAL, TYPALIGN_DOUBLE, TYPSTORAGE_PLAIN, InvalidOid,
F_XIDIN, F_XIDOUT},
{"cid", CIDOID, 0, 4, true, TYPALIGN_INT, TYPSTORAGE_PLAIN, InvalidOid,
F_CIDIN, F_CIDOUT},
@@ -221,7 +221,11 @@ BootstrapModeMain(int argc, char *argv[], bool check_only)
argv++;
argc--;
- while ((flag = getopt(argc, argv, "B:c:d:D:Fkr:X:-:")) != -1)
+ start_xid = 0;
+ start_mx_id = 0;
+ start_mx_offset = 0;
+
+ while ((flag = getopt(argc, argv, "B:c:d:D:Fkm:o:r:X:Z:-:")) != -1)
{
switch (flag)
{
@@ -250,9 +254,30 @@ BootstrapModeMain(int argc, char *argv[], bool check_only)
case 'k':
bootstrap_data_checksum_version = PG_DATA_CHECKSUM_VERSION;
break;
+ case 'm':
+ if (sscanf(optarg, XID_FMT, &start_mx_id) != 1
+ || !StartMultiXactIdIsValid(start_mx_id))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid start multixact id value")));
+ break;
+ case 'o':
+ if (sscanf(optarg, XID_FMT, &start_mx_offset) != 1
+ || !StartMultiXactOffsetIsValid(start_mx_offset))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid start multixact offset value")));
+ break;
case 'r':
strlcpy(OutputFileName, optarg, MAXPGPATH);
break;
+ case 'Z':
+ if (sscanf(optarg, XID_FMT, &start_xid) != 1
+ || !StartTransactionIdIsValid(start_xid))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid start xid value")));
+ break;
case 'X':
{
int WalSegSz = strtoul(optarg, NULL, 0);
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 7e99de88b34..10fdc5af6c6 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -173,8 +173,8 @@ static const FormData_pg_attribute a2 = {
.attnum = MinTransactionIdAttributeNumber,
.attcacheoff = -1,
.atttypmod = -1,
- .attbyval = true,
- .attalign = TYPALIGN_INT,
+ .attbyval = FLOAT8PASSBYVAL,
+ .attalign = TYPALIGN_DOUBLE,
.attstorage = TYPSTORAGE_PLAIN,
.attnotnull = true,
.attislocal = true,
@@ -201,8 +201,8 @@ static const FormData_pg_attribute a4 = {
.attnum = MaxTransactionIdAttributeNumber,
.attcacheoff = -1,
.atttypmod = -1,
- .attbyval = true,
- .attalign = TYPALIGN_INT,
+ .attbyval = FLOAT8PASSBYVAL,
+ .attalign = TYPALIGN_DOUBLE,
.attstorage = TYPSTORAGE_PLAIN,
.attnotnull = true,
.attislocal = true,
diff --git a/src/backend/catalog/pg_inherits.c b/src/backend/catalog/pg_inherits.c
index 92afbc2f258..3a1eda413f9 100644
--- a/src/backend/catalog/pg_inherits.c
+++ b/src/backend/catalog/pg_inherits.c
@@ -146,7 +146,7 @@ find_inheritance_children_extended(Oid parentrelId, bool omit_detached,
TransactionId xmin;
Snapshot snap;
- xmin = HeapTupleHeaderGetXmin(inheritsTuple->t_data);
+ xmin = HeapTupleGetXmin(inheritsTuple);
snap = GetActiveSnapshot();
if (!XidInMVCCSnapshot(xmin, snap))
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index 3e1b92df030..59ebf09a2e6 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -186,8 +186,8 @@ typedef struct AsyncQueueEntry
char data[NAMEDATALEN + NOTIFY_PAYLOAD_MAX_LENGTH];
} AsyncQueueEntry;
-/* Currently, no field of AsyncQueueEntry requires more than int alignment */
-#define QUEUEALIGN(len) INTALIGN(len)
+/* AsyncQueueEntry.xid requires 8-byte alignment */
+#define QUEUEALIGN(len) MAXALIGN(len)
#define AsyncQueueEntryEmptySize (offsetof(AsyncQueueEntry, data) + 2)
@@ -444,7 +444,7 @@ bool Trace_notify = false;
/* local function prototypes */
static int asyncQueuePageDiff(int p, int q);
-static bool asyncQueuePagePrecedes(int p, int q);
+static bool asyncQueuePagePrecedes(int64 p, int64 q);
static void queue_listen(ListenActionKind action, const char *channel);
static void Async_UnlistenOnExit(int code, Datum arg);
static void Exec_ListenPreCommit(void);
@@ -497,16 +497,10 @@ asyncQueuePageDiff(int p, int q)
return diff;
}
-/*
- * Is p < q, accounting for wraparound?
- *
- * Since asyncQueueIsFull() blocks creation of a page that could precede any
- * extant page, we need not assess entries within a page.
- */
static bool
-asyncQueuePagePrecedes(int p, int q)
+asyncQueuePagePrecedes(int64 p, int64 q)
{
- return asyncQueuePageDiff(p, q) < 0;
+ return p < q;
}
/*
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 27cb6307581..47f4c382751 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -55,16 +55,6 @@
*/
#define SEQ_LOG_VALS 32
-/*
- * The "special area" of a sequence's buffer page looks like this.
- */
-#define SEQ_MAGIC 0x1717
-
-typedef struct sequence_magic
-{
- uint32 magic;
-} sequence_magic;
-
/*
* We store a SeqTable item for every sequence we have touched in the current
* session. This is needed to hold onto nextval/currval state. (We can't
@@ -342,10 +332,9 @@ ResetSequence(Oid seq_relid)
static void
fill_seq_with_data(Relation rel, HeapTuple tuple)
{
- Buffer buf;
- Page page;
- sequence_magic *sm;
- OffsetNumber offnum;
+ Buffer buf;
+ Page page;
+ OffsetNumber offnum;
/* Initialize first page of relation with special magic number */
@@ -354,9 +343,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
page = BufferGetPage(buf);
- PageInit(page, BufferGetPageSize(buf), sizeof(sequence_magic));
- sm = (sequence_magic *) PageGetSpecialPointer(page);
- sm->magic = SEQ_MAGIC;
+ PageInit(page, BufferGetPageSize(buf), sizeof(HeapPageSpecialData));
+ HeapPageGetSpecial(page)->pd_xid_base = RecentXmin - FirstNormalTransactionId;
/* Now insert sequence tuple */
@@ -369,10 +357,10 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
* because if the current transaction aborts, no other xact will ever
* examine the sequence tuple anyway.
*/
- HeapTupleHeaderSetXmin(tuple->t_data, FrozenTransactionId);
+ HeapTupleSetXmin(tuple, FrozenTransactionId);
HeapTupleHeaderSetXminFrozen(tuple->t_data);
HeapTupleHeaderSetCmin(tuple->t_data, FirstCommandId);
- HeapTupleHeaderSetXmax(tuple->t_data, InvalidTransactionId);
+ HeapTupleSetXmax(tuple, InvalidTransactionId);
tuple->t_data->t_infomask |= HEAP_XMAX_INVALID;
ItemPointerSet(&tuple->t_data->t_ctid, 0, FirstOffsetNumber);
@@ -1170,25 +1158,19 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
{
Page page;
ItemId lp;
- sequence_magic *sm;
Form_pg_sequence_data seq;
*buf = ReadBuffer(rel, 0);
LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
page = BufferGetPage(*buf);
- sm = (sequence_magic *) PageGetSpecialPointer(page);
-
- if (sm->magic != SEQ_MAGIC)
- elog(ERROR, "bad magic number in sequence \"%s\": %08X",
- RelationGetRelationName(rel), sm->magic);
-
lp = PageGetItemId(page, FirstOffsetNumber);
Assert(ItemIdIsNormal(lp));
/* Note we currently only bother to set these two fields of *seqdatatuple */
seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
seqdatatuple->t_len = ItemIdGetLength(lp);
+ HeapTupleCopyBaseFromPage(seqdatatuple, page);
/*
* Previous releases of Postgres neglected to prevent SELECT FOR UPDATE on
@@ -1199,9 +1181,9 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
* this again if the update gets lost.
*/
Assert(!(seqdatatuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI));
- if (HeapTupleHeaderGetRawXmax(seqdatatuple->t_data) != InvalidTransactionId)
+ if (HeapTupleGetRawXmax(seqdatatuple) != InvalidTransactionId)
{
- HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId);
+ HeapTupleSetXmax(seqdatatuple, InvalidTransactionId);
seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED;
seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID;
MarkBufferDirtyHint(*buf, true);
@@ -1863,7 +1845,6 @@ seq_redo(XLogReaderState *record)
char *item;
Size itemsz;
xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
- sequence_magic *sm;
if (info != XLOG_SEQ_LOG)
elog(PANIC, "seq_redo: unknown op code %u", info);
@@ -1882,9 +1863,8 @@ seq_redo(XLogReaderState *record)
*/
localpage = (Page) palloc(BufferGetPageSize(buffer));
- PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
- sm = (sequence_magic *) PageGetSpecialPointer(localpage);
- sm->magic = SEQ_MAGIC;
+ PageInit(localpage, BufferGetPageSize(buffer), sizeof(HeapPageSpecialData));
+ HeapPageGetSpecial(localpage)->pd_xid_base = RecentXmin - FirstNormalTransactionId;
item = (char *) xlrec + sizeof(xl_seq_rec);
itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 283ffaea77d..347ba129f82 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -63,13 +63,12 @@
/*
* GUC parameters
*/
-int vacuum_freeze_min_age;
-int vacuum_freeze_table_age;
-int vacuum_multixact_freeze_min_age;
-int vacuum_multixact_freeze_table_age;
-int vacuum_failsafe_age;
-int vacuum_multixact_failsafe_age;
-
+int64 vacuum_freeze_min_age;
+int64 vacuum_freeze_table_age;
+int64 vacuum_multixact_freeze_min_age;
+int64 vacuum_multixact_freeze_table_age;
+int64 vacuum_failsafe_age;
+int64 vacuum_multixact_failsafe_age;
/* A few variables that don't seem worth passing around as parameters */
static MemoryContext vac_context = NULL;
@@ -962,24 +961,26 @@ get_all_vacuum_rels(int options)
*/
void
vacuum_set_xid_limits(Relation rel,
- int freeze_min_age,
- int freeze_table_age,
- int multixact_freeze_min_age,
- int multixact_freeze_table_age,
+ int64 freeze_min_age,
+ int64 freeze_table_age,
+ int64 multixact_freeze_min_age,
+ int64 multixact_freeze_table_age,
TransactionId *oldestXmin,
TransactionId *freezeLimit,
TransactionId *xidFullScanLimit,
MultiXactId *multiXactCutoff,
MultiXactId *mxactFullScanLimit)
{
- int freezemin;
- int mxid_freezemin;
- int effective_multixact_freeze_max_age;
+ int64 freezemin;
+ int64 mxid_freezemin;
+ int64 effective_multixact_freeze_max_age;
TransactionId limit;
TransactionId safeLimit;
+ TransactionId nextXid;
MultiXactId oldestMxact;
MultiXactId mxactLimit;
MultiXactId safeMxactLimit;
+ MultiXactId nextMxactId;
/*
* We can always ignore processes running lazy vacuum. This is because we
@@ -1028,8 +1029,10 @@ vacuum_set_xid_limits(Relation rel,
/*
* Compute the cutoff XID, being careful not to generate a "permanent" XID
*/
- limit = *oldestXmin - freezemin;
- if (!TransactionIdIsNormal(limit))
+ limit = *oldestXmin;
+ if (limit > FirstNormalTransactionId + freezemin)
+ limit -= freezemin;
+ else
limit = FirstNormalTransactionId;
/*
@@ -1037,15 +1040,17 @@ vacuum_set_xid_limits(Relation rel,
* autovacuum_freeze_max_age / 2 XIDs old), complain and force a minimum
* freeze age of zero.
*/
- safeLimit = ReadNextTransactionId() - autovacuum_freeze_max_age;
- if (!TransactionIdIsNormal(safeLimit))
+ nextXid = ReadNextTransactionId();
+ if (nextXid > FirstNormalTransactionId + autovacuum_freeze_max_age)
+ safeLimit = nextXid - autovacuum_freeze_max_age;
+ else
safeLimit = FirstNormalTransactionId;
if (TransactionIdPrecedes(limit, safeLimit))
{
ereport(WARNING,
(errmsg("oldest xmin is far in the past"),
- errhint("Close open transactions soon to avoid wraparound problems.\n"
+ errhint("Close open transactions soon to enable SLRU truncation.\n"
"You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
limit = *oldestXmin;
}
@@ -1057,7 +1062,7 @@ vacuum_set_xid_limits(Relation rel,
* normally autovacuum_multixact_freeze_max_age, but may be less if we are
* short of multixact member space.
*/
- effective_multixact_freeze_max_age = MultiXactMemberFreezeThreshold();
+ effective_multixact_freeze_max_age = autovacuum_multixact_freeze_max_age;
/*
* Determine the minimum multixact freeze age to use: as specified by
@@ -1078,16 +1083,23 @@ vacuum_set_xid_limits(Relation rel,
if (mxactLimit < FirstMultiXactId)
mxactLimit = FirstMultiXactId;
- safeMxactLimit =
- ReadNextMultiXactId() - effective_multixact_freeze_max_age;
- if (safeMxactLimit < FirstMultiXactId)
+ nextMxactId = ReadNextMultiXactId();
+ if (nextMxactId > FirstMultiXactId + effective_multixact_freeze_max_age)
+ safeMxactLimit = nextMxactId - effective_multixact_freeze_max_age;
+ else
safeMxactLimit = FirstMultiXactId;
if (MultiXactIdPrecedes(mxactLimit, safeMxactLimit))
{
ereport(WARNING,
- (errmsg("oldest multixact is far in the past"),
- errhint("Close open transactions with multixacts soon to avoid wraparound problems.")));
+ (errmsg("oldest multixact is far in the past: "
+ INT64_FORMAT " " INT64_FORMAT " "
+ INT64_FORMAT " " INT64_FORMAT " " INT64_FORMAT " "
+ INT64_FORMAT " " INT64_FORMAT " " INT64_FORMAT " ",
+ multixact_freeze_min_age, vacuum_multixact_freeze_min_age,
+ mxactLimit, mxid_freezemin, oldestMxact,
+ safeMxactLimit, effective_multixact_freeze_max_age, nextMxactId),
+ errhint("Close open transactions with multixacts soon to enable SLRU truncation.")));
/* Use the safe limit, unless an older mxact is still running */
if (MultiXactIdPrecedes(oldestMxact, safeMxactLimit))
mxactLimit = oldestMxact;
@@ -1099,7 +1111,7 @@ vacuum_set_xid_limits(Relation rel,
if (xidFullScanLimit != NULL)
{
- int freezetable;
+ int64 freezetable;
Assert(mxactFullScanLimit != NULL);
@@ -1120,8 +1132,10 @@ vacuum_set_xid_limits(Relation rel,
* Compute XID limit causing a full-table vacuum, being careful not to
* generate a "permanent" XID.
*/
- limit = ReadNextTransactionId() - freezetable;
- if (!TransactionIdIsNormal(limit))
+ limit = ReadNextTransactionId();
+ if (limit > FirstNormalTransactionId + freezetable)
+ limit -= freezetable;
+ else
limit = FirstNormalTransactionId;
*xidFullScanLimit = limit;
@@ -1145,8 +1159,10 @@ vacuum_set_xid_limits(Relation rel,
* Compute MultiXact limit causing a full-table vacuum, being careful
* to generate a valid MultiXact value.
*/
- mxactLimit = ReadNextMultiXactId() - freezetable;
- if (mxactLimit < FirstMultiXactId)
+ mxactLimit = ReadNextMultiXactId();
+ if (mxactLimit > FirstMultiXactId + freezetable)
+ mxactLimit -= freezetable;
+ else
mxactLimit = FirstMultiXactId;
*mxactFullScanLimit = mxactLimit;
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index d6f7d7c2d73..1276035aef8 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -3154,6 +3154,7 @@ ExecEvalFieldStoreDeForm(ExprState *state, ExprEvalStep *op, ExprContext *econte
tmptup.t_len = HeapTupleHeaderGetDatumLength(tuphdr);
ItemPointerSetInvalid(&(tmptup.t_self));
tmptup.t_tableOid = InvalidOid;
+ HeapTupleSetZeroBase(&tmptup);
tmptup.t_data = tuphdr;
heap_deform_tuple(&tmptup, tupDesc,
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 9df1f81ea89..d8c92f48469 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -1070,6 +1070,7 @@ GetAttributeByName(HeapTupleHeader tuple, const char *attname, bool *isNull)
tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
ItemPointerSetInvalid(&(tmptup.t_self));
tmptup.t_tableOid = InvalidOid;
+ HeapTupleSetZeroBase(&tmptup);
tmptup.t_data = tuple;
result = heap_getattr(&tmptup,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 5ec699a9bd1..8c2b9225a5b 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -2538,6 +2538,7 @@ ExecModifyTable(PlanState *pstate)
HeapTupleHeaderGetDatumLength(oldtupdata.t_data);
ItemPointerSetInvalid(&(oldtupdata.t_self));
/* Historically, view triggers see invalid t_tableOid. */
+ HeapTupleSetZeroBase(&oldtupdata);
oldtupdata.t_tableOid =
(relkind == RELKIND_VIEW) ? InvalidOid :
RelationGetRelid(resultRelInfo->ri_RelationDesc);
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index c93f90de9b7..c9f409286b4 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -1061,6 +1061,7 @@ SPI_modifytuple(Relation rel, HeapTuple tuple, int natts, int *attnum,
mtuple->t_data->t_ctid = tuple->t_data->t_ctid;
mtuple->t_self = tuple->t_self;
mtuple->t_tableOid = tuple->t_tableOid;
+ HeapTupleCopyBase(mtuple, tuple);
}
else
{
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 2b0236937aa..f413f5c8443 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2772,8 +2772,8 @@ _outIndexStmt(StringInfo str, const IndexStmt *node)
WRITE_STRING_FIELD(idxcomment);
WRITE_OID_FIELD(indexOid);
WRITE_OID_FIELD(oldNode);
- WRITE_UINT_FIELD(oldCreateSubid);
- WRITE_UINT_FIELD(oldFirstRelfilenodeSubid);
+ WRITE_UINT64_FIELD(oldCreateSubid);
+ WRITE_UINT64_FIELD(oldFirstRelfilenodeSubid);
WRITE_BOOL_FIELD(unique);
WRITE_BOOL_FIELD(primary);
WRITE_BOOL_FIELD(isconstraint);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index a5002ad8955..5461a38b41f 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -228,7 +228,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
* src/backend/access/heap/README.HOT for discussion.
*/
if (index->indcheckxmin &&
- !TransactionIdPrecedes(HeapTupleHeaderGetXmin(indexRelation->rd_indextuple->t_data),
+ !TransactionIdPrecedes(HeapTupleGetXmin(indexRelation->rd_indextuple),
TransactionXmin))
{
root->glob->transientPlan = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 681ef91b81e..98e5e48e194 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -121,8 +121,8 @@ int autovacuum_vac_ins_thresh;
double autovacuum_vac_ins_scale;
int autovacuum_anl_thresh;
double autovacuum_anl_scale;
-int autovacuum_freeze_max_age;
-int autovacuum_multixact_freeze_max_age;
+int64 autovacuum_freeze_max_age;
+int64 autovacuum_multixact_freeze_max_age;
double autovacuum_vac_cost_delay;
int autovacuum_vac_cost_limit;
@@ -148,10 +148,10 @@ static TransactionId recentXid;
static MultiXactId recentMulti;
/* Default freeze ages to use for autovacuum (varies by database) */
-static int default_freeze_min_age;
-static int default_freeze_table_age;
-static int default_multixact_freeze_min_age;
-static int default_multixact_freeze_table_age;
+static int64 default_freeze_min_age;
+static int64 default_freeze_table_age;
+static int64 default_multixact_freeze_min_age;
+static int64 default_multixact_freeze_table_age;
/* Memory context for long-lived data */
static MemoryContext AutovacMemCxt;
@@ -327,15 +327,15 @@ static void FreeWorkerInfo(int code, Datum arg);
static autovac_table *table_recheck_autovac(Oid relid, HTAB *table_toast_map,
TupleDesc pg_class_desc,
- int effective_multixact_freeze_max_age);
+ int64 effective_multixact_freeze_max_age);
static void recheck_relation_needs_vacanalyze(Oid relid, AutoVacOpts *avopts,
Form_pg_class classForm,
- int effective_multixact_freeze_max_age,
+ int64 effective_multixact_freeze_max_age,
bool *dovacuum, bool *doanalyze, bool *wraparound);
static void relation_needs_vacanalyze(Oid relid, AutoVacOpts *relopts,
Form_pg_class classForm,
PgStat_StatTabEntry *tabentry,
- int effective_multixact_freeze_max_age,
+ int64 effective_multixact_freeze_max_age,
bool *dovacuum, bool *doanalyze, bool *wraparound);
static void autovacuum_do_vac_analyze(autovac_table *tab,
@@ -1157,6 +1157,7 @@ do_start_worker(void)
ListCell *cell;
TransactionId xidForceLimit;
MultiXactId multiForceLimit;
+ int64 multiMembersThreshold;
bool for_xid_wrap;
bool for_multi_wrap;
avw_dbase *avdb;
@@ -1196,17 +1197,18 @@ do_start_worker(void)
* particular tables, but not loosened.)
*/
recentXid = ReadNextTransactionId();
- xidForceLimit = recentXid - autovacuum_freeze_max_age;
- /* ensure it's a "normal" XID, else TransactionIdPrecedes misbehaves */
- /* this can cause the limit to go backwards by 3, but that's OK */
- if (xidForceLimit < FirstNormalTransactionId)
- xidForceLimit -= FirstNormalTransactionId;
+ if (recentXid > FirstNormalTransactionId + autovacuum_freeze_max_age)
+ xidForceLimit = recentXid - autovacuum_freeze_max_age;
+ else
+ xidForceLimit = FirstNormalTransactionId;
/* Also determine the oldest datminmxid we will consider. */
recentMulti = ReadNextMultiXactId();
- multiForceLimit = recentMulti - MultiXactMemberFreezeThreshold();
- if (multiForceLimit < FirstMultiXactId)
- multiForceLimit -= FirstMultiXactId;
+ multiMembersThreshold = autovacuum_multixact_freeze_max_age;
+ if (recentMulti > FirstMultiXactId + multiMembersThreshold)
+ multiForceLimit = recentMulti - multiMembersThreshold;
+ else
+ multiForceLimit = FirstMultiXactId;
/*
* Choose a database to connect to. We pick the database that was least
@@ -1971,7 +1973,7 @@ do_autovacuum(void)
BufferAccessStrategy bstrategy;
ScanKeyData key;
TupleDesc pg_class_desc;
- int effective_multixact_freeze_max_age;
+ int64 effective_multixact_freeze_max_age;
bool did_vacuum = false;
bool found_concurrent_worker = false;
int i;
@@ -2007,7 +2009,7 @@ do_autovacuum(void)
* normally autovacuum_multixact_freeze_max_age, but may be less if we are
* short of multixact member space.
*/
- effective_multixact_freeze_max_age = MultiXactMemberFreezeThreshold();
+ effective_multixact_freeze_max_age = autovacuum_multixact_freeze_max_age;
/*
* Find the pg_database entry and select the default freeze ages. We use
@@ -2803,7 +2805,7 @@ get_pgstat_tabentry_relid(Oid relid, bool isshared, PgStat_StatDBEntry *shared,
static autovac_table *
table_recheck_autovac(Oid relid, HTAB *table_toast_map,
TupleDesc pg_class_desc,
- int effective_multixact_freeze_max_age)
+ int64 effective_multixact_freeze_max_age)
{
Form_pg_class classForm;
HeapTuple classTup;
@@ -2872,10 +2874,10 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
/* OK, it needs something done */
if (doanalyze || dovacuum)
{
- int freeze_min_age;
- int freeze_table_age;
- int multixact_freeze_min_age;
- int multixact_freeze_table_age;
+ int64 freeze_min_age;
+ int64 freeze_table_age;
+ int64 multixact_freeze_min_age;
+ int64 multixact_freeze_table_age;
int vac_cost_limit;
double vac_cost_delay;
int log_min_duration;
@@ -2995,7 +2997,7 @@ static void
recheck_relation_needs_vacanalyze(Oid relid,
AutoVacOpts *avopts,
Form_pg_class classForm,
- int effective_multixact_freeze_max_age,
+ int64 effective_multixact_freeze_max_age,
bool *dovacuum,
bool *doanalyze,
bool *wraparound)
@@ -3064,7 +3066,7 @@ relation_needs_vacanalyze(Oid relid,
AutoVacOpts *relopts,
Form_pg_class classForm,
PgStat_StatTabEntry *tabentry,
- int effective_multixact_freeze_max_age,
+ int64 effective_multixact_freeze_max_age,
/* output params below */
bool *dovacuum,
bool *doanalyze,
@@ -3093,8 +3095,8 @@ relation_needs_vacanalyze(Oid relid,
anltuples;
/* freeze parameters */
- int freeze_max_age;
- int multixact_freeze_max_age;
+ int64 freeze_max_age;
+ int64 multixact_freeze_max_age;
TransactionId xidForceLimit;
MultiXactId multiForceLimit;
@@ -3144,17 +3146,19 @@ relation_needs_vacanalyze(Oid relid,
av_enabled = (relopts ? relopts->enabled : true);
/* Force vacuum if table is at risk of wraparound */
- xidForceLimit = recentXid - freeze_max_age;
- if (xidForceLimit < FirstNormalTransactionId)
- xidForceLimit -= FirstNormalTransactionId;
+ if (recentXid > FirstNormalTransactionId + freeze_max_age)
+ xidForceLimit = recentXid - freeze_max_age;
+ else
+ xidForceLimit = FirstNormalTransactionId;
force_vacuum = (TransactionIdIsNormal(classForm->relfrozenxid) &&
TransactionIdPrecedes(classForm->relfrozenxid,
xidForceLimit));
if (!force_vacuum)
{
- multiForceLimit = recentMulti - multixact_freeze_max_age;
- if (multiForceLimit < FirstMultiXactId)
- multiForceLimit -= FirstMultiXactId;
+ if (recentMulti > FirstMultiXactId + multixact_freeze_max_age)
+ multiForceLimit = recentMulti - multixact_freeze_max_age;
+ else
+ multiForceLimit = FirstMultiXactId;
force_vacuum = MultiXactIdIsValid(classForm->relminmxid) &&
MultiXactIdPrecedes(classForm->relminmxid, multiForceLimit);
}
diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c
index 9b450c9f90e..161b2b955e4 100644
--- a/src/backend/replication/logical/decode.c
+++ b/src/backend/replication/logical/decode.c
@@ -846,8 +846,12 @@ DecodeInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
xl_heap_insert *xlrec;
ReorderBufferChange *change;
RelFileNode target_node;
+ bool isinit = (XLogRecGetInfo(r) & XLOG_HEAP_INIT_PAGE) != 0;
+ Pointer rec_data = (Pointer) XLogRecGetData(r);
- xlrec = (xl_heap_insert *) XLogRecGetData(r);
+ if (isinit)
+ rec_data += sizeof(TransactionId);
+ xlrec = (xl_heap_insert *) rec_data;
/*
* Ignore insert records without new tuples (this does happen when
@@ -903,8 +907,12 @@ DecodeUpdate(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
ReorderBufferChange *change;
char *data;
RelFileNode target_node;
+ bool isinit = (XLogRecGetInfo(r) & XLOG_HEAP_INIT_PAGE) != 0;
+ Pointer rec_data = (Pointer) XLogRecGetData(r);
- xlrec = (xl_heap_update *) XLogRecGetData(r);
+ if (isinit)
+ rec_data += sizeof(TransactionId);
+ xlrec = (xl_heap_update *) rec_data;
/* only interested in our database */
XLogRecGetBlockTag(r, 0, &target_node, NULL, NULL);
@@ -1064,8 +1072,12 @@ DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
char *tupledata;
Size tuplelen;
RelFileNode rnode;
+ bool isinit = (XLogRecGetInfo(r) & XLOG_HEAP_INIT_PAGE) != 0;
+ Pointer rec_data = (Pointer) XLogRecGetData(r);
- xlrec = (xl_heap_multi_insert *) XLogRecGetData(r);
+ if (isinit)
+ rec_data += sizeof(TransactionId);
+ xlrec = (xl_heap_multi_insert *) rec_data;
/*
* Ignore insert records without new tuples. This happens when a
@@ -1122,6 +1134,7 @@ DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
* We can only figure this out after reassembling the transactions.
*/
tuple->tuple.t_tableOid = InvalidOid;
+ HeapTupleSetZeroBase(&(tuple->tuple));
tuple->tuple.t_len = datalen + SizeofHeapTupleHeader;
@@ -1213,6 +1226,7 @@ DecodeXLogTuple(char *data, Size len, ReorderBufferTupleBuf *tuple)
/* we can only figure this out after reassembling the transactions */
tuple->tuple.t_tableOid = InvalidOid;
+ HeapTupleSetZeroBase(&(tuple->tuple));
/* data is not stored aligned, copy to aligned storage */
memcpy((char *) &xlhdr,
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 9bc3a2d8deb..0e00dac8d94 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -1614,7 +1614,7 @@ LogicalIncreaseXminForSlot(XLogRecPtr current_lsn, TransactionId xmin)
SpinLockRelease(&slot->mutex);
if (got_new_xmin)
- elog(DEBUG1, "got new catalog xmin %u at %X/%X", xmin,
+ elog(DEBUG1, "got new catalog xmin " XID_FMT " at %X/%X", xmin,
LSN_FORMAT_ARGS(current_lsn));
/* candidate already valid with the current flush position, apply */
diff --git a/src/backend/replication/logical/proto.c b/src/backend/replication/logical/proto.c
index 953942692ce..04560fbd889 100644
--- a/src/backend/replication/logical/proto.c
+++ b/src/backend/replication/logical/proto.c
@@ -50,7 +50,7 @@ logicalrep_write_begin(StringInfo out, ReorderBufferTXN *txn)
/* fixed fields */
pq_sendint64(out, txn->final_lsn);
pq_sendint64(out, txn->xact_time.commit_time);
- pq_sendint32(out, txn->xid);
+ pq_sendint64(out, txn->xid);
}
/*
@@ -64,7 +64,7 @@ logicalrep_read_begin(StringInfo in, LogicalRepBeginData *begin_data)
if (begin_data->final_lsn == InvalidXLogRecPtr)
elog(ERROR, "final_lsn not set in begin message");
begin_data->committime = pq_getmsgint64(in);
- begin_data->xid = pq_getmsgint(in, 4);
+ begin_data->xid = pq_getmsgint64(in);
}
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index d317a626e1e..f2855cadcf5 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -2442,7 +2442,7 @@ ReorderBufferProcessTXN(ReorderBuffer *rb, ReorderBufferTXN *txn,
/* this is just a sanity check against bad output plugin behaviour */
if (GetCurrentTransactionIdIfAny() != InvalidTransactionId)
- elog(ERROR, "output plugin used XID %u",
+ elog(ERROR, "output plugin used XID " XID_FMT,
GetCurrentTransactionId());
/*
@@ -2881,7 +2881,7 @@ ReorderBufferAbortOld(ReorderBuffer *rb, TransactionId oldestRunningXid)
if (TransactionIdPrecedes(txn->xid, oldestRunningXid))
{
- elog(DEBUG2, "aborting old transaction %u", txn->xid);
+ elog(DEBUG2, "aborting old transaction " XID_FMT, txn->xid);
/* remove potential on-disk data, and deallocate this tx */
ReorderBufferCleanupTXN(rb, txn);
@@ -3523,7 +3523,7 @@ ReorderBufferSerializeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
Size spilled = 0;
Size size = txn->size;
- elog(DEBUG2, "spill %u changes in XID %u to disk",
+ elog(DEBUG2, "spill %u changes in XID " XID_FMT " to disk",
(uint32) txn->nentries_mem, txn->xid);
/* do the same to all child TXs */
@@ -3800,7 +3800,7 @@ ReorderBufferSerializeChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
errno = save_errno ? save_errno : ENOSPC;
ereport(ERROR,
(errcode_for_file_access(),
- errmsg("could not write to data file for XID %u: %m",
+ errmsg("could not write to data file for XID %" PRIu64 ": %m",
txn->xid)));
}
pgstat_report_wait_end();
@@ -4443,7 +4443,7 @@ ReorderBufferSerializedPath(char *path, ReplicationSlot *slot, TransactionId xid
XLogSegNoOffsetToRecPtr(segno, 0, wal_segment_size, recptr);
- snprintf(path, MAXPGPATH, "pg_replslot/%s/xid-%u-lsn-%X-%X.spill",
+ snprintf(path, MAXPGPATH, "pg_replslot/%s/xid-" XID_FMT "-lsn-%X-%X.spill",
NameStr(MyReplicationSlot->data.name),
xid, LSN_FORMAT_ARGS(recptr));
}
@@ -5024,8 +5024,12 @@ UpdateLogicalMappings(HTAB *tuplecid_data, Oid relid, Snapshot snapshot)
TransactionId f_mapped_xid;
TransactionId f_create_xid;
XLogRecPtr f_lsn;
- uint32 f_hi,
- f_lo;
+ uint32 f_lsn_hi,
+ f_lsn_lo,
+ f_mapped_xid_hi,
+ f_mapped_xid_lo,
+ f_create_xid_hi,
+ f_create_xid_lo;
RewriteMappingFile *f;
if (strcmp(mapping_de->d_name, ".") == 0 ||
@@ -5037,11 +5041,14 @@ UpdateLogicalMappings(HTAB *tuplecid_data, Oid relid, Snapshot snapshot)
continue;
if (sscanf(mapping_de->d_name, LOGICAL_REWRITE_FORMAT,
- &f_dboid, &f_relid, &f_hi, &f_lo,
- &f_mapped_xid, &f_create_xid) != 6)
+ &f_dboid, &f_relid, &f_lsn_hi, &f_lsn_lo,
+ &f_mapped_xid_hi, &f_mapped_xid_lo,
+ &f_create_xid_hi, &f_create_xid_lo) != 8)
elog(ERROR, "could not parse filename \"%s\"", mapping_de->d_name);
- f_lsn = ((uint64) f_hi) << 32 | f_lo;
+ f_lsn = ((uint64) f_lsn_hi) << 32 | f_lsn_lo;
+ f_mapped_xid = ((uint64) f_mapped_xid_hi) << 32 | f_mapped_xid_lo;
+ f_create_xid = ((uint64) f_create_xid_hi) << 32 | f_create_xid_lo;
/* mapping for another database */
if (f_dboid != dboid)
@@ -5074,7 +5081,7 @@ UpdateLogicalMappings(HTAB *tuplecid_data, Oid relid, Snapshot snapshot)
{
RewriteMappingFile *f = (RewriteMappingFile *) lfirst(file);
- elog(DEBUG1, "applying mapping: \"%s\" in %u", f->fname,
+ elog(DEBUG1, "applying mapping: \"%s\" in " XID_FMT, f->fname,
snapshot->subxip[0]);
ApplyLogicalMappingFile(tuplecid_data, relid, f->fname);
pfree(f);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index ab04e946bc2..1cc0bd2d891 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -847,7 +847,7 @@ SnapBuildDistributeNewCatalogSnapshot(SnapBuild *builder, XLogRecPtr lsn)
if (rbtxn_prepared(txn) || rbtxn_skip_prepared(txn))
continue;
- elog(DEBUG2, "adding a new snapshot to %u at %X/%X",
+ elog(DEBUG2, "adding a new snapshot to " XID_FMT " at %X/%X",
txn->xid, LSN_FORMAT_ARGS(lsn));
/*
@@ -922,7 +922,7 @@ SnapBuildPurgeCommittedTxn(SnapBuild *builder)
memcpy(builder->committed.xip, workspace,
surviving_xids * sizeof(TransactionId));
- elog(DEBUG3, "purged committed transactions from %u to %u, xmin: %u, xmax: %u",
+ elog(DEBUG3, "purged committed transactions from %u to %u, xmin: " XID_FMT ", xmax: " XID_FMT,
(uint32) builder->committed.xcnt, (uint32) surviving_xids,
builder->xmin, builder->xmax);
builder->committed.xcnt = surviving_xids;
@@ -988,7 +988,7 @@ SnapBuildCommitTxn(SnapBuild *builder, XLogRecPtr lsn, TransactionId xid,
sub_needs_timetravel = true;
needs_snapshot = true;
- elog(DEBUG1, "found subtransaction %u:%u with catalog changes",
+ elog(DEBUG1, "found subtransaction " XID_FMT ":" XID_FMT " with catalog changes",
xid, subxid);
SnapBuildAddCommittedTxn(builder, subxid);
@@ -1014,7 +1014,7 @@ SnapBuildCommitTxn(SnapBuild *builder, XLogRecPtr lsn, TransactionId xid,
/* if top-level modified catalog, it'll need a snapshot */
if (ReorderBufferXidHasCatalogChanges(builder->reorder, xid))
{
- elog(DEBUG2, "found top level transaction %u, with catalog changes",
+ elog(DEBUG2, "found top level transaction " XID_FMT ", with catalog changes",
xid);
needs_snapshot = true;
needs_timetravel = true;
@@ -1027,7 +1027,7 @@ SnapBuildCommitTxn(SnapBuild *builder, XLogRecPtr lsn, TransactionId xid,
}
else if (needs_timetravel)
{
- elog(DEBUG2, "forced transaction %u to do timetravel", xid);
+ elog(DEBUG2, "forced transaction " XID_FMT " to do timetravel", xid);
SnapBuildAddCommittedTxn(builder, xid);
}
@@ -1149,7 +1149,7 @@ SnapBuildProcessRunningXacts(SnapBuild *builder, XLogRecPtr lsn, xl_running_xact
xmin = ReorderBufferGetOldestXmin(builder->reorder);
if (xmin == InvalidTransactionId)
xmin = running->oldestRunningXid;
- elog(DEBUG3, "xmin: %u, xmax: %u, oldest running: %u, oldest xmin: %u",
+ elog(DEBUG3, "xmin: " XID_FMT ", xmax: " XID_FMT ", oldest running: " XID_FMT ", oldest xmin: " XID_FMT,
builder->xmin, builder->xmax, running->oldestRunningXid, xmin);
LogicalIncreaseXminForSlot(lsn, xmin);
@@ -1239,7 +1239,7 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
ereport(DEBUG1,
(errmsg_internal("skipping snapshot at %X/%X while building logical decoding snapshot, xmin horizon too low",
LSN_FORMAT_ARGS(lsn)),
- errdetail_internal("initial xmin horizon of %u vs the snapshot's %u",
+ errdetail_internal("initial xmin horizon of " XID_FMT " vs the snapshot's " XID_FMT,
builder->initial_xmin_horizon, running->oldestRunningXid)));
@@ -1322,7 +1322,7 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
ereport(LOG,
(errmsg("logical decoding found initial starting point at %X/%X",
LSN_FORMAT_ARGS(lsn)),
- errdetail("Waiting for transactions (approximately %d) older than %u to end.",
+ errdetail("Waiting for transactions (approximately %d) older than %" PRIu64 " to end.",
running->xcnt, running->nextXid)));
SnapBuildWaitSnapshot(running, running->nextXid);
@@ -1346,7 +1346,7 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
ereport(LOG,
(errmsg("logical decoding found initial consistent point at %X/%X",
LSN_FORMAT_ARGS(lsn)),
- errdetail("Waiting for transactions (approximately %d) older than %u to end.",
+ errdetail("Waiting for transactions (approximately %d) older than %" PRIu64 " to end.",
running->xcnt, running->nextXid)));
SnapBuildWaitSnapshot(running, running->nextXid);
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index c9af775bc18..7a43c275cd3 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -1046,7 +1046,7 @@ apply_handle_stream_prepare(StringInfo s)
logicalrep_read_stream_prepare(s, &prepare_data);
set_apply_error_context_xact(prepare_data.xid, prepare_data.prepare_time);
- elog(DEBUG1, "received prepare for streamed transaction %u", prepare_data.xid);
+ elog(DEBUG1, "received prepare for streamed transaction " XID_FMT, prepare_data.xid);
/* Replay all the spooled operations. */
apply_spooled_messages(prepare_data.xid, prepare_data.prepare_lsn);
@@ -1428,7 +1428,7 @@ apply_handle_stream_commit(StringInfo s)
xid = logicalrep_read_stream_commit(s, &commit_data);
set_apply_error_context_xact(xid, commit_data.committime);
- elog(DEBUG1, "received commit for streamed transaction %u", xid);
+ elog(DEBUG1, "received commit for streamed transaction " XID_FMT, xid);
apply_spooled_messages(xid, commit_data.commit_lsn);
@@ -3203,14 +3203,14 @@ subxact_info_add(TransactionId xid)
static inline void
subxact_filename(char *path, Oid subid, TransactionId xid)
{
- snprintf(path, MAXPGPATH, "%u-%u.subxacts", subid, xid);
+ snprintf(path, MAXPGPATH, "%u-" XID_FMT ".subxacts", subid, xid);
}
/* format filename for file containing serialized changes */
static inline void
changes_filename(char *path, Oid subid, TransactionId xid)
{
- snprintf(path, MAXPGPATH, "%u-%u.changes", subid, xid);
+ snprintf(path, MAXPGPATH, "%u-" XID_FMT ".changes", subid, xid);
}
/*
@@ -3372,7 +3372,7 @@ TwoPhaseTransactionGid(Oid subid, TransactionId xid, char *gid, int szgid)
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg_internal("invalid two-phase transaction ID")));
- snprintf(gid, szgid, "pg_gid_%u_%u", subid, xid);
+ snprintf(gid, szgid, "pg_gid_%u_" XID_FMT, subid, xid);
}
/* Logical Replication Apply worker entry point */
@@ -3689,7 +3689,7 @@ apply_error_callback(void *arg)
/* append transaction information */
if (TransactionIdIsNormal(errarg->remote_xid))
{
- appendStringInfo(&buf, _(" in transaction %u"), errarg->remote_xid);
+ appendStringInfo(&buf, _(" in transaction " XID_FMT), errarg->remote_xid);
if (errarg->ts != 0)
appendStringInfo(&buf, _(" at %s"),
timestamptz_to_str(errarg->ts));
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index b39fce8c23c..a64249e5daf 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -1142,10 +1142,6 @@ static void
XLogWalRcvSendHSFeedback(bool immed)
{
TimestampTz now;
- FullTransactionId nextFullXid;
- TransactionId nextXid;
- uint32 xmin_epoch,
- catalog_xmin_epoch;
TransactionId xmin,
catalog_xmin;
static TimestampTz sendTime = 0;
@@ -1202,30 +1198,15 @@ XLogWalRcvSendHSFeedback(bool immed)
catalog_xmin = InvalidTransactionId;
}
- /*
- * Get epoch and adjust if nextXid and oldestXmin are different sides of
- * the epoch boundary.
- */
- nextFullXid = ReadNextFullTransactionId();
- nextXid = XidFromFullTransactionId(nextFullXid);
- xmin_epoch = EpochFromFullTransactionId(nextFullXid);
- catalog_xmin_epoch = xmin_epoch;
- if (nextXid < xmin)
- xmin_epoch--;
- if (nextXid < catalog_xmin)
- catalog_xmin_epoch--;
-
- elog(DEBUG2, "sending hot standby feedback xmin %u epoch %u catalog_xmin %u catalog_xmin_epoch %u",
- xmin, xmin_epoch, catalog_xmin, catalog_xmin_epoch);
+ elog(DEBUG2, "sending hot standby feedback xmin " XID_FMT " catalog_xmin " XID_FMT,
+ xmin, catalog_xmin);
/* Construct the message and send it. */
resetStringInfo(&reply_message);
pq_sendbyte(&reply_message, 'h');
pq_sendint64(&reply_message, GetCurrentTimestamp());
- pq_sendint32(&reply_message, xmin);
- pq_sendint32(&reply_message, xmin_epoch);
- pq_sendint32(&reply_message, catalog_xmin);
- pq_sendint32(&reply_message, catalog_xmin_epoch);
+ pq_sendint64(&reply_message, xmin);
+ pq_sendint64(&reply_message, catalog_xmin);
walrcv_send(wrconn, reply_message.data, reply_message.len);
if (TransactionIdIsValid(xmin) || TransactionIdIsValid(catalog_xmin))
primary_has_standby_xmin = true;
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 4cf95ce4392..9268bd62ad5 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -252,7 +252,6 @@ static void WalSndUpdateProgress(LogicalDecodingContext *ctx, XLogRecPtr lsn, Tr
static XLogRecPtr WalSndWaitForWal(XLogRecPtr loc);
static void LagTrackerWrite(XLogRecPtr lsn, TimestampTz local_flush_time);
static TimeOffset LagTrackerRead(int head, XLogRecPtr lsn, TimestampTz now);
-static bool TransactionIdInRecentPast(TransactionId xid, uint32 epoch);
static void WalSndSegmentOpen(XLogReaderState *state, XLogSegNo nextSegNo,
TimeLineID *tli_p);
@@ -2136,44 +2135,6 @@ PhysicalReplicationSlotNewXmin(TransactionId feedbackXmin, TransactionId feedbac
}
}
-/*
- * Check that the provided xmin/epoch are sane, that is, not in the future
- * and not so far back as to be already wrapped around.
- *
- * Epoch of nextXid should be same as standby, or if the counter has
- * wrapped, then one greater than standby.
- *
- * This check doesn't care about whether clog exists for these xids
- * at all.
- */
-static bool
-TransactionIdInRecentPast(TransactionId xid, uint32 epoch)
-{
- FullTransactionId nextFullXid;
- TransactionId nextXid;
- uint32 nextEpoch;
-
- nextFullXid = ReadNextFullTransactionId();
- nextXid = XidFromFullTransactionId(nextFullXid);
- nextEpoch = EpochFromFullTransactionId(nextFullXid);
-
- if (xid <= nextXid)
- {
- if (epoch != nextEpoch)
- return false;
- }
- else
- {
- if (epoch + 1 != nextEpoch)
- return false;
- }
-
- if (!TransactionIdPrecedesOrEquals(xid, nextXid))
- return false; /* epoch OK, but it's wrapped around */
-
- return true;
-}
-
/*
* Hot Standby feedback
*/
@@ -2181,9 +2142,7 @@ static void
ProcessStandbyHSFeedbackMessage(void)
{
TransactionId feedbackXmin;
- uint32 feedbackEpoch;
TransactionId feedbackCatalogXmin;
- uint32 feedbackCatalogEpoch;
TimestampTz replyTime;
/*
@@ -2192,10 +2151,8 @@ ProcessStandbyHSFeedbackMessage(void)
* of this message.
*/
replyTime = pq_getmsgint64(&reply_message);
- feedbackXmin = pq_getmsgint(&reply_message, 4);
- feedbackEpoch = pq_getmsgint(&reply_message, 4);
- feedbackCatalogXmin = pq_getmsgint(&reply_message, 4);
- feedbackCatalogEpoch = pq_getmsgint(&reply_message, 4);
+ feedbackXmin = pq_getmsgint64(&reply_message);
+ feedbackCatalogXmin = pq_getmsgint64(&reply_message);
if (message_level_is_interesting(DEBUG2))
{
@@ -2204,11 +2161,9 @@ ProcessStandbyHSFeedbackMessage(void)
/* Copy because timestamptz_to_str returns a static buffer */
replyTimeStr = pstrdup(timestamptz_to_str(replyTime));
- elog(DEBUG2, "hot standby feedback xmin %u epoch %u, catalog_xmin %u epoch %u reply_time %s",
+ elog(DEBUG2, "hot standby feedback xmin " XID_FMT ", catalog_xmin " XID_FMT " reply_time %s",
feedbackXmin,
- feedbackEpoch,
feedbackCatalogXmin,
- feedbackCatalogEpoch,
replyTimeStr);
pfree(replyTimeStr);
@@ -2239,18 +2194,6 @@ ProcessStandbyHSFeedbackMessage(void)
return;
}
- /*
- * Check that the provided xmin/epoch are sane, that is, not in the future
- * and not so far back as to be already wrapped around. Ignore if not.
- */
- if (TransactionIdIsNormal(feedbackXmin) &&
- !TransactionIdInRecentPast(feedbackXmin, feedbackEpoch))
- return;
-
- if (TransactionIdIsNormal(feedbackCatalogXmin) &&
- !TransactionIdInRecentPast(feedbackCatalogXmin, feedbackCatalogEpoch))
- return;
-
/*
* Set the WalSender's xmin equal to the standby's requested xmin, so that
* the xmin will be taken into account by GetSnapshotData() /
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index 87fe82ed114..793217f3bef 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -2431,6 +2431,7 @@ statext_expressions_load(Oid stxoid, bool inh, int idx)
ItemPointerSetInvalid(&(tmptup.t_self));
tmptup.t_tableOid = InvalidOid;
tmptup.t_data = td;
+ HeapTupleSetZeroBase(&tmptup);
tup = heap_copytuple(&tmptup);
diff --git a/src/backend/storage/buffer/Makefile b/src/backend/storage/buffer/Makefile
index fd7c40dcb08..ffcc0fc290e 100644
--- a/src/backend/storage/buffer/Makefile
+++ b/src/backend/storage/buffer/Makefile
@@ -17,6 +17,7 @@ OBJS = \
buf_table.o \
bufmgr.o \
freelist.o \
- localbuf.o
+ localbuf.o \
+ heap_convert.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index a2512e750c2..e30875422b0 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -457,7 +457,7 @@ ForgetPrivateRefCountEntry(PrivateRefCountEntry *ref)
)
-static Buffer ReadBuffer_common(SMgrRelation reln, char relpersistence,
+static Buffer ReadBuffer_common(Relation rel, SMgrRelation reln, char relpersistence,
ForkNumber forkNum, BlockNumber blockNum,
ReadBufferMode mode, BufferAccessStrategy strategy,
bool *hit);
@@ -759,7 +759,8 @@ ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum,
* miss.
*/
pgstat_count_buffer_read(reln);
- buf = ReadBuffer_common(RelationGetSmgr(reln), reln->rd_rel->relpersistence,
+ buf = ReadBuffer_common(reln, RelationGetSmgr(reln),
+ reln->rd_rel->relpersistence,
forkNum, blockNum, mode, strategy, &hit);
if (hit)
pgstat_count_buffer_hit(reln);
@@ -787,7 +788,7 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
Assert(InRecovery);
- return ReadBuffer_common(smgr, RELPERSISTENCE_PERMANENT, forkNum, blockNum,
+ return ReadBuffer_common(NULL, smgr, RELPERSISTENCE_PERMANENT, forkNum, blockNum,
mode, strategy, &hit);
}
@@ -798,7 +799,7 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
* *hit is set to true if the request was satisfied from shared buffer cache.
*/
static Buffer
-ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
+ReadBuffer_common(Relation rel, SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
BlockNumber blockNum, ReadBufferMode mode,
BufferAccessStrategy strategy, bool *hit)
{
@@ -1030,6 +1031,16 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
blockNum,
relpath(smgr->smgr_rnode, forkNum))));
}
+
+ if (PageGetPageLayoutVersion(bufBlock) != PG_PAGE_LAYOUT_VERSION &&
+ !PageIsNew((Page) bufBlock))
+ {
+ Buffer buf = BufferDescriptorGetBuffer(bufHdr);
+
+ LWLockAcquire(BufferDescriptorGetContentLock(bufHdr), LW_EXCLUSIVE);
+ convert_page(rel, bufBlock, buf, blockNum);
+ LWLockRelease(BufferDescriptorGetContentLock(bufHdr));
+ }
}
}
diff --git a/src/backend/storage/buffer/heap_convert.c b/src/backend/storage/buffer/heap_convert.c
new file mode 100644
index 00000000000..6c9c5456b04
--- /dev/null
+++ b/src/backend/storage/buffer/heap_convert.c
@@ -0,0 +1,496 @@
+/*
+ * heap_convert.c
+ * Heap page converter from 32bit to 64bit xid format
+ *
+ * Copyright (c) 2017, Postgres Professional
+ *
+ * src/backend/storage/buffer/heap_convert.c
+ */
+
+#include "postgres.h"
+
+#include "access/brin_page.h"
+#include "access/ginblock.h"
+#include "access/generic_xlog.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/nbtree.h"
+#include "access/transam.h"
+#include "access/xact.h"
+#include "catalog/pg_am.h"
+#include "catalog/pg_control.h"
+#include "common/controldata_utils.h"
+#include "miscadmin.h"
+#include "storage/bufmgr.h"
+#include "storage/bufpage.h"
+#include "storage/checksum.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+
+/*
+ * Sorting support for repack_tuples()
+ */
+typedef struct itemData
+{
+ uint16 index; /* linpointer array index */
+ int16 offset; /* page offset of item data */
+ uint16 alignedlen; /* MAXALIGN(item data len) */
+} itemData;
+
+/* Initialize special heap page area */
+static void
+InitHeapPageSpecial(PageHeader new, TransactionId xid_base,
+ MultiXactId multi_base, TransactionId prune_xid)
+{
+ HeapPageSpecial special;
+
+ new->pd_special = BLCKSZ - MAXALIGN(sizeof(HeapPageSpecialData));
+
+ special = (HeapPageSpecial) ((char *) new + new->pd_special);
+ special->pd_xid_base = xid_base;
+ special->pd_multi_base = multi_base;
+ HeapPageSetPruneXid(new, prune_xid);
+}
+
+/* Used by get_previous_edition() */
+ControlFileData *ControlFile = NULL;
+
+static bool was_32bit_xid(PageHeader page);
+static void convert_heap(Relation rel, Page page, Buffer buf, BlockNumber blkno);
+static void repack_heap_tuples(Relation rel, Page page, Buffer buf,
+ BlockNumber blkno, bool double_xmax);
+static void tuple_set_double_xmax(HeapTupleHeader tuple);
+
+
+/*
+ * itemoffcompare
+ * Sorting support for repack_tuples()
+ */
+int
+itemoffcompare(const void *item1, const void *item2)
+{
+ /* Sort in decreasing itemoff order */
+ return ((itemIdCompactData *) item2)->itemoff -
+ ((itemIdCompactData *) item1)->itemoff;
+}
+
+static bool
+was_32bit_xid(PageHeader page)
+{
+ return PageGetPageLayoutVersion(page) < 5;
+}
+
+void
+convert_page(Relation rel, Page page, Buffer buf, BlockNumber blkno)
+{
+ PageHeader hdr = (PageHeader) page;
+ GenericXLogState *state = NULL;
+ Page tmp_page = page;
+ uint16 checksum;
+
+ if (!rel)
+ return;
+
+ /* Verify checksum */
+ if (hdr->pd_checksum)
+ {
+ checksum = pg_checksum_page((char *) page, blkno);
+ if (checksum != hdr->pd_checksum)
+ ereport(ERROR,
+ (errcode(ERRCODE_INDEX_CORRUPTED),
+ errmsg("page verification failed, calculated checksum %u but expected %u",
+ checksum, hdr->pd_checksum)));
+ }
+
+ /* Start xlog record */
+ if (!XactReadOnly && XLogIsNeeded() && RelationNeedsWAL(rel))
+ {
+ state = GenericXLogStart(rel);
+ tmp_page = GenericXLogRegisterBuffer(state, buf, GENERIC_XLOG_FULL_IMAGE);
+ }
+
+ PageSetPageSizeAndVersion((hdr), PageGetPageSize(hdr),
+ PG_PAGE_LAYOUT_VERSION);
+
+ if (was_32bit_xid(hdr))
+ {
+ switch (rel->rd_rel->relkind)
+ {
+ case 'r':
+ case 'p':
+ case 't':
+ case 'm':
+ convert_heap(rel, tmp_page, buf, blkno);
+ break;
+ case 'i':
+ /* no need to convert index */
+ case 'S':
+ /* no real need to convert sequences */
+ break;
+ default:
+ elog(ERROR,
+ "Conversion for relkind '%c' is not implemented",
+ rel->rd_rel->relkind);
+ }
+ }
+
+ /*
+ * Mark buffer dirty unless this is a read-only transaction (e.g. query
+ * is running on hot standby instance)
+ */
+ if (!XactReadOnly)
+ {
+ /* Finish xlog record */
+ if (XLogIsNeeded() && RelationNeedsWAL(rel))
+ {
+ Assert(state != NULL);
+ GenericXLogFinish(state);
+ }
+
+ MarkBufferDirty(buf);
+ }
+
+ hdr = (PageHeader) page;
+ hdr->pd_checksum = pg_checksum_page((char *) page, blkno);
+}
+
+static void
+convert_heap(Relation rel, Page page, Buffer buf, BlockNumber blkno)
+{
+ PageHeader page_hdr = (PageHeader) page;
+ bool heap_special_fits;
+
+ /* Is there enough space to fit new page format? */
+ heap_special_fits = page_hdr->pd_upper - page_hdr->pd_lower >= SizeOfPageSpecial;
+ repack_heap_tuples(rel, page, buf, blkno, !heap_special_fits);
+}
+
+/*
+ * Convert possibly wrapped around heap tuple's transaction and
+ * multixact IDs after pg_upgrade.
+ */
+static void
+convert_heap_tuple_xids(HeapTupleHeader tuple, TransactionId xid_base,
+ MultiXactId mxid_base, bool double_xmax)
+{
+ TransactionId xid;
+
+ /* Convert xmin xid */
+ if (double_xmax)
+ tuple_set_double_xmax(tuple);
+ else
+ {
+ /* Subtract xid_base from normal xmin */
+ xid = tuple->t_choice.t_heap.t_xmin;
+
+ if (TransactionIdIsNormal(xid))
+ {
+ Assert(xid >= xid_base + FirstNormalTransactionId);
+ tuple->t_choice.t_heap.t_xmin = xid - xid_base;
+ }
+ }
+
+ /* If tuple has multixact flag, handle mxid wraparound */
+ if ((tuple->t_infomask & HEAP_XMAX_IS_MULTI) &&
+ !(tuple->t_infomask & HEAP_XMAX_INVALID))
+ {
+ MultiXactId mxid = tuple->t_choice.t_heap.t_xmax;
+
+ /* Handle mxid wraparound */
+ if (mxid < mxid_base)
+ {
+ mxid += ((MultiXactId) 1 << 32) - FirstMultiXactId;
+ Assert(mxid >= mxid_base);
+ }
+
+ if (double_xmax)
+ {
+ /* Save converted mxid into xmin/max */
+ HeapTupleHeaderSetDoubleXmax(tuple, mxid);
+ }
+ else
+ {
+ /*
+ * Save converted mxid offset relative to (minmxid - 1),
+ * which will be page's mxid base.
+ */
+ Assert(mxid - mxid_base + FirstMultiXactId <= PG_UINT32_MAX);
+ tuple->t_choice.t_heap.t_xmax =
+ (uint32) (mxid - mxid_base + FirstMultiXactId);
+ }
+ }
+ /* Convert xmax xid */
+ else if (!(tuple->t_infomask & HEAP_XMAX_INVALID))
+ {
+ xid = tuple->t_choice.t_heap.t_xmax;
+
+ if (double_xmax)
+ {
+ /* Save converted mxid into xmin/max */
+ HeapTupleHeaderSetDoubleXmax(tuple, xid);
+ }
+ else if (TransactionIdIsNormal(xid))
+ {
+ /* Subtract xid_base from normal xmax */
+ Assert(xid >= xid_base + FirstNormalTransactionId);
+ tuple->t_choice.t_heap.t_xmax = xid - xid_base;
+ }
+ }
+}
+
+/*
+ * Compute page's [m]xid min/max values for based/"double xmax"
+ * format conversions
+ */
+static void
+compute_xid_min_max(HeapTuple tuple, MultiXactId mxid_base,
+ TransactionId *xid_min, TransactionId *xid_max,
+ MultiXactId *mxid_min, MultiXactId *mxid_max)
+{
+ if (!HeapTupleHeaderXminInvalid(tuple->t_data) &&
+ !HeapTupleHeaderXminFrozen(tuple->t_data))
+ {
+ TransactionId xid = HeapTupleGetRawXmin(tuple);
+
+ if (TransactionIdIsNormal(xid))
+ {
+ if (*xid_max < xid)
+ *xid_max = xid;
+ if (*xid_min > xid)
+ *xid_min = xid;
+ }
+ }
+
+ if (!(tuple->t_data->t_infomask & HEAP_XMAX_INVALID))
+ {
+ TransactionId xid;
+
+ if (tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)
+ {
+ MultiXactId mxid = HeapTupleGetRawXmax(tuple);
+
+ Assert(MultiXactIdIsValid(mxid));
+
+ /* Handle mxid wraparound */
+ if (mxid < mxid_base)
+ {
+ mxid += ((MultiXactId) 1 << 32) - FirstMultiXactId;
+ Assert(mxid >= mxid_base);
+ }
+
+ if (*mxid_max < mxid)
+ *mxid_max = mxid;
+ if (*mxid_min > mxid)
+ *mxid_min = mxid;
+
+ /*
+ * Also take into account hidden update xid, which can be
+ * extracted by the vacuum.
+ */
+ if (tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY)
+ xid = InvalidTransactionId;
+ else
+ xid = HeapTupleGetUpdateXid(tuple);
+ }
+ else
+ {
+ xid = HeapTupleGetRawXmax(tuple);
+ }
+
+ if (TransactionIdIsNormal(xid))
+ {
+ if (*xid_max < xid)
+ *xid_max = xid;
+ if (*xid_min > xid)
+ *xid_min = xid;
+ }
+ }
+}
+
+/* Returns true, if "double xmax" format */
+static bool
+init_heap_page_header(Relation rel, BlockNumber blkno, PageHeader new_hdr,
+ TransactionId prune_xid, bool header_fits,
+ TransactionId xid_min, TransactionId xid_max,
+ MultiXactId mxid_min, MultiXactId mxid_max,
+ TransactionId *xid_base, MultiXactId *mxid_base)
+{
+ if (header_fits &&
+ (xid_max == InvalidTransactionId ||
+ xid_max - xid_min <= MaxShortTransactionId - FirstNormalTransactionId) &&
+ (mxid_max == InvalidMultiXactId ||
+ mxid_max - mxid_min <= MaxShortTransactionId - FirstMultiXactId))
+ {
+ Assert(xid_max == InvalidTransactionId || xid_max >= xid_min);
+ Assert(mxid_max == InvalidMultiXactId || mxid_max >= mxid_min);
+ *xid_base = xid_max == InvalidTransactionId ? InvalidTransactionId : xid_min - FirstNormalTransactionId;
+ *mxid_base = mxid_max == InvalidMultiXactId ? InvalidMultiXactId : mxid_min - FirstMultiXactId;
+
+ InitHeapPageSpecial(new_hdr, *xid_base, *mxid_base, prune_xid);
+ return false;
+ }
+ else
+ {
+ /* No space for special area, switch to "double xmax" format */
+ new_hdr->pd_special = BLCKSZ;
+
+ *xid_base = InvalidTransactionId;
+ *mxid_base = InvalidMultiXactId;
+
+ elog(DEBUG2, "convert heap page %u of relation %u to double xmax format",
+ blkno, RelationGetRelid(rel));
+ return true;
+ }
+}
+
+/*
+ * repack_heap_tuples
+ * Convert heap page format reusing space of dead tuples
+ */
+static void
+repack_heap_tuples(Relation rel, Page page, Buffer buf, BlockNumber blkno,
+ bool perhaps_double_xmax)
+{
+ itemIdCompactData items[MaxHeapTuplesPerPage];
+ itemIdCompact itemPtr = items;
+ ItemId lp;
+ int nitems = 0;
+ int maxoff = PageGetMaxOffsetNumber(page);
+ Offset upper;
+ int idx;
+ bool double_xmax;
+
+ PageHeader hdr;
+ PageHeader new_hdr;
+ char new_page[BLCKSZ];
+ MultiXactId mxid_base = rel->rd_rel->relminmxid;
+ MultiXactId mxid_min = MaxMultiXactId;
+ MultiXactId mxid_max = InvalidMultiXactId;
+ TransactionId xid_base = rel->rd_rel->relfrozenxid;
+ TransactionId xid_min = MaxTransactionId;
+ TransactionId xid_max = InvalidTransactionId;
+
+ int occupied_space = 0;
+
+ hdr = (PageHeader) page;
+
+ if (TransactionIdIsNormal(hdr->pd_prune_xid))
+ xid_min = xid_max = hdr->pd_prune_xid;
+
+ for (idx = 0; idx < maxoff; idx++)
+ {
+ HeapTupleData tuple;
+
+ lp = PageGetItemId(page, idx + 1);
+
+ /* Skip redirects and items without storage */
+ if (!ItemIdHasStorage(lp))
+ continue;
+
+ /* Build in-memory tuple representation */
+ tuple.t_tableOid = 1; /* doesn't matter in this case */
+ tuple.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+ tuple.t_xid_base = 0;
+ tuple.t_multi_base = 0;
+ tuple.t_len = ItemIdGetLength(lp);
+ ItemPointerSet(&(tuple.t_self), blkno, ItemIdGetOffset(lp));
+
+ /*
+ * This is only needed to determine whether tuple is HEAPTUPLE_DEAD or
+ * HEAPTUPLE_RECENTLY_DEAD. And since this is the first time we read
+ * page after pg_upgrade, it cannot be HEAPTUPLE_RECENTLY_DEAD. See
+ * HeapTupleSatisfiesVacuum() for details
+ */
+ if (perhaps_double_xmax &&
+ HeapTupleSatisfiesVacuum(&tuple, FirstUpgradedTransactionId, buf) == HEAPTUPLE_DEAD)
+ {
+ ItemIdSetDead(lp);
+ }
+
+ if (ItemIdIsNormal(lp) && ItemIdHasStorage(lp))
+ {
+ itemPtr->offsetindex = idx;
+ itemPtr->itemoff = ItemIdGetOffset(lp);
+ if (unlikely(itemPtr->itemoff < hdr->pd_upper ||
+ itemPtr->itemoff >= hdr->pd_special))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_CORRUPTED),
+ errmsg("corrupted item pointer: %u",
+ itemPtr->itemoff)));
+ itemPtr->alignedlen = MAXALIGN(ItemIdGetLength(lp));
+ occupied_space += itemPtr->alignedlen;
+ nitems++;
+ itemPtr++;
+ if (perhaps_double_xmax)
+ {
+ HeapTupleSetXmin(&tuple, FrozenTransactionId);
+ HeapTupleHeaderSetXminFrozen(tuple.t_data);
+ }
+
+ compute_xid_min_max(&tuple, mxid_base,
+ &xid_min, &xid_max,
+ &mxid_min, &mxid_max);
+ }
+ }
+
+ /* Write new header */
+ new_hdr = (PageHeader) new_page;
+ *new_hdr = *hdr;
+ new_hdr->pd_lower = SizeOfPageHeaderData + maxoff * sizeof(ItemIdData);
+
+ double_xmax = init_heap_page_header(rel, blkno, new_hdr,
+ hdr->pd_prune_xid,
+ BLCKSZ - new_hdr->pd_lower - occupied_space >= sizeof(HeapPageSpecialData),
+ xid_min, xid_max,
+ mxid_min, mxid_max,
+ &xid_base, &mxid_base);
+ if (!perhaps_double_xmax && double_xmax)
+ return repack_heap_tuples(rel, page, buf, blkno, true);
+
+ /* Copy ItemIds with an offset */
+ memcpy(new_page + SizeOfPageHeaderData,
+ page + SizeOfPageHeaderData,
+ hdr->pd_lower - SizeOfPageHeaderData);
+
+ /* Move live tuples */
+ upper = new_hdr->pd_special;
+ for (idx = 0; idx < nitems; idx++)
+ {
+ HeapTupleHeader tuple;
+
+ itemPtr = &items[idx];
+ lp = PageGetItemId(new_page, itemPtr->offsetindex + 1);
+ upper -= itemPtr->alignedlen;
+
+ memcpy((char *) new_page + upper,
+ (char *) page + itemPtr->itemoff,
+ itemPtr->alignedlen);
+
+ tuple = (HeapTupleHeader) (((char *) new_page) + upper);
+
+ convert_heap_tuple_xids(tuple, xid_base, mxid_base, double_xmax);
+
+ lp->lp_off = upper;
+
+ occupied_space -= itemPtr->alignedlen;
+ }
+ Assert(occupied_space == 0);
+
+ new_hdr->pd_upper = upper;
+ if (new_hdr->pd_lower > new_hdr->pd_upper)
+ elog(ERROR, "cannot convert block %u of relation '%s'",
+ blkno, RelationGetRelationName(rel));
+
+ memcpy(page, new_page, BLCKSZ);
+}
+
+/*
+ * Convert tuple for "double xmax" page format.
+ */
+static void
+tuple_set_double_xmax(HeapTupleHeader tuple)
+{
+ tuple->t_infomask |= HEAP_XMIN_FROZEN;
+ tuple->t_choice.t_heap.t_xmin = 0;
+}
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 3be60402890..5d53c77497b 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -66,7 +66,7 @@
#include "utils/rel.h"
#include "utils/snapmgr.h"
-#define UINT32_ACCESS_ONCE(var) ((uint32)(*((volatile uint32 *)&(var))))
+#define UINT64_ACCESS_ONCE(var) ((uint64)(*((volatile uint64 *)&(var))))
/* Our shared memory area */
typedef struct ProcArrayStruct
@@ -351,9 +351,6 @@ static inline void ProcArrayEndTransactionInternal(PGPROC *proc, TransactionId l
static void ProcArrayGroupClearXid(PGPROC *proc, TransactionId latestXid);
static void MaintainLatestCompletedXid(TransactionId latestXid);
static void MaintainLatestCompletedXidRecovery(TransactionId latestXid);
-
-static inline FullTransactionId FullXidRelativeTo(FullTransactionId rel,
- TransactionId xid);
static void GlobalVisUpdateApply(ComputeXidHorizonsResult *horizons);
/*
@@ -953,10 +950,7 @@ MaintainLatestCompletedXid(TransactionId latestXid)
Assert(LWLockHeldByMe(ProcArrayLock));
if (TransactionIdPrecedes(XidFromFullTransactionId(cur_latest), latestXid))
- {
- ShmemVariableCache->latestCompletedXid =
- FullXidRelativeTo(cur_latest, latestXid);
- }
+ ShmemVariableCache->latestCompletedXid = FullTransactionIdFromXid(latestXid);
Assert(IsBootstrapProcessingMode() ||
FullTransactionIdIsNormal(ShmemVariableCache->latestCompletedXid));
@@ -969,7 +963,6 @@ static void
MaintainLatestCompletedXidRecovery(TransactionId latestXid)
{
FullTransactionId cur_latest = ShmemVariableCache->latestCompletedXid;
- FullTransactionId rel;
Assert(AmStartupProcess() || !IsUnderPostmaster);
Assert(LWLockHeldByMe(ProcArrayLock));
@@ -979,14 +972,12 @@ MaintainLatestCompletedXidRecovery(TransactionId latestXid)
* latestCompletedXid to be initialized in recovery. But in recovery it's
* safe to access nextXid without a lock for the startup process.
*/
- rel = ShmemVariableCache->nextXid;
Assert(FullTransactionIdIsValid(ShmemVariableCache->nextXid));
if (!FullTransactionIdIsValid(cur_latest) ||
TransactionIdPrecedes(XidFromFullTransactionId(cur_latest), latestXid))
{
- ShmemVariableCache->latestCompletedXid =
- FullXidRelativeTo(rel, latestXid);
+ ShmemVariableCache->latestCompletedXid = FullTransactionIdFromXid(latestXid);
}
Assert(FullTransactionIdIsNormal(ShmemVariableCache->latestCompletedXid));
@@ -1096,7 +1087,7 @@ ProcArrayApplyRecoveryInfo(RunningTransactions running)
else
elog(trace_recovery(DEBUG1),
"recovery snapshot waiting for non-overflowed snapshot or "
- "until oldest active xid on standby is at least %u (now %u)",
+ "until oldest active xid on standby is at least " XID_FMT " (now " XID_FMT ")",
standbySnapshotPendingXmin,
running->oldestRunningXid);
return;
@@ -1177,7 +1168,7 @@ ProcArrayApplyRecoveryInfo(RunningTransactions running)
if (i > 0 && TransactionIdEquals(xids[i - 1], xids[i]))
{
elog(DEBUG1,
- "found duplicated transaction %u for KnownAssignedXids insertion",
+ "found duplicated transaction " XID_FMT " for KnownAssignedXids insertion",
xids[i]);
continue;
}
@@ -1262,7 +1253,7 @@ ProcArrayApplyRecoveryInfo(RunningTransactions running)
else
elog(trace_recovery(DEBUG1),
"recovery snapshot waiting for non-overflowed snapshot or "
- "until oldest active xid on standby is at least %u (now %u)",
+ "until oldest active xid on standby is at least " XID_FMT " (now " XID_FMT ")",
standbySnapshotPendingXmin,
running->oldestRunningXid);
}
@@ -1455,7 +1446,7 @@ TransactionIdIsInProgress(TransactionId xid)
continue;
/* Fetch xid just once - see GetNewTransactionId */
- pxid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
+ pxid = UINT64_ACCESS_ONCE(other_xids[pgxactoff]);
if (!TransactionIdIsValid(pxid))
continue;
@@ -1487,7 +1478,7 @@ TransactionIdIsInProgress(TransactionId xid)
for (j = pxids - 1; j >= 0; j--)
{
/* Fetch xid just once - see GetNewTransactionId */
- TransactionId cxid = UINT32_ACCESS_ONCE(proc->subxids.xids[j]);
+ TransactionId cxid = UINT64_ACCESS_ONCE(proc->subxids.xids[j]);
if (TransactionIdEquals(cxid, xid))
{
@@ -1611,7 +1602,7 @@ TransactionIdIsActive(TransactionId xid)
TransactionId pxid;
/* Fetch xid just once - see GetNewTransactionId */
- pxid = UINT32_ACCESS_ONCE(other_xids[i]);
+ pxid = UINT64_ACCESS_ONCE(other_xids[i]);
if (!TransactionIdIsValid(pxid))
continue;
@@ -1762,8 +1753,8 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
TransactionId xmin;
/* Fetch xid just once - see GetNewTransactionId */
- xid = UINT32_ACCESS_ONCE(other_xids[index]);
- xmin = UINT32_ACCESS_ONCE(proc->xmin);
+ xid = UINT64_ACCESS_ONCE(other_xids[index]);
+ xmin = UINT64_ACCESS_ONCE(proc->xmin);
/*
* Consider both the transaction's Xmin, and its Xid.
@@ -2304,7 +2295,7 @@ GetSnapshotData(Snapshot snapshot)
for (int pgxactoff = 0; pgxactoff < numProcs; pgxactoff++)
{
/* Fetch xid just once - see GetNewTransactionId */
- TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
+ TransactionId xid = UINT64_ACCESS_ONCE(other_xids[pgxactoff]);
uint8 statusFlags;
Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
@@ -2454,12 +2445,7 @@ GetSnapshotData(Snapshot snapshot)
FullTransactionId def_vis_fxid_data;
FullTransactionId oldestfxid;
- /*
- * Converting oldestXid is only safe when xid horizon cannot advance,
- * i.e. holding locks. While we don't hold the lock anymore, all the
- * necessary data has been gathered with lock held.
- */
- oldestfxid = FullXidRelativeTo(latest_completed, oldestxid);
+ oldestfxid = FullTransactionIdFromXid(oldestxid);
/* apply vacuum_defer_cleanup_age */
def_vis_xid_data =
@@ -2482,8 +2468,8 @@ GetSnapshotData(Snapshot snapshot)
def_vis_xid =
TransactionIdOlder(replication_slot_catalog_xmin, def_vis_xid);
- def_vis_fxid = FullXidRelativeTo(latest_completed, def_vis_xid);
- def_vis_fxid_data = FullXidRelativeTo(latest_completed, def_vis_xid_data);
+ def_vis_fxid = FullTransactionIdFromXid(def_vis_xid);
+ def_vis_fxid_data = FullTransactionIdFromXid(def_vis_xid_data);
/*
* Check if we can increase upper bound. As a previous
@@ -2502,7 +2488,7 @@ GetSnapshotData(Snapshot snapshot)
/* See temp_oldest_nonremovable computation in ComputeXidHorizons() */
if (TransactionIdIsNormal(myxid))
GlobalVisTempRels.definitely_needed =
- FullXidRelativeTo(latest_completed, myxid);
+ FullTransactionIdFromXid(myxid);
else
{
GlobalVisTempRels.definitely_needed = latest_completed;
@@ -2609,7 +2595,7 @@ ProcArrayInstallImportedXmin(TransactionId xmin,
/*
* Likewise, let's just make real sure its xmin does cover us.
*/
- xid = UINT32_ACCESS_ONCE(proc->xmin);
+ xid = UINT64_ACCESS_ONCE(proc->xmin);
if (!TransactionIdIsNormal(xid) ||
!TransactionIdPrecedesOrEquals(xid, xmin))
continue;
@@ -2664,7 +2650,7 @@ ProcArrayInstallRestoredXmin(TransactionId xmin, PGPROC *proc)
* can't go backwards. Also, make sure it's running in the same database,
* so that the per-database xmin cannot go backwards.
*/
- xid = UINT32_ACCESS_ONCE(proc->xmin);
+ xid = UINT64_ACCESS_ONCE(proc->xmin);
if (proc->databaseId == MyDatabaseId &&
TransactionIdIsNormal(xid) &&
TransactionIdPrecedesOrEquals(xid, xmin))
@@ -2782,7 +2768,7 @@ GetRunningTransactionData(void)
TransactionId xid;
/* Fetch xid just once - see GetNewTransactionId */
- xid = UINT32_ACCESS_ONCE(other_xids[index]);
+ xid = UINT64_ACCESS_ONCE(other_xids[index]);
/*
* We don't need to store transactions that don't have a TransactionId
@@ -2921,7 +2907,7 @@ GetOldestActiveTransactionId(void)
TransactionId xid;
/* Fetch xid just once - see GetNewTransactionId */
- xid = UINT32_ACCESS_ONCE(other_xids[index]);
+ xid = UINT64_ACCESS_ONCE(other_xids[index]);
if (!TransactionIdIsNormal(xid))
continue;
@@ -3019,7 +3005,7 @@ GetOldestSafeDecodingTransactionId(bool catalogOnly)
TransactionId xid;
/* Fetch xid just once - see GetNewTransactionId */
- xid = UINT32_ACCESS_ONCE(other_xids[index]);
+ xid = UINT64_ACCESS_ONCE(other_xids[index]);
if (!TransactionIdIsNormal(xid))
continue;
@@ -3301,7 +3287,7 @@ GetCurrentVirtualXIDs(TransactionId limitXmin, bool excludeXmin0,
if (allDbs || proc->databaseId == MyDatabaseId)
{
/* Fetch xmin just once - might change on us */
- TransactionId pxmin = UINT32_ACCESS_ONCE(proc->xmin);
+ TransactionId pxmin = UINT64_ACCESS_ONCE(proc->xmin);
if (excludeXmin0 && !TransactionIdIsValid(pxmin))
continue;
@@ -3396,7 +3382,7 @@ GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid)
proc->databaseId == dbOid)
{
/* Fetch xmin just once - can't change on us, but good coding */
- TransactionId pxmin = UINT32_ACCESS_ONCE(proc->xmin);
+ TransactionId pxmin = UINT64_ACCESS_ONCE(proc->xmin);
/*
* We ignore an invalid pxmin because this means that backend has
@@ -3978,7 +3964,7 @@ XidCacheRemoveRunningXids(TransactionId xid,
* debug warning.
*/
if (j < 0 && !MyProc->subxidStatus.overflowed)
- elog(WARNING, "did not find subXID %u in MyProc", anxid);
+ elog(WARNING, "did not find subXID " XID_FMT " in MyProc", anxid);
}
for (j = MyProc->subxidStatus.count - 1; j >= 0; j--)
@@ -3994,7 +3980,7 @@ XidCacheRemoveRunningXids(TransactionId xid,
}
/* Ordinarily we should have found it, unless the cache has overflowed */
if (j < 0 && !MyProc->subxidStatus.overflowed)
- elog(WARNING, "did not find subXID %u in MyProc", xid);
+ elog(WARNING, "did not find subXID " XID_FMT " in MyProc", xid);
/* Also advance global latestCompletedXid while holding the lock */
MaintainLatestCompletedXid(latestXid);
@@ -4101,17 +4087,13 @@ static void
GlobalVisUpdateApply(ComputeXidHorizonsResult *horizons)
{
GlobalVisSharedRels.maybe_needed =
- FullXidRelativeTo(horizons->latest_completed,
- horizons->shared_oldest_nonremovable);
+ FullTransactionIdFromXid(horizons->shared_oldest_nonremovable);
GlobalVisCatalogRels.maybe_needed =
- FullXidRelativeTo(horizons->latest_completed,
- horizons->catalog_oldest_nonremovable);
+ FullTransactionIdFromXid(horizons->catalog_oldest_nonremovable);
GlobalVisDataRels.maybe_needed =
- FullXidRelativeTo(horizons->latest_completed,
- horizons->data_oldest_nonremovable);
+ FullTransactionIdFromXid(horizons->data_oldest_nonremovable);
GlobalVisTempRels.maybe_needed =
- FullXidRelativeTo(horizons->latest_completed,
- horizons->temp_oldest_nonremovable);
+ FullTransactionIdFromXid(horizons->temp_oldest_nonremovable);
/*
* In longer running transactions it's possible that transactions we
@@ -4200,15 +4182,7 @@ GlobalVisTestIsRemovableXid(GlobalVisState *state, TransactionId xid)
{
FullTransactionId fxid;
- /*
- * Convert 32 bit argument to FullTransactionId. We can do so safely
- * because we know the xid has to, at the very least, be between
- * [oldestXid, nextFullXid), i.e. within 2 billion of xid. To avoid taking
- * a lock to determine either, we can just compare with
- * state->definitely_needed, which was based on those value at the time
- * the current snapshot was built.
- */
- fxid = FullXidRelativeTo(state->definitely_needed, xid);
+ fxid = FullTransactionIdFromXid(xid);
return GlobalVisTestIsRemovableFullXid(state, fxid);
}
@@ -4271,32 +4245,6 @@ GlobalVisCheckRemovableXid(Relation rel, TransactionId xid)
return GlobalVisTestIsRemovableXid(state, xid);
}
-/*
- * Convert a 32 bit transaction id into 64 bit transaction id, by assuming it
- * is within MaxTransactionId / 2 of XidFromFullTransactionId(rel).
- *
- * Be very careful about when to use this function. It can only safely be used
- * when there is a guarantee that xid is within MaxTransactionId / 2 xids of
- * rel. That e.g. can be guaranteed if the caller assures a snapshot is
- * held by the backend and xid is from a table (where vacuum/freezing ensures
- * the xid has to be within that range), or if xid is from the procarray and
- * prevents xid wraparound that way.
- */
-static inline FullTransactionId
-FullXidRelativeTo(FullTransactionId rel, TransactionId xid)
-{
- TransactionId rel_xid = XidFromFullTransactionId(rel);
-
- Assert(TransactionIdIsValid(xid));
- Assert(TransactionIdIsValid(rel_xid));
-
- /* not guaranteed to find issues, but likely to catch mistakes */
- AssertTransactionIdInAllowableRange(xid);
-
- return FullTransactionIdFromU64(U64FromFullTransactionId(rel)
- + (int32) (xid - rel_xid));
-}
-
/* ----------------------------------------------
* KnownAssignedTransactionIds sub-module
@@ -4371,7 +4319,7 @@ RecordKnownAssignedTransactionIds(TransactionId xid)
Assert(TransactionIdIsValid(xid));
Assert(TransactionIdIsValid(latestObservedXid));
- elog(trace_recovery(DEBUG4), "record known xact %u latestObservedXid %u",
+ elog(trace_recovery(DEBUG4), "record known xact " XID_FMT " latestObservedXid " XID_FMT,
xid, latestObservedXid);
/*
@@ -4882,7 +4830,7 @@ KnownAssignedXidsRemove(TransactionId xid)
{
Assert(TransactionIdIsValid(xid));
- elog(trace_recovery(DEBUG4), "remove KnownAssignedXid %u", xid);
+ elog(trace_recovery(DEBUG4), "remove KnownAssignedXid " XID_FMT, xid);
/*
* Note: we cannot consider it an error to remove an XID that's not
@@ -4942,7 +4890,7 @@ KnownAssignedXidsRemovePreceding(TransactionId removeXid)
return;
}
- elog(trace_recovery(DEBUG4), "prune KnownAssignedXids to %u", removeXid);
+ elog(trace_recovery(DEBUG4), "prune KnownAssignedXids to " XID_FMT, removeXid);
/*
* Mark entries invalid starting at the tail. Since array is sorted, we
@@ -5130,7 +5078,7 @@ KnownAssignedXidsDisplay(int trace_level)
if (KnownAssignedXidsValid[i])
{
nxids++;
- appendStringInfo(&buf, "[%d]=%u ", i, KnownAssignedXids[i]);
+ appendStringInfo(&buf, "[%d]=" XID_FMT " ", i, KnownAssignedXids[i]);
}
}
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index 87ac0f74b27..123f0e2c2c5 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -36,7 +36,7 @@
#include "utils/timestamp.h"
/* User-settable GUC parameters */
-int vacuum_defer_cleanup_age;
+int64 vacuum_defer_cleanup_age;
int max_standby_archive_delay = 30 * 1000;
int max_standby_streaming_delay = 30 * 1000;
bool log_recovery_conflict_waits = false;
@@ -994,13 +994,13 @@ StandbyReleaseLockList(List *locks)
LOCKTAG locktag;
elog(trace_recovery(DEBUG4),
- "releasing recovery lock: xid %u db %u rel %u",
+ "releasing recovery lock: xid " XID_FMT " db %u rel %u",
lock->xid, lock->dbOid, lock->relOid);
SET_LOCKTAG_RELATION(locktag, lock->dbOid, lock->relOid);
if (!LockRelease(&locktag, AccessExclusiveLock, true))
{
elog(LOG,
- "RecoveryLockLists contains entry for lock no longer recorded by lock manager: xid %u database %u relation %u",
+ "RecoveryLockLists contains entry for lock no longer recorded by lock manager: xid " XID_FMT " database %u relation %u",
lock->xid, lock->dbOid, lock->relOid);
Assert(false);
}
@@ -1305,7 +1305,7 @@ LogCurrentRunningXacts(RunningTransactions CurrRunningXacts)
if (CurrRunningXacts->subxid_overflow)
elog(trace_recovery(DEBUG2),
- "snapshot of %u running transactions overflowed (lsn %X/%X oldest xid %u latest complete %u next xid %u)",
+ "snapshot of %u running transactions overflowed (lsn %X/%X oldest xid " XID_FMT " latest complete " XID_FMT " next xid " XID_FMT ")",
CurrRunningXacts->xcnt,
LSN_FORMAT_ARGS(recptr),
CurrRunningXacts->oldestRunningXid,
@@ -1313,7 +1313,7 @@ LogCurrentRunningXacts(RunningTransactions CurrRunningXacts)
CurrRunningXacts->nextXid);
else
elog(trace_recovery(DEBUG2),
- "snapshot of %u+%u running transaction ids (lsn %X/%X oldest xid %u latest complete %u next xid %u)",
+ "snapshot of %u+%u running transaction ids (lsn %X/%X oldest xid " XID_FMT " latest complete " XID_FMT " next xid " XID_FMT ")",
CurrRunningXacts->xcnt, CurrRunningXacts->subxcnt,
LSN_FORMAT_ARGS(recptr),
CurrRunningXacts->oldestRunningXid,
diff --git a/src/backend/storage/lmgr/lmgr.c b/src/backend/storage/lmgr/lmgr.c
index 5ae52dd14db..5246dd3660a 100644
--- a/src/backend/storage/lmgr/lmgr.c
+++ b/src/backend/storage/lmgr/lmgr.c
@@ -1135,10 +1135,17 @@ DescribeLockTag(StringInfo buf, const LOCKTAG *tag)
tag->locktag_field1);
break;
case LOCKTAG_TRANSACTION:
- appendStringInfo(buf,
- _("transaction %u"),
- tag->locktag_field1);
+ {
+ char xid_str[32];
+
+ /* make translatable string */
+ snprintf(xid_str, sizeof(xid_str), XID_FMT,
+ (TransactionId) tag->locktag_field1 |
+ ((TransactionId) tag->locktag_field2 << 32));
+
+ appendStringInfo(buf, _("transaction %s"), xid_str);
break;
+ }
case LOCKTAG_VIRTUALTRANSACTION:
appendStringInfo(buf,
_("virtual transaction %d/%u"),
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index 25e7e4e37bf..b907844e1a2 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -438,7 +438,7 @@ static void SetPossibleUnsafeConflict(SERIALIZABLEXACT *roXact, SERIALIZABLEXACT
static void ReleaseRWConflict(RWConflict conflict);
static void FlagSxactUnsafe(SERIALIZABLEXACT *sxact);
-static bool SerialPagePrecedesLogically(int page1, int page2);
+static bool SerialPagePrecedesLogically(int64 page1, int64 page2);
static void SerialInit(void);
static void SerialAdd(TransactionId xid, SerCommitSeqNo minConflictCommitSeqNo);
static SerCommitSeqNo SerialGetMinConflictCommitSeqNo(TransactionId xid);
@@ -788,27 +788,21 @@ FlagSxactUnsafe(SERIALIZABLEXACT *sxact)
* Analogous to CLOGPagePrecedes().
*/
static bool
-SerialPagePrecedesLogically(int page1, int page2)
+SerialPagePrecedesLogically(int64 page1, int64 page2)
{
- TransactionId xid1;
- TransactionId xid2;
-
- xid1 = ((TransactionId) page1) * SERIAL_ENTRIESPERPAGE;
- xid1 += FirstNormalTransactionId + 1;
- xid2 = ((TransactionId) page2) * SERIAL_ENTRIESPERPAGE;
- xid2 += FirstNormalTransactionId + 1;
-
- return (TransactionIdPrecedes(xid1, xid2) &&
- TransactionIdPrecedes(xid1, xid2 + SERIAL_ENTRIESPERPAGE - 1));
+ return page1 < page2;
}
#ifdef USE_ASSERT_CHECKING
+#define SerialPagePrecedesLogically(p1, p2) ((p1) < (p2))
+
static void
SerialPagePrecedesLogicallyUnitTests(void)
{
+#if 0 /* XXX remove unit tests */
int per_page = SERIAL_ENTRIESPERPAGE,
offset = per_page / 2;
- int newestPage,
+ int64 newestPage,
oldestPage,
headPage,
targetPage;
@@ -856,9 +850,11 @@ SerialPagePrecedesLogicallyUnitTests(void)
#if 0
Assert(SerialPagePrecedesLogically(headPage, targetPage));
#endif
+#endif
}
#endif
+
/*
* Initialize for the tracking of old serializable committed xids.
*/
@@ -4168,7 +4164,7 @@ CheckForSerializableConflictOut(Relation relation, TransactionId xid, Snapshot s
ereport(ERROR,
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
errmsg("could not serialize access due to read/write dependencies among transactions"),
- errdetail_internal("Reason code: Canceled on conflict out to old pivot %u.", xid),
+ errdetail_internal("Reason code: Canceled on conflict out to old pivot " XID_FMT ".", xid),
errhint("The transaction might succeed if retried.")));
if (SxactHasSummaryConflictIn(MySerializableXact)
@@ -4176,7 +4172,7 @@ CheckForSerializableConflictOut(Relation relation, TransactionId xid, Snapshot s
ereport(ERROR,
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
errmsg("could not serialize access due to read/write dependencies among transactions"),
- errdetail_internal("Reason code: Canceled on identification as a pivot, with conflict out to old committed transaction %u.", xid),
+ errdetail_internal("Reason code: Canceled on identification as a pivot, with conflict out to old committed transaction " XID_FMT ".", xid),
errhint("The transaction might succeed if retried.")));
MySerializableXact->flags |= SXACT_FLAG_SUMMARY_CONFLICT_OUT;
@@ -4806,7 +4802,7 @@ OnConflict_CheckForSerializationFailure(const SERIALIZABLEXACT *reader,
ereport(ERROR,
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
errmsg("could not serialize access due to read/write dependencies among transactions"),
- errdetail_internal("Reason code: Canceled on conflict out to pivot %u, during read.", writer->topXid),
+ errdetail_internal("Reason code: Canceled on conflict out to pivot " XID_FMT ", during read.", writer->topXid),
errhint("The transaction might succeed if retried.")));
}
writer->flags |= SXACT_FLAG_DOOMED;
diff --git a/src/backend/storage/page/bufpage.c b/src/backend/storage/page/bufpage.c
index 147ba4d9232..0edfbceb423 100644
--- a/src/backend/storage/page/bufpage.c
+++ b/src/backend/storage/page/bufpage.c
@@ -21,11 +21,26 @@
#include "storage/checksum.h"
#include "utils/memdebug.h"
#include "utils/memutils.h"
+#include "utils/snapmgr.h"
/* GUC variable */
bool ignore_checksum_failure = false;
+/*
+ * HeapPageSpecialData used when pd_special == BLCKSZ. This is special format
+ * used when page with 32-bit xids doesn't fit HeapPageSpecialData. Then
+ * all xmin's are frozen (can do this for all live tuples after pg_upgrade),
+ * while 64-bit xmax is stored in both t_heap.t_xmin and t_heap.t_xmax.
+ * This is so-called "double xmax" format.
+ */
+static HeapPageSpecialData doubleXmaxSpecialData =
+{
+ .pd_xid_base = MaxTransactionId,
+ .pd_multi_base = MaxTransactionId
+};
+HeapPageSpecial doubleXmaxSpecial = &doubleXmaxSpecialData;
+
/* ----------------------------------------------------------------
* Page support functions
@@ -432,15 +447,131 @@ PageRestoreTempPage(Page tempPage, Page oldPage)
}
/*
- * Tuple defrag support for PageRepairFragmentation and PageIndexMultiDelete
+ * Get minimum and maximum values of xid and multixact on "double xmax" page.
*/
-typedef struct itemIdCompactData
+static void
+heap_page_double_xmax_get_min_max(Page page,
+ TransactionId *xid_min,
+ TransactionId *xid_max,
+ MultiXactId *multi_min,
+ MultiXactId *multi_max)
{
- uint16 offsetindex; /* linp array index */
- int16 itemoff; /* page offset of item data */
- uint16 alignedlen; /* MAXALIGN(item data len) */
-} itemIdCompactData;
-typedef itemIdCompactData *itemIdCompact;
+ bool xid_found = false,
+ multi_found = false;
+ OffsetNumber offnum,
+ maxoff;
+
+ maxoff = PageGetMaxOffsetNumber(page);
+
+ for (offnum = FirstOffsetNumber;
+ offnum <= maxoff;
+ offnum = OffsetNumberNext(offnum))
+ {
+ ItemId itemid;
+ HeapTupleHeader htup;
+ TransactionId xmax;
+
+ itemid = PageGetItemId(page, offnum);
+
+ if (!ItemIdIsNormal(itemid))
+ continue;
+
+ htup = (HeapTupleHeader) PageGetItem(page, itemid);
+
+ xmax = HeapTupleHeaderGetDoubleXmax(htup);
+
+ if (!TransactionIdIsNormal(xmax))
+ continue;
+
+ if (!(htup->t_infomask & HEAP_XMAX_IS_MULTI))
+ {
+ if (!xid_found)
+ {
+ *xid_min = *xid_max = xmax;
+ xid_found = true;
+ }
+ else
+ {
+ *xid_min = Min(*xid_min, xmax);
+ *xid_max = Max(*xid_max, xmax);
+ }
+ }
+ else
+ {
+ if (!multi_found)
+ {
+ *multi_min = *multi_max = xmax;
+ multi_found = true;
+ }
+ else
+ {
+ *multi_min = Min(*multi_min, xmax);
+ *multi_max = Max(*multi_max, xmax);
+ }
+ }
+ }
+}
+
+/*
+ * Add special area to heap page, so convert from "double xmax" to normal
+ * format.
+ */
+static void
+heap_page_add_special_area(itemIdCompact itemidbase, int nitems, Page page,
+ TransactionId xid_base, MultiXactId multi_base)
+{
+ char newPage[BLCKSZ];
+ PageHeader phdr = (PageHeader) page;
+ PageHeader new_phdr = (PageHeader) newPage;
+ HeapPageSpecial special;
+ Offset upper;
+ int i;
+
+ memcpy(newPage, page, phdr->pd_lower);
+
+ /* Add special area */
+ new_phdr->pd_special = PageGetPageSize(newPage) - sizeof(HeapPageSpecialData);
+ special = (HeapPageSpecial) ((Pointer) (newPage) + new_phdr->pd_special);
+ special->pd_xid_base = xid_base;
+ special->pd_multi_base = multi_base;
+
+ /* sort itemIdSortData array into decreasing itemoff order */
+ qsort((char *) itemidbase, nitems, sizeof(itemIdCompactData),
+ itemoffcompare);
+
+ upper = new_phdr->pd_special;
+ for (i = 0; i < nitems; i++)
+ {
+ itemIdCompact itemidptr = &itemidbase[i];
+ ItemId lp;
+ HeapTupleHeader old_htup;
+ HeapTupleHeader new_htup;
+ TransactionId xmax;
+
+ lp = PageGetItemId(page, itemidptr->offsetindex + 1);
+ old_htup = (HeapTupleHeader) PageGetItem(page, lp);
+ upper -= itemidptr->alignedlen;
+ memcpy((Pointer) newPage + upper,
+ (Pointer) page + itemidptr->itemoff,
+ itemidptr->alignedlen);
+ lp = PageGetItemId(newPage, itemidptr->offsetindex + 1);
+ lp->lp_off = upper;
+ new_htup = (HeapTupleHeader) PageGetItem(newPage, lp);
+
+ /* Convert xmax value */
+ new_htup->t_choice.t_heap.t_xmin = FrozenTransactionId;
+ xmax = HeapTupleHeaderGetDoubleXmax(old_htup);
+ if (!(new_htup->t_infomask & HEAP_XMAX_IS_MULTI))
+ new_htup->t_choice.t_heap.t_xmax = NormalTransactionIdToShort(xid_base, xmax);
+ else
+ new_htup->t_choice.t_heap.t_xmax = NormalTransactionIdToShort(multi_base, xmax);
+ }
+
+ new_phdr->pd_upper = upper;
+
+ memcpy(page, newPage, PageGetPageSize(newPage));
+ elog(DEBUG2, "convert heap page from double xmax to normal format");
+}
/*
* After removing or marking some line pointers unused, move the tuples to
@@ -471,7 +602,8 @@ typedef itemIdCompactData *itemIdCompact;
* Callers must ensure that nitems is > 0
*/
static void
-compactify_tuples(itemIdCompact itemidbase, int nitems, Page page, bool presorted)
+compactify_tuples(itemIdCompact itemidbase, int nitems, Page page,
+ bool presorted, bool addspecial)
{
PageHeader phdr = (PageHeader) page;
Offset upper;
@@ -483,9 +615,33 @@ compactify_tuples(itemIdCompact itemidbase, int nitems, Page page, bool presorte
/* Code within will not work correctly if nitems == 0 */
Assert(nitems > 0);
- if (presorted)
+ /* Add special area to the heap page if possible */
+ if (addspecial)
{
+ TransactionId xid_min = FirstNormalTransactionId,
+ xid_max = FirstNormalTransactionId;
+ MultiXactId multi_min = FirstNormalTransactionId,
+ multi_max = FirstNormalTransactionId;
+
+ Assert(phdr->pd_special == PageGetPageSize(page));
+ heap_page_double_xmax_get_min_max(page, &xid_min, &xid_max,
+ &multi_min, &multi_max);
+
+ if (xid_max - xid_min < (TransactionId) (MaxShortTransactionId - FirstNormalTransactionId) &&
+ multi_max - multi_min < (TransactionId) (MaxShortTransactionId - FirstNormalTransactionId))
+ {
+ Assert(xid_min >= FirstNormalTransactionId);
+ Assert(multi_min >= FirstNormalTransactionId);
+ heap_page_add_special_area(itemidbase, nitems, page,
+ xid_min - FirstNormalTransactionId,
+ multi_min - FirstNormalTransactionId);
+ return;
+ }
+ }
+
+ if (presorted)
+ {
#ifdef USE_ASSERT_CHECKING
{
/*
@@ -783,11 +939,21 @@ PageRepairFragmentation(Page page)
nstorage = itemidptr - itemidbase;
if (nstorage == 0)
{
+ if (pd_special == PageGetPageSize(page))
+ {
+ pd_special = PageGetPageSize(page) - sizeof(HeapPageSpecialData);
+ ((PageHeader) page)->pd_special = pd_special;
+ HeapPageGetSpecial(page)->pd_xid_base = 0;
+ HeapPageGetSpecial(page)->pd_multi_base = 0;
+ }
+
/* Page is completely empty, so just reset it quickly */
((PageHeader) page)->pd_upper = pd_special;
}
else
{
+ bool addspecial = false;
+
/* Need to compact the page the hard way */
if (totallen > (Size) (pd_special - pd_lower))
ereport(ERROR,
@@ -795,7 +961,15 @@ PageRepairFragmentation(Page page)
errmsg("corrupted item lengths: total %u, available space %u",
(unsigned int) totallen, pd_special - pd_lower)));
- compactify_tuples(itemidbase, nstorage, page, presorted);
+ /*
+ * Try to add special area to the heap page if it has enough of free
+ * space.
+ */
+ if (pd_special == PageGetPageSize(page) &&
+ (Size) (pd_special - pd_lower) - totallen >= sizeof(HeapPageSpecialData))
+ addspecial = true;
+
+ compactify_tuples(itemidbase, nstorage, page, presorted, addspecial);
}
/* Set hint bit for PageAddItemExtended */
@@ -985,6 +1159,9 @@ PageGetHeapFreeSpace(Page page)
{
Size space;
+ if (HeapPageIsDoubleXmax(page))
+ return 0;
+
space = PageGetFreeSpace(page);
if (space > 0)
{
@@ -1268,7 +1445,7 @@ PageIndexMultiDelete(Page page, OffsetNumber *itemnos, int nitems)
/* and compactify the tuple data */
if (nused > 0)
- compactify_tuples(itemidbase, nused, page, presorted);
+ compactify_tuples(itemidbase, nused, page, presorted, false);
else
phdr->pd_upper = pd_special;
}
diff --git a/src/backend/utils/adt/enum.c b/src/backend/utils/adt/enum.c
index 0cc7a6d8ad0..04ca6ff16e7 100644
--- a/src/backend/utils/adt/enum.c
+++ b/src/backend/utils/adt/enum.c
@@ -76,7 +76,7 @@ check_safe_enum_use(HeapTuple enumval_tup)
* Usually, a row would get hinted as committed when it's read or loaded
* into syscache; but just in case not, let's check the xmin directly.
*/
- xmin = HeapTupleHeaderGetXmin(enumval_tup->t_data);
+ xmin = HeapTupleGetXmin(enumval_tup);
if (!TransactionIdIsInProgress(xmin) &&
TransactionIdDidCommit(xmin))
return;
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 0273f883d4f..b6a2bbb7582 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -3340,6 +3340,7 @@ populate_record(TupleDesc tupdesc,
tuple.t_len = HeapTupleHeaderGetDatumLength(defaultval);
ItemPointerSetInvalid(&(tuple.t_self));
tuple.t_tableOid = InvalidOid;
+ HeapTupleSetZeroBase(&tuple);
tuple.t_data = defaultval;
/* Break down the tuple into fields */
@@ -3776,6 +3777,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
tuple.t_len = HeapTupleHeaderGetDatumLength(tuphead);
ItemPointerSetInvalid(&(tuple.t_self));
tuple.t_tableOid = InvalidOid;
+ HeapTupleSetZeroBase(&tuple);
tuple.t_data = tuphead;
tuplestore_puttuple(state->tuple_store, &tuple);
diff --git a/src/backend/utils/adt/lockfuncs.c b/src/backend/utils/adt/lockfuncs.c
index 023a004ac89..95aa922e8cc 100644
--- a/src/backend/utils/adt/lockfuncs.c
+++ b/src/backend/utils/adt/lockfuncs.c
@@ -77,9 +77,9 @@ VXIDGetDatum(BackendId bid, LocalTransactionId lxid)
* The representation is "<bid>/<lxid>", decimal and unsigned decimal
* respectively. Note that elog.c also knows how to format a vxid.
*/
- char vxidstr[32];
+ char vxidstr[64];
- snprintf(vxidstr, sizeof(vxidstr), "%d/%u", bid, lxid);
+ snprintf(vxidstr, sizeof(vxidstr), "%d/" XID_FMT, bid, lxid);
return CStringGetTextDatum(vxidstr);
}
@@ -292,7 +292,9 @@ pg_lock_status(PG_FUNCTION_ARGS)
break;
case LOCKTAG_TRANSACTION:
values[6] =
- TransactionIdGetDatum(instance->locktag.locktag_field1);
+ TransactionIdGetDatum(
+ (TransactionId)instance->locktag.locktag_field1 |
+ ((TransactionId)instance->locktag.locktag_field2 << 32));
nulls[1] = true;
nulls[2] = true;
nulls[3] = true;
@@ -304,7 +306,8 @@ pg_lock_status(PG_FUNCTION_ARGS)
break;
case LOCKTAG_VIRTUALTRANSACTION:
values[5] = VXIDGetDatum(instance->locktag.locktag_field1,
- instance->locktag.locktag_field2);
+ (TransactionId)instance->locktag.locktag_field2 |
+ ((TransactionId)instance->locktag.locktag_field3 << 32));
nulls[1] = true;
nulls[2] = true;
nulls[3] = true;
diff --git a/src/backend/utils/adt/numutils.c b/src/backend/utils/adt/numutils.c
index 898a9e3f9a1..5d0443fbe8a 100644
--- a/src/backend/utils/adt/numutils.c
+++ b/src/backend/utils/adt/numutils.c
@@ -606,3 +606,25 @@ pg_ultostr(char *str, uint32 value)
return str + len;
}
+
+/*
+ * pg_strtouint64
+ * Converts 'str' into an unsigned 64-bit integer.
+ *
+ * This has the identical API to strtoul(3), except that it will handle
+ * 64-bit ints even where "long" is narrower than that.
+ *
+ * For the moment it seems sufficient to assume that the platform has
+ * such a function somewhere; let's not roll our own.
+ */
+uint64
+pg_strtouint64(const char *str, char **endptr, int base)
+{
+#ifdef _MSC_VER /* MSVC only */
+ return _strtoui64(str, endptr, base);
+#elif defined(HAVE_STRTOULL) && SIZEOF_LONG < 8
+ return strtoull(str, endptr, base);
+#else
+ return strtoul(str, endptr, base);
+#endif
+}
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 15cb17ace4f..983c99b7786 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -15,6 +15,7 @@
#include "postgres.h"
#include "access/htup_details.h"
+#include "access/xact.h"
#include "access/xlog.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_type.h"
diff --git a/src/backend/utils/adt/rowtypes.c b/src/backend/utils/adt/rowtypes.c
index db843a0fbf0..5e08d02d645 100644
--- a/src/backend/utils/adt/rowtypes.c
+++ b/src/backend/utils/adt/rowtypes.c
@@ -327,6 +327,7 @@ record_out(PG_FUNCTION_ARGS)
tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
ItemPointerSetInvalid(&(tuple.t_self));
tuple.t_tableOid = InvalidOid;
+ HeapTupleSetZeroBase(&tuple);
tuple.t_data = rec;
/*
@@ -694,6 +695,7 @@ record_send(PG_FUNCTION_ARGS)
tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
ItemPointerSetInvalid(&(tuple.t_self));
tuple.t_tableOid = InvalidOid;
+ HeapTupleSetZeroBase(&tuple);
tuple.t_data = rec;
/*
@@ -844,10 +846,12 @@ record_cmp(FunctionCallInfo fcinfo)
tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
ItemPointerSetInvalid(&(tuple1.t_self));
tuple1.t_tableOid = InvalidOid;
+ HeapTupleSetZeroBase(&tuple1);
tuple1.t_data = record1;
tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
ItemPointerSetInvalid(&(tuple2.t_self));
tuple2.t_tableOid = InvalidOid;
+ HeapTupleSetZeroBase(&tuple2);
tuple2.t_data = record2;
/*
@@ -1089,10 +1093,12 @@ record_eq(PG_FUNCTION_ARGS)
ItemPointerSetInvalid(&(tuple1.t_self));
tuple1.t_tableOid = InvalidOid;
tuple1.t_data = record1;
+ HeapTupleSetZeroBase(&tuple1);
tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
ItemPointerSetInvalid(&(tuple2.t_self));
tuple2.t_tableOid = InvalidOid;
tuple2.t_data = record2;
+ HeapTupleSetZeroBase(&tuple2);
/*
* We arrange to look up the needed comparison info just once per series
@@ -1351,10 +1357,12 @@ record_image_cmp(FunctionCallInfo fcinfo)
ItemPointerSetInvalid(&(tuple1.t_self));
tuple1.t_tableOid = InvalidOid;
tuple1.t_data = record1;
+ HeapTupleSetZeroBase(&tuple1);
tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
ItemPointerSetInvalid(&(tuple2.t_self));
tuple2.t_tableOid = InvalidOid;
tuple2.t_data = record2;
+ HeapTupleSetZeroBase(&tuple2);
/*
* We arrange to look up the needed comparison info just once per series
@@ -1597,10 +1605,12 @@ record_image_eq(PG_FUNCTION_ARGS)
ItemPointerSetInvalid(&(tuple1.t_self));
tuple1.t_tableOid = InvalidOid;
tuple1.t_data = record1;
+ HeapTupleSetZeroBase(&tuple1);
tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
ItemPointerSetInvalid(&(tuple2.t_self));
tuple2.t_tableOid = InvalidOid;
tuple2.t_data = record2;
+ HeapTupleSetZeroBase(&tuple2);
/*
* We arrange to look up the needed comparison info just once per series
@@ -1800,6 +1810,7 @@ hash_record(PG_FUNCTION_ARGS)
ItemPointerSetInvalid(&(tuple.t_self));
tuple.t_tableOid = InvalidOid;
tuple.t_data = record;
+ HeapTupleSetZeroBase(&tuple);
/*
* We arrange to look up the needed hashing info just once per series of
@@ -1921,6 +1932,7 @@ hash_record_extended(PG_FUNCTION_ARGS)
ItemPointerSetInvalid(&(tuple.t_self));
tuple.t_tableOid = InvalidOid;
tuple.t_data = record;
+ HeapTupleSetZeroBase(&tuple);
/*
* We arrange to look up the needed hashing info just once per series of
diff --git a/src/backend/utils/adt/xid.c b/src/backend/utils/adt/xid.c
index e6633e9cffe..c357ef2d3e8 100644
--- a/src/backend/utils/adt/xid.c
+++ b/src/backend/utils/adt/xid.c
@@ -32,16 +32,16 @@ xidin(PG_FUNCTION_ARGS)
{
char *str = PG_GETARG_CSTRING(0);
- PG_RETURN_TRANSACTIONID((TransactionId) strtoul(str, NULL, 0));
+ PG_RETURN_TRANSACTIONID((TransactionId) pg_strtouint64(str, NULL, 0));
}
Datum
xidout(PG_FUNCTION_ARGS)
{
TransactionId transactionId = PG_GETARG_TRANSACTIONID(0);
- char *result = (char *) palloc(16);
+ char *result = (char *) palloc(32);
- snprintf(result, 16, "%lu", (unsigned long) transactionId);
+ snprintf(result, 32, XID_FMT, transactionId);
PG_RETURN_CSTRING(result);
}
@@ -52,8 +52,12 @@ Datum
xidrecv(PG_FUNCTION_ARGS)
{
StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
+ uint32 lo, hi;
+
+ lo = (uint32) pq_getmsgint(buf, sizeof(TransactionId));
+ hi = (uint32) pq_getmsgint(buf, sizeof(TransactionId));
- PG_RETURN_TRANSACTIONID((TransactionId) pq_getmsgint(buf, sizeof(TransactionId)));
+ PG_RETURN_TRANSACTIONID((uint64) lo + ((uint64) hi << 32));
}
/*
@@ -64,9 +68,14 @@ xidsend(PG_FUNCTION_ARGS)
{
TransactionId arg1 = PG_GETARG_TRANSACTIONID(0);
StringInfoData buf;
+ uint32 lo, hi;
+
+ lo = (uint32) (arg1 & 0xFFFFFFFF);
+ hi = (uint32) (arg1 >> 32);
pq_begintypsend(&buf);
- pq_sendint32(&buf, arg1);
+ pq_sendint(&buf, lo, sizeof(lo));
+ pq_sendint(&buf, hi, sizeof(hi));
PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
}
@@ -105,9 +114,9 @@ xid_age(PG_FUNCTION_ARGS)
/* Permanent XIDs are always infinitely old */
if (!TransactionIdIsNormal(xid))
- PG_RETURN_INT32(INT_MAX);
+ PG_RETURN_INT64(PG_INT8_MAX);
- PG_RETURN_INT32((int32) (now - xid));
+ PG_RETURN_INT64((int64) (now - xid));
}
/*
@@ -120,9 +129,9 @@ mxid_age(PG_FUNCTION_ARGS)
MultiXactId now = ReadNextMultiXactId();
if (!MultiXactIdIsValid(xid))
- PG_RETURN_INT32(INT_MAX);
+ PG_RETURN_INT64(PG_INT8_MAX);
- PG_RETURN_INT32((int32) (now - xid));
+ PG_RETURN_INT64((int64) (now - xid));
}
/*
diff --git a/src/backend/utils/adt/xid8funcs.c b/src/backend/utils/adt/xid8funcs.c
index 60e57876de6..a508d74d2f8 100644
--- a/src/backend/utils/adt/xid8funcs.c
+++ b/src/backend/utils/adt/xid8funcs.c
@@ -79,8 +79,7 @@ typedef struct
* It is an ERROR if the xid is in the future. Otherwise, returns true if
* the transaction is still new enough that we can determine whether it
* committed and false otherwise. If *extracted_xid is not NULL, it is set
- * to the low 32 bits of the transaction ID (i.e. the actual XID, without the
- * epoch).
+ * to the actual transaction ID.
*
* The caller must hold XactTruncationLock since it's dealing with arbitrary
* XIDs, and must continue to hold it until it's done with any clog lookups
@@ -89,15 +88,10 @@ typedef struct
static bool
TransactionIdInRecentPast(FullTransactionId fxid, TransactionId *extracted_xid)
{
- uint32 xid_epoch = EpochFromFullTransactionId(fxid);
TransactionId xid = XidFromFullTransactionId(fxid);
- uint32 now_epoch;
- TransactionId now_epoch_next_xid;
FullTransactionId now_fullxid;
now_fullxid = ReadNextFullTransactionId();
- now_epoch_next_xid = XidFromFullTransactionId(now_fullxid);
- now_epoch = EpochFromFullTransactionId(now_fullxid);
if (extracted_xid != NULL)
*extracted_xid = xid;
@@ -127,48 +121,15 @@ TransactionIdInRecentPast(FullTransactionId fxid, TransactionId *extracted_xid)
Assert(LWLockHeldByMe(XactTruncationLock));
/*
- * If the transaction ID has wrapped around, it's definitely too old to
- * determine the commit status. Otherwise, we can compare it to
- * ShmemVariableCache->oldestClogXid to determine whether the relevant
- * CLOG entry is guaranteed to still exist.
+ * We compare xid to ShmemVariableCache->oldestClogXid to determine whether
+ * the relevant CLOG entry is guaranteed to still exist.
*/
- if (xid_epoch + 1 < now_epoch
- || (xid_epoch + 1 == now_epoch && xid < now_epoch_next_xid)
- || TransactionIdPrecedes(xid, ShmemVariableCache->oldestClogXid))
+ if (TransactionIdPrecedes(xid, ShmemVariableCache->oldestClogXid))
return false;
return true;
}
-/*
- * Convert a TransactionId obtained from a snapshot held by the caller to a
- * FullTransactionId. Use next_fxid as a reference FullTransactionId, so that
- * we can compute the high order bits. It must have been obtained by the
- * caller with ReadNextFullTransactionId() after the snapshot was created.
- */
-static FullTransactionId
-widen_snapshot_xid(TransactionId xid, FullTransactionId next_fxid)
-{
- TransactionId next_xid = XidFromFullTransactionId(next_fxid);
- uint32 epoch = EpochFromFullTransactionId(next_fxid);
-
- /* Special transaction ID. */
- if (!TransactionIdIsNormal(xid))
- return FullTransactionIdFromEpochAndXid(0, xid);
-
- /*
- * The 64 bit result must be <= next_fxid, since next_fxid hadn't been
- * issued yet when the snapshot was created. Every TransactionId in the
- * snapshot must therefore be from the same epoch as next_fxid, or the
- * epoch before. We know this because next_fxid is never allow to get
- * more than one epoch ahead of the TransactionIds in any snapshot.
- */
- if (xid > next_xid)
- epoch--;
-
- return FullTransactionIdFromEpochAndXid(epoch, xid);
-}
-
/*
* txid comparator for qsort/bsearch
*/
@@ -397,7 +358,6 @@ pg_current_snapshot(PG_FUNCTION_ARGS)
uint32 nxip,
i;
Snapshot cur;
- FullTransactionId next_fxid = ReadNextFullTransactionId();
cur = GetActiveSnapshot();
if (cur == NULL)
@@ -415,11 +375,11 @@ pg_current_snapshot(PG_FUNCTION_ARGS)
snap = palloc(PG_SNAPSHOT_SIZE(nxip));
/* fill */
- snap->xmin = widen_snapshot_xid(cur->xmin, next_fxid);
- snap->xmax = widen_snapshot_xid(cur->xmax, next_fxid);
+ snap->xmin = FullTransactionIdFromXid(cur->xmin);
+ snap->xmax = FullTransactionIdFromXid(cur->xmax);
snap->nxip = nxip;
for (i = 0; i < nxip; i++)
- snap->xip[i] = widen_snapshot_xid(cur->xip[i], next_fxid);
+ snap->xip[i] = FullTransactionIdFromXid(cur->xip[i]);
/*
* We want them guaranteed to be in ascending order. This also removes
@@ -655,8 +615,7 @@ pg_snapshot_xip(PG_FUNCTION_ARGS)
* Report the status of a recent transaction ID, or null for wrapped,
* truncated away or otherwise too old XIDs.
*
- * The passed epoch-qualified xid is treated as a normal xid, not a
- * multixact id.
+ * The passed xid is treated as a normal xid, not a multixact id.
*
* If it points to a committed subxact the result is the subxact status even
* though the parent xact may still be in progress or may have aborted.
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index eb830880893..5ad5cc707e2 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1845,6 +1845,7 @@ CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, Datum *arguments,
memcpy((char *) ct->tuple.t_data,
(const char *) dtp->t_data,
dtp->t_len);
+ HeapTupleCopyBase(&ct->tuple, dtp);
MemoryContextSwitchTo(oldcxt);
if (dtp != ntp)
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2e760e8a3bd..0f4d37ab942 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -2284,8 +2284,9 @@ RelationReloadIndexInfo(Relation relation)
relation->rd_index->indislive = index->indislive;
/* Copy xmin too, as that is needed to make sense of indcheckxmin */
- HeapTupleHeaderSetXmin(relation->rd_indextuple->t_data,
- HeapTupleHeaderGetXmin(tuple->t_data));
+ relation->rd_indextuple->t_xid_base = tuple->t_xid_base;
+ HeapTupleSetXmin(relation->rd_indextuple,
+ HeapTupleGetXmin(tuple));
ReleaseSysCache(tuple);
}
diff --git a/src/backend/utils/error/csvlog.c b/src/backend/utils/error/csvlog.c
index 89f78b447d9..8bf5213d7e1 100644
--- a/src/backend/utils/error/csvlog.c
+++ b/src/backend/utils/error/csvlog.c
@@ -153,11 +153,11 @@ write_csvlog(ErrorData *edata)
/* Virtual transaction id */
/* keep VXID format in sync with lockfuncs.c */
if (MyProc != NULL && MyProc->backendId != InvalidBackendId)
- appendStringInfo(&buf, "%d/%u", MyProc->backendId, MyProc->lxid);
+ appendStringInfo(&buf, "%d/" XID_FMT, MyProc->backendId, MyProc->lxid);
appendStringInfoChar(&buf, ',');
/* Transaction id */
- appendStringInfo(&buf, "%u", GetTopTransactionIdIfAny());
+ appendStringInfo(&buf, XID_FMT, GetTopTransactionIdIfAny());
appendStringInfoChar(&buf, ',');
/* Error severity */
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 7402696986b..ab6a0f8c09c 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -2754,12 +2754,12 @@ log_line_prefix(StringInfo buf, ErrorData *edata)
{
char strfbuf[128];
- snprintf(strfbuf, sizeof(strfbuf) - 1, "%d/%u",
+ snprintf(strfbuf, sizeof(strfbuf) - 1, "%d/" XID_FMT,
MyProc->backendId, MyProc->lxid);
appendStringInfo(buf, "%*s", padding, strfbuf);
}
else
- appendStringInfo(buf, "%d/%u", MyProc->backendId, MyProc->lxid);
+ appendStringInfo(buf, "%d/" XID_FMT, MyProc->backendId, MyProc->lxid);
}
else if (padding != 0)
appendStringInfoSpaces(buf,
@@ -2767,9 +2767,9 @@ log_line_prefix(StringInfo buf, ErrorData *edata)
break;
case 'x':
if (padding != 0)
- appendStringInfo(buf, "%*u", padding, GetTopTransactionIdIfAny());
+ appendStringInfo(buf, "%*" INT64_MODIFIER "u", padding, GetTopTransactionIdIfAny());
else
- appendStringInfo(buf, "%u", GetTopTransactionIdIfAny());
+ appendStringInfo(buf, XID_FMT, GetTopTransactionIdIfAny());
break;
case 'e':
if (padding != 0)
diff --git a/src/backend/utils/error/jsonlog.c b/src/backend/utils/error/jsonlog.c
index 843641c865f..7ef41813d00 100644
--- a/src/backend/utils/error/jsonlog.c
+++ b/src/backend/utils/error/jsonlog.c
@@ -198,11 +198,11 @@ write_jsonlog(ErrorData *edata)
/* Virtual transaction id */
/* keep VXID format in sync with lockfuncs.c */
if (MyProc != NULL && MyProc->backendId != InvalidBackendId)
- appendJSONKeyValueFmt(&buf, "vxid", true, "%d/%u", MyProc->backendId,
+ appendJSONKeyValueFmt(&buf, "vxid", true, "%d/" XID_FMT, MyProc->backendId,
MyProc->lxid);
/* Transaction id */
- appendJSONKeyValueFmt(&buf, "txid", false, "%u",
+ appendJSONKeyValueFmt(&buf, "txid", false, XID_FMT,
GetTopTransactionIdIfAny());
/* Error severity */
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index b2e72b3243f..478447e4d4d 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -540,7 +540,7 @@ lookup_C_func(HeapTuple procedureTuple)
NULL);
if (entry == NULL)
return NULL; /* no such entry */
- if (entry->fn_xmin == HeapTupleHeaderGetRawXmin(procedureTuple->t_data) &&
+ if (entry->fn_xmin == HeapTupleGetRawXmin(procedureTuple) &&
ItemPointerEquals(&entry->fn_tid, &procedureTuple->t_self))
return entry; /* OK */
return NULL; /* entry is out of date */
@@ -576,7 +576,7 @@ record_C_func(HeapTuple procedureTuple,
HASH_ENTER,
&found);
/* OID is already filled in */
- entry->fn_xmin = HeapTupleHeaderGetRawXmin(procedureTuple->t_data);
+ entry->fn_xmin = HeapTupleGetRawXmin(procedureTuple);
entry->fn_tid = procedureTuple->t_self;
entry->user_fn = user_fn;
entry->inforec = inforec;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index fea143834e3..4d997a10b11 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -2649,74 +2649,6 @@ static struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
- {
- {"vacuum_freeze_min_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
- gettext_noop("Minimum age at which VACUUM should freeze a table row."),
- NULL
- },
- &vacuum_freeze_min_age,
- 50000000, 0, 1000000000,
- NULL, NULL, NULL
- },
-
- {
- {"vacuum_freeze_table_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
- gettext_noop("Age at which VACUUM should scan whole table to freeze tuples."),
- NULL
- },
- &vacuum_freeze_table_age,
- 150000000, 0, 2000000000,
- NULL, NULL, NULL
- },
-
- {
- {"vacuum_multixact_freeze_min_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
- gettext_noop("Minimum age at which VACUUM should freeze a MultiXactId in a table row."),
- NULL
- },
- &vacuum_multixact_freeze_min_age,
- 5000000, 0, 1000000000,
- NULL, NULL, NULL
- },
-
- {
- {"vacuum_multixact_freeze_table_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
- gettext_noop("Multixact age at which VACUUM should scan whole table to freeze tuples."),
- NULL
- },
- &vacuum_multixact_freeze_table_age,
- 150000000, 0, 2000000000,
- NULL, NULL, NULL
- },
-
- {
- {"vacuum_defer_cleanup_age", PGC_SIGHUP, REPLICATION_PRIMARY,
- gettext_noop("Number of transactions by which VACUUM and HOT cleanup should be deferred, if any."),
- NULL
- },
- &vacuum_defer_cleanup_age,
- 0, 0, 1000000, /* see ComputeXidHorizons */
- NULL, NULL, NULL
- },
- {
- {"vacuum_failsafe_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
- gettext_noop("Age at which VACUUM should trigger failsafe to avoid a wraparound outage."),
- NULL
- },
- &vacuum_failsafe_age,
- 1600000000, 0, 2100000000,
- NULL, NULL, NULL
- },
- {
- {"vacuum_multixact_failsafe_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
- gettext_noop("Multixact age at which VACUUM should trigger failsafe to avoid a wraparound outage."),
- NULL
- },
- &vacuum_multixact_failsafe_age,
- 1600000000, 0, 2100000000,
- NULL, NULL, NULL
- },
-
/*
* See also CheckRequiredParameterValues() if this parameter changes
*/
@@ -3315,28 +3247,6 @@ static struct config_int ConfigureNamesInt[] =
50, 0, INT_MAX,
NULL, NULL, NULL
},
- {
- /* see varsup.c for why this is PGC_POSTMASTER not PGC_SIGHUP */
- {"autovacuum_freeze_max_age", PGC_POSTMASTER, AUTOVACUUM,
- gettext_noop("Age at which to autovacuum a table to prevent transaction ID wraparound."),
- NULL
- },
- &autovacuum_freeze_max_age,
-
- /* see vacuum_failsafe_age if you change the upper-limit value. */
- 200000000, 100000, 2000000000,
- NULL, NULL, NULL
- },
- {
- /* see multixact.c for why this is PGC_POSTMASTER not PGC_SIGHUP */
- {"autovacuum_multixact_freeze_max_age", PGC_POSTMASTER, AUTOVACUUM,
- gettext_noop("Multixact age at which to autovacuum a table to prevent multixact wraparound."),
- NULL
- },
- &autovacuum_multixact_freeze_max_age,
- 400000000, 10000, 2000000000,
- NULL, NULL, NULL
- },
{
/* see max_connections */
{"autovacuum_max_workers", PGC_POSTMASTER, AUTOVACUUM,
@@ -3883,6 +3793,100 @@ static struct config_real ConfigureNamesReal[] =
static struct config_int64 ConfigureNamesInt64[] =
{
+ {
+ {"vacuum_freeze_min_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+ gettext_noop("Minimum age at which VACUUM should freeze a table row."),
+ NULL
+ },
+ &vacuum_freeze_min_age,
+ INT64CONST(50000000), INT64CONST(0), INT64CONST(0x7FFFFFFFFFFFFFFF),
+ NULL, NULL, NULL
+ },
+
+ {
+ {"vacuum_freeze_table_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+ gettext_noop("Age at which VACUUM should scan whole table to freeze tuples."),
+ NULL
+ },
+ &vacuum_freeze_table_age,
+ INT64CONST(150000000), INT64CONST(0), INT64CONST(0x7FFFFFFFFFFFFFFF),
+ NULL, NULL, NULL
+ },
+
+ {
+ {"vacuum_multixact_freeze_min_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+ gettext_noop("Minimum age at which VACUUM should freeze a MultiXactId in a table row."),
+ NULL
+ },
+ &vacuum_multixact_freeze_min_age,
+ INT64CONST(5000000), INT64CONST(0), INT64CONST(0x7FFFFFFFFFFFFFFF),
+ NULL, NULL, NULL
+ },
+
+ {
+ {"vacuum_multixact_freeze_table_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+ gettext_noop("Multixact age at which VACUUM should scan whole table to freeze tuples."),
+ NULL
+ },
+ &vacuum_multixact_freeze_table_age,
+ INT64CONST(150000000), INT64CONST(0), INT64CONST(0x7FFFFFFFFFFFFFFF),
+ NULL, NULL, NULL
+ },
+
+ {
+ {"vacuum_defer_cleanup_age", PGC_SIGHUP, REPLICATION_PRIMARY,
+ gettext_noop("Number of transactions by which VACUUM and HOT cleanup should be deferred, if any."),
+ NULL
+ },
+ &vacuum_defer_cleanup_age,
+ INT64CONST(0), INT64CONST(0), INT64CONST(1000000),
+ NULL, NULL, NULL
+ },
+
+ {
+ {"vacuum_failsafe_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+ gettext_noop("Age at which VACUUM should trigger failsafe to avoid a wraparound outage."),
+ NULL
+ },
+ &vacuum_failsafe_age,
+ INT64CONST(1600000000), INT64CONST(0), INT64CONST(2100000000),
+ NULL, NULL, NULL
+ },
+
+ {
+ {"vacuum_multixact_failsafe_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+ gettext_noop("Multixact age at which VACUUM should trigger failsafe to avoid a wraparound outage."),
+ NULL
+ },
+ &vacuum_multixact_failsafe_age,
+ INT64CONST(1600000000), INT64CONST(0), INT64CONST(2100000000),
+ NULL, NULL, NULL
+ },
+
+ {
+ /* see varsup.c for why this is PGC_POSTMASTER not PGC_SIGHUP */
+ {"autovacuum_freeze_max_age", PGC_POSTMASTER, AUTOVACUUM,
+ gettext_noop("Age at which to autovacuum a table to prevent transaction ID wraparound."),
+ NULL
+ },
+ &autovacuum_freeze_max_age,
+
+ /* see vacuum_failsafe_age if you change the upper-limit value. */
+ INT64CONST(10000000000), INT64CONST(100000), INT64CONST(0x7FFFFFFFFFFFFFFF),
+ NULL, NULL, NULL
+ },
+
+ {
+ /* see multixact.c for why this is PGC_POSTMASTER not PGC_SIGHUP */
+ {"autovacuum_multixact_freeze_max_age", PGC_POSTMASTER, AUTOVACUUM,
+ gettext_noop("Multixact age at which to autovacuum a table to prevent multixact wraparound."),
+ NULL
+ },
+ &autovacuum_multixact_freeze_max_age,
+ INT64CONST(20000000000), INT64CONST(10000), INT64CONST(0x7FFFFFFFFFFFFFFF),
+ NULL, NULL, NULL
+ },
+
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, 0.0, 0.0, 0.0, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/help_config.c b/src/backend/utils/misc/help_config.c
index 61c83f35901..8c7c50a3ef0 100644
--- a/src/backend/utils/misc/help_config.c
+++ b/src/backend/utils/misc/help_config.c
@@ -33,6 +33,7 @@ typedef union
struct config_bool _bool;
struct config_real real;
struct config_int integer;
+ struct config_int64 integer8;
struct config_string string;
struct config_enum _enum;
} mixedStruct;
@@ -107,7 +108,12 @@ printMixedStruct(mixedStruct *structToPrint)
structToPrint->integer.min,
structToPrint->integer.max);
break;
-
+ case PGC_INT64:
+ printf("INT64\t" INT64_FORMAT "\t" INT64_FORMAT "\t" INT64_FORMAT "\t",
+ structToPrint->integer8.reset_val,
+ structToPrint->integer8.min,
+ structToPrint->integer8.max);
+ break;
case PGC_REAL:
printf("REAL\t%g\t%g\t%g\t",
structToPrint->real.reset_val,
diff --git a/src/backend/utils/misc/pg_controldata.c b/src/backend/utils/misc/pg_controldata.c
index 781f8b87580..54b719ef3d9 100644
--- a/src/backend/utils/misc/pg_controldata.c
+++ b/src/backend/utils/misc/pg_controldata.c
@@ -164,8 +164,7 @@ pg_control_checkpoint(PG_FUNCTION_ARGS)
values[5] = BoolGetDatum(ControlFile->checkPointCopy.fullPageWrites);
nulls[5] = false;
- values[6] = CStringGetTextDatum(psprintf("%u:%u",
- EpochFromFullTransactionId(ControlFile->checkPointCopy.nextXid),
+ values[6] = CStringGetTextDatum(psprintf(XID_FMT,
XidFromFullTransactionId(ControlFile->checkPointCopy.nextXid)));
nulls[6] = false;
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 817d5f53246..2b6c2e6e44e 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -640,9 +640,9 @@
#autovacuum_vacuum_insert_scale_factor = 0.2 # fraction of inserts over table
# size before insert vacuum
#autovacuum_analyze_scale_factor = 0.1 # fraction of table size before analyze
-#autovacuum_freeze_max_age = 200000000 # maximum XID age before forced vacuum
+#autovacuum_freeze_max_age = 10000000000 # maximum XID age before forced vacuum
# (change requires restart)
-#autovacuum_multixact_freeze_max_age = 400000000 # maximum multixact age
+#autovacuum_multixact_freeze_max_age = 20000000000 # maximum multixact age
# before forced vacuum
# (change requires restart)
#autovacuum_vacuum_cost_delay = 2ms # default vacuum cost delay for
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index a3f22d7357f..4bdde51e473 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -4078,11 +4078,13 @@ static void
writetup_cluster(Tuplesortstate *state, LogicalTape *tape, SortTuple *stup)
{
HeapTuple tuple = (HeapTuple) stup->tuple;
- unsigned int tuplen = tuple->t_len + sizeof(ItemPointerData) + sizeof(int);
+ unsigned int tuplen = tuple->t_len + sizeof(ItemPointerData) + 2 * sizeof(TransactionId) + sizeof(int);
/* We need to store t_self, but not other fields of HeapTupleData */
LogicalTapeWrite(tape, &tuplen, sizeof(tuplen));
LogicalTapeWrite(tape, &tuple->t_self, sizeof(ItemPointerData));
+ LogicalTapeWrite(tape, &tuple->t_xid_base, sizeof(TransactionId));
+ LogicalTapeWrite(tape, &tuple->t_multi_base, sizeof(TransactionId));
LogicalTapeWrite(tape, tuple->t_data, tuple->t_len);
if (state->randomAccess) /* need trailing length word? */
LogicalTapeWrite(tape, &tuplen, sizeof(tuplen));
@@ -4098,7 +4100,7 @@ static void
readtup_cluster(Tuplesortstate *state, SortTuple *stup,
LogicalTape *tape, unsigned int tuplen)
{
- unsigned int t_len = tuplen - sizeof(ItemPointerData) - sizeof(int);
+ unsigned int t_len = tuplen - sizeof(ItemPointerData) - 2 * sizeof(TransactionId) - sizeof(int);
HeapTuple tuple = (HeapTuple) readtup_alloc(state,
t_len + HEAPTUPLESIZE);
@@ -4106,6 +4108,8 @@ readtup_cluster(Tuplesortstate *state, SortTuple *stup,
tuple->t_data = (HeapTupleHeader) ((char *) tuple + HEAPTUPLESIZE);
tuple->t_len = t_len;
LogicalTapeReadExact(tape, &tuple->t_self, sizeof(ItemPointerData));
+ LogicalTapeReadExact(tape, &tuple->t_xid_base, sizeof(TransactionId));
+ LogicalTapeReadExact(tape, &tuple->t_multi_base, sizeof(TransactionId));
/* We don't currently bother to reconstruct t_tableOid */
tuple->t_tableOid = InvalidOid;
/* Read in the tuple body */
diff --git a/src/backend/utils/time/combocid.c b/src/backend/utils/time/combocid.c
index 6613dc05340..ca3ac89db74 100644
--- a/src/backend/utils/time/combocid.c
+++ b/src/backend/utils/time/combocid.c
@@ -101,12 +101,13 @@ static CommandId GetRealCmax(CommandId combocid);
*/
CommandId
-HeapTupleHeaderGetCmin(HeapTupleHeader tup)
+HeapTupleGetCmin(HeapTuple tuple)
{
+ HeapTupleHeader tup = tuple->t_data;
CommandId cid = HeapTupleHeaderGetRawCommandId(tup);
Assert(!(tup->t_infomask & HEAP_MOVED));
- Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tup)));
+ Assert(TransactionIdIsCurrentTransactionId(HeapTupleGetXmin(tuple)));
if (tup->t_infomask & HEAP_COMBOCID)
return GetRealCmin(cid);
@@ -115,8 +116,9 @@ HeapTupleHeaderGetCmin(HeapTupleHeader tup)
}
CommandId
-HeapTupleHeaderGetCmax(HeapTupleHeader tup)
+HeapTupleGetCmax(HeapTuple tuple)
{
+ HeapTupleHeader tup = tuple->t_data;
CommandId cid = HeapTupleHeaderGetRawCommandId(tup);
Assert(!(tup->t_infomask & HEAP_MOVED));
@@ -128,7 +130,7 @@ HeapTupleHeaderGetCmax(HeapTupleHeader tup)
* things too much.
*/
Assert(CritSectionCount > 0 ||
- TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tup)));
+ TransactionIdIsCurrentTransactionId(HeapTupleGetUpdateXidAny(tuple)));
if (tup->t_infomask & HEAP_COMBOCID)
return GetRealCmax(cid);
@@ -150,7 +152,7 @@ HeapTupleHeaderGetCmax(HeapTupleHeader tup)
* changes the tuple in shared buffers.
*/
void
-HeapTupleHeaderAdjustCmax(HeapTupleHeader tup,
+HeapTupleHeaderAdjustCmax(HeapTuple tup,
CommandId *cmax,
bool *iscombo)
{
@@ -160,10 +162,10 @@ HeapTupleHeaderAdjustCmax(HeapTupleHeader tup,
* Test for HeapTupleHeaderXminCommitted() first, because it's cheaper
* than a TransactionIdIsCurrentTransactionId call.
*/
- if (!HeapTupleHeaderXminCommitted(tup) &&
- TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tup)))
+ if (!HeapTupleHeaderXminCommitted(tup->t_data) &&
+ TransactionIdIsCurrentTransactionId(HeapTupleGetRawXmin(tup)))
{
- CommandId cmin = HeapTupleHeaderGetCmin(tup);
+ CommandId cmin = HeapTupleGetCmin(tup);
*cmax = GetComboCommandId(cmin, *cmax);
*iscombo = true;
diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c
index a0b703a5195..d0b6446605c 100644
--- a/src/backend/utils/time/snapmgr.c
+++ b/src/backend/utils/time/snapmgr.c
@@ -1172,8 +1172,9 @@ ExportSnapshot(Snapshot snapshot)
* Generate file path for the snapshot. We start numbering of snapshots
* inside the transaction from 1.
*/
- snprintf(path, sizeof(path), SNAPSHOT_EXPORT_DIR "/%08X-%08X-%d",
- MyProc->backendId, MyProc->lxid, list_length(exportedSnapshots) + 1);
+ snprintf(path, sizeof(path), SNAPSHOT_EXPORT_DIR "/%08X-%08X%08X-%d",
+ MyProc->backendId, (uint32) (MyProc->lxid >> 32),
+ (uint32) MyProc->lxid, list_length(exportedSnapshots) + 1);
/*
* Copy the snapshot into TopTransactionContext, add it to the
@@ -1200,14 +1201,14 @@ ExportSnapshot(Snapshot snapshot)
*/
initStringInfo(&buf);
- appendStringInfo(&buf, "vxid:%d/%u\n", MyProc->backendId, MyProc->lxid);
+ appendStringInfo(&buf, "vxid:%d/" XID_FMT "\n", MyProc->backendId, MyProc->lxid);
appendStringInfo(&buf, "pid:%d\n", MyProcPid);
appendStringInfo(&buf, "dbid:%u\n", MyDatabaseId);
appendStringInfo(&buf, "iso:%d\n", XactIsoLevel);
appendStringInfo(&buf, "ro:%d\n", XactReadOnly);
- appendStringInfo(&buf, "xmin:%u\n", snapshot->xmin);
- appendStringInfo(&buf, "xmax:%u\n", snapshot->xmax);
+ appendStringInfo(&buf, "xmin:" XID_FMT "\n", snapshot->xmin);
+ appendStringInfo(&buf, "xmax:" XID_FMT "\n", snapshot->xmax);
/*
* We must include our own top transaction ID in the top-xid data, since
@@ -1224,9 +1225,9 @@ ExportSnapshot(Snapshot snapshot)
TransactionIdPrecedes(topXid, snapshot->xmax)) ? 1 : 0;
appendStringInfo(&buf, "xcnt:%d\n", snapshot->xcnt + addTopXid);
for (i = 0; i < snapshot->xcnt; i++)
- appendStringInfo(&buf, "xip:%u\n", snapshot->xip[i]);
+ appendStringInfo(&buf, "xip:" XID_FMT "\n", snapshot->xip[i]);
if (addTopXid)
- appendStringInfo(&buf, "xip:%u\n", topXid);
+ appendStringInfo(&buf, "xip:" XID_FMT "\n", topXid);
/*
* Similarly, we add our subcommitted child XIDs to the subxid data. Here,
@@ -1240,9 +1241,9 @@ ExportSnapshot(Snapshot snapshot)
appendStringInfoString(&buf, "sof:0\n");
appendStringInfo(&buf, "sxcnt:%d\n", snapshot->subxcnt + nchildren);
for (i = 0; i < snapshot->subxcnt; i++)
- appendStringInfo(&buf, "sxp:%u\n", snapshot->subxip[i]);
+ appendStringInfo(&buf, "sxp:" XID_FMT "\n", snapshot->subxip[i]);
for (i = 0; i < nchildren; i++)
- appendStringInfo(&buf, "sxp:%u\n", children[i]);
+ appendStringInfo(&buf, "sxp:" XID_FMT "\n", children[i]);
}
appendStringInfo(&buf, "rec:%u\n", snapshot->takenDuringRecovery);
@@ -1345,7 +1346,7 @@ parseXidFromText(const char *prefix, char **s, const char *filename)
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid snapshot data in file \"%s\"", filename)));
ptr += prefixlen;
- if (sscanf(ptr, "%u", &val) != 1)
+ if (sscanf(ptr, XID_FMT, &val) != 1)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid snapshot data in file \"%s\"", filename)));
@@ -1370,7 +1371,7 @@ parseVxidFromText(const char *prefix, char **s, const char *filename,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid snapshot data in file \"%s\"", filename)));
ptr += prefixlen;
- if (sscanf(ptr, "%d/%u", &vxid->backendId, &vxid->localTransactionId) != 2)
+ if (sscanf(ptr, "%d/" XID_FMT, &vxid->backendId, &vxid->localTransactionId) != 2)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid snapshot data in file \"%s\"", filename)));
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 0a2dba7d18e..1b68593462f 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -148,6 +148,9 @@ static bool data_checksums = false;
static char *xlog_dir = NULL;
static char *str_wal_segment_size_mb = NULL;
static int wal_segment_size_mb;
+static TransactionId start_xid = 0;
+static MultiXactId start_mx_id = 0;
+static MultiXactOffset start_mx_offset = 0;
/* internal vars */
@@ -1408,10 +1411,13 @@ bootstrap_template1(void)
unsetenv("PGCLIENTENCODING");
snprintf(cmd, sizeof(cmd),
- "\"%s\" --boot -X %d %s %s %s %s",
+ "\"%s\" --boot -X %d %s %s " XID_FMT " %s " XID_FMT " %s " XID_FMT " %s %s %s",
backend_exec,
wal_segment_size_mb * (1024 * 1024),
data_checksums ? "-k" : "",
+ "-Z", start_xid,
+ "-m", start_mx_id,
+ "-o", start_mx_offset,
boot_options, extra_options,
debug ? "-d 5" : "");
@@ -2186,13 +2192,21 @@ usage(const char *progname)
printf(_(" -W, --pwprompt prompt for a password for the new superuser\n"));
printf(_(" -X, --waldir=WALDIR location for the write-ahead log directory\n"));
printf(_(" --wal-segsize=SIZE size of WAL segments, in megabytes\n"));
+ printf(_(" -x, --xid=START_XID specify start xid value in decimal format for new db instance to test 64-bit xids,\n"
+ " default value is 0, max value is 2^62-1\n"));
printf(_("\nLess commonly used options:\n"));
printf(_(" -d, --debug generate lots of debugging output\n"));
printf(_(" --discard-caches set debug_discard_caches=1\n"));
printf(_(" -L DIRECTORY where to find the input files\n"));
+ printf(_(" -m, --multixact-id=START_MX_ID\n"
+ " specify start multixact id value in decimal format for new db instance\n"
+ " to test 64-bit xids, default value is 0, max value is 2^62-1\n"));
printf(_(" -n, --no-clean do not clean up after errors\n"));
printf(_(" -N, --no-sync do not wait for changes to be written safely to disk\n"));
printf(_(" --no-instructions do not print instructions for next steps\n"));
+ printf(_(" -o, --multixact-offset=START_MX_OFFSET\n"
+ " specify start multixact offset value in decimal format for new db instance\n"
+ " to test 64-bit xids, default value is 0, max value is 2^62-1\n"));
printf(_(" -s, --show show internal settings\n"));
printf(_(" -S, --sync-only only sync database files to disk, then exit\n"));
printf(_("\nOther options:\n"));
@@ -2869,6 +2883,10 @@ main(int argc, char *argv[])
{"data-checksums", no_argument, NULL, 'k'},
{"allow-group-access", no_argument, NULL, 'g'},
{"discard-caches", no_argument, NULL, 14},
+ {"no-data-checksums", no_argument, NULL, 15},
+ {"xid", required_argument, NULL, 'x'},
+ {"multixact-id", required_argument, NULL, 'm'},
+ {"multixact-offset", required_argument, NULL, 'o'},
{NULL, 0, NULL, 0}
};
@@ -2910,7 +2928,7 @@ main(int argc, char *argv[])
/* process command-line options */
- while ((c = getopt_long(argc, argv, "A:dD:E:gkL:nNsST:U:WX:", long_options, &option_index)) != -1)
+ while ((c = getopt_long(argc, argv, "A:dD:E:gkL:m:nNo:sST:U:Wx:X:", long_options, &option_index)) != -1)
{
switch (c)
{
@@ -2949,6 +2967,20 @@ main(int argc, char *argv[])
debug = true;
printf(_("Running in debug mode.\n"));
break;
+ case 'm':
+ if (sscanf(optarg, XID_FMT, &start_mx_id) != 1)
+ {
+ fprintf(stderr, "%s: invalid decimal START_MX_ID value\n",
+ progname);
+ exit(1);
+ }
+ if (!StartMultiXactIdIsValid(start_mx_id))
+ {
+ fprintf(stderr, "%s: out-of-range START_MX_ID value (the value must be less than 2^62)\n",
+ progname);
+ exit(1);
+ }
+ break;
case 'n':
noclean = true;
printf(_("Running in no-clean mode. Mistakes will not be cleaned up.\n"));
@@ -2956,6 +2988,20 @@ main(int argc, char *argv[])
case 'N':
do_sync = false;
break;
+ case 'o':
+ if (sscanf(optarg, XID_FMT, &start_mx_offset) != 1)
+ {
+ fprintf(stderr, "%s: invalid decimal START_MX_OFFSET value\n",
+ progname);
+ exit(1);
+ }
+ if (!StartMultiXactOffsetIsValid(start_mx_offset))
+ {
+ fprintf(stderr, "%s: out-of-range START_MX_OFFSET value (the value must be less than 2^62)\n",
+ progname);
+ exit(1);
+ }
+ break;
case 'S':
sync_only = true;
break;
@@ -3015,6 +3061,23 @@ main(int argc, char *argv[])
extra_options,
"-c debug_discard_caches=1");
break;
+ case 15:
+ data_checksums = false;
+ break;
+ case 'x':
+ if (sscanf(optarg, XID_FMT, &start_xid) != 1)
+ {
+ fprintf(stderr, "%s: invalid decimal START_XID value\n",
+ progname);
+ exit(1);
+ }
+ if (!StartTransactionIdIsValid(start_xid))
+ {
+ fprintf(stderr, "%s: out-of-range START_XID value (the value must be less than 2^62)\n",
+ progname);
+ exit(1);
+ }
+ break;
default:
/* getopt_long already emitted a complaint */
fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
diff --git a/src/bin/pg_amcheck/t/004_verify_heapam.pl b/src/bin/pg_amcheck/t/004_verify_heapam.pl
index 84e75862769..c11c8326649 100644
--- a/src/bin/pg_amcheck/t/004_verify_heapam.pl
+++ b/src/bin/pg_amcheck/t/004_verify_heapam.pl
@@ -10,6 +10,8 @@ use PostgreSQL::Test::Utils;
use Fcntl qw(:seek);
use Test::More;
+use Data::Dumper;
+
# This regression test demonstrates that the pg_amcheck binary correctly
# identifies specific kinds of corruption within pages. To test this, we need
# a mechanism to create corrupt pages with predictable, repeatable corruption.
@@ -86,6 +88,60 @@ use Test::More;
use constant HEAPTUPLE_PACK_CODE => 'LLLSSSSSCCLLCCCCCCCCCCllLL';
use constant HEAPTUPLE_PACK_LENGTH => 58; # Total size
+use constant HEAPPAGE_SPECIAL_PACK_CODE => 'QQ';
+use constant HEAPPAGE_SPECIAL_PACK_LENGTH => 16;
+use constant HEAPPAGE_SIZE => 8192;
+
+# Some #define constants from access/htup_details.h for use while corrupting.
+use constant HEAP_HASNULL => 0x0001;
+use constant HEAP_XMAX_LOCK_ONLY => 0x0080;
+use constant HEAP_XMIN_COMMITTED => 0x0100;
+use constant HEAP_XMIN_INVALID => 0x0200;
+use constant HEAP_XMAX_COMMITTED => 0x0400;
+use constant HEAP_XMAX_INVALID => 0x0800;
+use constant HEAP_NATTS_MASK => 0x07FF;
+use constant HEAP_XMAX_IS_MULTI => 0x1000;
+use constant HEAP_KEYS_UPDATED => 0x2000;
+
+use constant FIRST_NORMAL_TRANSACTION_ID => 3;
+
+# Read page special data
+sub read_special_data
+{
+ my ($fh, $offset) = @_;
+ my ($buffer, %special);
+ $offset -= $offset % HEAPPAGE_SIZE;
+ $offset += HEAPPAGE_SIZE - HEAPPAGE_SPECIAL_PACK_LENGTH;
+ seek($fh, $offset, SEEK_SET)
+ or BAIL_OUT("seek failed: $!");
+ defined(sysread($fh, $buffer, HEAPPAGE_SPECIAL_PACK_LENGTH))
+ or BAIL_OUT("sysread failed: $!");
+
+ @_ = unpack(HEAPPAGE_SPECIAL_PACK_CODE, $buffer);
+ %special = (
+ pd_xid_base => shift,
+ pd_multi_base => shift);
+ return \%special;
+}
+
+# Write page special data
+sub write_special_data
+{
+ my ($fh, $offset, $special) = @_;
+
+ $offset -= $offset % HEAPPAGE_SIZE;
+ $offset += HEAPPAGE_SIZE - HEAPPAGE_SPECIAL_PACK_LENGTH;
+
+ my $buffer = pack(
+ HEAPPAGE_SPECIAL_PACK_CODE,
+ $special->{pd_xid_base}, $special->{pd_multi_base});
+
+ seek($fh, $offset, SEEK_SET)
+ or BAIL_OUT("seek failed: $!");
+ defined(syswrite($fh, $buffer, HEAPPAGE_SPECIAL_PACK_LENGTH))
+ or BAIL_OUT("syswrite failed: $!");
+ return;
+}
# Read a tuple of our table from a heap page.
#
@@ -97,7 +153,7 @@ use constant HEAPTUPLE_PACK_LENGTH => 58; # Total size
#
sub read_tuple
{
- my ($fh, $offset) = @_;
+ my ($fh, $offset, $raw) = @_;
my ($buffer, %tup);
seek($fh, $offset, SEEK_SET)
or BAIL_OUT("seek failed: $!");
@@ -134,6 +190,18 @@ sub read_tuple
c_va_toastrelid => shift);
# Stitch together the text for column 'b'
$tup{b} = join('', map { chr($tup{"b_body$_"}) } (1 .. 7));
+
+ if (!$raw)
+ {
+ my $special = read_special_data($fh, $offset);
+
+ $tup{t_xmin} += $special->{pd_xid_base};
+ my $is_multi = $tup{t_infomask} & HEAP_XMAX_IS_MULTI;
+ $tup{t_xmax} += !$is_multi ?
+ $special->{pd_xid_base} :
+ $special->{pd_multi_base};
+ }
+
return \%tup;
}
@@ -149,7 +217,32 @@ sub read_tuple
#
sub write_tuple
{
- my ($fh, $offset, $tup) = @_;
+ my ($fh, $offset, $tup, $raw) = @_;
+ if (!$raw)
+ {
+ my $special = read_special_data($fh, $offset);
+
+ my $xmin = $tup->{t_xmin} - $special->{pd_xid_base};
+ die "tuple x_min $tup->{t_xmin} is too smal for pd_xid_base $special->{pd_xid_base}"
+ if $xmin < 3;
+ $tup->{t_xmin} = $xmin;
+
+ if (($tup->{t_infomask} & HEAP_XMAX_IS_MULTI) == 0)
+ {
+ my $xmax = $tup->{t_xmax} - $special->{pd_xid_base};
+ die "tuple x_max $tup->{t_xmax} is too smal for pd_xid_base $special->{pd_xid_base}"
+ if $xmax < 3;
+ $tup->{t_xmax} = $xmax;
+ }
+ else
+ {
+ my $xmax = $tup->{t_xmax} - $special->{pd_multi_base};
+ die "tuple multi x_max $tup->{t_xmax} is too smal for pd_multi_base $special->{pd_multi_base}"
+ if $xmax < 3;
+ $tup->{t_xmax} = $xmax;
+ }
+ }
+
my $buffer = pack(
HEAPTUPLE_PACK_CODE,
$tup->{t_xmin}, $tup->{t_xmax},
@@ -172,6 +265,41 @@ sub write_tuple
return;
}
+# move pd_xid_base and pd_multi_base to more suitable position for tests.
+sub fixup_page
+{
+ my ($fh, $page, $xid_base, $multi_base, $lp_off) = @_;
+ my $offset = $page * HEAPPAGE_SIZE;
+ my $special = read_special_data($fh, $offset);
+
+ die "xid_base $xid_base should be lesser than existed $special->{pd_xid_base}"
+ if ($xid_base > $special->{pd_xid_base});
+ die "multi_base $multi_base should be lesser than existed $special->{pd_multi_base}"
+ if ($multi_base > $special->{pd_multi_base} && $special->{pd_multi_base} != 0);
+ return if ($xid_base == $special->{pd_xid_base} &&
+ $multi_base == $special->{pd_multi_base});
+
+ my $xid_delta = $special->{pd_xid_base} - $xid_base;
+ my $multi_delta = $special->{pd_multi_base} - $multi_base;
+
+ for my $off (@$lp_off)
+ {
+ # change only tuples on this page.
+ next if ($off < $offset && $off > $offset + HEAPPAGE_SIZE);
+
+ my $tup = read_tuple($fh, $off, 1);
+ $tup->{t_xmin} += $xid_delta;
+ my $is_multi = $tup->{t_infomask} & HEAP_XMAX_IS_MULTI;
+ $tup->{t_xmax} += !$is_multi ? $xid_delta : $multi_delta;
+ write_tuple($fh, $off, $tup, 1);
+ }
+
+ $special->{pd_xid_base} = $xid_base;
+ $special->{pd_multi_base} = $multi_base;
+
+ write_special_data($fh, $offset, $special);
+}
+
# Set umask so test directories and files are created with default permissions
umask(0077);
@@ -234,6 +362,10 @@ my $relfrozenxid = $node->safe_psql('postgres',
q(select relfrozenxid from pg_class where relname = 'test'));
my $datfrozenxid = $node->safe_psql('postgres',
q(select datfrozenxid from pg_database where datname = 'postgres'));
+my $datminmxid = $node->safe_psql('postgres',
+ q(select datminmxid from pg_database where datname = 'postgres'));
+my $txid_current = $node->safe_psql('postgres',
+ q(select txid_current()));
# Sanity check that our 'test' table has a relfrozenxid newer than the
# datfrozenxid for the database, and that the datfrozenxid is greater than the
@@ -292,6 +424,11 @@ for (my $tupidx = 0; $tupidx < ROWCOUNT; $tupidx++)
# Determine endianness of current platform from the 1-byte varlena header
$ENDIANNESS = $tup->{b_header} == 0x11 ? "little" : "big";
}
+
+# Set 64bit xid bases a bit in the past therefore we can set xmin/xmax a bit
+# in the past
+fixup_page($file, 0, $datfrozenxid - 100, $datminmxid - 100, \@lp_off);
+
close($file)
or BAIL_OUT("close failed: $!");
$node->start;
@@ -310,17 +447,6 @@ $node->command_ok([ 'pg_amcheck', '-p', $port, 'postgres' ],
$node->stop;
-# Some #define constants from access/htup_details.h for use while corrupting.
-use constant HEAP_HASNULL => 0x0001;
-use constant HEAP_XMAX_LOCK_ONLY => 0x0080;
-use constant HEAP_XMIN_COMMITTED => 0x0100;
-use constant HEAP_XMIN_INVALID => 0x0200;
-use constant HEAP_XMAX_COMMITTED => 0x0400;
-use constant HEAP_XMAX_INVALID => 0x0800;
-use constant HEAP_NATTS_MASK => 0x07FF;
-use constant HEAP_XMAX_IS_MULTI => 0x1000;
-use constant HEAP_KEYS_UPDATED => 0x2000;
-
# Helper function to generate a regular expression matching the header we
# expect verify_heapam() to return given which fields we expect to be non-null.
sub header
@@ -364,39 +490,39 @@ for (my $tupidx = 0; $tupidx < ROWCOUNT; $tupidx++)
# Expected corruption report
push @expected,
- qr/${header}xmin $xmin precedes relation freeze threshold 0:\d+/;
+ qr/${header}xmin $xmin precedes relation freeze threshold \d+/;
}
if ($offnum == 2)
{
# Corruptly set xmin < datfrozenxid
- my $xmin = 3;
+ my $xmin = $datfrozenxid - 10;
$tup->{t_xmin} = $xmin;
$tup->{t_infomask} &= ~HEAP_XMIN_COMMITTED;
$tup->{t_infomask} &= ~HEAP_XMIN_INVALID;
push @expected,
- qr/${$header}xmin $xmin precedes oldest valid transaction ID 0:\d+/;
+ qr/${$header}xmin $xmin precedes oldest valid transaction ID \d+/;
}
elsif ($offnum == 3)
{
- # Corruptly set xmin < datfrozenxid, further back, noting circularity
- # of xid comparison. For a new cluster with epoch = 0, the corrupt
- # xmin will be interpreted as in the future
- $tup->{t_xmin} = 4026531839;
+ # Corruptly set xmin > next transaction id.
+ my $xmin = $relfrozenxid + 1000000;
+ $tup->{t_xmin} = $xmin;
$tup->{t_infomask} &= ~HEAP_XMIN_COMMITTED;
$tup->{t_infomask} &= ~HEAP_XMIN_INVALID;
push @expected,
- qr/${$header}xmin 4026531839 equals or exceeds next valid transaction ID 0:\d+/;
+ qr/${$header}xmin $xmin equals or exceeds next valid transaction ID \d+/;
}
elsif ($offnum == 4)
{
- # Corruptly set xmax < relminmxid;
- $tup->{t_xmax} = 4026531839;
+ # Corruptly set xmax > next transaction id.
+ my $xmax = $relfrozenxid + 1000000;
+ $tup->{t_xmax} = $xmax;
$tup->{t_infomask} &= ~HEAP_XMAX_INVALID;
push @expected,
- qr/${$header}xmax 4026531839 equals or exceeds next valid transaction ID 0:\d+/;
+ qr/${$header}xmax $xmax equals or exceeds next valid transaction ID \d+/;
}
elsif ($offnum == 5)
{
@@ -499,20 +625,22 @@ for (my $tupidx = 0; $tupidx < ROWCOUNT; $tupidx++)
# Set both HEAP_XMAX_COMMITTED and HEAP_XMAX_IS_MULTI
$tup->{t_infomask} |= HEAP_XMAX_COMMITTED;
$tup->{t_infomask} |= HEAP_XMAX_IS_MULTI;
- $tup->{t_xmax} = 4;
+ my $xmax = $datminmxid + 1000000;
+ $tup->{t_xmax} = $xmax;
push @expected,
- qr/${header}multitransaction ID 4 equals or exceeds next valid multitransaction ID 1/;
+ qr/${header}multitransaction ID $xmax equals or exceeds next valid multitransaction ID \d+/;
}
elsif ($offnum == 15) # Last offnum must equal ROWCOUNT
{
# Set both HEAP_XMAX_COMMITTED and HEAP_XMAX_IS_MULTI
$tup->{t_infomask} |= HEAP_XMAX_COMMITTED;
$tup->{t_infomask} |= HEAP_XMAX_IS_MULTI;
- $tup->{t_xmax} = 4000000000;
+ my $xmax = $datminmxid - 10;
+ $tup->{t_xmax} = $xmax;
push @expected,
- qr/${header}multitransaction ID 4000000000 precedes relation minimum multitransaction ID threshold 1/;
+ qr/${header}multitransaction ID $xmax precedes relation minimum multitransaction ID threshold \d+/;
}
write_tuple($file, $offset, $tup);
}
diff --git a/src/bin/pg_controldata/pg_controldata.c b/src/bin/pg_controldata/pg_controldata.c
index f911f98d946..997df822e8e 100644
--- a/src/bin/pg_controldata/pg_controldata.c
+++ b/src/bin/pg_controldata/pg_controldata.c
@@ -247,28 +247,27 @@ main(int argc, char *argv[])
ControlFile->checkPointCopy.PrevTimeLineID);
printf(_("Latest checkpoint's full_page_writes: %s\n"),
ControlFile->checkPointCopy.fullPageWrites ? _("on") : _("off"));
- printf(_("Latest checkpoint's NextXID: %u:%u\n"),
- EpochFromFullTransactionId(ControlFile->checkPointCopy.nextXid),
+ printf(_("Latest checkpoint's NextXID: " XID_FMT "\n"),
XidFromFullTransactionId(ControlFile->checkPointCopy.nextXid));
printf(_("Latest checkpoint's NextOID: %u\n"),
ControlFile->checkPointCopy.nextOid);
- printf(_("Latest checkpoint's NextMultiXactId: %u\n"),
+ printf(_("Latest checkpoint's NextMultiXactId: " XID_FMT "\n"),
ControlFile->checkPointCopy.nextMulti);
- printf(_("Latest checkpoint's NextMultiOffset: %u\n"),
+ printf(_("Latest checkpoint's NextMultiOffset: " XID_FMT "\n"),
ControlFile->checkPointCopy.nextMultiOffset);
- printf(_("Latest checkpoint's oldestXID: %u\n"),
+ printf(_("Latest checkpoint's oldestXID: " XID_FMT "\n"),
ControlFile->checkPointCopy.oldestXid);
printf(_("Latest checkpoint's oldestXID's DB: %u\n"),
ControlFile->checkPointCopy.oldestXidDB);
- printf(_("Latest checkpoint's oldestActiveXID: %u\n"),
+ printf(_("Latest checkpoint's oldestActiveXID: " XID_FMT "\n"),
ControlFile->checkPointCopy.oldestActiveXid);
- printf(_("Latest checkpoint's oldestMultiXid: %u\n"),
+ printf(_("Latest checkpoint's oldestMultiXid: " XID_FMT "\n"),
ControlFile->checkPointCopy.oldestMulti);
printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
ControlFile->checkPointCopy.oldestMultiDB);
- printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
+ printf(_("Latest checkpoint's oldestCommitTsXid:" XID_FMT "\n"),
ControlFile->checkPointCopy.oldestCommitTsXid);
- printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
+ printf(_("Latest checkpoint's newestCommitTsXid:" XID_FMT "\n"),
ControlFile->checkPointCopy.newestCommitTsXid);
printf(_("Time of latest checkpoint: %s\n"),
ckpttime_str);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 7c2f1d30447..80881b6278c 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -314,6 +314,7 @@ static void appendReloptionsArrayAH(PQExpBuffer buffer, const char *reloptions,
static char *get_synchronized_snapshot(Archive *fout);
static void setupDumpWorker(Archive *AHX);
static TableInfo *getRootTableInfo(const TableInfo *tbinfo);
+static uint64 pg_strtouint64(const char *str, char **endptr, int base);
int
@@ -2773,7 +2774,7 @@ dumpDatabase(Archive *fout)
*datistemplate,
*datconnlimit,
*tablespace;
- uint32 frozenxid,
+ uint64 frozenxid,
minmxid;
char *qdatname;
@@ -2822,8 +2823,8 @@ dumpDatabase(Archive *fout)
encoding = PQgetvalue(res, 0, i_encoding);
collate = PQgetvalue(res, 0, i_collate);
ctype = PQgetvalue(res, 0, i_ctype);
- frozenxid = atooid(PQgetvalue(res, 0, i_frozenxid));
- minmxid = atooid(PQgetvalue(res, 0, i_minmxid));
+ frozenxid = pg_strtouint64(PQgetvalue(res, 0, i_frozenxid), NULL, 0);
+ minmxid = pg_strtouint64(PQgetvalue(res, 0, i_minmxid), NULL, 0);
dbdacl.acl = PQgetvalue(res, 0, i_datacl);
dbdacl.acldefault = PQgetvalue(res, 0, i_acldefault);
datistemplate = PQgetvalue(res, 0, i_datistemplate);
@@ -3008,7 +3009,7 @@ dumpDatabase(Archive *fout)
{
appendPQExpBufferStr(creaQry, "\n-- For binary upgrade, set datfrozenxid and datminmxid.\n");
appendPQExpBuffer(creaQry, "UPDATE pg_catalog.pg_database\n"
- "SET datfrozenxid = '%u', datminmxid = '%u'\n"
+ "SET datfrozenxid = '" XID_FMT "', datminmxid = '" XID_FMT "'\n"
"WHERE datname = ",
frozenxid, minmxid);
appendStringLiteralAH(creaQry, datname, fout);
@@ -3058,10 +3059,10 @@ dumpDatabase(Archive *fout)
appendPQExpBufferStr(loOutQry, "\n-- For binary upgrade, set pg_largeobject relfrozenxid and relminmxid\n");
appendPQExpBuffer(loOutQry, "UPDATE pg_catalog.pg_class\n"
- "SET relfrozenxid = '%u', relminmxid = '%u'\n"
+ "SET relfrozenxid = '%s', relminmxid = '%s'\n"
"WHERE oid = %u;\n",
- atooid(PQgetvalue(lo_res, 0, i_relfrozenxid)),
- atooid(PQgetvalue(lo_res, 0, i_relminmxid)),
+ (PQgetvalue(lo_res, 0, i_relfrozenxid)),
+ (PQgetvalue(lo_res, 0, i_relminmxid)),
LargeObjectRelationId);
ArchiveEntry(fout, nilCatalogId, createDumpId(),
ARCHIVE_OPTS(.tag = "pg_largeobject",
@@ -6242,11 +6243,11 @@ getTables(Archive *fout, int *numTables)
tblinfo[i].relreplident = *(PQgetvalue(res, i, i_relreplident));
tblinfo[i].rowsec = (strcmp(PQgetvalue(res, i, i_relrowsec), "t") == 0);
tblinfo[i].forcerowsec = (strcmp(PQgetvalue(res, i, i_relforcerowsec), "t") == 0);
- tblinfo[i].frozenxid = atooid(PQgetvalue(res, i, i_relfrozenxid));
- tblinfo[i].toast_frozenxid = atooid(PQgetvalue(res, i, i_toastfrozenxid));
+ tblinfo[i].frozenxid = pg_strtouint64(PQgetvalue(res, i, i_relfrozenxid), NULL, 0);
+ tblinfo[i].toast_frozenxid = pg_strtouint64(PQgetvalue(res, i, i_toastfrozenxid), NULL, 0);
tblinfo[i].toast_oid = atooid(PQgetvalue(res, i, i_toastoid));
- tblinfo[i].minmxid = atooid(PQgetvalue(res, i, i_relminmxid));
- tblinfo[i].toast_minmxid = atooid(PQgetvalue(res, i, i_toastminmxid));
+ tblinfo[i].minmxid = pg_strtouint64(PQgetvalue(res, i, i_relminmxid), NULL, 0);
+ tblinfo[i].toast_minmxid = pg_strtouint64(PQgetvalue(res, i, i_toastminmxid), NULL, 0);
tblinfo[i].reloptions = pg_strdup(PQgetvalue(res, i, i_reloptions));
if (PQgetisnull(res, i, i_checkoption))
tblinfo[i].checkoption = NULL;
@@ -15371,7 +15372,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
{
appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
- "SET relfrozenxid = '%u', relminmxid = '%u'\n"
+ "SET relfrozenxid = '" XID_FMT "', relminmxid = '" XID_FMT "'\n"
"WHERE oid = ",
tbinfo->frozenxid, tbinfo->minmxid);
appendStringLiteralAH(q, qualrelname, fout);
@@ -15385,7 +15386,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
*/
appendPQExpBufferStr(q, "\n-- For binary upgrade, set toast's relfrozenxid and relminmxid\n");
appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
- "SET relfrozenxid = '%u', relminmxid = '%u'\n"
+ "SET relfrozenxid = '" XID_FMT "', relminmxid = '" XID_FMT "'\n"
"WHERE oid = '%u';\n",
tbinfo->toast_frozenxid,
tbinfo->toast_minmxid, tbinfo->toast_oid);
@@ -17946,3 +17947,27 @@ appendReloptionsArrayAH(PQExpBuffer buffer, const char *reloptions,
if (!res)
pg_log_warning("could not parse %s array", "reloptions");
}
+
+/*
+ * Copied from src/backend/utils/adt/numutils.c
+ *
+ * pg_strtouint64
+ * Converts 'str' into an unsigned 64-bit integer.
+ *
+ * This has the identical API to strtoul(3), except that it will handle
+ * 64-bit ints even where "long" is narrower than that.
+ *
+ * For the moment it seems sufficient to assume that the platform has
+ * such a function somewhere; let's not roll our own.
+ */
+static uint64
+pg_strtouint64(const char *str, char **endptr, int base)
+{
+#ifdef _MSC_VER /* MSVC only */
+ return _strtoui64(str, endptr, base);
+#elif defined(HAVE_STRTOULL) && SIZEOF_LONG < 8
+ return strtoull(str, endptr, base);
+#else
+ return strtoul(str, endptr, base);
+#endif
+}
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 066a129ee5b..42da3aee822 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -298,11 +298,11 @@ typedef struct _tableInfo
bool rowsec; /* is row security enabled? */
bool forcerowsec; /* is row security forced? */
bool hasoids; /* does it have OIDs? */
- uint32 frozenxid; /* table's relfrozenxid */
- uint32 minmxid; /* table's relminmxid */
+ uint64 frozenxid; /* table's relfrozenxid */
+ uint64 minmxid; /* table's relminmxid */
Oid toast_oid; /* toast table's OID, or 0 if none */
- uint32 toast_frozenxid; /* toast table's relfrozenxid, if any */
- uint32 toast_minmxid; /* toast table's relminmxid */
+ uint64 toast_frozenxid; /* toast table's relfrozenxid, if any */
+ uint64 toast_minmxid; /* toast table's relminmxid */
int ncheck; /* # of CHECK expressions */
Oid reltype; /* OID of table's composite type, if any */
Oid reloftype; /* underlying type for typed table */
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 1eb4509fcac..0bfe1a14615 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -63,7 +63,6 @@ static ControlFileData ControlFile; /* pg_control values */
static XLogSegNo newXlogSegNo; /* new XLOG segment # */
static bool guessed = false; /* T if we had to guess at any values */
static const char *progname;
-static uint32 set_xid_epoch = (uint32) -1;
static TransactionId set_oldest_xid = 0;
static TransactionId set_xid = 0;
static TransactionId set_oldest_commit_ts_xid = 0;
@@ -87,6 +86,7 @@ static void KillExistingXLOG(void);
static void KillExistingArchiveStatus(void);
static void WriteEmptyXLOG(void);
static void usage(void);
+static uint64 str2uint64(const char *str, char **endptr, int base);
int
@@ -95,7 +95,6 @@ main(int argc, char *argv[])
static struct option long_options[] = {
{"commit-timestamp-ids", required_argument, NULL, 'c'},
{"pgdata", required_argument, NULL, 'D'},
- {"epoch", required_argument, NULL, 'e'},
{"force", no_argument, NULL, 'f'},
{"next-wal-file", required_argument, NULL, 'l'},
{"multixact-ids", required_argument, NULL, 'm'},
@@ -137,7 +136,7 @@ main(int argc, char *argv[])
}
- while ((c = getopt_long(argc, argv, "c:D:e:fl:m:no:O:u:x:", long_options, NULL)) != -1)
+ while ((c = getopt_long(argc, argv, "c:D:fl:m:no:O:u:x:", long_options, NULL)) != -1)
{
switch (c)
{
@@ -153,27 +152,9 @@ main(int argc, char *argv[])
noupdate = true;
break;
- case 'e':
- errno = 0;
- set_xid_epoch = strtoul(optarg, &endptr, 0);
- if (endptr == optarg || *endptr != '\0' || errno != 0)
- {
- /*------
- translator: the second %s is a command line argument (-e, etc) */
- pg_log_error("invalid argument for option %s", "-e");
- fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
- exit(1);
- }
- if (set_xid_epoch == -1)
- {
- pg_log_error("transaction ID epoch (-e) must not be -1");
- exit(1);
- }
- break;
-
case 'u':
errno = 0;
- set_oldest_xid = strtoul(optarg, &endptr, 0);
+ set_oldest_xid = str2uint64(optarg, &endptr, 0);
if (endptr == optarg || *endptr != '\0' || errno != 0)
{
pg_log_error("invalid argument for option %s", "-u");
@@ -182,14 +163,14 @@ main(int argc, char *argv[])
}
if (!TransactionIdIsNormal(set_oldest_xid))
{
- pg_log_error("oldest transaction ID (-u) must be greater than or equal to %u", FirstNormalTransactionId);
+ pg_log_error("oldest transaction ID (-u) must be greater than or equal to " XID_FMT, FirstNormalTransactionId);
exit(1);
}
break;
case 'x':
errno = 0;
- set_xid = strtoul(optarg, &endptr, 0);
+ set_xid = str2uint64(optarg, &endptr, 0);
if (endptr == optarg || *endptr != '\0' || errno != 0)
{
pg_log_error("invalid argument for option %s", "-x");
@@ -198,21 +179,21 @@ main(int argc, char *argv[])
}
if (!TransactionIdIsNormal(set_xid))
{
- pg_log_error("transaction ID (-x) must be greater than or equal to %u", FirstNormalTransactionId);
+ pg_log_error("transaction ID (-x) must be greater than or equal to " XID_FMT, FirstNormalTransactionId);
exit(1);
}
break;
case 'c':
errno = 0;
- set_oldest_commit_ts_xid = strtoul(optarg, &endptr, 0);
+ set_oldest_commit_ts_xid = str2uint64(optarg, &endptr, 0);
if (endptr == optarg || *endptr != ',' || errno != 0)
{
pg_log_error("invalid argument for option %s", "-c");
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
exit(1);
}
- set_newest_commit_ts_xid = strtoul(endptr + 1, &endptr2, 0);
+ set_newest_commit_ts_xid = str2uint64(endptr + 1, &endptr2, 0);
if (endptr2 == endptr + 1 || *endptr2 != '\0' || errno != 0)
{
pg_log_error("invalid argument for option %s", "-c");
@@ -253,7 +234,7 @@ main(int argc, char *argv[])
case 'm':
errno = 0;
- set_mxid = strtoul(optarg, &endptr, 0);
+ set_mxid = str2uint64(optarg, &endptr, 0);
if (endptr == optarg || *endptr != ',' || errno != 0)
{
pg_log_error("invalid argument for option %s", "-m");
@@ -261,7 +242,7 @@ main(int argc, char *argv[])
exit(1);
}
- set_oldestmxid = strtoul(endptr + 1, &endptr2, 0);
+ set_oldestmxid = str2uint64(endptr + 1, &endptr2, 0);
if (endptr2 == endptr + 1 || *endptr2 != '\0' || errno != 0)
{
pg_log_error("invalid argument for option %s", "-m");
@@ -287,7 +268,7 @@ main(int argc, char *argv[])
case 'O':
errno = 0;
- set_mxoff = strtoul(optarg, &endptr, 0);
+ set_mxoff = str2uint64(optarg, &endptr, 0);
if (endptr == optarg || *endptr != '\0' || errno != 0)
{
pg_log_error("invalid argument for option %s", "-O");
@@ -448,11 +429,6 @@ main(int argc, char *argv[])
* Adjust fields if required by switches. (Do this now so that printout,
* if any, includes these values.)
*/
- if (set_xid_epoch != -1)
- ControlFile.checkPointCopy.nextXid =
- FullTransactionIdFromEpochAndXid(set_xid_epoch,
- XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-
if (set_oldest_xid != 0)
{
ControlFile.checkPointCopy.oldestXid = set_oldest_xid;
@@ -460,9 +436,7 @@ main(int argc, char *argv[])
}
if (set_xid != 0)
- ControlFile.checkPointCopy.nextXid =
- FullTransactionIdFromEpochAndXid(EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
- set_xid);
+ ControlFile.checkPointCopy.nextXid = FullTransactionIdFromXid(set_xid);
if (set_oldest_commit_ts_xid != 0)
ControlFile.checkPointCopy.oldestCommitTsXid = set_oldest_commit_ts_xid;
@@ -702,7 +676,7 @@ GuessControlValues(void)
ControlFile.checkPointCopy.PrevTimeLineID = 1;
ControlFile.checkPointCopy.fullPageWrites = false;
ControlFile.checkPointCopy.nextXid =
- FullTransactionIdFromEpochAndXid(0, FirstNormalTransactionId);
+ FullTransactionIdFromXid(FirstNormalTransactionId);
ControlFile.checkPointCopy.nextOid = FirstGenbkiObjectId;
ControlFile.checkPointCopy.nextMulti = FirstMultiXactId;
ControlFile.checkPointCopy.nextMultiOffset = 0;
@@ -753,10 +727,17 @@ GuessControlValues(void)
*
* NB: this display should be just those fields that will not be
* reset by RewriteControlFile().
+ *
+ * Special macros help to make translatable strings.
*/
static void
PrintControlValues(bool guessed)
{
+ char xid_str[32];
+
+#define FORMAT_XID(val) \
+ (snprintf(xid_str, sizeof(xid_str), XID_FMT, (val)), xid_str)
+
if (guessed)
printf(_("Guessed pg_control values:\n\n"));
else
@@ -772,29 +753,28 @@ PrintControlValues(bool guessed)
ControlFile.checkPointCopy.ThisTimeLineID);
printf(_("Latest checkpoint's full_page_writes: %s\n"),
ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
- printf(_("Latest checkpoint's NextXID: %u:%u\n"),
- EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
- XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
+ printf(_("Latest checkpoint's NextXID: %s\n"),
+ FORMAT_XID(XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid)));
printf(_("Latest checkpoint's NextOID: %u\n"),
ControlFile.checkPointCopy.nextOid);
- printf(_("Latest checkpoint's NextMultiXactId: %u\n"),
- ControlFile.checkPointCopy.nextMulti);
- printf(_("Latest checkpoint's NextMultiOffset: %u\n"),
+ printf(_("Latest checkpoint's NextMultiXactId: %s\n"),
+ FORMAT_XID(ControlFile.checkPointCopy.nextMulti));
+ printf(_("Latest checkpoint's NextMultiOffset: " INT64_FORMAT "\n"),
ControlFile.checkPointCopy.nextMultiOffset);
- printf(_("Latest checkpoint's oldestXID: %u\n"),
- ControlFile.checkPointCopy.oldestXid);
+ printf(_("Latest checkpoint's oldestXID: %s\n"),
+ FORMAT_XID(ControlFile.checkPointCopy.oldestXid));
printf(_("Latest checkpoint's oldestXID's DB: %u\n"),
ControlFile.checkPointCopy.oldestXidDB);
- printf(_("Latest checkpoint's oldestActiveXID: %u\n"),
- ControlFile.checkPointCopy.oldestActiveXid);
- printf(_("Latest checkpoint's oldestMultiXid: %u\n"),
- ControlFile.checkPointCopy.oldestMulti);
+ printf(_("Latest checkpoint's oldestActiveXID: %s\n"),
+ FORMAT_XID(ControlFile.checkPointCopy.oldestActiveXid));
+ printf(_("Latest checkpoint's oldestMultiXid: %s\n"),
+ FORMAT_XID(ControlFile.checkPointCopy.oldestMulti));
printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
ControlFile.checkPointCopy.oldestMultiDB);
- printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
- ControlFile.checkPointCopy.oldestCommitTsXid);
- printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
- ControlFile.checkPointCopy.newestCommitTsXid);
+ printf(_("Latest checkpoint's oldestCommitTsXid:%s\n"),
+ FORMAT_XID(ControlFile.checkPointCopy.oldestCommitTsXid));
+ printf(_("Latest checkpoint's newestCommitTsXid:%s\n"),
+ FORMAT_XID(ControlFile.checkPointCopy.newestCommitTsXid));
printf(_("Maximum data alignment: %u\n"),
ControlFile.maxAlign);
/* we don't print floatFormat since can't say much useful about it */
@@ -830,6 +810,7 @@ PrintControlValues(bool guessed)
static void
PrintNewControlValues(void)
{
+ char xid_str[32];
char fname[MAXFNAMELEN];
/* This will be always printed in order to keep format same. */
@@ -841,18 +822,18 @@ PrintNewControlValues(void)
if (set_mxid != 0)
{
- printf(_("NextMultiXactId: %u\n"),
- ControlFile.checkPointCopy.nextMulti);
- printf(_("OldestMultiXid: %u\n"),
- ControlFile.checkPointCopy.oldestMulti);
+ printf(_("NextMultiXactId: %s\n"),
+ FORMAT_XID(ControlFile.checkPointCopy.nextMulti));
+ printf(_("OldestMultiXid: %s\n"),
+ FORMAT_XID(ControlFile.checkPointCopy.oldestMulti));
printf(_("OldestMulti's DB: %u\n"),
ControlFile.checkPointCopy.oldestMultiDB);
}
if (set_mxoff != -1)
{
- printf(_("NextMultiOffset: %u\n"),
- ControlFile.checkPointCopy.nextMultiOffset);
+ printf(_("NextMultiOffset: %s\n"),
+ FORMAT_XID(ControlFile.checkPointCopy.nextMultiOffset));
}
if (set_oid != 0)
@@ -863,29 +844,23 @@ PrintNewControlValues(void)
if (set_xid != 0)
{
- printf(_("NextXID: %u\n"),
- XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
- printf(_("OldestXID: %u\n"),
- ControlFile.checkPointCopy.oldestXid);
+ printf(_("NextXID: %s\n"),
+ FORMAT_XID(XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid)));
+ printf(_("OldestXID: %s\n"),
+ FORMAT_XID(ControlFile.checkPointCopy.oldestXid));
printf(_("OldestXID's DB: %u\n"),
ControlFile.checkPointCopy.oldestXidDB);
}
- if (set_xid_epoch != -1)
- {
- printf(_("NextXID epoch: %u\n"),
- EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
- }
-
if (set_oldest_commit_ts_xid != 0)
{
- printf(_("oldestCommitTsXid: %u\n"),
- ControlFile.checkPointCopy.oldestCommitTsXid);
+ printf(_("oldestCommitTsXid: %s\n"),
+ FORMAT_XID(ControlFile.checkPointCopy.oldestCommitTsXid));
}
if (set_newest_commit_ts_xid != 0)
{
- printf(_("newestCommitTsXid: %u\n"),
- ControlFile.checkPointCopy.newestCommitTsXid);
+ printf(_("newestCommitTsXid: %s\n"),
+ FORMAT_XID(ControlFile.checkPointCopy.newestCommitTsXid));
}
if (set_wal_segsize != 0)
@@ -1228,7 +1203,6 @@ usage(void)
" set oldest and newest transactions bearing\n"
" commit timestamp (zero means no change)\n"));
printf(_(" [-D, --pgdata=]DATADIR data directory\n"));
- printf(_(" -e, --epoch=XIDEPOCH set next transaction ID epoch\n"));
printf(_(" -f, --force force update to be done\n"));
printf(_(" -l, --next-wal-file=WALFILE set minimum starting location for new WAL\n"));
printf(_(" -m, --multixact-ids=MXID,MXID set next and oldest multitransaction ID\n"));
@@ -1243,3 +1217,20 @@ usage(void)
printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
}
+
+/*
+ * str2uint64()
+ *
+ * convert string to 64-bit unsigned int
+ */
+static uint64
+str2uint64(const char *str, char **endptr, int base)
+{
+#ifdef _MSC_VER /* MSVC only */
+ return _strtoui64(str, endptr, base);
+#elif defined(HAVE_STRTOULL) && SIZEOF_LONG < 8
+ return strtoull(str, endptr, base);
+#else
+ return strtoul(str, endptr, base);
+#endif
+}
diff --git a/src/bin/pg_upgrade/Makefile b/src/bin/pg_upgrade/Makefile
index 44d06be5a61..77bf5fc955d 100644
--- a/src/bin/pg_upgrade/Makefile
+++ b/src/bin/pg_upgrade/Makefile
@@ -20,6 +20,7 @@ OBJS = \
parallel.o \
pg_upgrade.o \
relfilenode.o \
+ segresize.o \
server.o \
tablespace.o \
util.o \
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 3d218c2ad24..df6d0f5fc9b 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -31,6 +31,7 @@ static void check_for_pg_role_prefix(ClusterInfo *cluster);
static void check_for_new_tablespace_dir(ClusterInfo *new_cluster);
static void check_for_user_defined_encoding_conversions(ClusterInfo *cluster);
static char *get_canonical_locale_name(int category, const char *locale);
+static void check_for_32bit_xid_usage(ClusterInfo *cluster);
/*
@@ -159,6 +160,17 @@ check_and_dump_old_cluster(bool live_check)
if (GET_MAJOR_VERSION(old_cluster.major_version) <= 903)
old_9_3_check_for_line_data_type_usage(&old_cluster);
+ /* Prepare for 64bit xid */
+ if (!ALREADY_64bit_XID(old_cluster))
+ {
+ /* Check if 32-bit xid type is used in tables */
+ check_for_32bit_xid_usage(&old_cluster);
+ /* Check indexes to be upgraded */
+ invalidate_spgist_indexes(&old_cluster, true);
+ invalidate_gin_indexes(&old_cluster, true);
+ invalidate_external_indexes(&old_cluster, true);
+ }
+
/*
* While not a check option, we do this now because this is the only time
* the old server is running.
@@ -233,6 +245,17 @@ issue_warnings_and_set_wal_level(void)
if (GET_MAJOR_VERSION(old_cluster.major_version) <= 906)
old_9_6_invalidate_hash_indexes(&new_cluster, false);
+ /* Raindex for 64bit xid */
+ if (!ALREADY_64bit_XID(old_cluster))
+ {
+ /* Check if 32-bit xid type is used in tables */
+ check_for_32bit_xid_usage(&old_cluster);
+ /* Check indexes to be upgraded */
+ invalidate_spgist_indexes(&old_cluster, true);
+ invalidate_gin_indexes(&old_cluster, true);
+ invalidate_external_indexes(&old_cluster, true);
+ }
+
report_extension_updates(&new_cluster);
stop_postmaster(false);
@@ -1323,3 +1346,94 @@ get_canonical_locale_name(int category, const char *locale)
return res;
}
+
+/*
+ * check_for_32bit_xid_usage()
+ *
+ * Postgres Pro Enterprise changes xid storage format to 64-bit. Check if
+ * xid type is used in tables.
+ */
+static void
+check_for_32bit_xid_usage(ClusterInfo *cluster)
+{
+ int dbnum;
+ FILE *script = NULL;
+ bool found = false;
+ char output_path[MAXPGPATH];
+
+ prep_status("Checking for incompatible \"xid\" data type");
+
+ snprintf(output_path, sizeof(output_path), "tables_using_xid.txt");
+
+ for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
+ {
+ PGresult *res;
+ bool db_used = false;
+ int ntups;
+ int rowno;
+ int i_nspname,
+ i_relname,
+ i_attname;
+ DbInfo *active_db = &cluster->dbarr.dbs[dbnum];
+ PGconn *conn = connectToServer(cluster, active_db->db_name);
+
+ /*
+ * While several relkinds don't store any data, e.g. views, they can
+ * be used to define data types of other columns, so we check all
+ * relkinds.
+ */
+ res = executeQueryOrDie(conn,
+ "SELECT n.nspname, c.relname, a.attname "
+ "FROM pg_catalog.pg_class c, "
+ " pg_catalog.pg_namespace n, "
+ " pg_catalog.pg_attribute a "
+ "WHERE c.oid = a.attrelid AND "
+ " a.attnum >= 1 AND "
+ " a.atttypid = 'pg_catalog.xid'::pg_catalog.regtype AND "
+ " c.relnamespace = n.oid AND "
+ /* exclude possible orphaned temp tables */
+ " n.nspname !~ '^pg_temp_' AND "
+ " n.nspname NOT IN ('pg_catalog', 'information_schema')");
+
+ ntups = PQntuples(res);
+ i_nspname = PQfnumber(res, "nspname");
+ i_relname = PQfnumber(res, "relname");
+ i_attname = PQfnumber(res, "attname");
+ for (rowno = 0; rowno < ntups; rowno++)
+ {
+ found = true;
+ if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
+ pg_fatal("could not open file \"%s\": %s\n",
+ output_path, strerror(errno));
+ if (!db_used)
+ {
+ fprintf(script, "Database: %s\n", active_db->db_name);
+ db_used = true;
+ }
+ fprintf(script, " %s.%s.%s\n",
+ PQgetvalue(res, rowno, i_nspname),
+ PQgetvalue(res, rowno, i_relname),
+ PQgetvalue(res, rowno, i_attname));
+ }
+
+ PQclear(res);
+
+ PQfinish(conn);
+ }
+
+ if (script)
+ fclose(script);
+
+ if (found)
+ {
+ pg_log(PG_REPORT, "fatal\n");
+ pg_fatal("Your installation contains the \"xid\" data type in user tables.\n"
+ "The internal format of \"xid\" changed in Postgres Pro Enterprise so this cluster\n"
+ "cannot currently be upgraded. Note that even dropped attributes cause a problem.\n"
+ "You can remove the problem tables and restart the upgrade.\n"
+ "A list of the problem columns is in the file:\n"
+ " %s\n\n", output_path);
+ }
+ else
+ check_ok();
+}
\ No newline at end of file
diff --git a/src/bin/pg_upgrade/controldata.c b/src/bin/pg_upgrade/controldata.c
index 41b8f69b8cb..09e6efddd3f 100644
--- a/src/bin/pg_upgrade/controldata.c
+++ b/src/bin/pg_upgrade/controldata.c
@@ -269,9 +269,11 @@ get_control_data(ClusterInfo *cluster, bool live_check)
pg_fatal("%d: controldata retrieval problem\n", __LINE__);
p++; /* remove ':' char */
- cluster->controldata.chkpnt_nxtepoch = str2uint(p);
+ cluster->controldata.chkpnt_nxtxid = str2uint64(p);
/*
+ * Try to read 32-bit XID format 'epoch:xid'.
+ *
* Delimiter changed from '/' to ':' in 9.6. We don't test for
* the catalog version of the change because the catalog version
* is pulled from pg_controldata too, and it isn't worth adding an
@@ -284,11 +286,19 @@ get_control_data(ClusterInfo *cluster, bool live_check)
else
p = NULL;
- if (p == NULL || strlen(p) <= 1)
- pg_fatal("%d: controldata retrieval problem\n", __LINE__);
+ if (p)
+ {
+ /* Read lowest 32 bits of xid, epoch was read previously */
+ if (strlen(p) <= 1)
+ pg_fatal("%d: controldata retrieval problem\n", __LINE__);
+
+ p++; /* remove '/' or ':' char */
+
+ Assert((cluster->controldata.chkpnt_nxtxid >> 32) == 0);
+ cluster->controldata.chkpnt_nxtxid <<= 32;
+ cluster->controldata.chkpnt_nxtxid |= str2uint(p);
+ }
- p++; /* remove '/' or ':' char */
- cluster->controldata.chkpnt_nxtxid = str2uint(p);
got_xid = true;
}
else if ((p = strstr(bufin, "Latest checkpoint's NextOID:")) != NULL)
@@ -310,7 +320,7 @@ get_control_data(ClusterInfo *cluster, bool live_check)
pg_fatal("%d: controldata retrieval problem\n", __LINE__);
p++; /* remove ':' char */
- cluster->controldata.chkpnt_nxtmulti = str2uint(p);
+ cluster->controldata.chkpnt_nxtmulti = str2uint64(p);
got_multi = true;
}
else if ((p = strstr(bufin, "Latest checkpoint's oldestXID:")) != NULL)
@@ -321,7 +331,7 @@ get_control_data(ClusterInfo *cluster, bool live_check)
pg_fatal("%d: controldata retrieval problem\n", __LINE__);
p++; /* remove ':' char */
- cluster->controldata.chkpnt_oldstxid = str2uint(p);
+ cluster->controldata.chkpnt_oldstxid = str2uint64(p);
got_oldestxid = true;
}
else if ((p = strstr(bufin, "Latest checkpoint's oldestMultiXid:")) != NULL)
@@ -332,7 +342,7 @@ get_control_data(ClusterInfo *cluster, bool live_check)
pg_fatal("%d: controldata retrieval problem\n", __LINE__);
p++; /* remove ':' char */
- cluster->controldata.chkpnt_oldstMulti = str2uint(p);
+ cluster->controldata.chkpnt_oldstMulti = str2uint64(p);
got_oldestmulti = true;
}
else if ((p = strstr(bufin, "Latest checkpoint's NextMultiOffset:")) != NULL)
@@ -343,7 +353,7 @@ get_control_data(ClusterInfo *cluster, bool live_check)
pg_fatal("%d: controldata retrieval problem\n", __LINE__);
p++; /* remove ':' char */
- cluster->controldata.chkpnt_nxtmxoff = str2uint(p);
+ cluster->controldata.chkpnt_nxtmxoff = str2uint64(p);
got_mxoff = true;
}
else if ((p = strstr(bufin, "First log segment after reset:")) != NULL)
diff --git a/src/bin/pg_upgrade/file.c b/src/bin/pg_upgrade/file.c
index b84868c7510..4ae6a40ae97 100644
--- a/src/bin/pg_upgrade/file.c
+++ b/src/bin/pg_upgrade/file.c
@@ -25,6 +25,7 @@
#include "storage/bufpage.h"
#include "storage/checksum.h"
#include "storage/checksum_impl.h"
+#include "storage/fsm_internals.h"
/*
@@ -152,6 +153,112 @@ linkFile(const char *src, const char *dst,
schemaName, relName, src, dst, strerror(errno));
}
+/* Context for file rewriting */
+typedef struct FileRewriteContext
+{
+ const char *fromfile;
+ const char *tofile;
+ const char *schemaName;
+ const char *relName;
+ int src_fd;
+ int dst_fd;
+ ssize_t src_filesize;
+ ssize_t totalBytesRead;
+ BlockNumber last_blkno;
+ bool old_lastblk;
+} FileRewriteContext;
+
+/* Initialize context for file rewriting */
+static void
+rewriteFileInit(FileRewriteContext *cxt,
+ const char *fromfile, const char *tofile,
+ const char *schemaName, const char *relName)
+{
+ struct stat statbuf;
+
+ cxt->fromfile = fromfile;
+ cxt->tofile = tofile;
+ cxt->schemaName = schemaName;
+ cxt->relName = relName;
+ cxt->totalBytesRead = 0;
+ cxt->last_blkno = InvalidBlockNumber;
+ cxt->old_lastblk = false;
+
+ /* Open old and new files */
+ if ((cxt->src_fd = open(fromfile, O_RDONLY | PG_BINARY, 0)) < 0)
+ pg_fatal("error while copying relation \"%s.%s\": could not open file \"%s\": %s\n",
+ schemaName, relName, fromfile, strerror(errno));
+
+ if (fstat(cxt->src_fd, &statbuf) != 0)
+ pg_fatal("error while copying relation \"%s.%s\": could not stat file \"%s\": %s\n",
+ schemaName, relName, fromfile, strerror(errno));
+
+ if ((cxt->dst_fd = open(tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
+ pg_file_create_mode)) < 0)
+ pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
+ schemaName, relName, tofile, strerror(errno));
+
+ /* Save old file size */
+ cxt->src_filesize = statbuf.st_size;
+}
+
+/* Clean up file rewriting context */
+static void
+rewriteFileCleanup(FileRewriteContext *cxt)
+{
+ close(cxt->dst_fd);
+ close(cxt->src_fd);
+}
+
+/* Read old page of the rewritten file */
+static ssize_t
+rewriteFileReadPage(FileRewriteContext *cxt, Page page)
+{
+ ssize_t bytesRead;
+
+ if (cxt->totalBytesRead >= cxt->src_filesize)
+ return 0;
+
+ if ((bytesRead = read(cxt->src_fd, page, BLCKSZ)) != BLCKSZ)
+ {
+ if (bytesRead < 0)
+ pg_fatal("error while copying relation \"%s.%s\": could not read file \"%s\": %s\n",
+ cxt->schemaName, cxt->relName, cxt->fromfile, strerror(errno));
+ else
+ pg_fatal("error while copying relation \"%s.%s\": partial page found in file \"%s\"\n",
+ cxt->schemaName, cxt->relName, cxt->fromfile);
+ }
+
+ cxt->totalBytesRead += BLCKSZ;
+ cxt->old_lastblk = (cxt->totalBytesRead == cxt->src_filesize);
+
+ return bytesRead;
+}
+
+/* Write new page of the rewritten file */
+static void
+rewriteFileWritePage(FileRewriteContext *cxt, Page page, BlockNumber blkno)
+{
+ /* Set new checksum for page, if enabled */
+ if (new_cluster.controldata.data_checksum_version != 0)
+ ((PageHeader) page)->pd_checksum = pg_checksum_page(page, blkno);
+
+ /* Write page */
+ errno = 0;
+
+ if ((blkno != (cxt->last_blkno == InvalidBlockNumber ? 0 : cxt->last_blkno + 1) &&
+ lseek(cxt->dst_fd, (off_t) BLCKSZ * blkno, SEEK_SET) != (off_t) BLCKSZ * blkno) ||
+ write(cxt->dst_fd, page, BLCKSZ) != BLCKSZ)
+ {
+ /* if write didn't set errno, assume problem is no disk space */
+ if (errno == 0)
+ errno = ENOSPC;
+ pg_fatal("error while copying relation \"%s.%s\": could not write file \"%s\": %s\n",
+ cxt->schemaName, cxt->relName, cxt->tofile, strerror(errno));
+ }
+
+ cxt->last_blkno = blkno;
+}
/*
* rewriteVisibilityMap()
@@ -171,36 +278,19 @@ linkFile(const char *src, const char *dst,
*/
void
rewriteVisibilityMap(const char *fromfile, const char *tofile,
- const char *schemaName, const char *relName)
+ const char *schemaName, const char *relName)
{
- int src_fd;
- int dst_fd;
+ FileRewriteContext cxt;
PGAlignedBlock buffer;
PGAlignedBlock new_vmbuf;
- ssize_t totalBytesRead = 0;
- ssize_t src_filesize;
int rewriteVmBytesPerPage;
BlockNumber new_blkno = 0;
- struct stat statbuf;
+ ssize_t bytesRead;
/* Compute number of old-format bytes per new page */
rewriteVmBytesPerPage = (BLCKSZ - SizeOfPageHeaderData) / 2;
- if ((src_fd = open(fromfile, O_RDONLY | PG_BINARY, 0)) < 0)
- pg_fatal("error while copying relation \"%s.%s\": could not open file \"%s\": %s\n",
- schemaName, relName, fromfile, strerror(errno));
-
- if (fstat(src_fd, &statbuf) != 0)
- pg_fatal("error while copying relation \"%s.%s\": could not stat file \"%s\": %s\n",
- schemaName, relName, fromfile, strerror(errno));
-
- if ((dst_fd = open(tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- pg_file_create_mode)) < 0)
- pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
- schemaName, relName, tofile, strerror(errno));
-
- /* Save old file size */
- src_filesize = statbuf.st_size;
+ rewriteFileInit(&cxt, fromfile, tofile, schemaName, relName);
/*
* Turn each visibility map page into 2 pages one by one. Each new page
@@ -208,27 +298,12 @@ rewriteVisibilityMap(const char *fromfile, const char *tofile,
* last page is empty, we skip it, mostly to avoid turning one-page
* visibility maps for small relations into two pages needlessly.
*/
- while (totalBytesRead < src_filesize)
+ while ((bytesRead = rewriteFileReadPage(&cxt, buffer.data)) > 0)
{
- ssize_t bytesRead;
char *old_cur;
char *old_break;
char *old_blkend;
PageHeaderData pageheader;
- bool old_lastblk;
-
- if ((bytesRead = read(src_fd, buffer.data, BLCKSZ)) != BLCKSZ)
- {
- if (bytesRead < 0)
- pg_fatal("error while copying relation \"%s.%s\": could not read file \"%s\": %s\n",
- schemaName, relName, fromfile, strerror(errno));
- else
- pg_fatal("error while copying relation \"%s.%s\": partial page found in file \"%s\"\n",
- schemaName, relName, fromfile);
- }
-
- totalBytesRead += BLCKSZ;
- old_lastblk = (totalBytesRead == src_filesize);
/* Save the page header data */
memcpy(&pageheader, buffer.data, SizeOfPageHeaderData);
@@ -253,7 +328,7 @@ rewriteVisibilityMap(const char *fromfile, const char *tofile,
memcpy(new_vmbuf.data, &pageheader, SizeOfPageHeaderData);
/* Rewriting the last part of the last old page? */
- old_lastpart = old_lastblk && (old_break == old_blkend);
+ old_lastpart = cxt.old_lastblk && (old_break == old_blkend);
new_cur = new_vmbuf.data + SizeOfPageHeaderData;
@@ -287,20 +362,7 @@ rewriteVisibilityMap(const char *fromfile, const char *tofile,
if (old_lastpart && empty)
break;
- /* Set new checksum for visibility map page, if enabled */
- if (new_cluster.controldata.data_checksum_version != 0)
- ((PageHeader) new_vmbuf.data)->pd_checksum =
- pg_checksum_page(new_vmbuf.data, new_blkno);
-
- errno = 0;
- if (write(dst_fd, new_vmbuf.data, BLCKSZ) != BLCKSZ)
- {
- /* if write didn't set errno, assume problem is no disk space */
- if (errno == 0)
- errno = ENOSPC;
- pg_fatal("error while copying relation \"%s.%s\": could not write file \"%s\": %s\n",
- schemaName, relName, tofile, strerror(errno));
- }
+ rewriteFileWritePage(&cxt, new_vmbuf.data, new_blkno);
/* Advance for next new page */
old_break += rewriteVmBytesPerPage;
@@ -308,9 +370,7 @@ rewriteVisibilityMap(const char *fromfile, const char *tofile,
}
}
- /* Clean up */
- close(dst_fd);
- close(src_fd);
+ rewriteFileCleanup(&cxt);
}
void
@@ -371,4 +431,4 @@ check_hard_link(void)
strerror(errno));
unlink(new_link_file);
-}
+}
\ No newline at end of file
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 07defacd673..bbf83c3af87 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -42,6 +42,9 @@
#include <langinfo.h>
#endif
+#include "access/multixact.h"
+#include "access/transam.h"
+#include "access/xlog_internal.h"
#include "catalog/pg_class_d.h"
#include "common/file_perm.h"
#include "common/logging.h"
@@ -261,7 +264,6 @@ setup(char *argv0, bool *live_check)
}
}
-
static void
prepare_new_cluster(void)
{
@@ -404,11 +406,10 @@ create_new_objects(void)
check_ok();
/*
- * We don't have minmxids for databases or relations in pre-9.3 clusters,
- * so set those after we have restored the schema.
+ * Refix datfrozenxid and datminmxid
*/
- if (GET_MAJOR_VERSION(old_cluster.major_version) <= 902)
- set_frozenxids(true);
+ if (ALREADY_64bit_XID(old_cluster) != ALREADY_64bit_XID(new_cluster))
+ set_frozenxids(false);
/* update new_cluster info now that we have objects in the databases */
get_db_and_rel_infos(&new_cluster);
@@ -462,18 +463,36 @@ copy_subdir_files(const char *old_subdir, const char *new_subdir)
static void
copy_xact_xlog_xid(void)
{
- /*
- * Copy old commit logs to new data dir. pg_clog has been renamed to
- * pg_xact in post-10 clusters.
- */
- copy_subdir_files(GET_MAJOR_VERSION(old_cluster.major_version) <= 906 ?
- "pg_clog" : "pg_xact",
- GET_MAJOR_VERSION(new_cluster.major_version) <= 906 ?
- "pg_clog" : "pg_xact");
+ TransactionId next_xid;
+
+#define GetClogDirName(cluster) \
+ GET_MAJOR_VERSION(cluster.major_version) <= 906 ? "pg_clog" : "pg_xact"
+
+ /* Set next xid to 2^32 if we're upgrading from 32 bit postgres */
+ next_xid = ALREADY_64bit_XID(old_cluster) == ALREADY_64bit_XID(new_cluster) ?
+ old_cluster.controldata.chkpnt_nxtxid :
+ FirstUpgradedTransactionId;
+
+ if (ALREADY_64bit_XID(old_cluster) == ALREADY_64bit_XID(new_cluster))
+ {
+ /*
+ * Copy old commit logs to new data dir. pg_clog has been renamed to
+ * pg_xact in post-10 clusters.
+ */
+ copy_subdir_files(GetClogDirName(old_cluster), GetClogDirName(new_cluster));
+ }
+ else
+ {
+ /* Convert commit logs and copy to the new data dir */
+ prep_status("Transforming commit log segments");
+ convert_clog(psprintf("%s/%s", old_cluster.pgdata, GetClogDirName(old_cluster)),
+ psprintf("%s/%s", new_cluster.pgdata, GetClogDirName(new_cluster)));
+ check_ok();
+ }
prep_status("Setting oldest XID for new cluster");
exec_prog(UTILITY_LOG_FILE, NULL, true, true,
- "\"%s/pg_resetwal\" -f -u %u \"%s\"",
+ "\"%s/pg_resetwal\" -f -u " XID_FMT " \"%s\"",
new_cluster.bindir, old_cluster.controldata.chkpnt_oldstxid,
new_cluster.pgdata);
check_ok();
@@ -481,19 +500,21 @@ copy_xact_xlog_xid(void)
/* set the next transaction id and epoch of the new cluster */
prep_status("Setting next transaction ID and epoch for new cluster");
exec_prog(UTILITY_LOG_FILE, NULL, true, true,
- "\"%s/pg_resetwal\" -f -x %u \"%s\"",
- new_cluster.bindir, old_cluster.controldata.chkpnt_nxtxid,
+ "\"%s/pg_resetwal\" -f -x " XID_FMT " \"%s\"",
+ new_cluster.bindir, next_xid,
new_cluster.pgdata);
+#ifdef NOT_USED
exec_prog(UTILITY_LOG_FILE, NULL, true, true,
"\"%s/pg_resetwal\" -f -e %u \"%s\"",
new_cluster.bindir, old_cluster.controldata.chkpnt_nxtepoch,
new_cluster.pgdata);
+#endif
/* must reset commit timestamp limits also */
exec_prog(UTILITY_LOG_FILE, NULL, true, true,
- "\"%s/pg_resetwal\" -f -c %u,%u \"%s\"",
+ "\"%s/pg_resetwal\" -f -c " XID_FMT "," XID_FMT " \"%s\"",
new_cluster.bindir,
- old_cluster.controldata.chkpnt_nxtxid,
- old_cluster.controldata.chkpnt_nxtxid,
+ next_xid,
+ next_xid,
new_cluster.pgdata);
check_ok();
@@ -506,8 +527,45 @@ copy_xact_xlog_xid(void)
if (old_cluster.controldata.cat_ver >= MULTIXACT_FORMATCHANGE_CAT_VER &&
new_cluster.controldata.cat_ver >= MULTIXACT_FORMATCHANGE_CAT_VER)
{
- copy_subdir_files("pg_multixact/offsets", "pg_multixact/offsets");
- copy_subdir_files("pg_multixact/members", "pg_multixact/members");
+ uint64 oldest_mxid = old_cluster.controldata.chkpnt_oldstMulti;
+ uint64 next_mxid = old_cluster.controldata.chkpnt_nxtmulti;
+ uint64 next_mxoff = old_cluster.controldata.chkpnt_nxtmxoff;
+
+ if (ALREADY_64bit_XID(old_cluster))
+ {
+ copy_subdir_files("pg_multixact/offsets", "pg_multixact/offsets");
+ copy_subdir_files("pg_multixact/members", "pg_multixact/members");
+ }
+ else
+ {
+ MultiXactOffset oldest_mxoff;
+
+ remove_new_subdir("pg_multixact/offsets", false);
+ oldest_mxoff = convert_multixact_offsets("pg_multixact/offsets", "pg_multixact/offsets");
+
+ remove_new_subdir("pg_multixact/members", false);
+ convert_multixact_members("pg_multixact/members", "pg_multixact/members", oldest_mxoff);
+
+ /*
+ * Handle wraparound if we're upgrading from 32 bit postgres.
+ * Invalid 0 mxids/offsets are skipped, so 1 becomes 2^32.
+ */
+ if (oldest_mxoff)
+ {
+ if (next_mxid < oldest_mxid)
+ next_mxid += ((uint64) 1 << 32) - FirstMultiXactId;
+
+ if (next_mxoff < oldest_mxoff)
+ next_mxoff += ((uint64) 1 << 32) - 1;
+
+ /* Offsets and members were rewritten, oldest_mxoff = 1 */
+ next_mxoff -= oldest_mxoff - 1;
+ oldest_mxoff = 1;
+
+ /* Save converted next_mxid for possible usage in set_frozenxids() */
+ old_cluster.controldata.chkpnt_nxtmulti = next_mxid;
+ }
+ }
prep_status("Setting next multixact ID and offset for new cluster");
@@ -516,11 +574,9 @@ copy_xact_xlog_xid(void)
* counters here and the oldest multi present on system.
*/
exec_prog(UTILITY_LOG_FILE, NULL, true, true,
- "\"%s/pg_resetwal\" -O %u -m %u,%u \"%s\"",
+ "\"%s/pg_resetwal\" -O " XID_FMT " -m " XID_FMT "," XID_FMT " \"%s\"",
new_cluster.bindir,
- old_cluster.controldata.chkpnt_nxtmxoff,
- old_cluster.controldata.chkpnt_nxtmulti,
- old_cluster.controldata.chkpnt_oldstMulti,
+ next_mxoff, next_mxid, oldest_mxid,
new_cluster.pgdata);
check_ok();
}
@@ -544,7 +600,7 @@ copy_xact_xlog_xid(void)
* next=MaxMultiXactId, but multixact.c can cope with that just fine.
*/
exec_prog(UTILITY_LOG_FILE, NULL, true, true,
- "\"%s/pg_resetwal\" -m %u,%u \"%s\"",
+ "\"%s/pg_resetwal\" -m " XID_FMT "," XID_FMT " \"%s\"",
new_cluster.bindir,
old_cluster.controldata.chkpnt_nxtmulti + 1,
old_cluster.controldata.chkpnt_nxtmulti,
@@ -594,6 +650,8 @@ set_frozenxids(bool minmxid_only)
int ntups;
int i_datname;
int i_datallowconn;
+ TransactionId frozen_xid;
+ MultiXactId minmxid;
if (!minmxid_only)
prep_status("Setting frozenxid and minmxid counters in new cluster");
@@ -602,18 +660,24 @@ set_frozenxids(bool minmxid_only)
conn_template1 = connectToServer(&new_cluster, "template1");
+ frozen_xid = ALREADY_64bit_XID(old_cluster) == ALREADY_64bit_XID(new_cluster) ?
+ old_cluster.controldata.chkpnt_nxtxid :
+ FirstNormalTransactionId;
+
+ minmxid = old_cluster.controldata.chkpnt_nxtmulti;
+
if (!minmxid_only)
/* set pg_database.datfrozenxid */
PQclear(executeQueryOrDie(conn_template1,
"UPDATE pg_catalog.pg_database "
- "SET datfrozenxid = '%u'",
- old_cluster.controldata.chkpnt_nxtxid));
+ "SET datfrozenxid = '" XID_FMT "'",
+ frozen_xid));
/* set pg_database.datminmxid */
PQclear(executeQueryOrDie(conn_template1,
"UPDATE pg_catalog.pg_database "
- "SET datminmxid = '%u'",
- old_cluster.controldata.chkpnt_nxtmulti));
+ "SET datminmxid = '" XID_FMT "'",
+ minmxid));
/* get database names */
dbres = executeQueryOrDie(conn_template1,
@@ -647,24 +711,24 @@ set_frozenxids(bool minmxid_only)
/* set pg_class.relfrozenxid */
PQclear(executeQueryOrDie(conn,
"UPDATE pg_catalog.pg_class "
- "SET relfrozenxid = '%u' "
+ "SET relfrozenxid = '" XID_FMT "' "
/* only heap, materialized view, and TOAST are vacuumed */
"WHERE relkind IN ("
CppAsString2(RELKIND_RELATION) ", "
CppAsString2(RELKIND_MATVIEW) ", "
CppAsString2(RELKIND_TOASTVALUE) ")",
- old_cluster.controldata.chkpnt_nxtxid));
+ frozen_xid));
/* set pg_class.relminmxid */
PQclear(executeQueryOrDie(conn,
"UPDATE pg_catalog.pg_class "
- "SET relminmxid = '%u' "
+ "SET relminmxid = '" XID_FMT "' "
/* only heap, materialized view, and TOAST are vacuumed */
"WHERE relkind IN ("
CppAsString2(RELKIND_RELATION) ", "
CppAsString2(RELKIND_MATVIEW) ", "
CppAsString2(RELKIND_TOASTVALUE) ")",
- old_cluster.controldata.chkpnt_nxtmulti));
+ minmxid));
PQfinish(conn);
/* Reset datallowconn flag */
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index da6770d0f83..1701d7bb672 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -21,6 +21,7 @@
#define MESSAGE_WIDTH 60
#define GET_MAJOR_VERSION(v) ((v) / 100)
+#define ALREADY_64bit_XID(cluster) (GET_MAJOR_VERSION((cluster).major_version) >= 1500)
/* contains both global db information and CREATE DATABASE commands */
#define GLOBALS_DUMP_FILE "pg_upgrade_dump_globals.sql"
@@ -184,13 +185,13 @@ typedef struct
uint32 ctrl_ver;
uint32 cat_ver;
char nextxlogfile[25];
- uint32 chkpnt_nxtxid;
- uint32 chkpnt_nxtepoch;
+ uint64 chkpnt_nxtxid;
+ uint32 chkpnt_nxtepoch; /* for 32bit xids only */
uint32 chkpnt_nxtoid;
- uint32 chkpnt_nxtmulti;
- uint32 chkpnt_nxtmxoff;
- uint32 chkpnt_oldstMulti;
- uint32 chkpnt_oldstxid;
+ uint64 chkpnt_nxtmulti;
+ uint64 chkpnt_nxtmxoff;
+ uint64 chkpnt_oldstMulti;
+ uint64 chkpnt_oldstxid;
uint32 align;
uint32 blocksz;
uint32 largesz;
@@ -418,6 +419,7 @@ void end_progress_output(void);
void prep_status(const char *fmt,...) pg_attribute_printf(1, 2);
void check_ok(void);
unsigned int str2uint(const char *str);
+uint64 str2uint64(const char *str);
/* version.c */
@@ -436,6 +438,10 @@ void old_9_6_invalidate_hash_indexes(ClusterInfo *cluster,
void old_11_check_for_sql_identifier_data_type_usage(ClusterInfo *cluster);
void report_extension_updates(ClusterInfo *cluster);
+void invalidate_spgist_indexes(ClusterInfo *cluster, bool check_mode);
+void invalidate_gin_indexes(ClusterInfo *cluster, bool check_mode);
+void invalidate_external_indexes(ClusterInfo *cluster, bool check_mode);
+
/* parallel.c */
void parallel_exec_prog(const char *log_file, const char *opt_log_file,
const char *fmt,...) pg_attribute_printf(3, 4);
@@ -443,3 +449,9 @@ void parallel_transfer_all_new_dbs(DbInfoArr *old_db_arr, DbInfoArr *new_db_arr
char *old_pgdata, char *new_pgdata,
char *old_tablespace);
bool reap_child(bool wait_for_child);
+
+/* segresize.c */
+void convert_clog(const char *olddir, const char *newdir);
+MultiXactOffset convert_multixact_offsets(const char *olddir, const char *newdir);
+void convert_multixact_members(const char *olddir, const char *newdir,
+ MultiXactOffset oldest_mxoff);
\ No newline at end of file
diff --git a/src/bin/pg_upgrade/segresize.c b/src/bin/pg_upgrade/segresize.c
new file mode 100644
index 00000000000..dc3eb1f5915
--- /dev/null
+++ b/src/bin/pg_upgrade/segresize.c
@@ -0,0 +1,571 @@
+/*
+ * segresize.c
+ * Segment resize utility
+ *
+ * Copyright (c) 2015-2016, Postgres Professional
+ * src/bin/pg_upgrade/segresize.c
+ */
+
+
+#include "postgres_fe.h"
+
+#include "pg_upgrade.h"
+#include "access/multixact.h"
+#include "access/transam.h"
+
+
+#define OldFileName(path, seg) \
+ psprintf("%s/%04X", path, seg)
+
+#define NewFileName(path, seg) \
+ psprintf("%s/%04X%08X", path, \
+ (uint32) ((seg) >> 32), (uint32) ((seg) & (int64)0xFFFFFFFF))
+
+#define SLRU_PAGES_PER_SEGMENT_OLD 32
+#define SLRU_PAGES_PER_SEGMENT_NEW 2048 /* XXX SLRU_PAGES_PER_SEGMENT */
+
+
+static FILE*
+create_target_file(const char *dir, int64 segno, char **pfn)
+{
+ char *fn;
+ FILE *filedesc;
+
+ fn = NewFileName(dir, segno);
+ filedesc = fopen(fn, "wb");
+
+ if (!filedesc)
+ pg_fatal("Cannot create file: %s", fn);
+
+ if (pfn)
+ {
+ if (*pfn)
+ pfree(*pfn);
+ *pfn = fn;
+ }
+ else
+ pfree(fn);
+
+ return filedesc;
+}
+
+typedef struct SLRUSegmentState
+{
+ const char *dir;
+ char *fn;
+ FILE *file;
+ uint64 segno;
+ uint64 pageno;
+ bool leading_gap;
+} SLRUSegmentState;
+
+static void
+close_segment(SLRUSegmentState *state)
+{
+ if (state->file != NULL)
+ {
+ fclose(state->file);
+ state->file = NULL;
+ }
+
+ if (state->fn)
+ {
+ pfree(state->fn);
+ state->fn = NULL;
+ }
+}
+
+static int
+read_old_segment_page(SLRUSegmentState *state, void *buf, bool *is_empty)
+{
+ size_t len;
+
+ /* Open next segment file, if needed */
+ if (!state->fn)
+ {
+ if (!state->segno)
+ state->leading_gap = true;
+ state->fn = OldFileName(state->dir, (uint32) state->segno);
+ state->file = fopen(state->fn, "rb");
+
+ /* Set position to the needed page */
+ if (state->file && state->pageno > 0)
+ {
+ if (fseek(state->file, state->pageno * BLCKSZ, SEEK_SET))
+ {
+ fclose(state->file);
+ state->file = NULL;
+ }
+ }
+ }
+
+ if (state->file)
+ {
+ /* Segment file do exists, read page from it */
+ state->leading_gap = false;
+
+ len = fread(buf, sizeof(char), BLCKSZ, state->file);
+
+ /* Are we done or was there an error? */
+ if (len <= 0)
+ {
+ if (ferror(state->file))
+ pg_fatal("Error reading file: %s", state->fn);
+
+ if (feof(state->file))
+ {
+ *is_empty = true;
+ len = -1;
+ fclose(state->file);
+ state->file = NULL;
+ }
+ }
+ else
+ *is_empty = false;
+ }
+ else if (!state->leading_gap)
+ {
+ /* We reached the last segment */
+ len = -1;
+ *is_empty = true;
+ }
+ else
+ {
+ /* Skip few first segments if they were frozen and removed */
+ len = BLCKSZ;
+ *is_empty = true;
+ }
+
+ state->pageno++;
+
+ if (state->pageno >= SLRU_PAGES_PER_SEGMENT_OLD)
+ {
+ /* Start new segment */
+ state->segno++;
+ state->pageno = 0;
+ close_segment(state);
+ }
+
+ return (int) len;
+}
+
+static void
+write_new_segment_page(SLRUSegmentState *state, void *buf, bool is_empty)
+{
+ /*
+ * Create a new segment file if we still didn't. Creation is
+ * postponed until the first non-empty page is found. This helps
+ * not to create completely empty segments.
+ */
+ if (!state->file && !is_empty)
+ {
+ state->file = create_target_file(state->dir, state->segno, &state->fn);
+
+ /* Write zeroes to the previously skipped prefix */
+ if (state->pageno > 0)
+ {
+ char zerobuf[BLCKSZ] = {0};
+
+ for (int64 i = 0; i < state->pageno; i++)
+ {
+ if (fwrite(zerobuf, sizeof(char), BLCKSZ, state->file) != BLCKSZ)
+ pg_fatal("Could not write file: %s", state->fn);
+ }
+ }
+ }
+
+ /* Write page to the new segment (if it was created) */
+ if (state->file)
+ {
+ if (is_empty)
+ memset(buf, 0, BLCKSZ);
+
+ if (fwrite(buf, sizeof(char), BLCKSZ, state->file) != BLCKSZ)
+ pg_fatal("Could not write file: %s", state->fn);
+ }
+
+ state->pageno++;
+
+ /*
+ * Did we reach the maximum page number? Then close segment file
+ * and create a new one on the next iteration
+ */
+ if (state->pageno >= SLRU_PAGES_PER_SEGMENT_NEW)
+ {
+ state->segno++;
+ state->pageno = 0;
+ close_segment(state);
+ }
+}
+
+#define CLOG_BITS_PER_XACT 2
+#define CLOG_XACTS_PER_BYTE 4
+#define CLOG_XACTS_PER_PAGE (BLCKSZ * CLOG_XACTS_PER_BYTE)
+
+#define MaxTransactionIdOld ((TransactionId) 0xFFFFFFFF)
+
+/*
+ * Convert pg_xact segments.
+ */
+void
+convert_clog(const char *old_subdir, const char *new_subdir)
+{
+ SLRUSegmentState oldseg = {0};
+ SLRUSegmentState newseg = {0};
+ TransactionId oldest_xid = old_cluster.controldata.chkpnt_oldstxid;
+ TransactionId next_xid = old_cluster.controldata.chkpnt_nxtxid;
+ TransactionId xid;
+ uint64 pageno;
+ char buf[BLCKSZ] = {0};
+
+ oldseg.dir = old_subdir;
+ newseg.dir = new_subdir;
+
+ pageno = oldest_xid / CLOG_XACTS_PER_PAGE;
+
+ oldseg.segno = pageno / SLRU_PAGES_PER_SEGMENT_OLD;
+ oldseg.pageno = pageno % SLRU_PAGES_PER_SEGMENT_OLD;
+
+ newseg.segno = pageno / SLRU_PAGES_PER_SEGMENT_NEW;
+ newseg.pageno = pageno % SLRU_PAGES_PER_SEGMENT_NEW;
+
+ if (next_xid < oldest_xid)
+ next_xid += FirstUpgradedTransactionId; /* wraparound */
+
+ /* Copy xid flags reading only needed segment pages */
+ for (xid = oldest_xid & ~(CLOG_XACTS_PER_PAGE - 1);
+ xid <= ((next_xid - 1) & ~(CLOG_XACTS_PER_PAGE - 1));
+ xid += CLOG_XACTS_PER_PAGE)
+ {
+ bool is_empty;
+ int len;
+
+ /* Handle possible segment wraparound */
+ if (oldseg.segno > MaxTransactionIdOld / CLOG_XACTS_PER_PAGE / SLRU_PAGES_PER_SEGMENT_OLD) {
+ pageno = (MaxTransactionIdOld + 1) / CLOG_XACTS_PER_PAGE;
+
+ Assert(oldseg.segno == pageno / SLRU_PAGES_PER_SEGMENT_OLD);
+ Assert(!oldseg.pageno);
+ Assert(!oldseg.file && !oldseg.fn);
+ oldseg.segno = 0;
+
+ Assert(newseg.segno == pageno / SLRU_PAGES_PER_SEGMENT_NEW);
+ Assert(!newseg.pageno);
+ Assert(!newseg.file);
+ newseg.segno = 0;
+ }
+
+ len = read_old_segment_page(&oldseg, buf, &is_empty);
+
+ /*
+ * Ignore read errors, copy all existing segment pages in the
+ * interesting xid range.
+ */
+ is_empty |= len <= 0;
+
+ if (!is_empty && len < BLCKSZ)
+ memset(&buf[len], 0, BLCKSZ - len);
+
+ write_new_segment_page(&newseg, buf, is_empty);
+ }
+
+ /* Release resources */
+ close_segment(&oldseg);
+ close_segment(&newseg);
+}
+
+typedef uint32 MultiXactIdOld;
+typedef uint64 MultiXactIdNew;
+
+typedef uint32 MultiXactOffsetOld;
+typedef uint64 MultiXactOffsetNew;
+
+#define MaxMultiXactIdOld ((MultiXactIdOld) 0xFFFFFFFF)
+#define MaxMultiXactOffsetOld ((MultiXactOffsetOld) 0xFFFFFFFF)
+
+#define MXACT_OFFSETS_PER_BLOCK_OLD (BLCKSZ / sizeof(MultiXactOffsetOld))
+#define MXACT_OFFSETS_PER_BLOCK_NEW (BLCKSZ / sizeof(MultiXactOffsetNew))
+
+/*
+ * Convert pg_multixact/offsets segments and return oldest mxid offset.
+ */
+MultiXactOffsetNew
+convert_multixact_offsets(const char *old_subdir, const char *new_subdir)
+{
+ SLRUSegmentState oldseg = {0};
+ SLRUSegmentState newseg = {0};
+ MultiXactOffsetOld oldbuf[MXACT_OFFSETS_PER_BLOCK_OLD] = {0};
+ MultiXactOffsetNew newbuf[MXACT_OFFSETS_PER_BLOCK_NEW] = {0};
+ MultiXactOffsetOld oldest_mxoff = 0;
+ MultiXactId oldest_mxid = old_cluster.controldata.chkpnt_oldstMulti;
+ MultiXactId next_mxid = old_cluster.controldata.chkpnt_nxtmulti;
+ MultiXactId mxid;
+ uint64 old_entry;
+ uint64 new_entry;
+ bool oldest_mxoff_known = false;
+
+ oldseg.dir = psprintf("%s/%s", old_cluster.pgdata, old_subdir);
+ newseg.dir = psprintf("%s/%s", new_cluster.pgdata, new_subdir);
+
+ old_entry = oldest_mxid % MXACT_OFFSETS_PER_BLOCK_OLD;
+ oldseg.pageno = oldest_mxid / MXACT_OFFSETS_PER_BLOCK_OLD;
+ oldseg.segno = oldseg.pageno / SLRU_PAGES_PER_SEGMENT_OLD;
+ oldseg.pageno %= SLRU_PAGES_PER_SEGMENT_OLD;
+
+ new_entry = oldest_mxid % MXACT_OFFSETS_PER_BLOCK_NEW;
+ newseg.pageno = oldest_mxid / MXACT_OFFSETS_PER_BLOCK_NEW;
+ newseg.segno = newseg.pageno / SLRU_PAGES_PER_SEGMENT_NEW;
+ newseg.pageno %= SLRU_PAGES_PER_SEGMENT_NEW;
+
+ if (next_mxid < oldest_mxid)
+ next_mxid += (uint64) 1 << 32; /* wraparound */
+
+ prep_status("Converting old %s to new format", old_subdir);
+
+ /* Copy mxid offsets reading only needed segment pages */
+ for (mxid = oldest_mxid; mxid < next_mxid; old_entry = 0)
+ {
+ int oldlen;
+ bool is_empty;
+
+ /* Handle possible segment wraparound */
+ if (oldseg.segno > MaxMultiXactIdOld / MXACT_OFFSETS_PER_BLOCK_OLD / SLRU_PAGES_PER_SEGMENT_OLD) /* 0xFFFF */
+ oldseg.segno = 0;
+
+ oldlen = read_old_segment_page(&oldseg, oldbuf, &is_empty);
+
+ if (oldlen <= 0 || is_empty)
+ {
+ char pageno_str[32];
+
+ snprintf(pageno_str, sizeof(pageno_str), UINT64_FORMAT, oldseg.pageno);
+ pg_fatal("Cannot read page %s from segment: %s\n",
+ pageno_str, oldseg.fn);
+ }
+
+ if (oldlen < BLCKSZ)
+ memset((char *) oldbuf + oldlen, 0, BLCKSZ - oldlen);
+
+ /* Save oldest mxid offset */
+ if (!oldest_mxoff_known)
+ {
+ oldest_mxoff = oldbuf[old_entry];
+ oldest_mxoff_known = true;
+ }
+
+ /* Skip wrapped-around invalid MultiXactIds */
+ if (mxid == (MultiXactId) 1 << 32)
+ {
+ Assert(oldseg.segno == 0);
+ Assert(oldseg.pageno == 1);
+ Assert(old_entry == 0);
+ mxid += FirstMultiXactId;
+ old_entry = FirstMultiXactId;
+ }
+
+ /* Copy entries to the new page */
+ for (; mxid < next_mxid && old_entry < MXACT_OFFSETS_PER_BLOCK_OLD;
+ mxid++, old_entry++)
+ {
+ MultiXactOffsetNew mxoff = oldbuf[old_entry];
+
+ /* Handle possible offset wraparound (1 becomes 2^32) */
+ if (mxoff < oldest_mxoff)
+ mxoff += ((uint64) 1 << 32) - 1;
+
+ /* Subtract oldest_mxoff, so new offsets will start from 1 */
+ newbuf[new_entry++] = mxoff - oldest_mxoff + 1;
+
+ if (new_entry >= MXACT_OFFSETS_PER_BLOCK_NEW)
+ {
+ /* Write new page */
+ write_new_segment_page(&newseg, newbuf, false);
+ new_entry = 0;
+ }
+ }
+ }
+
+ /* Write the last incomplete page */
+ if (new_entry > 0 || oldest_mxid == next_mxid)
+ {
+ memset(&newbuf[new_entry], 0,
+ sizeof(newbuf[0]) * (MXACT_OFFSETS_PER_BLOCK_NEW - new_entry));
+ write_new_segment_page(&newseg, newbuf, false);
+ }
+
+ /* Use next_mxoff as oldest_mxoff, if oldest_mxid == next_mxid */
+ if (!oldest_mxoff_known)
+ {
+ Assert(oldest_mxid == next_mxid);
+ oldest_mxoff = (MultiXactOffsetNew) old_cluster.controldata.chkpnt_nxtmxoff;
+ }
+
+ /* Release resources */
+ close_segment(&oldseg);
+ close_segment(&newseg);
+
+ pfree((char *) oldseg.dir);
+ pfree((char *) newseg.dir);
+
+ check_ok();
+
+ return oldest_mxoff;
+}
+
+typedef uint32 TransactionIdOld;
+typedef uint64 TransactionIdNew;
+
+#define MXACT_MEMBERS_FLAG_BYTES 1
+
+#define MXACT_MEMBERS_PER_GROUP_OLD 4
+#define MXACT_MEMBERS_GROUP_SIZE_OLD (MXACT_MEMBERS_PER_GROUP_OLD * (sizeof(TransactionIdOld) + MXACT_MEMBERS_FLAG_BYTES))
+#define MXACT_MEMBER_GROUPS_PER_PAGE_OLD (BLCKSZ / MXACT_MEMBERS_GROUP_SIZE_OLD)
+#define MXACT_MEMBERS_PER_PAGE_OLD (MXACT_MEMBERS_PER_GROUP_OLD * MXACT_MEMBER_GROUPS_PER_PAGE_OLD)
+#define MXACT_MEMBER_FLAG_BYTES_PER_GROUP_OLD MXACT_MEMBERS_FLAG_BYTES * MXACT_MEMBERS_PER_GROUP_OLD
+
+#define MXACT_MEMBERS_PER_GROUP_NEW 8
+#define MXACT_MEMBERS_GROUP_SIZE_NEW (MXACT_MEMBERS_PER_GROUP_NEW * (sizeof(TransactionIdNew) + MXACT_MEMBERS_FLAG_BYTES))
+#define MXACT_MEMBER_GROUPS_PER_PAGE_NEW (BLCKSZ / MXACT_MEMBERS_GROUP_SIZE_NEW)
+
+/*
+ * Convert pg_multixact/members segments, offsets will start from 1.
+ */
+void
+convert_multixact_members(const char *old_subdir, const char *new_subdir,
+ MultiXactOffset oldest_mxoff)
+{
+ MultiXactOffsetNew next_mxoff = (MultiXactOffsetNew) old_cluster.controldata.chkpnt_nxtmxoff;
+ MultiXactOffsetNew mxoff;
+ SLRUSegmentState oldseg = { 0 };
+ SLRUSegmentState newseg = { 0 };
+ char oldbuf[BLCKSZ] = { 0 };
+ char newbuf[BLCKSZ] = { 0 };
+ int newgroup;
+ int newmember;
+ char *newflag = newbuf;
+ TransactionIdNew *newxid = (TransactionIdNew *)(newflag + MXACT_MEMBERS_FLAG_BYTES * MXACT_MEMBERS_PER_GROUP_NEW);
+ int newidx;
+ int oldidx;
+
+ oldseg.dir = psprintf("%s/%s", old_cluster.pgdata, old_subdir);
+ newseg.dir = psprintf("%s/%s", new_cluster.pgdata, new_subdir);
+
+ prep_status("Converting old %s to new format", old_subdir);
+
+ if (next_mxoff < oldest_mxoff)
+ next_mxoff += (uint64) 1 << 32;
+
+ /* Initialize old starting position */
+ oldidx = oldest_mxoff % MXACT_MEMBERS_PER_PAGE_OLD;
+ oldseg.pageno = oldest_mxoff / MXACT_MEMBERS_PER_PAGE_OLD;
+ oldseg.segno = oldseg.pageno / SLRU_PAGES_PER_SEGMENT_OLD;
+ oldseg.pageno %= SLRU_PAGES_PER_SEGMENT_OLD;
+
+ /* Initialize new starting position (skip invalid zero offset) */
+ newgroup = 0;
+ newidx = 1;
+ newmember = 1;
+ newflag++;
+ newxid++;
+
+ /* Iterate through the original directory */
+ for (mxoff = oldest_mxoff; mxoff < next_mxoff; oldidx = 0)
+ {
+ bool old_is_empty;
+ int oldlen = read_old_segment_page(&oldseg, oldbuf, &old_is_empty);
+ int ngroups;
+ int oldgroup;
+ int oldmember;
+
+ if (old_is_empty || oldlen <= 0)
+ {
+ char pageno_str[32];
+
+ snprintf(pageno_str, sizeof(pageno_str), UINT64_FORMAT, oldseg.pageno);
+ pg_fatal("Cannot read page %s from segment: %s\n",
+ pageno_str, oldseg.fn);
+ }
+
+ if (oldlen < BLCKSZ)
+ {
+ memset(oldbuf + oldlen, 0, BLCKSZ - oldlen);
+ oldlen = BLCKSZ;
+ }
+
+ ngroups = oldlen / MXACT_MEMBERS_GROUP_SIZE_OLD;
+
+ /* Iterate through old member groups */
+ for (oldgroup = oldidx / MXACT_MEMBERS_PER_GROUP_OLD,
+ oldmember = oldidx % MXACT_MEMBERS_PER_GROUP_OLD;
+ oldgroup < ngroups && mxoff < next_mxoff;
+ oldgroup++, oldmember = 0)
+ {
+ char *oldflag = (char *) oldbuf + oldgroup * MXACT_MEMBERS_GROUP_SIZE_OLD;
+ TransactionIdOld *oldxid = (TransactionIdOld *)(oldflag + MXACT_MEMBER_FLAG_BYTES_PER_GROUP_OLD);
+
+ oldxid += oldmember;
+ oldflag += oldmember;
+
+ /* Iterate through old members */
+ for (int j = 0;
+ j < MXACT_MEMBERS_PER_GROUP_OLD && mxoff < next_mxoff;
+ j++)
+ {
+ /* Copy member's xid and flags to the new page */
+ *newflag++ = *oldflag++;
+ *newxid++ = (TransactionIdNew) *oldxid++;
+
+ newidx++;
+ oldidx++;
+ mxoff++;
+
+ if (++newmember >= MXACT_MEMBERS_PER_GROUP_NEW)
+ {
+ /* Start next member group */
+ newmember = 0;
+
+ if (++newgroup >= MXACT_MEMBER_GROUPS_PER_PAGE_NEW)
+ {
+ /* Write current page and start new */
+ newgroup = 0;
+ newidx = 0;
+ write_new_segment_page(&newseg, newbuf, false);
+ memset(newbuf, 0, BLCKSZ);
+ }
+
+ newflag = (char *) newbuf + newgroup * MXACT_MEMBERS_GROUP_SIZE_NEW;
+ newxid = (TransactionIdNew *)(newflag + MXACT_MEMBERS_FLAG_BYTES * MXACT_MEMBERS_PER_GROUP_NEW);
+ }
+
+ /* Handle offset wraparound */
+ if (mxoff > MaxMultiXactOffsetOld)
+ {
+ Assert(mxoff == (uint64) 1 << 32);
+ Assert(oldseg.segno == MaxMultiXactOffsetOld / MXACT_MEMBERS_PER_PAGE_OLD / SLRU_PAGES_PER_SEGMENT_OLD);
+ Assert(oldseg.pageno == MaxMultiXactOffsetOld / MXACT_MEMBERS_PER_PAGE_OLD % SLRU_PAGES_PER_SEGMENT_OLD);
+ Assert(oldmember == MaxMultiXactOffsetOld % MXACT_MEMBERS_PER_PAGE_OLD);
+
+ /* Switch to segment 0000 */
+ close_segment(&oldseg);
+ oldseg.segno = 0;
+ oldseg.pageno = 0;
+
+ oldidx = 1; /* skip invalid zero mxid offset */
+ }
+ }
+ }
+ }
+
+ /* Write last page, unless it is empty */
+ if (newflag > (char *) newbuf || oldest_mxoff == next_mxoff)
+ write_new_segment_page(&newseg, newbuf, false);
+
+ /* Release resources */
+ close_segment(&oldseg);
+ close_segment(&newseg);
+
+ pfree((char *) oldseg.dir);
+ pfree((char *) newseg.dir);
+
+ check_ok();
+}
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index ef328b3062f..f46b4241155 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -24,7 +24,7 @@ standard_initdb() {
# without increasing test runtime, run these tests with a custom setting.
# Also, specify "-A trust" explicitly to suppress initdb's warning.
# --allow-group-access and --wal-segsize have been added in v11.
- "$1" -N --wal-segsize 1 --allow-group-access -A trust
+ "$1" -N --wal-segsize 1 --allow-group-access -A trust -x 21000000000
if [ -n "$TEMP_CONFIG" -a -r "$TEMP_CONFIG" ]
then
cat "$TEMP_CONFIG" >> "$PGDATA/postgresql.conf"
@@ -195,6 +195,12 @@ if "$MAKE" -C "$oldsrc" installcheck-parallel; then
fi
fi
+ psql -X -d regression << EOF
+ CREATE TABLE t1 (id SERIAL NOT NULL PRIMARY KEY, plt text, pln NUMERIC(8, 4));
+ INSERT INTO t1 (plt, pln) SELECT md5(random()::text), random() * 9999 FROM generate_series(1, 1000);
+EOF
+ psql -X -d regression -c"SELECT relfrozenxid, relminmxid FROM pg_class WHERE relname = 't1';" > "$temp_root"/old_xids.txt
+
pg_dumpall $extra_dump_options --no-sync \
-f "$temp_root"/dump1.sql || pg_dumpall1_status=$?
@@ -255,6 +261,17 @@ esac
pg_ctl start -l "$logdir/postmaster2.log" -o "$POSTMASTER_OPTS" -w
+psql -X -d regression -c"SELECT relfrozenxid, relminmxid FROM pg_class WHERE relname = 't1';" > "$temp_root"/new_xids.txt
+
+if diff -u "$temp_root"/new_xids.txt "$temp_root"/old_xids.txt > "$temp_root"/xids.diff; then
+ rm "$temp_root"/xids.diff
+ echo "xids are identical, PASSED"
+else
+ echo "Files $temp_root/new_xids.txt and $temp_root/old_xids.txt differ"
+ echo "See $temp_root/xids.diff"
+ exit 1
+fi
+
pg_dumpall $extra_dump_options --no-sync \
-f "$temp_root"/dump2.sql || pg_dumpall2_status=$?
pg_ctl -m fast stop
diff --git a/src/bin/pg_upgrade/util.c b/src/bin/pg_upgrade/util.c
index d98deb7f24e..02839bec0e2 100644
--- a/src/bin/pg_upgrade/util.c
+++ b/src/bin/pg_upgrade/util.c
@@ -241,3 +241,20 @@ str2uint(const char *str)
{
return strtoul(str, NULL, 10);
}
+
+/*
+ * str2uint64()
+ *
+ * convert string to 64-bit unsigned int
+ */
+uint64
+str2uint64(const char *str)
+{
+#ifdef _MSC_VER /* MSVC only */
+ return _strtoui64(str, NULL, 10);
+#elif defined(HAVE_STRTOULL) && SIZEOF_LONG < 8
+ return strtoull(str, NULL, 10);
+#else
+ return strtoul(str, NULL, 10);
+#endif
+}
diff --git a/src/bin/pg_upgrade/version.c b/src/bin/pg_upgrade/version.c
index c694558c3d6..d3a04373ad0 100644
--- a/src/bin/pg_upgrade/version.c
+++ b/src/bin/pg_upgrade/version.c
@@ -9,6 +9,7 @@
#include "postgres_fe.h"
+#include "access/transam.h"
#include "catalog/pg_class_d.h"
#include "fe_utils/string_utils.h"
#include "pg_upgrade.h"
@@ -238,19 +239,21 @@ old_9_6_check_for_unknown_data_type_usage(ClusterInfo *cluster)
}
/*
- * old_9_6_invalidate_hash_indexes()
- * 9.6 -> 10
- * Hash index binary format has changed from 9.6->10.0
+ * invalidate_indexes()
+ * Invalidates all indexes satisfying given predicate.
*/
-void
-old_9_6_invalidate_hash_indexes(ClusterInfo *cluster, bool check_mode)
+static void
+invalidate_indexes(ClusterInfo *cluster, bool check_mode,
+ const char *name, const char *pred)
{
int dbnum;
FILE *script = NULL;
bool found = false;
- char *output_path = "reindex_hash.sql";
+ char output_path[MAXPGPATH];
+
+ snprintf(output_path, sizeof(output_path), "reindex_%s.sql", name);
- prep_status("Checking for hash indexes");
+ prep_status("Checking for %s indexes", name);
for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
{
@@ -263,18 +266,28 @@ old_9_6_invalidate_hash_indexes(ClusterInfo *cluster, bool check_mode)
DbInfo *active_db = &cluster->dbarr.dbs[dbnum];
PGconn *conn = connectToServer(cluster, active_db->db_name);
- /* find hash indexes */
- res = executeQueryOrDie(conn,
- "SELECT n.nspname, c.relname "
- "FROM pg_catalog.pg_class c, "
- " pg_catalog.pg_index i, "
- " pg_catalog.pg_am a, "
- " pg_catalog.pg_namespace n "
- "WHERE i.indexrelid = c.oid AND "
- " c.relam = a.oid AND "
- " c.relnamespace = n.oid AND "
- " a.amname = 'hash'"
- );
+
+ /*
+ * Find indexes satisfying predicate.
+ *
+ * System indexes (with oids < FirstNormalObjectId) are excluded from
+ * the search as they are recreated in the new cluster during initdb.
+ */
+ res = executeQueryOrDie(
+ conn,
+ "SELECT n.nspname, c.relname, i.indexrelid "
+ "FROM pg_catalog.pg_class c, "
+ " pg_catalog.pg_index i, "
+ " pg_catalog.pg_am a, "
+ " pg_catalog.pg_namespace n "
+ "WHERE i.indexrelid = c.oid AND "
+ " c.relam = a.oid AND "
+ " c.relnamespace = n.oid AND "
+ " i.indexrelid >= '%u'::pg_catalog.oid AND "
+ " %s "
+ "ORDER BY i.indexrelid ASC",
+ FirstNormalObjectId,
+ pred);
ntups = PQntuples(res);
i_nspname = PQfnumber(res, "nspname");
@@ -307,17 +320,26 @@ old_9_6_invalidate_hash_indexes(ClusterInfo *cluster, bool check_mode)
if (!check_mode && db_used)
{
- /* mark hash indexes as invalid */
- PQclear(executeQueryOrDie(conn,
- "UPDATE pg_catalog.pg_index i "
- "SET indisvalid = false "
- "FROM pg_catalog.pg_class c, "
- " pg_catalog.pg_am a, "
- " pg_catalog.pg_namespace n "
- "WHERE i.indexrelid = c.oid AND "
- " c.relam = a.oid AND "
- " c.relnamespace = n.oid AND "
- " a.amname = 'hash'"));
+ /*
+ * Mark indexes satisfying predicate as invalid.
+ *
+ * System indexes (with oids < FirstNormalObjectId) are excluded
+ * from the search (see above).
+ */
+ PQclear(executeQueryOrDie(
+ conn,
+ "UPDATE pg_catalog.pg_index i "
+ "SET indisvalid = false "
+ "FROM pg_catalog.pg_class c, "
+ " pg_catalog.pg_am a, "
+ " pg_catalog.pg_namespace n "
+ "WHERE i.indexrelid = c.oid AND "
+ " c.relam = a.oid AND "
+ " c.relnamespace = n.oid AND "
+ " i.indexrelid >= '%u'::pg_catalog.oid AND "
+ " %s",
+ FirstNormalObjectId,
+ pred));
}
PQfinish(conn);
@@ -331,24 +353,37 @@ old_9_6_invalidate_hash_indexes(ClusterInfo *cluster, bool check_mode)
report_status(PG_WARNING, "warning");
if (check_mode)
pg_log(PG_WARNING, "\n"
- "Your installation contains hash indexes. These indexes have different\n"
+ "Your installation contains %s indexes. These indexes have different\n"
"internal formats between your old and new clusters, so they must be\n"
"reindexed with the REINDEX command. After upgrading, you will be given\n"
- "REINDEX instructions.\n\n");
+ "REINDEX instructions.\n\n",
+ name);
else
pg_log(PG_WARNING, "\n"
- "Your installation contains hash indexes. These indexes have different\n"
+ "Your installation contains %s indexes. These indexes have different\n"
"internal formats between your old and new clusters, so they must be\n"
"reindexed with the REINDEX command. The file\n"
" %s\n"
"when executed by psql by the database superuser will recreate all invalid\n"
"indexes; until then, none of these indexes will be used.\n\n",
+ name,
output_path);
}
else
check_ok();
}
+/*
+ * old_9_6_invalidate_hash_indexes()
+ * 9.6 -> 10
+ * Hash index binary format has changed from 9.6->10.0
+ */
+void
+old_9_6_invalidate_hash_indexes(ClusterInfo *cluster, bool check_mode)
+{
+ invalidate_indexes(cluster, check_mode, "hash", "a.amname = 'hash'");
+}
+
/*
* old_11_check_for_sql_identifier_data_type_usage()
* 11 -> 12
@@ -458,3 +493,36 @@ report_extension_updates(ClusterInfo *cluster)
else
check_ok();
}
+
+/*
+ * invalidate_spgist_indexes()
+ * 32bit -> 64bit
+ * SP-GIST contains xids.
+ */
+void
+invalidate_spgist_indexes(ClusterInfo *cluster, bool check_mode)
+{
+ invalidate_indexes(cluster, check_mode, "spgist", "a.amname = 'spgist'");
+}
+
+/*
+ * invalidate_gin_indexes()
+ * 32bit -> 64bit
+ * Gin indexes contains xids in deleted pages.
+ */
+void
+invalidate_gin_indexes(ClusterInfo *cluster, bool check_mode)
+{
+ invalidate_indexes(cluster, check_mode, "gin", "a.amname = 'gin'");
+}
+
+/*
+ * invalidate_external_indexes()
+ * Generate script to REINDEX non standard external indexes (like RUM etc)
+ */
+void
+invalidate_external_indexes(ClusterInfo *cluster, bool check_mode)
+{
+ invalidate_indexes(cluster, check_mode, "external",
+ "NOT a.amname IN ('btree', 'hash', 'gist', 'gin', 'spgist', 'brin')");
+}
diff --git a/src/bin/pg_verifybackup/t/003_corruption.pl b/src/bin/pg_verifybackup/t/003_corruption.pl
index 53be2efd87d..9681d6d800c 100644
--- a/src/bin/pg_verifybackup/t/003_corruption.pl
+++ b/src/bin/pg_verifybackup/t/003_corruption.pl
@@ -176,7 +176,7 @@ sub mutilate_extra_tablespace_file
sub mutilate_missing_file
{
my ($backup_path) = @_;
- my $pathname = "$backup_path/pg_xact/0000";
+ my $pathname = "$backup_path/pg_xact/0000000048C0";
unlink($pathname) || die "$pathname: $!";
return;
}
diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c
index a6251e1a961..5e143a4a8ea 100644
--- a/src/bin/pg_waldump/pg_waldump.c
+++ b/src/bin/pg_waldump/pg_waldump.c
@@ -490,7 +490,7 @@ XLogDumpDisplayRecord(XLogDumpConfig *config, XLogReaderState *record)
XLogDumpRecordLen(record, &rec_len, &fpi_len);
- printf("rmgr: %-11s len (rec/tot): %6u/%6u, tx: %10u, lsn: %X/%08X, prev %X/%08X, ",
+ printf("rmgr: %-11s len (rec/tot): %6u/%6u, tx: " XID_FMT ", lsn: %X/%08X, prev %X/%08X, ",
desc->rm_name,
rec_len, XLogRecGetTotalLen(record),
XLogRecGetXid(record),
@@ -950,7 +950,7 @@ main(int argc, char **argv)
}
break;
case 'x':
- if (sscanf(optarg, "%u", &config.filter_by_xid) != 1)
+ if (sscanf(optarg, XID_FMT, &config.filter_by_xid) != 1)
{
pg_log_error("could not parse \"%s\" as a transaction ID",
optarg);
diff --git a/src/include/access/clog.h b/src/include/access/clog.h
index 543f2e2643a..73bc172309a 100644
--- a/src/include/access/clog.h
+++ b/src/include/access/clog.h
@@ -31,7 +31,7 @@ typedef int XidStatus;
typedef struct xl_clog_truncate
{
- int pageno;
+ int64 pageno;
TransactionId oldestXact;
Oid oldestXactDb;
} xl_clog_truncate;
diff --git a/src/include/access/commit_ts.h b/src/include/access/commit_ts.h
index 7662f8e1a9c..d2cfb02e145 100644
--- a/src/include/access/commit_ts.h
+++ b/src/include/access/commit_ts.h
@@ -60,7 +60,7 @@ typedef struct xl_commit_ts_set
typedef struct xl_commit_ts_truncate
{
- int pageno;
+ int64 pageno;
TransactionId oldestXid;
} xl_commit_ts_truncate;
diff --git a/src/include/access/ginblock.h b/src/include/access/ginblock.h
index 9347f464f34..4db042c3196 100644
--- a/src/include/access/ginblock.h
+++ b/src/include/access/ginblock.h
@@ -133,8 +133,15 @@ typedef struct GinMetaPageData
* We should reclaim deleted page only once every transaction started before
* its deletion is over.
*/
-#define GinPageGetDeleteXid(page) ( ((PageHeader) (page))->pd_prune_xid )
-#define GinPageSetDeleteXid(page, xid) ( ((PageHeader) (page))->pd_prune_xid = xid)
+#define GinPageGetDeleteXid(page) ( \
+ (((PageHeader) (page))->pd_upper == BLCKSZ - sizeof(GinPageOpaqueData) - sizeof(TransactionId)) ? \
+ *((TransactionId *) ((char *) (page) + BLCKSZ - sizeof(GinPageOpaqueData) - sizeof(TransactionId))) : \
+ InvalidTransactionId )
+#define GinPageSetDeleteXid(page, xid) \
+ do { \
+ ((PageHeader) (page))->pd_upper = BLCKSZ - sizeof(GinPageOpaqueData) - sizeof(TransactionId); \
+ *((TransactionId *) ((char *) (page) + BLCKSZ - sizeof(GinPageOpaqueData) - sizeof(TransactionId))) = xid; \
+ } while (false)
extern bool GinPageIsRecyclable(Page page);
/*
diff --git a/src/include/access/gist.h b/src/include/access/gist.h
index a3337627b8f..41de3052fdd 100644
--- a/src/include/access/gist.h
+++ b/src/include/access/gist.h
@@ -223,7 +223,7 @@ GistPageGetDeleteXid(Page page)
return ((GISTDeletedPageContents *) PageGetContents(page))->deleteXid;
}
else
- return FullTransactionIdFromEpochAndXid(0, FirstNormalTransactionId);
+ return FullTransactionIdFromXid(FirstNormalTransactionId);
}
/*
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 0ad87730e17..ac9201c7e36 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -146,6 +146,10 @@ extern void ReleaseBulkInsertStatePin(BulkInsertState bistate);
extern void heap_insert(Relation relation, HeapTuple tup, CommandId cid,
int options, BulkInsertState bistate);
+extern bool heap_page_prepare_for_xid(Relation relation, Buffer buffer,
+ TransactionId xid, bool multi);
+extern bool rewrite_page_prepare_for_xid(Page page, TransactionId xid,
+ bool multi);
extern void heap_multi_insert(Relation relation, struct TupleTableSlot **slots,
int ntuples, CommandId cid, int options,
BulkInsertState bistate);
@@ -164,10 +168,10 @@ extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
Buffer *buffer, struct TM_FailureData *tmfd);
extern void heap_inplace_update(Relation relation, HeapTuple tuple);
-extern bool heap_freeze_tuple(HeapTupleHeader tuple,
+extern bool heap_freeze_tuple(HeapTuple tuple,
TransactionId relfrozenxid, TransactionId relminmxid,
TransactionId cutoff_xid, TransactionId cutoff_multi);
-extern bool heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
+extern bool heap_tuple_needs_freeze(HeapTuple tuple, TransactionId cutoff_xid,
MultiXactId cutoff_multi, Buffer buf);
extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
@@ -187,11 +191,13 @@ extern int heap_page_prune(Relation relation, Buffer buffer,
TransactionId old_snap_xmin,
TimestampTz old_snap_ts_ts,
int *nnewlpdead,
- OffsetNumber *off_loc);
+ OffsetNumber *off_loc,
+ bool repairFragmentation);
extern void heap_page_prune_execute(Buffer buffer,
OffsetNumber *redirected, int nredirected,
OffsetNumber *nowdead, int ndead,
- OffsetNumber *nowunused, int nunused);
+ OffsetNumber *nowunused, int nunused,
+ bool repairFragmentation);
extern void heap_get_root_tuples(Page page, OffsetNumber *root_offsets);
/* in heap/vacuumlazy.c */
@@ -210,7 +216,7 @@ extern HTSV_Result HeapTupleSatisfiesVacuumHorizon(HeapTuple stup, Buffer buffer
TransactionId *dead_after);
extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
uint16 infomask, TransactionId xid);
-extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple);
+extern bool HeapTupleIsOnlyLocked(HeapTuple htup);
extern bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
extern bool HeapTupleIsSurelyDead(HeapTuple htup,
struct GlobalVisState *vistest);
diff --git a/src/include/access/heapam_xlog.h b/src/include/access/heapam_xlog.h
index 5c47fdcec80..c5a227e364d 100644
--- a/src/include/access/heapam_xlog.h
+++ b/src/include/access/heapam_xlog.h
@@ -59,6 +59,8 @@
#define XLOG_HEAP2_LOCK_UPDATED 0x60
#define XLOG_HEAP2_NEW_CID 0x70
+#define XLOG_HEAP3_BASE_SHIFT 0x00
+
/*
* xl_heap_insert/xl_heap_multi_insert flag values, 8 bits are available.
*/
@@ -389,7 +391,16 @@ typedef struct xl_heap_rewrite_mapping
XLogRecPtr start_lsn; /* Insert LSN at begin of rewrite */
} xl_heap_rewrite_mapping;
-extern void HeapTupleHeaderAdvanceLatestRemovedXid(HeapTupleHeader tuple,
+/* shift the base of xids on heap page */
+typedef struct xl_heap_base_shift
+{
+ int64 delta; /* delta value to shift the base */
+ bool multi; /* true to shift multixact base */
+} xl_heap_base_shift;
+
+#define SizeOfHeapBaseShift (offsetof(xl_heap_base_shift, multi) + sizeof(bool))
+
+extern void HeapTupleHeaderAdvanceLatestRemovedXid(HeapTuple tuple,
TransactionId *latestRemovedXid);
extern void heap_redo(XLogReaderState *record);
@@ -399,19 +410,24 @@ extern void heap_mask(char *pagedata, BlockNumber blkno);
extern void heap2_redo(XLogReaderState *record);
extern void heap2_desc(StringInfo buf, XLogReaderState *record);
extern const char *heap2_identify(uint8 info);
+extern void heap3_redo(XLogReaderState *record);
+extern void heap3_desc(StringInfo buf, XLogReaderState *record);
+extern const char *heap3_identify(uint8 info);
extern void heap_xlog_logical_rewrite(XLogReaderState *r);
extern XLogRecPtr log_heap_freeze(Relation reln, Buffer buffer,
TransactionId cutoff_xid, xl_heap_freeze_tuple *tuples,
int ntuples);
-extern bool heap_prepare_freeze_tuple(HeapTupleHeader tuple,
+extern bool heap_prepare_freeze_tuple(HeapTuple htup,
TransactionId relfrozenxid,
TransactionId relminmxid,
TransactionId cutoff_xid,
TransactionId cutoff_multi,
xl_heap_freeze_tuple *frz,
bool *totally_frozen);
-extern void heap_execute_freeze_tuple(HeapTupleHeader tuple,
+extern void heap_execute_freeze_tuple(HeapTuple tuple,
+ xl_heap_freeze_tuple *xlrec_tp);
+extern void heap_execute_freeze_tuple_page(Page page, HeapTupleHeader tuple,
xl_heap_freeze_tuple *xlrec_tp);
extern XLogRecPtr log_heap_visible(RelFileNode rnode, Buffer heap_buffer,
Buffer vm_buffer, TransactionId cutoff_xid, uint8 flags);
diff --git a/src/include/access/heaptoast.h b/src/include/access/heaptoast.h
index a75699054af..8c599ad7d90 100644
--- a/src/include/access/heaptoast.h
+++ b/src/include/access/heaptoast.h
@@ -22,7 +22,7 @@
*/
#define MaximumBytesPerTuple(tuplesPerPage) \
MAXALIGN_DOWN((BLCKSZ - \
- MAXALIGN(SizeOfPageHeaderData + (tuplesPerPage) * sizeof(ItemIdData))) \
+ MAXALIGN(SizeOfPageHeaderData + (tuplesPerPage) * sizeof(ItemIdData)) - MAXALIGN(sizeof(HeapPageSpecialData))) \
/ (tuplesPerPage))
/*
diff --git a/src/include/access/htup.h b/src/include/access/htup.h
index a4bc7256ed5..6d2d82d1570 100644
--- a/src/include/access/htup.h
+++ b/src/include/access/htup.h
@@ -54,6 +54,10 @@ typedef MinimalTupleData *MinimalTuple;
* this can't be told apart from case #1 by inspection; code setting up
* or destroying this representation has to know what it's doing.
*
+ * t_xid_base and t_multi_base are base values for calculation of transaction
+ * identifiers from t_xmin and t_xmax in heap tuple header. Normally they
+ * are copied from page header or another tuple.
+ *
* t_len should always be valid, except in the pointer-to-nothing case.
* t_self and t_tableOid should be valid if the HeapTupleData points to
* a disk buffer, or if it represents a copy of a tuple on disk. They
@@ -62,9 +66,11 @@ typedef MinimalTupleData *MinimalTuple;
typedef struct HeapTupleData
{
uint32 t_len; /* length of *t_data */
+ TransactionId t_xid_base; /* base value for normal transaction ids */
+ TransactionId t_multi_base; /* base value for mutlixact */
ItemPointerData t_self; /* SelfItemPointer */
Oid t_tableOid; /* table the tuple came from */
-#define FIELDNO_HEAPTUPLEDATA_DATA 3
+#define FIELDNO_HEAPTUPLEDATA_DATA 5
HeapTupleHeader t_data; /* -> tuple header and data */
} HeapTupleData;
@@ -78,12 +84,12 @@ typedef HeapTupleData *HeapTuple;
#define HeapTupleIsValid(tuple) PointerIsValid(tuple)
/* HeapTupleHeader functions implemented in utils/time/combocid.c */
-extern CommandId HeapTupleHeaderGetCmin(HeapTupleHeader tup);
-extern CommandId HeapTupleHeaderGetCmax(HeapTupleHeader tup);
-extern void HeapTupleHeaderAdjustCmax(HeapTupleHeader tup,
+extern CommandId HeapTupleGetCmin(HeapTuple tup);
+extern CommandId HeapTupleGetCmax(HeapTuple tup);
+extern void HeapTupleHeaderAdjustCmax(HeapTuple tup,
CommandId *cmax, bool *iscombo);
/* Prototype for HeapTupleHeader accessors in heapam.c */
-extern TransactionId HeapTupleGetUpdateXid(HeapTupleHeader tuple);
+extern TransactionId HeapTupleGetUpdateXid(HeapTuple tuple);
#endif /* HTUP_H */
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index b2d52ed16c8..e2d77de1436 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -120,13 +120,13 @@
typedef struct HeapTupleFields
{
- TransactionId t_xmin; /* inserting xact ID */
- TransactionId t_xmax; /* deleting or locking xact ID */
+ ShortTransactionId t_xmin; /* inserting xact ID */
+ ShortTransactionId t_xmax; /* deleting or locking xact ID */
union
{
CommandId t_cid; /* inserting or deleting command ID, or both */
- TransactionId t_xvac; /* old-style VACUUM FULL xact ID */
+ ShortTransactionId t_xvac; /* old-style VACUUM FULL xact ID */
} t_field3;
} HeapTupleFields;
@@ -222,7 +222,7 @@ struct HeapTupleHeaderData
* HEAP_XMAX_LOCK_ONLY bit is set; or, for pg_upgrade's sake, if the Xmax is
* not a multi and the EXCL_LOCK bit is set.
*
- * See also HeapTupleHeaderIsOnlyLocked, which also checks for a possible
+ * See also HeapTupleIsOnlyLocked, which also checks for a possible
* aborted updater transaction.
*
* Beware of multiple evaluations of the argument.
@@ -297,6 +297,63 @@ struct HeapTupleHeaderData
* macros evaluate their other argument only once.
*/
+/*
+ * Copy base values for xid and multixacts from page to heap tuple. Should be
+ * called each time tuple is read from page. Otherwise, it would be impossible
+ * to correctly read tuple xmin and xmax.
+ */
+#define HeapTupleCopyBaseFromPage(tup, page) \
+{ \
+ (tup)->t_xid_base = HeapPageGetSpecial(page)->pd_xid_base; \
+ (tup)->t_multi_base = HeapPageGetSpecial(page)->pd_multi_base; \
+}
+
+/*
+ * Copy base values for xid and multixacts from one heap tuple to heap tuple.
+ * Should be called on tuple copy or making desc tuple on the base on src tuple
+ * saving visibility information.
+ */
+#define HeapTupleCopyBase(dest, src) \
+{ \
+ (dest)->t_xid_base = (src)->t_xid_base; \
+ (dest)->t_multi_base = (src)->t_multi_base; \
+}
+
+/*
+ * Set base values for tuple xids/multixacts to zero. Used when visibility
+ * infromation is negligible or will be set later.
+ */
+#define HeapTupleSetZeroBase(tup) \
+{ \
+ (tup)->t_xid_base = InvalidTransactionId; \
+ (tup)->t_multi_base = InvalidTransactionId; \
+}
+
+/*
+ * Macros for accessing "double xmax". On pg_upgraded instances, it might
+ * happend that we can't fit new special area to the page. But we still
+ * might neep to write xmax of tuples for updates and deletes. The trick is
+ * that we actually don't need xmin field. After pg_upgrade (wich implies
+ * restart) no insertions went to this page yet (otherwise special area could
+ * fit). So, if tuple is visible (othewise it would be deleted), then it's
+ * visible for everybody. Thus, t_xmin isn't needed. Therefore, we can use
+ * both t_xmin and t_xmax to store 64-bit xmax.
+ *
+ * See heap_convert.c for details.
+ */
+#define HeapTupleIsDoubleXmax(tup) \
+ ((tup)->t_xid_base == MaxTransactionId)
+
+#define HeapTupleHeaderGetDoubleXmax(tup) \
+ ((TransactionId)(tup)->t_choice.t_heap.t_xmax + \
+ ((TransactionId)(tup)->t_choice.t_heap.t_xmin << 32))
+
+#define HeapTupleHeaderSetDoubleXmax(tup, xid) \
+do { \
+ (tup)->t_choice.t_heap.t_xmax = (TransactionId) (xid) & 0xFFFFFFFF; \
+ (tup)->t_choice.t_heap.t_xmin = ((TransactionId) (xid) >> 32) & 0xFFFFFFFF; \
+} while (0)
+
/*
* HeapTupleHeaderGetRawXmin returns the "raw" xmin field, which is the xid
* originally used to insert the tuple. However, the tuple might actually
@@ -305,20 +362,29 @@ struct HeapTupleHeaderData
* the xmin to FrozenTransactionId, and that value may still be encountered
* on disk.
*/
-#define HeapTupleHeaderGetRawXmin(tup) \
+#define HeapTupleGetRawXmin(tup) \
( \
- (tup)->t_choice.t_heap.t_xmin \
+ HeapTupleIsDoubleXmax(tup) ? \
+ FrozenTransactionId : \
+ ShortTransactionIdToNormal((tup)->t_xid_base, (tup)->t_data->t_choice.t_heap.t_xmin) \
)
-#define HeapTupleHeaderGetXmin(tup) \
+#define HeapTupleGetXmin(tup) \
( \
- HeapTupleHeaderXminFrozen(tup) ? \
- FrozenTransactionId : HeapTupleHeaderGetRawXmin(tup) \
+ HeapTupleHeaderXminFrozen((tup)->t_data) ? \
+ FrozenTransactionId : HeapTupleGetRawXmin(tup) \
)
-#define HeapTupleHeaderSetXmin(tup, xid) \
+#define HeapTupleSetXmin(tup, xid) \
( \
- (tup)->t_choice.t_heap.t_xmin = (xid) \
+ AssertMacro(!HeapTupleIsDoubleXmax(tup)), \
+ (tup)->t_data->t_choice.t_heap.t_xmin = NormalTransactionIdToShort((tup)->t_xid_base, (xid)) \
+)
+
+#define HeapTupleHeaderSetXmin(page, tup, xid) \
+( \
+ AssertMacro(!HeapPageIsDoubleXmax(page)), \
+ (tup)->t_choice.t_heap.t_xmin = NormalTransactionIdToShort(HeapPageGetSpecial(page)->pd_xid_base, (xid)) \
)
#define HeapTupleHeaderXminCommitted(tup) \
@@ -362,30 +428,60 @@ struct HeapTupleHeaderData
* to resolve the MultiXactId if necessary. This might involve multixact I/O,
* so it should only be used if absolutely necessary.
*/
-#define HeapTupleHeaderGetUpdateXid(tup) \
+#define HeapTupleGetUpdateXidAny(tup) \
( \
- (!((tup)->t_infomask & HEAP_XMAX_INVALID) && \
- ((tup)->t_infomask & HEAP_XMAX_IS_MULTI) && \
- !((tup)->t_infomask & HEAP_XMAX_LOCK_ONLY)) ? \
+ (!((tup)->t_data->t_infomask & HEAP_XMAX_INVALID) && \
+ ((tup)->t_data->t_infomask & HEAP_XMAX_IS_MULTI) && \
+ !((tup)->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY)) ? \
HeapTupleGetUpdateXid(tup) \
: \
- HeapTupleHeaderGetRawXmax(tup) \
+ HeapTupleGetRawXmax(tup) \
)
-#define HeapTupleHeaderGetRawXmax(tup) \
+#define HeapTupleGetRawXmax(tup) \
( \
- (tup)->t_choice.t_heap.t_xmax \
+ HeapTupleIsDoubleXmax(tup) ? \
+ HeapTupleHeaderGetDoubleXmax((tup)->t_data) : \
+ ShortTransactionIdToNormal( \
+ ((tup)->t_data->t_infomask & HEAP_XMAX_IS_MULTI) ? (tup)->t_multi_base : (tup)->t_xid_base, \
+ (tup)->t_data->t_choice.t_heap.t_xmax) \
)
-#define HeapTupleHeaderSetXmax(tup, xid) \
+#define HeapTupleHeaderGetRawXmax(page, tup) \
( \
- (tup)->t_choice.t_heap.t_xmax = (xid) \
+ HeapPageIsDoubleXmax(page) ? \
+ HeapTupleHeaderGetDoubleXmax(tup) : \
+ ShortTransactionIdToNormal( \
+ ((tup)->t_infomask & HEAP_XMAX_IS_MULTI) ? HeapPageGetSpecial(page)->pd_multi_base : HeapPageGetSpecial(page)->pd_xid_base, \
+ (tup)->t_choice.t_heap.t_xmax) \
)
+#define HeapTupleSetXmax(tup, xid) \
+do { \
+ if (HeapTupleIsDoubleXmax(tup)) \
+ HeapTupleHeaderSetDoubleXmax((tup)->t_data, (xid)); \
+ else \
+ (tup)->t_data->t_choice.t_heap.t_xmax = \
+ NormalTransactionIdToShort( \
+ ((tup)->t_data->t_infomask & HEAP_XMAX_IS_MULTI) ? (tup)->t_multi_base : (tup)->t_xid_base, \
+ (xid)); \
+} while (0)
+
+#define HeapTupleHeaderSetXmax(page, tup, xid) \
+do { \
+ if (HeapPageIsDoubleXmax(tup)) \
+ HeapTupleHeaderSetDoubleXmax((tup), (xid)); \
+ else \
+ (tup)->t_choice.t_heap.t_xmax = \
+ NormalTransactionIdToShort( \
+ ((tup)->t_infomask & HEAP_XMAX_IS_MULTI) ? HeapPageGetSpecial(page)->pd_multi_base : HeapPageGetSpecial(page)->pd_xid_base, \
+ (xid)); \
+} while (0)
+
/*
* HeapTupleHeaderGetRawCommandId will give you what's in the header whether
- * it is useful or not. Most code should use HeapTupleHeaderGetCmin or
- * HeapTupleHeaderGetCmax instead, but note that those Assert that you can
+ * it is useful or not. Most code should use HeapTupleGetCmin or
+ * HeapTupleGetCmax instead, but note that those Assert that you can
* get a legitimate result, ie you are in the originating transaction!
*/
#define HeapTupleHeaderGetRawCommandId(tup) \
@@ -556,7 +652,7 @@ do { \
* ItemIds and tuples have different alignment requirements, don't assume that
* you can, say, fit 2 tuples of size MaxHeapTupleSize/2 on the same page.
*/
-#define MaxHeapTupleSize (BLCKSZ - MAXALIGN(SizeOfPageHeaderData + sizeof(ItemIdData)))
+#define MaxHeapTupleSize (BLCKSZ - MAXALIGN(SizeOfPageHeaderData + sizeof(ItemIdData)) - MAXALIGN(sizeof(HeapPageSpecialData)))
#define MinHeapTupleSize MAXALIGN(SizeofHeapTupleHeader)
/*
diff --git a/src/include/access/multixact.h b/src/include/access/multixact.h
index a5600a320ae..c474f572e20 100644
--- a/src/include/access/multixact.h
+++ b/src/include/access/multixact.h
@@ -18,16 +18,16 @@
/*
* The first two MultiXactId values are reserved to store the truncation Xid
- * and epoch of the first segment, so we start assigning multixact values from
+ * and base of the first segment, so we start assigning multixact values from
* 2.
*/
-#define InvalidMultiXactId ((MultiXactId) 0)
-#define FirstMultiXactId ((MultiXactId) 1)
-#define MaxMultiXactId ((MultiXactId) 0xFFFFFFFF)
+#define InvalidMultiXactId UINT64CONST(0)
+#define FirstMultiXactId UINT64CONST(1)
+#define MaxMultiXactId UINT64CONST(0xFFFFFFFFFFFFFFFF)
#define MultiXactIdIsValid(multi) ((multi) != InvalidMultiXactId)
-#define MaxMultiXactOffset ((MultiXactOffset) 0xFFFFFFFF)
+#define MaxMultiXactOffset UINT64CONST(0xFFFFFFFFFFFFFFFF)
/* Number of SLRU buffers to use for multixact */
#define NUM_MULTIXACTOFFSET_BUFFERS 8
@@ -114,9 +114,6 @@ extern bool MultiXactIdIsRunning(MultiXactId multi, bool isLockOnly);
extern void MultiXactIdSetOldestMember(void);
extern int GetMultiXactIdMembers(MultiXactId multi, MultiXactMember **xids,
bool allow_old, bool isLockOnly);
-extern bool MultiXactIdPrecedes(MultiXactId multi1, MultiXactId multi2);
-extern bool MultiXactIdPrecedesOrEquals(MultiXactId multi1,
- MultiXactId multi2);
extern int multixactoffsetssyncfiletag(const FileTag *ftag, char *path);
extern int multixactmemberssyncfiletag(const FileTag *ftag, char *path);
@@ -146,7 +143,6 @@ extern void MultiXactSetNextMXact(MultiXactId nextMulti,
extern void MultiXactAdvanceNextMXact(MultiXactId minMulti,
MultiXactOffset minMultiOffset);
extern void MultiXactAdvanceOldest(MultiXactId oldestMulti, Oid oldestMultiDB);
-extern int MultiXactMemberFreezeThreshold(void);
extern void multixact_twophase_recover(TransactionId xid, uint16 info,
void *recdata, uint32 len);
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 9fec6fb1a8a..2d6157f269f 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -62,8 +62,10 @@ typedef uint16 BTCycleId;
typedef struct BTPageOpaqueData
{
BlockNumber btpo_prev; /* left sibling, or P_NONE if leftmost */
+ /* ... or next transaction ID (lower part) */
BlockNumber btpo_next; /* right sibling, or P_NONE if rightmost */
uint32 btpo_level; /* tree level --- zero for leaf pages */
+ /* ... or next transaction ID (lower part) */
uint16 btpo_flags; /* flag bits, see below */
BTCycleId btpo_cycleid; /* vacuum cycle ID of latest split */
} BTPageOpaqueData;
@@ -90,6 +92,14 @@ typedef BTPageOpaqueData *BTPageOpaque;
*/
#define MAX_BT_CYCLE_ID 0xFF7F
+/* Macros for access xact */
+#define BTP_GET_XACT(opaque) (((uint64) ((BTPageOpaque) opaque)->btpo_prev << 32) | \
+ (uint64) ((BTPageOpaque) opaque)->btpo_level)
+#define BTP_SET_XACT(opaque, xact) \
+do { \
+ ((BTPageOpaque) opaque)->btpo_prev = (uint32) (xact >> 32); \
+ ((BTPageOpaque) opaque)->btpo_level = (uint32) xact; \
+} while (0)
/*
* The Meta page is always the first page in the btree index.
diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h
index aa5c48f219a..052789a12d7 100644
--- a/src/include/access/rewriteheap.h
+++ b/src/include/access/rewriteheap.h
@@ -51,7 +51,7 @@ typedef struct LogicalRewriteMappingData
* 6) xid of the xact performing the mapping
* ---
*/
-#define LOGICAL_REWRITE_FORMAT "map-%x-%x-%X_%X-%x-%x"
+#define LOGICAL_REWRITE_FORMAT "map-%x-%x-%X_%X-%x_%x-%x_%x"
void CheckPointLogicalRewriteHeap(void);
#endif /* REWRITE_HEAP_H */
diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 9a74721c97c..bf748ea6cba 100644
--- a/src/include/access/rmgrlist.h
+++ b/src/include/access/rmgrlist.h
@@ -47,3 +47,4 @@ PG_RMGR(RM_COMMIT_TS_ID, "CommitTs", commit_ts_redo, commit_ts_desc, commit_ts_i
PG_RMGR(RM_REPLORIGIN_ID, "ReplicationOrigin", replorigin_redo, replorigin_desc, replorigin_identify, NULL, NULL, NULL, NULL)
PG_RMGR(RM_GENERIC_ID, "Generic", generic_redo, generic_desc, generic_identify, NULL, NULL, generic_mask, NULL)
PG_RMGR(RM_LOGICALMSG_ID, "LogicalMessage", logicalmsg_redo, logicalmsg_desc, logicalmsg_identify, NULL, NULL, NULL, logicalmsg_decode)
+PG_RMGR(RM_HEAP3_ID, "Heap3", heap3_redo, heap3_desc, heap3_identify, NULL, NULL, heap_mask, NULL)
diff --git a/src/include/access/slru.h b/src/include/access/slru.h
index 130c41c8632..bd87d08bee0 100644
--- a/src/include/access/slru.h
+++ b/src/include/access/slru.h
@@ -31,7 +31,7 @@
* take no explicit notice of that fact in slru.c, except when comparing
* segment and page numbers in SimpleLruTruncate (see PagePrecedes()).
*/
-#define SLRU_PAGES_PER_SEGMENT 32
+#define SLRU_PAGES_PER_SEGMENT 2048
/*
* Page status codes. Note that these do not include the "dirty" bit.
@@ -64,7 +64,7 @@ typedef struct SlruSharedData
char **page_buffer;
SlruPageStatus *page_status;
bool *page_dirty;
- int *page_number;
+ int64 *page_number;
int *page_lru_count;
LWLockPadded *buffer_locks;
@@ -95,7 +95,7 @@ typedef struct SlruSharedData
* this is not critical data, since we use it only to avoid swapping out
* the latest page.
*/
- int latest_page_number;
+ int64 latest_page_number;
/* SLRU's index for statistics purposes (might not be unique) */
int slru_stats_idx;
@@ -127,7 +127,7 @@ typedef struct SlruCtlData
* the behavior of this callback has no functional implications.) Use
* SlruPagePrecedesUnitTests() in SLRUs meeting its criteria.
*/
- bool (*PagePrecedes) (int, int);
+ bool (*PagePrecedes) (int64, int64);
/*
* Dir is set during SimpleLruInit and does not change thereafter. Since
@@ -143,10 +143,10 @@ extern Size SimpleLruShmemSize(int nslots, int nlsns);
extern void SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
LWLock *ctllock, const char *subdir, int tranche_id,
SyncRequestHandler sync_handler);
-extern int SimpleLruZeroPage(SlruCtl ctl, int pageno);
-extern int SimpleLruReadPage(SlruCtl ctl, int pageno, bool write_ok,
+extern int SimpleLruZeroPage(SlruCtl ctl, int64 pageno);
+extern int SimpleLruReadPage(SlruCtl ctl, int64 pageno, bool write_ok,
TransactionId xid);
-extern int SimpleLruReadPage_ReadOnly(SlruCtl ctl, int pageno,
+extern int SimpleLruReadPage_ReadOnly(SlruCtl ctl, int64 pageno,
TransactionId xid);
extern void SimpleLruWritePage(SlruCtl ctl, int slotno);
extern void SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied);
@@ -155,20 +155,20 @@ extern void SlruPagePrecedesUnitTests(SlruCtl ctl, int per_page);
#else
#define SlruPagePrecedesUnitTests(ctl, per_page) do {} while (0)
#endif
-extern void SimpleLruTruncate(SlruCtl ctl, int cutoffPage);
-extern bool SimpleLruDoesPhysicalPageExist(SlruCtl ctl, int pageno);
+extern void SimpleLruTruncate(SlruCtl ctl, int64 cutoffPage);
+extern bool SimpleLruDoesPhysicalPageExist(SlruCtl ctl, int64 pageno);
-typedef bool (*SlruScanCallback) (SlruCtl ctl, char *filename, int segpage,
- void *data);
+typedef bool (*SlruScanCallback) (SlruCtl ctl, char *filename, int64 segpage,
+ void *data);
extern bool SlruScanDirectory(SlruCtl ctl, SlruScanCallback callback, void *data);
-extern void SlruDeleteSegment(SlruCtl ctl, int segno);
+extern void SlruDeleteSegment(SlruCtl ctl, int64 segno);
extern int SlruSyncFileTag(SlruCtl ctl, const FileTag *ftag, char *path);
/* SlruScanDirectory public callbacks */
extern bool SlruScanDirCbReportPresence(SlruCtl ctl, char *filename,
- int segpage, void *data);
-extern bool SlruScanDirCbDeleteAll(SlruCtl ctl, char *filename, int segpage,
+ int64 segpage, void *data);
+extern bool SlruScanDirCbDeleteAll(SlruCtl ctl, char *filename, int64 segpage,
void *data);
#endif /* SLRU_H */
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index bb365736b75..15c43e110b0 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -118,7 +118,7 @@ typedef enum TM_Result
* cmax is the outdating command's CID, but only when the failure code is
* TM_SelfModified (i.e., something in the current transaction outdated the
* tuple); otherwise cmax is zero. (We make this restriction because
- * HeapTupleHeaderGetCmax doesn't work for tuples outdated in other
+ * HeapTupleGetCmax doesn't work for tuples outdated in other
* transactions.)
*/
typedef struct TM_FailureData
diff --git a/src/include/access/transam.h b/src/include/access/transam.h
index 338dfca5a0b..56750ee2bba 100644
--- a/src/include/access/transam.h
+++ b/src/include/access/transam.h
@@ -17,6 +17,10 @@
#include "access/xlogdefs.h"
+#ifndef FRONTEND
+#include "utils/elog.h"
+#endif
+
/* ----------------
* Special transaction ID values
*
@@ -28,11 +32,15 @@
* Note: if you need to change it, you must change pg_class.h as well.
* ----------------
*/
-#define InvalidTransactionId ((TransactionId) 0)
-#define BootstrapTransactionId ((TransactionId) 1)
-#define FrozenTransactionId ((TransactionId) 2)
-#define FirstNormalTransactionId ((TransactionId) 3)
-#define MaxTransactionId ((TransactionId) 0xFFFFFFFF)
+#define InvalidTransactionId UINT64CONST(0)
+#define BootstrapTransactionId UINT64CONST(1)
+#define FrozenTransactionId UINT64CONST(2)
+#define FirstNormalTransactionId UINT64CONST(3)
+#define MaxTransactionId UINT64CONST(0xFFFFFFFFFFFFFFFF)
+#define MaxShortTransactionId ((TransactionId) 0x7FFFFFFF)
+
+/* First TransactionId after upgrade from 32bit xid */
+#define FirstUpgradedTransactionId ((TransactionId) 1 << 32)
/* ----------------
* transaction ID manipulation macros
@@ -44,8 +52,37 @@
#define TransactionIdStore(xid, dest) (*(dest) = (xid))
#define StoreInvalidTransactionId(dest) (*(dest) = InvalidTransactionId)
-#define EpochFromFullTransactionId(x) ((uint32) ((x).value >> 32))
-#define XidFromFullTransactionId(x) ((uint32) (x).value)
+/*
+ * Convert short xid from/to full xid. Assertion should fail if we full xid
+ * doesn't fit to xid base.
+ */
+#define ShortTransactionIdToNormal(base, xid) \
+ (TransactionIdIsNormal(xid) ? (TransactionId)(xid) + (base) : (TransactionId)(xid))
+
+#ifdef USE_ASSERT_CHECKING
+static inline ShortTransactionId
+NormalTransactionIdToShort(TransactionId base, TransactionId xid)
+{
+ if (!TransactionIdIsNormal(xid))
+ return (ShortTransactionId)(xid);
+
+#ifndef FRONTEND
+ if (xid < base + FirstNormalTransactionId ||
+ xid > base + MaxShortTransactionId)
+ elog(PANIC, "Xid " XID_FMT " does not fit into valid range for base " XID_FMT, xid, base);
+#endif
+
+ return (ShortTransactionId)(xid - base);
+}
+#else
+#define NormalTransactionIdToShort(base, xid) \
+ (TransactionIdIsNormal(xid) ? (ShortTransactionId)( \
+ AssertMacro((xid) >= (base) + FirstNormalTransactionId), \
+ AssertMacro((xid) <= (base) + MaxShortTransactionId), \
+ (xid) - (base)) : (ShortTransactionId)(xid))
+#endif
+
+#define XidFromFullTransactionId(x) ((x).value)
#define U64FromFullTransactionId(x) ((x).value)
#define FullTransactionIdEquals(a, b) ((a).value == (b).value)
#define FullTransactionIdPrecedes(a, b) ((a).value < (b).value)
@@ -53,8 +90,8 @@
#define FullTransactionIdFollows(a, b) ((a).value > (b).value)
#define FullTransactionIdFollowsOrEquals(a, b) ((a).value >= (b).value)
#define FullTransactionIdIsValid(x) TransactionIdIsValid(XidFromFullTransactionId(x))
-#define InvalidFullTransactionId FullTransactionIdFromEpochAndXid(0, InvalidTransactionId)
-#define FirstNormalFullTransactionId FullTransactionIdFromEpochAndXid(0, FirstNormalTransactionId)
+#define InvalidFullTransactionId FullTransactionIdFromXid(InvalidTransactionId)
+#define FirstNormalFullTransactionId FullTransactionIdFromXid(FirstNormalTransactionId)
#define FullTransactionIdIsNormal(x) FullTransactionIdFollowsOrEquals(x, FirstNormalFullTransactionId)
/*
@@ -68,11 +105,11 @@ typedef struct FullTransactionId
} FullTransactionId;
static inline FullTransactionId
-FullTransactionIdFromEpochAndXid(uint32 epoch, TransactionId xid)
+FullTransactionIdFromXid(TransactionId xid)
{
FullTransactionId result;
- result.value = ((uint64) epoch) << 32 | xid;
+ result.value = xid;
return result;
}
@@ -91,8 +128,7 @@ FullTransactionIdFromU64(uint64 value)
#define TransactionIdAdvance(dest) \
do { \
(dest)++; \
- if ((dest) < FirstNormalTransactionId) \
- (dest) = FirstNormalTransactionId; \
+ Assert((dest) > FirstNormalTransactionId); \
} while(0)
/*
@@ -140,18 +176,19 @@ FullTransactionIdAdvance(FullTransactionId *dest)
/* back up a transaction ID variable, handling wraparound correctly */
#define TransactionIdRetreat(dest) \
do { \
+ Assert((dest) > FirstNormalTransactionId); \
(dest)--; \
- } while ((dest) < FirstNormalTransactionId)
+ } while(0)
/* compare two XIDs already known to be normal; this is a macro for speed */
#define NormalTransactionIdPrecedes(id1, id2) \
(AssertMacro(TransactionIdIsNormal(id1) && TransactionIdIsNormal(id2)), \
- (int32) ((id1) - (id2)) < 0)
+ (int64) ((id1) - (id2)) < 0)
/* compare two XIDs already known to be normal; this is a macro for speed */
#define NormalTransactionIdFollows(id1, id2) \
(AssertMacro(TransactionIdIsNormal(id1) && TransactionIdIsNormal(id2)), \
- (int32) ((id1) - (id2)) > 0)
+ (int64) ((id1) - (id2)) > 0)
/* ----------
* Object ID (OID) zero is InvalidOid.
@@ -221,9 +258,6 @@ typedef struct VariableCacheData
TransactionId oldestXid; /* cluster-wide minimum datfrozenxid */
TransactionId xidVacLimit; /* start forcing autovacuums here */
- TransactionId xidWarnLimit; /* start complaining here */
- TransactionId xidStopLimit; /* refuse to advance nextXid beyond here */
- TransactionId xidWrapLimit; /* where the world ends */
Oid oldestXidDB; /* database with minimum datfrozenxid */
/*
@@ -277,10 +311,6 @@ extern bool TransactionIdIsKnownCompleted(TransactionId transactionId);
extern void TransactionIdCommitTree(TransactionId xid, int nxids, TransactionId *xids);
extern void TransactionIdAsyncCommitTree(TransactionId xid, int nxids, TransactionId *xids, XLogRecPtr lsn);
extern void TransactionIdAbortTree(TransactionId xid, int nxids, TransactionId *xids);
-extern bool TransactionIdPrecedes(TransactionId id1, TransactionId id2);
-extern bool TransactionIdPrecedesOrEquals(TransactionId id1, TransactionId id2);
-extern bool TransactionIdFollows(TransactionId id1, TransactionId id2);
-extern bool TransactionIdFollowsOrEquals(TransactionId id1, TransactionId id2);
extern TransactionId TransactionIdLatest(TransactionId mainxid,
int nxids, const TransactionId *xids);
extern XLogRecPtr TransactionIdGetCommitLSN(TransactionId xid);
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 17a6fa4abdb..ff605c5f0ae 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -226,7 +226,7 @@ typedef struct xl_xact_xinfo
* four so following records don't have to care about alignment. Commit
* records can be large, so copying large portions isn't attractive.
*/
- uint32 xinfo;
+ uint64 xinfo;
} xl_xact_xinfo;
typedef struct xl_xact_dbinfo
@@ -258,7 +258,12 @@ typedef struct xl_xact_invals
typedef struct xl_xact_twophase
{
- TransactionId xid;
+ /*
+ * TransactionId is split into 32-bit parts because
+ * xl_xact_twophase is only int-aligned.
+ */
+ uint32 xid_lo;
+ uint32 xid_hi;
} xl_xact_twophase;
typedef struct xl_xact_origin
@@ -276,7 +281,7 @@ typedef struct xl_xact_commit
/* xl_xact_subxacts follows if XINFO_HAS_SUBXACT */
/* xl_xact_relfilenodes follows if XINFO_HAS_RELFILENODES */
/* xl_xact_invals follows if XINFO_HAS_INVALS */
- /* xl_xact_twophase follows if XINFO_HAS_TWOPHASE */
+ /* xl_xact_twophase follows if XINFO_HAS_TWOPHASE (xid is int-aligned!) */
/* twophase_gid follows if XINFO_HAS_GID. As a null-terminated string. */
/* xl_xact_origin follows if XINFO_HAS_ORIGIN, stored unaligned! */
} xl_xact_commit;
@@ -291,7 +296,7 @@ typedef struct xl_xact_abort
/* xl_xact_subxacts follows if XINFO_HAS_SUBXACT */
/* xl_xact_relfilenodes follows if XINFO_HAS_RELFILENODES */
/* No invalidation messages needed. */
- /* xl_xact_twophase follows if XINFO_HAS_TWOPHASE */
+ /* xl_xact_twophase follows if XINFO_HAS_TWOPHASE (xid is int-aligned!) */
/* twophase_gid follows if XINFO_HAS_GID. As a null-terminated string. */
/* xl_xact_origin follows if XINFO_HAS_ORIGIN, stored unaligned! */
} xl_xact_abort;
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index bb0c52686a5..ab7eeddcb88 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -149,6 +149,14 @@ typedef enum RecoveryPauseState
extern PGDLLIMPORT int wal_level;
+/*
+ * these parameters specifies starting xid, multixact id and multixact offset
+ * for testing 64 bit xids
+ */
+extern TransactionId start_xid;
+extern MultiXactId start_mx_id;
+extern MultiXactOffset start_mx_offset;
+
/* Is WAL archiving enabled (always or only while server is running normally)? */
#define XLogArchivingActive() \
(AssertMacro(XLogArchiveMode == ARCHIVE_MODE_OFF || wal_level >= WAL_LEVEL_REPLICA), XLogArchiveMode > ARCHIVE_MODE_OFF)
diff --git a/src/include/access/xlogreader.h b/src/include/access/xlogreader.h
index 477f0efe26a..945e9eb66e8 100644
--- a/src/include/access/xlogreader.h
+++ b/src/include/access/xlogreader.h
@@ -327,10 +327,6 @@ extern bool DecodeXLogRecord(XLogReaderState *state, XLogRecord *record,
#define XLogRecBlockImageApply(decoder, block_id) \
((decoder)->blocks[block_id].apply_image)
-#ifndef FRONTEND
-extern FullTransactionId XLogRecGetFullXid(XLogReaderState *record);
-#endif
-
extern bool RestoreBlockImage(XLogReaderState *record, uint8 block_id, char *page);
extern char *XLogRecGetBlockData(XLogReaderState *record, uint8 block_id, Size *len);
extern bool XLogRecGetBlockTag(XLogReaderState *record, uint8 block_id,
diff --git a/src/include/c.h b/src/include/c.h
index 4f16e589b3e..cb3c4cdd84d 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -75,6 +75,10 @@
#include <libintl.h>
#endif
+#if HAVE_INTTYPES_H
+#include "inttypes.h"
+#endif
+
/* ----------------------------------------------------------------
* Section 1: compiler characteristics
@@ -483,6 +487,16 @@ typedef unsigned long long int uint64;
#define INT64_FORMAT "%" INT64_MODIFIER "d"
#define UINT64_FORMAT "%" INT64_MODIFIER "u"
+/*
+ * Used to make translatable strings.
+ * Also on macOS externally defined PRIu64 doesn't match postgres int64
+ * definition causing strom of printf warnings. So redefine it anyway. -- sk.
+ */
+#ifdef PRIu64
+#undef PRIu64
+#endif
+#define PRIu64 INT64_MODIFIER "u"
+
/*
* 128-bit signed and unsigned integers
* There currently is only limited support for such types.
@@ -584,19 +598,41 @@ typedef double float8;
typedef Oid regproc;
typedef regproc RegProcedure;
-typedef uint32 TransactionId;
+#define MAX_START_XID UINT64CONST(0x3fffffffffffffff)
+
+typedef uint64 TransactionId;
-typedef uint32 LocalTransactionId;
+#define TransactionIdPrecedes(id1, id2) ((id1) < (id2))
+#define TransactionIdPrecedesOrEquals(id1, id2) ((id1) <= (id2))
+#define TransactionIdFollows(id1, id2) ((id1) > (id2))
+#define TransactionIdFollowsOrEquals(id1, id2) ((id1) >= (id2))
-typedef uint32 SubTransactionId;
+#define StartTransactionIdIsValid(start_xid) ((start_xid) <= MAX_START_XID)
+
+typedef uint32 ShortTransactionId;
+
+typedef uint64 LocalTransactionId;
+
+typedef uint64 SubTransactionId;
#define InvalidSubTransactionId ((SubTransactionId) 0)
#define TopSubTransactionId ((SubTransactionId) 1)
+#define XID_FMT UINT64_FORMAT
+
/* MultiXactId must be equivalent to TransactionId, to fit in t_xmax */
typedef TransactionId MultiXactId;
-typedef uint32 MultiXactOffset;
+#define MultiXactIdPrecedes(id1, id2) ((id1) < (id2))
+#define MultiXactIdPrecedesOrEquals(id1, id2) ((id1) <= (id2))
+#define MultiXactIdFollows(id1, id2) ((id1) > (id2))
+#define MultiXactIdFollowsOrEquals(id1, id2) ((id1) >= (id2))
+
+#define StartMultiXactIdIsValid(start_mx_id) ((start_mx_id) <= MAX_START_XID)
+
+typedef uint64 MultiXactOffset;
+
+#define StartMultiXactOffsetIsValid(start_mx_offset) ((start_mx_offset) <= MAX_START_XID)
typedef uint32 CommandId;
diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat
index 4cc129bebd8..4f209776350 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -403,9 +403,9 @@
amprocrighttype => 'bytea', amprocnum => '2',
amproc => 'hashvarlenaextended' },
{ amprocfamily => 'hash/xid_ops', amproclefttype => 'xid',
- amprocrighttype => 'xid', amprocnum => '1', amproc => 'hashint4' },
+ amprocrighttype => 'xid', amprocnum => '1', amproc => 'hashint8' },
{ amprocfamily => 'hash/xid_ops', amproclefttype => 'xid',
- amprocrighttype => 'xid', amprocnum => '2', amproc => 'hashint4extended' },
+ amprocrighttype => 'xid', amprocnum => '2', amproc => 'hashint8extended' },
{ amprocfamily => 'hash/xid8_ops', amproclefttype => 'xid8',
amprocrighttype => 'xid8', amprocnum => '1', amproc => 'hashint8' },
{ amprocfamily => 'hash/xid8_ops', amproclefttype => 'xid8',
diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h
index 1f3dc24ac11..8ca122777dd 100644
--- a/src/include/catalog/pg_control.h
+++ b/src/include/catalog/pg_control.h
@@ -249,4 +249,10 @@ typedef struct ControlFileData
*/
#define PG_CONTROL_FILE_SIZE 8192
+#define CONTROLFILE_GET_OLDEDITION(control) \
+ ((control)->pg_old_version >> 16)
+
+#define CONTROLFILE_SET_OLDEDITION(control, v) \
+ (control)->pg_old_version = ((v) << 16)
+
#endif /* PG_CONTROL_H */
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index bc5f8213f3a..912ab48ecf4 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -183,16 +183,16 @@
oprresult => 'bool', oprcom => '=(xid,xid)', oprnegate => '<>(xid,xid)',
oprcode => 'xideq', oprrest => 'eqsel', oprjoin => 'eqjoinsel' },
{ oid => '353', descr => 'equal',
- oprname => '=', oprleft => 'xid', oprright => 'int4', oprresult => 'bool',
- oprnegate => '<>(xid,int4)', oprcode => 'xideqint4', oprrest => 'eqsel',
+ oprname => '=', oprleft => 'xid', oprright => 'int8', oprresult => 'bool',
+ oprnegate => '<>(xid,int8)', oprcode => 'xideqint8', oprrest => 'eqsel',
oprjoin => 'eqjoinsel' },
{ oid => '3315', descr => 'not equal',
oprname => '<>', oprleft => 'xid', oprright => 'xid', oprresult => 'bool',
oprcom => '<>(xid,xid)', oprnegate => '=(xid,xid)', oprcode => 'xidneq',
oprrest => 'neqsel', oprjoin => 'neqjoinsel' },
{ oid => '3316', descr => 'not equal',
- oprname => '<>', oprleft => 'xid', oprright => 'int4', oprresult => 'bool',
- oprnegate => '=(xid,int4)', oprcode => 'xidneqint4', oprrest => 'neqsel',
+ oprname => '<>', oprleft => 'xid', oprright => 'int8', oprresult => 'bool',
+ oprnegate => '=(xid,int8)', oprcode => 'xidneqint8', oprrest => 'neqsel',
oprjoin => 'neqjoinsel' },
{ oid => '5068', descr => 'equal',
oprname => '=', oprcanmerge => 't', oprcanhash => 't', oprleft => 'xid8',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0859dc81cac..99e6fb4f752 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -2360,10 +2360,10 @@
{ oid => '1181',
descr => 'age of a transaction ID, in transactions before current transaction',
proname => 'age', provolatile => 's', proparallel => 'r',
- prorettype => 'int4', proargtypes => 'xid', prosrc => 'xid_age' },
+ prorettype => 'int8', proargtypes => 'xid', prosrc => 'xid_age' },
{ oid => '3939',
descr => 'age of a multi-transaction ID, in multi-transactions before current multi-transaction',
- proname => 'mxid_age', provolatile => 's', prorettype => 'int4',
+ proname => 'mxid_age', provolatile => 's', prorettype => 'int8',
proargtypes => 'xid', prosrc => 'mxid_age' },
{ oid => '1188',
@@ -2698,11 +2698,11 @@
prosrc => 'bpcharlen' },
{ oid => '1319',
- proname => 'xideqint4', proleakproof => 't', prorettype => 'bool',
- proargtypes => 'xid int4', prosrc => 'xideq' },
+ proname => 'xideqint8', proleakproof => 't', prorettype => 'bool',
+ proargtypes => 'xid int8', prosrc => 'xideq' },
{ oid => '3309',
- proname => 'xidneqint4', proleakproof => 't', prorettype => 'bool',
- proargtypes => 'xid int4', prosrc => 'xidneq' },
+ proname => 'xidneqint8', proleakproof => 't', prorettype => 'bool',
+ proargtypes => 'xid int8', prosrc => 'xidneq' },
{ oid => '1326',
proname => 'interval_div', prorettype => 'interval',
diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index df458794635..48e864eeb90 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -95,9 +95,9 @@
typinput => 'tidin', typoutput => 'tidout', typreceive => 'tidrecv',
typsend => 'tidsend', typalign => 's' },
{ oid => '28', array_type_oid => '1011', descr => 'transaction id',
- typname => 'xid', typlen => '4', typbyval => 't', typcategory => 'U',
+ typname => 'xid', typlen => '8', typbyval => 'FLOAT8PASSBYVAL', typcategory => 'U',
typinput => 'xidin', typoutput => 'xidout', typreceive => 'xidrecv',
- typsend => 'xidsend', typalign => 'i' },
+ typsend => 'xidsend', typalign => 'd' },
{ oid => '29', array_type_oid => '1012',
descr => 'command identifier type, sequence in transaction id',
typname => 'cid', typlen => '4', typbyval => 't', typcategory => 'U',
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 5d0bdfa4279..98288a48996 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -213,12 +213,12 @@ typedef enum VacOptValue
*/
typedef struct VacuumParams
{
- bits32 options; /* bitmask of VACOPT_* */
- int freeze_min_age; /* min freeze age, -1 to use default */
- int freeze_table_age; /* age at which to scan whole table */
- int multixact_freeze_min_age; /* min multixact freeze age, -1 to
+ bits32 options; /* bitmask of VacuumOption */
+ int64 freeze_min_age; /* min freeze age, -1 to use default */
+ int64 freeze_table_age; /* age at which to scan whole table */
+ int64 multixact_freeze_min_age; /* min multixact freeze age, -1 to
* use default */
- int multixact_freeze_table_age; /* multixact age at which to scan
+ int64 multixact_freeze_table_age; /* multixact age at which to scan
* whole table */
bool is_wraparound; /* force a for-wraparound vacuum */
int log_min_duration; /* minimum execution threshold in ms at
@@ -252,12 +252,12 @@ typedef struct VacDeadItems
/* GUC parameters */
extern PGDLLIMPORT int default_statistics_target; /* PGDLLIMPORT for PostGIS */
-extern int vacuum_freeze_min_age;
-extern int vacuum_freeze_table_age;
-extern int vacuum_multixact_freeze_min_age;
-extern int vacuum_multixact_freeze_table_age;
-extern int vacuum_failsafe_age;
-extern int vacuum_multixact_failsafe_age;
+extern int64 vacuum_freeze_min_age;
+extern int64 vacuum_freeze_table_age;
+extern int64 vacuum_multixact_freeze_min_age;
+extern int64 vacuum_multixact_freeze_table_age;
+extern int64 vacuum_failsafe_age;
+extern int64 vacuum_multixact_failsafe_age;
/* Variables for cost-based parallel vacuum */
extern pg_atomic_uint32 *VacuumSharedCostBalance;
@@ -285,9 +285,9 @@ extern void vac_update_relstats(Relation relation,
MultiXactId minmulti,
bool in_outer_xact);
extern void vacuum_set_xid_limits(Relation rel,
- int freeze_min_age, int freeze_table_age,
- int multixact_freeze_min_age,
- int multixact_freeze_table_age,
+ int64 freeze_min_age, int64 freeze_table_age,
+ int64 multixact_freeze_min_age,
+ int64 multixact_freeze_table_age,
TransactionId *oldestXmin,
TransactionId *freezeLimit,
TransactionId *xidFullScanLimit,
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 6560e462d66..0eee14afd70 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -281,6 +281,7 @@ extern struct varlena *pg_detoast_datum_packed(struct varlena *datum);
#define PG_GETARG_FLOAT4(n) DatumGetFloat4(PG_GETARG_DATUM(n))
#define PG_GETARG_FLOAT8(n) DatumGetFloat8(PG_GETARG_DATUM(n))
#define PG_GETARG_INT64(n) DatumGetInt64(PG_GETARG_DATUM(n))
+#define PG_GETARG_TRANSACTIONID(n) DatumGetTransactionId(PG_GETARG_DATUM(n))
/* use this if you want the raw, possibly-toasted input datum: */
#define PG_GETARG_RAW_VARLENA_P(n) ((struct varlena *) PG_GETARG_POINTER(n))
/* use this if you want the input datum de-toasted: */
@@ -367,6 +368,7 @@ extern struct varlena *pg_detoast_datum_packed(struct varlena *datum);
#define PG_RETURN_FLOAT8(x) return Float8GetDatum(x)
#define PG_RETURN_INT64(x) return Int64GetDatum(x)
#define PG_RETURN_UINT64(x) return UInt64GetDatum(x)
+#define PG_RETURN_TRANSACTIONID(x) return TransactionIdGetDatum(x)
/* RETURN macros for other pass-by-ref types will typically look like this: */
#define PG_RETURN_BYTEA_P(x) PG_RETURN_POINTER(x)
#define PG_RETURN_TEXT_P(x) PG_RETURN_POINTER(x)
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 9d9bd6b9efc..50804edbcc5 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -965,6 +965,9 @@
# endif
#endif
+/* Postgres Pro use 64bit xids */
+#undef XID_IS_64BIT
+
/* Size of a WAL file block. This need have no particular relation to BLCKSZ.
XLOG_BLCKSZ must be a power of 2, and if your system supports O_DIRECT I/O,
XLOG_BLCKSZ must be a multiple of the alignment requirement for direct-I/O
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 31358110dca..e8b4b1f9dfb 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -555,21 +555,21 @@ typedef struct NullableDatum
* Returns transaction identifier value of a datum.
*/
-#define DatumGetTransactionId(X) ((TransactionId) (X))
+#define DatumGetTransactionId(X) (DatumGetUInt64(X))
/*
* TransactionIdGetDatum
* Returns datum representation for a transaction identifier.
*/
-#define TransactionIdGetDatum(X) ((Datum) (X))
+#define TransactionIdGetDatum(X) (UInt64GetDatum(X))
/*
* MultiXactIdGetDatum
* Returns datum representation for a multixact identifier.
*/
-#define MultiXactIdGetDatum(X) ((Datum) (X))
+#define MultiXactIdGetDatum(X) (UInt64GetDatum(X))
/*
* DatumGetCommandId
diff --git a/src/include/postmaster/autovacuum.h b/src/include/postmaster/autovacuum.h
index 30a2b3274f8..099bd827baa 100644
--- a/src/include/postmaster/autovacuum.h
+++ b/src/include/postmaster/autovacuum.h
@@ -37,8 +37,8 @@ extern int autovacuum_vac_ins_thresh;
extern double autovacuum_vac_ins_scale;
extern int autovacuum_anl_thresh;
extern double autovacuum_anl_scale;
-extern int autovacuum_freeze_max_age;
-extern int autovacuum_multixact_freeze_max_age;
+extern int64 autovacuum_freeze_max_age;
+extern int64 autovacuum_multixact_freeze_max_age;
extern double autovacuum_vac_cost_delay;
extern int autovacuum_vac_cost_limit;
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index dd01841c300..d649574e3b1 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -245,6 +245,8 @@ extern void TestForOldSnapshot_impl(Snapshot snapshot, Relation relation);
extern BufferAccessStrategy GetAccessStrategy(BufferAccessStrategyType btype);
extern void FreeAccessStrategy(BufferAccessStrategy strategy);
+/* old tuple format support */
+extern void convert_page(Relation rel, Page orig_page, Buffer buf, BlockNumber blkno);
/* inline functions */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index e9f253f2c8a..63837e86411 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -159,12 +159,84 @@ typedef struct PageHeaderData
LocationIndex pd_upper; /* offset to end of free space */
LocationIndex pd_special; /* offset to start of special space */
uint16 pd_pagesize_version;
- TransactionId pd_prune_xid; /* oldest prunable XID, or zero if none */
+ ShortTransactionId pd_prune_xid; /* oldest prunable XID, or zero if none */
ItemIdData pd_linp[FLEXIBLE_ARRAY_MEMBER]; /* line pointer array */
} PageHeaderData;
typedef PageHeaderData *PageHeader;
+
+/*
+ * HeapPageSpecialData -- data that stored at the end of each heap page.
+ *
+ * pd_xid_base - base value for transaction IDs on page
+ * pd_multi_base - base value for multixact IDs on page
+ *
+ * pd_xid_base and pd_multi_base are base values for calculation of transaction
+ * identifiers from t_xmin and t_xmax in each heap tuple header on the page.
+ */
+typedef struct HeapPageSpecialData
+{
+ TransactionId pd_xid_base; /* base value for transaction IDs on page */
+ TransactionId pd_multi_base; /* base value for multixact IDs on page */
+} HeapPageSpecialData;
+
+#define SizeOfPageSpecial MAXALIGN(sizeof(HeapPageSpecialData))
+
+typedef HeapPageSpecialData *HeapPageSpecial;
+
+extern PGDLLIMPORT HeapPageSpecial doubleXmaxSpecial;
+
+/*
+ * Get pointer to HeapPageSpecialData without using pd_special of the page
+ * (for the sake of speed) assuming all heap pages have same size of special
+ * data.
+ *
+ * Return doubleXmaxSpecial when pd_special == BLCKSZ. See comment in bufpage.c
+ * for details.
+ */
+#define HeapPageGetSpecial(page) ( \
+ (((PageHeader) (page))->pd_special == BLCKSZ) ? \
+ ((HeapPageSpecial) doubleXmaxSpecial) : \
+ (AssertMacro(((PageHeader) (page))->pd_special == BLCKSZ - MAXALIGN(sizeof(HeapPageSpecialData))), \
+ (HeapPageSpecial) ((Pointer) (page) + BLCKSZ - MAXALIGN(sizeof(HeapPageSpecialData)))) \
+)
+
+/*
+ * Version of HeapPageGetSpecial() without assertions about pd_special. Used
+ * for non-consistent reads from non-locked pages.
+ */
+#define HeapPageGetSpecialNoAssert(page) ( \
+ (((PageHeader) (page))->pd_special == BLCKSZ) ? \
+ ((HeapPageSpecial) doubleXmaxSpecial) : \
+ (HeapPageSpecial) ((Pointer) (page) + BLCKSZ - MAXALIGN(sizeof(HeapPageSpecialData))) \
+)
+
+ShortTransactionId HeapPageSetPruneXidInternal(Page page, TransactionId xid);
+
+#define HeapPageSetPruneXid(page, xid) \
+ HeapPageSetPruneXidInternal((Page)(page), xid)
+
+#define HeapPageGetPruneXid(page) \
+( \
+ ShortTransactionIdToNormal(HeapPageGetSpecial(page)->pd_xid_base, ((PageHeader) (page))->pd_prune_xid) \
+)
+
+/*
+ * Read pd_prune_xid from non-locked page. May return invalid value, but doen't
+ * causes assert failures.
+ */
+#define HeapPageGetPruneXidNoAssert(page) \
+( \
+ ShortTransactionIdToNormal(HeapPageGetSpecialNoAssert(page)->pd_xid_base, ((PageHeader) (page))->pd_prune_xid) \
+)
+
+#define XidFitsPage(page, xid) \
+( \
+ (xid) >= HeapPageGetSpecial(page)->pd_xid_base + FirstNormalTransactionId && \
+ (xid) <= HeapPageGetSpecial(page)->pd_xid_base + MaxShortTransactionId \
+)
+
/*
* pd_flags contains the following flag bits. Undefined bits are initialized
* to zero and may be used in the future.
@@ -192,11 +264,13 @@ typedef PageHeaderData *PageHeader;
* Release 8.3 uses 4; it changed the HeapTupleHeader layout again, and
* added the pd_flags field (by stealing some bits from pd_tli),
* as well as adding the pd_prune_xid field (which enlarges the header).
+ * PgPro Enterprise 10 uses version number (0x00FF - 1), and should not
+ * collide with vanilla versions due to page conversion after pg_upgrade.
*
* As of Release 9.3, the checksum version must also be considered when
* handling pages.
*/
-#define PG_PAGE_LAYOUT_VERSION 4
+#define PG_PAGE_LAYOUT_VERSION 5
#define PG_DATA_CHECKSUM_VERSION 1
/* ----------------------------------------------------------------
@@ -389,16 +463,18 @@ PageValidateSpecialPointer(Page page)
#define PageClearAllVisible(page) \
(((PageHeader) (page))->pd_flags &= ~PD_ALL_VISIBLE)
+/* Check if page is in "double xmax" format */
+#define HeapPageIsDoubleXmax(page) \
+ (((PageHeader) (page))->pd_special == BLCKSZ)
+
#define PageSetPrunable(page, xid) \
do { \
Assert(TransactionIdIsNormal(xid)); \
- if (!TransactionIdIsValid(((PageHeader) (page))->pd_prune_xid) || \
- TransactionIdPrecedes(xid, ((PageHeader) (page))->pd_prune_xid)) \
- ((PageHeader) (page))->pd_prune_xid = (xid); \
+ if (!HeapPageIsDoubleXmax(page) && \
+ (!TransactionIdIsValid(HeapPageGetPruneXid(page)) || \
+ TransactionIdPrecedes(xid, HeapPageGetPruneXid(page)))) \
+ HeapPageSetPruneXid(page, xid); \
} while (0)
-#define PageClearPrunable(page) \
- (((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
-
/* ----------------------------------------------------------------
* extern declarations
@@ -432,6 +508,19 @@ do { \
StaticAssertDecl(BLCKSZ == ((BLCKSZ / sizeof(size_t)) * sizeof(size_t)),
"BLCKSZ has to be a multiple of sizeof(size_t)");
+/*
+ * Tuple defrag support for PageRepairFragmentation and PageIndexMultiDelete
+ */
+typedef struct itemIdCompactData
+{
+ uint16 offsetindex; /* linp array index */
+ int16 itemoff; /* page offset of item data */
+ uint16 alignedlen; /* MAXALIGN(item data len) */
+} itemIdCompactData;
+typedef itemIdCompactData *itemIdCompact;
+
+extern int itemoffcompare(const void *item1, const void *item2);
+
extern void PageInit(Page page, Size pageSize, Size specialSize);
extern bool PageIsVerifiedExtended(Page page, BlockNumber blkno, int flags);
extern OffsetNumber PageAddItemExtended(Page page, Item item, Size size,
diff --git a/src/include/storage/itemid.h b/src/include/storage/itemid.h
index e33637ff21e..442a72d658d 100644
--- a/src/include/storage/itemid.h
+++ b/src/include/storage/itemid.h
@@ -78,6 +78,8 @@ typedef uint16 ItemLength;
#define ItemIdGetRedirect(itemId) \
((itemId)->lp_off)
+#define ItemIdGetTupleEnd(itemId) \
+ (MAXALIGN(ItemIdGetLength((itemId))) + ItemIdGetOffset((itemId)))
/*
* ItemIdIsValid
* True iff item identifier is valid.
diff --git a/src/include/storage/lock.h b/src/include/storage/lock.h
index dc537e20f2c..c4d2d8a1222 100644
--- a/src/include/storage/lock.h
+++ b/src/include/storage/lock.h
@@ -227,8 +227,8 @@ typedef struct LOCKTAG
/* ID info for a transaction is its TransactionId */
#define SET_LOCKTAG_TRANSACTION(locktag,xid) \
- ((locktag).locktag_field1 = (xid), \
- (locktag).locktag_field2 = 0, \
+ ((locktag).locktag_field1 = (uint32)((xid) & 0xFFFFFFFF), \
+ (locktag).locktag_field2 = (uint32)((xid) >> 32), \
(locktag).locktag_field3 = 0, \
(locktag).locktag_field4 = 0, \
(locktag).locktag_type = LOCKTAG_TRANSACTION, \
@@ -237,8 +237,8 @@ typedef struct LOCKTAG
/* ID info for a virtual transaction is its VirtualTransactionId */
#define SET_LOCKTAG_VIRTUALTRANSACTION(locktag,vxid) \
((locktag).locktag_field1 = (vxid).backendId, \
- (locktag).locktag_field2 = (vxid).localTransactionId, \
- (locktag).locktag_field3 = 0, \
+ (locktag).locktag_field2 = (uint32)((vxid).localTransactionId & 0xFFFFFFFF), \
+ (locktag).locktag_field3 = (uint32)((vxid).localTransactionId >> 32), \
(locktag).locktag_field4 = 0, \
(locktag).locktag_type = LOCKTAG_VIRTUALTRANSACTION, \
(locktag).locktag_lockmethodid = DEFAULT_LOCKMETHOD)
@@ -248,9 +248,9 @@ typedef struct LOCKTAG
* its speculative insert counter.
*/
#define SET_LOCKTAG_SPECULATIVE_INSERTION(locktag,xid,token) \
- ((locktag).locktag_field1 = (xid), \
- (locktag).locktag_field2 = (token), \
- (locktag).locktag_field3 = 0, \
+ ((locktag).locktag_field1 = (uint32)((xid) & 0xFFFFFFFF), \
+ (locktag).locktag_field2 = (uint32)((xid) >> 32), \
+ (locktag).locktag_field3 = (token), \
(locktag).locktag_field4 = 0, \
(locktag).locktag_type = LOCKTAG_SPECULATIVE_TOKEN, \
(locktag).locktag_lockmethodid = DEFAULT_LOCKMETHOD)
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index a58888f9e90..e86edd66a63 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -238,7 +238,7 @@ struct PGPROC
TransactionId clogGroupMemberXid; /* transaction id of clog group member */
XidStatus clogGroupMemberXidStatus; /* transaction status of clog
* group member */
- int clogGroupMemberPage; /* clog page corresponding to
+ int64 clogGroupMemberPage; /* clog page corresponding to
* transaction id of clog group member */
XLogRecPtr clogGroupMemberLsn; /* WAL location of commit record for clog
* group member */
diff --git a/src/include/storage/standby.h b/src/include/storage/standby.h
index 58ea21aa132..5094c50a53f 100644
--- a/src/include/storage/standby.h
+++ b/src/include/storage/standby.h
@@ -21,7 +21,7 @@
#include "storage/standbydefs.h"
/* User-settable GUC parameters */
-extern int vacuum_defer_cleanup_age;
+extern int64 vacuum_defer_cleanup_age;
extern int max_standby_archive_delay;
extern int max_standby_streaming_delay;
extern bool log_recovery_conflict_waits;
diff --git a/src/include/storage/sync.h b/src/include/storage/sync.h
index 9737e1eb67c..35fc1dff1ab 100644
--- a/src/include/storage/sync.h
+++ b/src/include/storage/sync.h
@@ -52,7 +52,7 @@ typedef struct FileTag
int16 handler; /* SyncRequestHandler value, saving space */
int16 forknum; /* ForkNumber, saving space */
RelFileNode rnode;
- uint32 segno;
+ uint64 segno;
} FileTag;
extern void InitSync(void);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 7ac4780e3fc..597c7f31c4b 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -53,6 +53,7 @@ extern int pg_ltoa(int32 l, char *a);
extern int pg_lltoa(int64 ll, char *a);
extern char *pg_ultostr_zeropad(char *str, uint32 value, int32 minwidth);
extern char *pg_ultostr(char *str, uint32 value);
+extern uint64 pg_strtouint64(const char *s, char **endptr, int base);
/* oid.c */
extern oidvector *buildoidvector(const Oid *oids, int n);
diff --git a/src/include/utils/combocid.h b/src/include/utils/combocid.h
index 80fe6d2ceac..8465768b6f1 100644
--- a/src/include/utils/combocid.h
+++ b/src/include/utils/combocid.h
@@ -15,7 +15,7 @@
#define COMBOCID_H
/*
- * HeapTupleHeaderGetCmin and HeapTupleHeaderGetCmax function prototypes
+ * HeapTupleGetCmin and HeapTupleGetCmax function prototypes
* are in access/htup.h, because that's where the macro definitions that
* those functions replaced used to be.
*/
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 6da1b220cdc..e77600a0f26 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -295,12 +295,12 @@ typedef struct AutoVacOpts
int vacuum_ins_threshold;
int analyze_threshold;
int vacuum_cost_limit;
- int freeze_min_age;
- int freeze_max_age;
- int freeze_table_age;
- int multixact_freeze_min_age;
- int multixact_freeze_max_age;
- int multixact_freeze_table_age;
+ int64 freeze_min_age;
+ int64 freeze_max_age;
+ int64 freeze_table_age;
+ int64 multixact_freeze_min_age;
+ int64 multixact_freeze_max_age;
+ int64 multixact_freeze_table_age;
int log_min_duration;
float8 vacuum_cost_delay;
float8 vacuum_scale_factor;
diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c
index 1c77211ac45..d0113fb8972 100644
--- a/src/pl/plperl/plperl.c
+++ b/src/pl/plperl/plperl.c
@@ -2659,7 +2659,7 @@ validate_plperl_function(plperl_proc_ptr *proc_ptr, HeapTuple procTup)
* This is needed because CREATE OR REPLACE FUNCTION can modify the
* function's pg_proc entry without changing its OID.
************************************************************/
- uptodate = (prodesc->fn_xmin == HeapTupleHeaderGetRawXmin(procTup->t_data) &&
+ uptodate = (prodesc->fn_xmin == HeapTupleGetRawXmin(procTup) &&
ItemPointerEquals(&prodesc->fn_tid, &procTup->t_self));
if (uptodate)
@@ -2783,7 +2783,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger, bool is_event_trigger)
MemoryContextSetIdentifier(proc_cxt, prodesc->proname);
prodesc->fn_cxt = proc_cxt;
prodesc->fn_refcount = 0;
- prodesc->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data);
+ prodesc->fn_xmin = HeapTupleGetRawXmin(procTup);
prodesc->fn_tid = procTup->t_self;
prodesc->nargs = procStruct->pronargs;
prodesc->arg_out_func = (FmgrInfo *) palloc0(prodesc->nargs * sizeof(FmgrInfo));
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index b791c23f066..820db4c0e47 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -171,7 +171,7 @@ recheck:
if (function)
{
/* We have a compiled function, but is it still valid? */
- if (function->fn_xmin == HeapTupleHeaderGetRawXmin(procTup->t_data) &&
+ if (function->fn_xmin == HeapTupleGetRawXmin(procTup) &&
ItemPointerEquals(&function->fn_tid, &procTup->t_self))
function_valid = true;
else
@@ -348,7 +348,7 @@ do_compile(FunctionCallInfo fcinfo,
function->fn_signature = format_procedure(fcinfo->flinfo->fn_oid);
MemoryContextSetIdentifier(func_cxt, function->fn_signature);
function->fn_oid = fcinfo->flinfo->fn_oid;
- function->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data);
+ function->fn_xmin = HeapTupleGetRawXmin(procTup);
function->fn_tid = procTup->t_self;
function->fn_input_collation = fcinfo->fncollation;
function->fn_cxt = func_cxt;
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 70c4a752955..1bf48085084 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -7349,6 +7349,7 @@ deconstruct_composite_datum(Datum value, HeapTupleData *tmptup)
tmptup->t_len = HeapTupleHeaderGetDatumLength(td);
ItemPointerSetInvalid(&(tmptup->t_self));
tmptup->t_tableOid = InvalidOid;
+ HeapTupleSetZeroBase(tmptup);
tmptup->t_data = td;
/* Extract rowtype info and find a tupdesc */
@@ -7523,6 +7524,7 @@ exec_move_row_from_datum(PLpgSQL_execstate *estate,
tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
ItemPointerSetInvalid(&(tmptup.t_self));
tmptup.t_tableOid = InvalidOid;
+ HeapTupleSetZeroBase(&tmptup);
tmptup.t_data = td;
/* Extract rowtype info */
diff --git a/src/pl/plpython/plpy_procedure.c b/src/pl/plpython/plpy_procedure.c
index 494f109b323..9884f74fa78 100644
--- a/src/pl/plpython/plpy_procedure.c
+++ b/src/pl/plpython/plpy_procedure.c
@@ -178,7 +178,7 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
proc->proname = pstrdup(NameStr(procStruct->proname));
MemoryContextSetIdentifier(cxt, proc->proname);
proc->pyname = pstrdup(procName);
- proc->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data);
+ proc->fn_xmin = HeapTupleGetRawXmin(procTup);
proc->fn_tid = procTup->t_self;
proc->fn_readonly = (procStruct->provolatile != PROVOLATILE_VOLATILE);
proc->is_setof = procStruct->proretset;
@@ -419,7 +419,7 @@ PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup)
return false;
/* If the pg_proc tuple has changed, it's not valid */
- if (!(proc->fn_xmin == HeapTupleHeaderGetRawXmin(procTup->t_data) &&
+ if (!(proc->fn_xmin == HeapTupleGetRawXmin(procTup) &&
ItemPointerEquals(&proc->fn_tid, &procTup->t_self)))
return false;
diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c
index 7c045f45607..52361811dcd 100644
--- a/src/pl/tcl/pltcl.c
+++ b/src/pl/tcl/pltcl.c
@@ -1426,7 +1426,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
* function's pg_proc entry without changing its OID.
************************************************************/
if (prodesc != NULL &&
- prodesc->fn_xmin == HeapTupleHeaderGetRawXmin(procTup->t_data) &&
+ prodesc->fn_xmin == HeapTupleGetRawXmin(procTup) &&
ItemPointerEquals(&prodesc->fn_tid, &procTup->t_self))
{
/* It's still up-to-date, so we can use it */
@@ -1493,7 +1493,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
prodesc->internal_proname = pstrdup(internal_proname);
prodesc->fn_cxt = proc_cxt;
prodesc->fn_refcount = 0;
- prodesc->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data);
+ prodesc->fn_xmin = HeapTupleGetRawXmin(procTup);
prodesc->fn_tid = procTup->t_self;
prodesc->nargs = procStruct->pronargs;
prodesc->arg_out_func = (FmgrInfo *) palloc0(prodesc->nargs * sizeof(FmgrInfo));
diff --git a/src/test/Makefile b/src/test/Makefile
index 46275915ff3..ec6558e33ba 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -12,7 +12,8 @@ subdir = src/test
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
-SUBDIRS = perl regress isolation modules authentication recovery subscription
+SUBDIRS = perl regress isolation modules authentication recovery subscription \
+ xid-64
# Test suites that are not safe by default but can be run if selected
# by the user via the whitespace-separated list in variable
diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index 265f3ae6574..1e9e4eb905c 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -431,6 +431,7 @@ sub init
mkdir $self->archive_dir;
PostgreSQL::Test::Utils::system_or_bail('initdb', '-D', $pgdata, '-A', 'trust', '-N',
+ '-x', '1249835483136', '-m', '2422361554944', '-o', '3594887626752',
@{ $params{extra} });
PostgreSQL::Test::Utils::system_or_bail($ENV{PG_REGRESS}, '--config-auth', $pgdata,
@{ $params{auth_extra} });
diff --git a/src/test/recovery/t/003_recovery_targets.pl b/src/test/recovery/t/003_recovery_targets.pl
index 24da78c0bcd..62ccac68ef4 100644
--- a/src/test/recovery/t/003_recovery_targets.pl
+++ b/src/test/recovery/t/003_recovery_targets.pl
@@ -57,7 +57,7 @@ $node_primary->init(has_archiving => 1, allows_streaming => 1);
# Bump the transaction ID epoch. This is useful to stress the portability
# of recovery_target_xid parsing.
-system_or_bail('pg_resetwal', '--epoch', '1', $node_primary->data_dir);
+system_or_bail('pg_resetwal', $node_primary->data_dir);
# Start it
$node_primary->start;
diff --git a/src/test/regress/expected/indirect_toast.out b/src/test/regress/expected/indirect_toast.out
index ab1fa5e707c..df2c354364a 100644
--- a/src/test/regress/expected/indirect_toast.out
+++ b/src/test/regress/expected/indirect_toast.out
@@ -153,6 +153,14 @@ SELECT substring(indtoasttest::text, 1, 200) FROM indtoasttest;
("one-toasted,one-null, via indirect",0,1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
(5 rows)
+create or replace function random_string(len integer) returns text as $$
+select substr((select string_agg(r,'') from (select random()::text as r from generate_series(1,(len+15)/16)) s1), 1, len);
+$$ language sql;
+create table toasttest_main(t text);
+alter table toasttest_main alter column t set storage main;
+insert into toasttest_main (select random_string(len) from generate_series(8000,9000) len);
DROP TABLE indtoasttest;
+DROP TABLE toasttest_main;
DROP FUNCTION update_using_indirect();
+DROP FUNCTION random_string(integer);
RESET default_toast_compression;
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 5063a3dc221..fb7a1628332 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -100,7 +100,7 @@ SELECT pg_size_pretty(pg_relation_size('large_tuple_test'::regclass, 'main'));
INSERT INTO large_tuple_test (select 3, NULL);
-- now this tuple won't fit on the second page, but the insert should
-- still succeed by extending the relation
-INSERT INTO large_tuple_test (select 4, repeat('a', 8126));
+INSERT INTO large_tuple_test (select 4, repeat('a', 8112));
DROP TABLE large_tuple_test;
--
-- check indirection (field/array assignment), cf bug #14265
@@ -1002,3 +1002,17 @@ insert into returningwrtest values (2, 'foo') returning returningwrtest;
(1 row)
drop table returningwrtest;
+-- Check for MaxHeapTupleSize
+create table maxheaptuplesize_test(value text);
+alter table maxheaptuplesize_test alter column value set storage external;
+insert into maxheaptuplesize_test values (repeat('x', 8104));
+insert into maxheaptuplesize_test values (repeat('x', 8112));
+insert into maxheaptuplesize_test values (repeat('x', 8120));
+insert into maxheaptuplesize_test values (repeat('x', 8128));
+insert into maxheaptuplesize_test values (repeat('x', 8136));
+insert into maxheaptuplesize_test values (repeat('x', 8144));
+insert into maxheaptuplesize_test values (repeat('x', 8152));
+insert into maxheaptuplesize_test values (repeat('x', 8160));
+insert into maxheaptuplesize_test values (repeat('x', 8168));
+insert into maxheaptuplesize_test values (repeat('x', 8176));
+drop table maxheaptuplesize_test;
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 562b586d8e0..0c47f94ee0b 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -197,7 +197,7 @@ WHERE p1.oid != p2.oid AND
ORDER BY 1, 2;
proargtypes | proargtypes
-----------------------------+--------------------------
- integer | xid
+ bigint | xid
timestamp without time zone | timestamp with time zone
bit | bit varying
txid_snapshot | pg_snapshot
@@ -704,7 +704,7 @@ int8(oid)
tideq(tid,tid)
timestamptz_cmp(timestamp with time zone,timestamp with time zone)
interval_cmp(interval,interval)
-xideqint4(xid,integer)
+xideqint8(xid,bigint)
timetz_eq(time with time zone,time with time zone)
timetz_ne(time with time zone,time with time zone)
timetz_lt(time with time zone,time with time zone)
@@ -818,7 +818,7 @@ pg_lsn_gt(pg_lsn,pg_lsn)
pg_lsn_ne(pg_lsn,pg_lsn)
pg_lsn_cmp(pg_lsn,pg_lsn)
xidneq(xid,xid)
-xidneqint4(xid,integer)
+xidneqint8(xid,bigint)
sha224(bytea)
sha256(bytea)
sha384(bytea)
diff --git a/src/test/regress/expected/select_views.out b/src/test/regress/expected/select_views.out
index 1aeed8452bd..d3be84754c1 100644
--- a/src/test/regress/expected/select_views.out
+++ b/src/test/regress/expected/select_views.out
@@ -2,9 +2,22 @@
-- SELECT_VIEWS
-- test the views defined in CREATE_VIEWS
--
-SELECT * FROM street;
+SELECT * FROM street ORDER BY name COLLATE "C", thepath::text COLLATE "C";
name | thepath | cname
------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------
+ 100th Ave | [(-122.1657,37.429),(-122.1647,37.432)] | Oakland
+ 107th Ave | [(-122.1555,37.403),(-122.1531,37.41)] | Oakland
+ 14th St | [(-122.299,37.147),(-122.3,37.148)] | Lafayette
+ 19th Ave | [(-122.2366,37.897),(-122.2359,37.905)] | Berkeley
+ 1st St | [(-121.75508,37.89294),(-121.753581,37.90031)] | Oakland
+ 5th St | [(-122.278,37),(-122.2792,37.005),(-122.2803,37.009)] | Lafayette
+ 5th St | [(-122.296,37.615),(-122.2953,37.598)] | Berkeley
+ 82nd Ave | [(-122.1695,37.596),(-122.1681,37.603)] | Berkeley
+ 85th Ave | [(-122.1877,37.466),(-122.186,37.476)] | Oakland
+ 89th Ave | [(-122.1822,37.459),(-122.1803,37.471)] | Oakland
+ 98th Ave | [(-122.1568,37.498),(-122.1558,37.502)] | Oakland
+ 98th Ave | [(-122.1693,37.438),(-122.1682,37.444)] | Oakland
+ 98th Ave | [(-122.2001,37.258),(-122.1974,37.27)] | Lafayette
Access Rd 25 | [(-121.9283,37.894),(-121.9283,37.9)] | Oakland
Ada St | [(-122.2487,37.398),(-122.2496,37.401)] | Lafayette
Agua Fria Creek | [(-121.9254,37.922),(-121.9281,37.889)] | Oakland
@@ -22,8 +35,8 @@ SELECT * FROM street;
Arroyo Las Positas | [(-121.7973,37.997),(-121.7957,37.005)] | Oakland
Arroyo Seco | [(-121.7073,37.766),(-121.6997,37.729)] | Oakland
Ash St | [(-122.0408,37.31),(-122.04,37.292)] | Oakland
- Avenue 134th | [(-122.1823,37.002),(-122.1851,37.992)] | Oakland
Avenue 134th | [(-122.1823,37.002),(-122.1851,37.992)] | Berkeley
+ Avenue 134th | [(-122.1823,37.002),(-122.1851,37.992)] | Oakland
Avenue 140th | [(-122.1656,37.003),(-122.1691,37.988)] | Oakland
Avenue 140th | [(-122.1656,37.003),(-122.1691,37.988)] | Berkeley
Avenue D | [(-122.298,37.848),(-122.3024,37.849)] | Berkeley
@@ -37,14 +50,14 @@ SELECT * FROM street;
Broadmore Ave | [(-122.095,37.522),(-122.0936,37.497)] | Oakland
Broadway | [(-122.2409,37.586),(-122.2395,37.601)] | Berkeley
Buckingham Blvd | [(-122.2231,37.59),(-122.2214,37.606)] | Berkeley
+ Butterfield Dr | [(-122.0838,37.002),(-122.0834,37.987)] | Berkeley
Butterfield Dr | [(-122.0838,37.002),(-122.0834,37.987)] | Oakland
Butterfield Dr | [(-122.0838,37.002),(-122.0834,37.987)] | Oakland
- Butterfield Dr | [(-122.0838,37.002),(-122.0834,37.987)] | Berkeley
C St | [(-122.1768,37.46),(-122.1749,37.435)] | Oakland
Calaveras Creek | [(-121.8203,37.035),(-121.8207,37.931)] | Oakland
Calaveras Creek | [(-121.8203,37.035),(-121.8207,37.931)] | Oakland
- California St | [(-122.2032,37.005),(-122.2016,37.996)] | Berkeley
California St | [(-122.2032,37.005),(-122.2016,37.996)] | Lafayette
+ California St | [(-122.2032,37.005),(-122.2016,37.996)] | Berkeley
Cameron Ave | [(-122.1316,37.502),(-122.1327,37.481)] | Oakland
Campus Dr | [(-122.1704,37.905),(-122.1678,37.868),(-122.1671,37.865)] | Berkeley
Capricorn Ave | [(-122.2176,37.404),(-122.2164,37.384)] | Lafayette
@@ -55,8 +68,8 @@ SELECT * FROM street;
Central Ave | [(-122.2343,37.602),(-122.2331,37.595)] | Berkeley
Chambers Dr | [(-122.2004,37.352),(-122.1972,37.368)] | Lafayette
Chambers Lane | [(-122.2001,37.359),(-122.1975,37.371)] | Lafayette
- Champion St | [(-122.214,37.991),(-122.2147,37.002)] | Berkeley
Champion St | [(-122.214,37.991),(-122.2147,37.002)] | Lafayette
+ Champion St | [(-122.214,37.991),(-122.2147,37.002)] | Berkeley
Chapman Dr | [(-122.0421,37.504),(-122.0414,37.498)] | Oakland
Charles St | [(-122.0255,37.505),(-122.0252,37.499)] | Oakland
Cherry St | [(-122.0437,37.42),(-122.0434,37.413)] | Oakland
@@ -77,9 +90,9 @@ SELECT * FROM street;
Cull Canyon Road | [(-122.0536,37.435),(-122.0499,37.315)] | Oakland
Cull Creek | [(-122.0624,37.875),(-122.0582,37.527)] | Berkeley
D St | [(-122.1811,37.505),(-122.1805,37.497)] | Oakland
+ Decoto Road | [(-122.0159,37.006),(-122.016,37.002),(-122.0164,37.993)] | Berkeley
Decoto Road | [(-122.0159,37.006),(-122.016,37.002),(-122.0164,37.993)] | Oakland
Decoto Road | [(-122.0159,37.006),(-122.016,37.002),(-122.0164,37.993)] | Oakland
- Decoto Road | [(-122.0159,37.006),(-122.016,37.002),(-122.0164,37.993)] | Berkeley
Deering St | [(-122.2146,37.904),(-122.2126,37.897)] | Berkeley
Dimond Ave | [(-122.2167,37.994),(-122.2162,37.006)] | Berkeley
Dimond Ave | [(-122.2167,37.994),(-122.2162,37.006)] | Lafayette
@@ -117,12 +130,12 @@ SELECT * FROM street;
I- 580 | [(-121.9322,37.989),(-121.9243,37.006),(-121.9217,37.014)] | Oakland
I- 580 | [(-122.018,37.019),(-122.0009,37.032),(-121.9787,37.983),(-121.958,37.984),(-121.9571,37.986)] | Oakland
I- 580 | [(-122.018,37.019),(-122.0009,37.032),(-121.9787,37.983),(-121.958,37.984),(-121.9571,37.986)] | Oakland
- I- 580 | [(-122.1108,37.023),(-122.1101,37.02),(-122.108103,37.00764),(-122.108,37.007),(-122.1069,37.998),(-122.1064,37.994),(-122.1053,37.982),(-122.1048,37.977),(-122.1032,37.958),(-122.1026,37.953),(-122.1013,37.938),(-122.0989,37.911),(-122.0984,37.91),(-122.098,37.908)] | Oakland
I- 580 | [(-122.1108,37.023),(-122.1101,37.02),(-122.108103,37.00764),(-122.108,37.007),(-122.1069,37.998),(-122.1064,37.994),(-122.1053,37.982),(-122.1048,37.977),(-122.1032,37.958),(-122.1026,37.953),(-122.1013,37.938),(-122.0989,37.911),(-122.0984,37.91),(-122.098,37.908)] | Berkeley
+ I- 580 | [(-122.1108,37.023),(-122.1101,37.02),(-122.108103,37.00764),(-122.108,37.007),(-122.1069,37.998),(-122.1064,37.994),(-122.1053,37.982),(-122.1048,37.977),(-122.1032,37.958),(-122.1026,37.953),(-122.1013,37.938),(-122.0989,37.911),(-122.0984,37.91),(-122.098,37.908)] | Oakland
I- 580 | [(-122.1543,37.703),(-122.1535,37.694),(-122.1512,37.655),(-122.1475,37.603),(-122.1468,37.583),(-122.1472,37.569),(-122.149044,37.54874),(-122.1493,37.546),(-122.1501,37.532),(-122.1506,37.509),(-122.1495,37.482),(-122.1487,37.467),(-122.1477,37.447),(-122.1414,37.383),(-122.1404,37.376),(-122.1398,37.372),(-122.139,37.356),(-122.1388,37.353),(-122.1385,37.34),(-122.1382,37.33),(-122.1378,37.316)] | Oakland
I- 580 | [(-122.1543,37.703),(-122.1535,37.694),(-122.1512,37.655),(-122.1475,37.603),(-122.1468,37.583),(-122.1472,37.569),(-122.149044,37.54874),(-122.1493,37.546),(-122.1501,37.532),(-122.1506,37.509),(-122.1495,37.482),(-122.1487,37.467),(-122.1477,37.447),(-122.1414,37.383),(-122.1404,37.376),(-122.1398,37.372),(-122.139,37.356),(-122.1388,37.353),(-122.1385,37.34),(-122.1382,37.33),(-122.1378,37.316)] | Berkeley
- I- 580 | [(-122.2197,37.99),(-122.22,37.99),(-122.222092,37.99523),(-122.2232,37.998),(-122.224146,37.99963),(-122.2261,37.003),(-122.2278,37.007),(-122.2302,37.026),(-122.2323,37.043),(-122.2344,37.059),(-122.235405,37.06427),(-122.2365,37.07)] | Berkeley
I- 580 | [(-122.2197,37.99),(-122.22,37.99),(-122.222092,37.99523),(-122.2232,37.998),(-122.224146,37.99963),(-122.2261,37.003),(-122.2278,37.007),(-122.2302,37.026),(-122.2323,37.043),(-122.2344,37.059),(-122.235405,37.06427),(-122.2365,37.07)] | Lafayette
+ I- 580 | [(-122.2197,37.99),(-122.22,37.99),(-122.222092,37.99523),(-122.2232,37.998),(-122.224146,37.99963),(-122.2261,37.003),(-122.2278,37.007),(-122.2302,37.026),(-122.2323,37.043),(-122.2344,37.059),(-122.235405,37.06427),(-122.2365,37.07)] | Berkeley
I- 580 Ramp | [(-121.8521,37.011),(-121.8479,37.999),(-121.8476,37.999),(-121.8456,37.01),(-121.8455,37.011)] | Oakland
I- 580 Ramp | [(-121.8521,37.011),(-121.8479,37.999),(-121.8476,37.999),(-121.8456,37.01),(-121.8455,37.011)] | Oakland
I- 580 Ramp | [(-121.8743,37.014),(-121.8722,37.999),(-121.8714,37.999)] | Oakland
@@ -136,8 +149,8 @@ SELECT * FROM street;
I- 580 Ramp | [(-122.0941,37.897),(-122.0943,37.902)] | Berkeley
I- 580 Ramp | [(-122.096,37.888),(-122.0962,37.891),(-122.0964,37.9)] | Berkeley
I- 580 Ramp | [(-122.101,37.898),(-122.1005,37.902),(-122.0989,37.911)] | Berkeley
- I- 580 Ramp | [(-122.1086,37.003),(-122.1068,37.993),(-122.1066,37.992),(-122.1053,37.982)] | Oakland
I- 580 Ramp | [(-122.1086,37.003),(-122.1068,37.993),(-122.1066,37.992),(-122.1053,37.982)] | Berkeley
+ I- 580 Ramp | [(-122.1086,37.003),(-122.1068,37.993),(-122.1066,37.992),(-122.1053,37.982)] | Oakland
I- 580 Ramp | [(-122.1414,37.383),(-122.1407,37.376),(-122.1403,37.372),(-122.139,37.356)] | Oakland
I- 580/I-680 Ramp | ((-121.9207,37.988),(-121.9192,37.016)) | Oakland
I- 580/I-680 Ramp | ((-121.9207,37.988),(-121.9192,37.016)) | Oakland
@@ -158,16 +171,16 @@ SELECT * FROM street;
I- 880 | ((-121.9669,37.075),(-121.9663,37.071),(-121.9656,37.065),(-121.9618,37.037),(-121.95689,37),(-121.948,37.933)) | Oakland
I- 880 | [(-121.948,37.933),(-121.9471,37.925),(-121.9467,37.923),(-121.946,37.918),(-121.9452,37.912),(-121.937,37.852)] | Oakland
I- 880 | [(-122.0219,37.466),(-122.0205,37.447),(-122.020331,37.44447),(-122.020008,37.43962),(-122.0195,37.432),(-122.0193,37.429),(-122.0164,37.393),(-122.010219,37.34771),(-122.0041,37.313)] | Oakland
- I- 880 | [(-122.0375,37.632),(-122.0359,37.619),(-122.0358,37.616),(-122.034514,37.60409),(-122.031876,37.57965),(-122.031193,37.57332),(-122.03016,37.56375),(-122.02943,37.55698),(-122.028689,37.54929),(-122.027833,37.53908),(-122.025979,37.51698),(-122.0238,37.491)] | Oakland
I- 880 | [(-122.0375,37.632),(-122.0359,37.619),(-122.0358,37.616),(-122.034514,37.60409),(-122.031876,37.57965),(-122.031193,37.57332),(-122.03016,37.56375),(-122.02943,37.55698),(-122.028689,37.54929),(-122.027833,37.53908),(-122.025979,37.51698),(-122.0238,37.491)] | Berkeley
- I- 880 | [(-122.0612,37.003),(-122.0604,37.991),(-122.0596,37.982),(-122.0585,37.967),(-122.0583,37.961),(-122.0553,37.918),(-122.053635,37.89475),(-122.050759,37.8546),(-122.05,37.844),(-122.0485,37.817),(-122.0483,37.813),(-122.0482,37.811)] | Oakland
+ I- 880 | [(-122.0375,37.632),(-122.0359,37.619),(-122.0358,37.616),(-122.034514,37.60409),(-122.031876,37.57965),(-122.031193,37.57332),(-122.03016,37.56375),(-122.02943,37.55698),(-122.028689,37.54929),(-122.027833,37.53908),(-122.025979,37.51698),(-122.0238,37.491)] | Oakland
I- 880 | [(-122.0612,37.003),(-122.0604,37.991),(-122.0596,37.982),(-122.0585,37.967),(-122.0583,37.961),(-122.0553,37.918),(-122.053635,37.89475),(-122.050759,37.8546),(-122.05,37.844),(-122.0485,37.817),(-122.0483,37.813),(-122.0482,37.811)] | Oakland
I- 880 | [(-122.0612,37.003),(-122.0604,37.991),(-122.0596,37.982),(-122.0585,37.967),(-122.0583,37.961),(-122.0553,37.918),(-122.053635,37.89475),(-122.050759,37.8546),(-122.05,37.844),(-122.0485,37.817),(-122.0483,37.813),(-122.0482,37.811)] | Berkeley
+ I- 880 | [(-122.0612,37.003),(-122.0604,37.991),(-122.0596,37.982),(-122.0585,37.967),(-122.0583,37.961),(-122.0553,37.918),(-122.053635,37.89475),(-122.050759,37.8546),(-122.05,37.844),(-122.0485,37.817),(-122.0483,37.813),(-122.0482,37.811)] | Oakland
I- 880 | [(-122.0831,37.312),(-122.0819,37.296),(-122.081,37.285),(-122.0786,37.248),(-122.078,37.24),(-122.077642,37.23496),(-122.076983,37.22567),(-122.076599,37.22026),(-122.076229,37.21505),(-122.0758,37.209)] | Oakland
I- 880 | [(-122.0978,37.528),(-122.096,37.496),(-122.0931,37.453),(-122.09277,37.4496),(-122.090189,37.41442),(-122.0896,37.405),(-122.085,37.34)] | Oakland
I- 880 | [(-122.1365,37.902),(-122.1358,37.898),(-122.1333,37.881),(-122.1323,37.874),(-122.1311,37.866),(-122.1308,37.865),(-122.1307,37.864),(-122.1289,37.851),(-122.1277,37.843),(-122.1264,37.834),(-122.1231,37.812),(-122.1165,37.766),(-122.1104,37.72),(-122.109695,37.71094),(-122.109,37.702),(-122.108312,37.69168),(-122.1076,37.681)] | Berkeley
- I- 880 | [(-122.1755,37.185),(-122.1747,37.178),(-122.1742,37.173),(-122.1692,37.126),(-122.167792,37.11594),(-122.16757,37.11435),(-122.1671,37.111),(-122.1655,37.1),(-122.165169,37.09811),(-122.1641,37.092),(-122.1596,37.061),(-122.158381,37.05275),(-122.155991,37.03657),(-122.1531,37.017),(-122.1478,37.98),(-122.1407,37.932),(-122.1394,37.924),(-122.1389,37.92),(-122.1376,37.91)] | Oakland
I- 880 | [(-122.1755,37.185),(-122.1747,37.178),(-122.1742,37.173),(-122.1692,37.126),(-122.167792,37.11594),(-122.16757,37.11435),(-122.1671,37.111),(-122.1655,37.1),(-122.165169,37.09811),(-122.1641,37.092),(-122.1596,37.061),(-122.158381,37.05275),(-122.155991,37.03657),(-122.1531,37.017),(-122.1478,37.98),(-122.1407,37.932),(-122.1394,37.924),(-122.1389,37.92),(-122.1376,37.91)] | Berkeley
+ I- 880 | [(-122.1755,37.185),(-122.1747,37.178),(-122.1742,37.173),(-122.1692,37.126),(-122.167792,37.11594),(-122.16757,37.11435),(-122.1671,37.111),(-122.1655,37.1),(-122.165169,37.09811),(-122.1641,37.092),(-122.1596,37.061),(-122.158381,37.05275),(-122.155991,37.03657),(-122.1531,37.017),(-122.1478,37.98),(-122.1407,37.932),(-122.1394,37.924),(-122.1389,37.92),(-122.1376,37.91)] | Oakland
I- 880 | [(-122.2214,37.711),(-122.2202,37.699),(-122.2199,37.695),(-122.219,37.682),(-122.2184,37.672),(-122.2173,37.652),(-122.2159,37.638),(-122.2144,37.616),(-122.2138,37.612),(-122.2135,37.609),(-122.212,37.592),(-122.2116,37.586),(-122.2111,37.581)] | Berkeley
I- 880 | [(-122.2707,37.975),(-122.2693,37.972),(-122.2681,37.966),(-122.267,37.962),(-122.2659,37.957),(-122.2648,37.952),(-122.2636,37.946),(-122.2625,37.935),(-122.2617,37.927),(-122.2607,37.921),(-122.2593,37.916),(-122.258,37.911),(-122.2536,37.898),(-122.2432,37.858),(-122.2408,37.845),(-122.2386,37.827),(-122.2374,37.811)] | Berkeley
I- 880 Ramp | [(-122.0019,37.301),(-122.002,37.293)] | Oakland
@@ -202,28 +215,28 @@ SELECT * FROM street;
Laguna Ave | [(-122.2099,37.989),(-122.2089,37)] | Berkeley
Laguna Ave | [(-122.2099,37.989),(-122.2089,37)] | Lafayette
Lakehurst Cir | [(-122.284729,37.89025),(-122.286096,37.90364)] | Berkeley
- Lakeshore Ave | [(-122.2586,37.99),(-122.2556,37.006)] | Berkeley
Lakeshore Ave | [(-122.2586,37.99),(-122.2556,37.006)] | Lafayette
+ Lakeshore Ave | [(-122.2586,37.99),(-122.2556,37.006)] | Berkeley
Las Positas Road | [(-121.764488,37.99199),(-121.75569,37.02022)] | Oakland
Las Positas Road | [(-121.764488,37.99199),(-121.75569,37.02022)] | Oakland
- Linden St | [(-122.2867,37.998),(-122.2864,37.008)] | Berkeley
Linden St | [(-122.2867,37.998),(-122.2864,37.008)] | Lafayette
+ Linden St | [(-122.2867,37.998),(-122.2864,37.008)] | Berkeley
Livermore Ave | [(-121.7687,37.448),(-121.769,37.375)] | Oakland
Livermore Ave | [(-121.7687,37.448),(-121.769,37.375)] | Oakland
Livermore Ave | [(-121.772719,37.99085),(-121.7728,37.001)] | Oakland
Livermore Ave | [(-121.772719,37.99085),(-121.7728,37.001)] | Oakland
- Locust St | [(-122.1606,37.007),(-122.1593,37.987)] | Oakland
Locust St | [(-122.1606,37.007),(-122.1593,37.987)] | Berkeley
+ Locust St | [(-122.1606,37.007),(-122.1593,37.987)] | Oakland
Logan Ct | [(-122.0053,37.492),(-122.0061,37.484)] | Oakland
Magnolia St | [(-122.0971,37.5),(-122.0962,37.484)] | Oakland
Mandalay Road | [(-122.2322,37.397),(-122.2321,37.403)] | Lafayette
Marin Ave | [(-122.2741,37.894),(-122.272,37.901)] | Berkeley
Martin Luther King Jr Way | [(-122.2712,37.608),(-122.2711,37.599)] | Berkeley
Mattos Dr | [(-122.0005,37.502),(-122.000898,37.49683)] | Oakland
- Maubert Ave | [(-122.1114,37.009),(-122.1096,37.995)] | Oakland
Maubert Ave | [(-122.1114,37.009),(-122.1096,37.995)] | Berkeley
- McClure Ave | [(-122.1431,37.001),(-122.1436,37.998)] | Oakland
+ Maubert Ave | [(-122.1114,37.009),(-122.1096,37.995)] | Oakland
McClure Ave | [(-122.1431,37.001),(-122.1436,37.998)] | Berkeley
+ McClure Ave | [(-122.1431,37.001),(-122.1436,37.998)] | Oakland
Medlar Dr | [(-122.0627,37.378),(-122.0625,37.375)] | Oakland
Mildred Ct | [(-122.0002,37.388),(-121.9998,37.386)] | Oakland
Miller Road | [(-122.0902,37.645),(-122.0865,37.545)] | Berkeley
@@ -242,8 +255,8 @@ SELECT * FROM street;
Parkridge Dr | [(-122.1438,37.884),(-122.1428,37.9)] | Berkeley
Parkside Dr | [(-122.0475,37.603),(-122.0443,37.596)] | Berkeley
Paseo Padre Pkwy | [(-121.9143,37.005),(-121.913522,37)] | Oakland
- Paseo Padre Pkwy | [(-122.0021,37.639),(-121.996,37.628)] | Oakland
Paseo Padre Pkwy | [(-122.0021,37.639),(-121.996,37.628)] | Berkeley
+ Paseo Padre Pkwy | [(-122.0021,37.639),(-121.996,37.628)] | Oakland
Pearl St | [(-122.2383,37.594),(-122.2366,37.615)] | Berkeley
Periwinkle Road | [(-122.0451,37.301),(-122.044758,37.29844)] | Oakland
Pimlico Dr | [(-121.8616,37.998),(-121.8618,37.008)] | Oakland
@@ -254,11 +267,11 @@ SELECT * FROM street;
Railroad Ave | [(-122.0245,37.013),(-122.0234,37.003),(-122.0223,37.993)] | Oakland
Railroad Ave | [(-122.0245,37.013),(-122.0234,37.003),(-122.0223,37.993)] | Berkeley
Ranspot Dr | [(-122.0972,37.999),(-122.0959,37)] | Oakland
- Ranspot Dr | [(-122.0972,37.999),(-122.0959,37)] | Oakland
Ranspot Dr | [(-122.0972,37.999),(-122.0959,37)] | Berkeley
+ Ranspot Dr | [(-122.0972,37.999),(-122.0959,37)] | Oakland
Redding St | [(-122.1978,37.901),(-122.1975,37.895)] | Berkeley
- Redwood Road | [(-122.1493,37.98),(-122.1437,37.001)] | Oakland
Redwood Road | [(-122.1493,37.98),(-122.1437,37.001)] | Berkeley
+ Redwood Road | [(-122.1493,37.98),(-122.1437,37.001)] | Oakland
Roca Dr | [(-122.0335,37.609),(-122.0314,37.599)] | Berkeley
Rosedale Ct | [(-121.9232,37.9),(-121.924,37.897)] | Oakland
Sacramento St | [(-122.2799,37.606),(-122.2797,37.597)] | Berkeley
@@ -266,8 +279,8 @@ SELECT * FROM street;
Saginaw Ct | [(-121.8803,37.898),(-121.8806,37.901)] | Oakland
San Andreas Dr | [(-122.0609,37.9),(-122.0614,37.895)] | Berkeley
Santa Maria Ave | [(-122.0773,37),(-122.0773,37.98)] | Oakland
- Santa Maria Ave | [(-122.0773,37),(-122.0773,37.98)] | Oakland
Santa Maria Ave | [(-122.0773,37),(-122.0773,37.98)] | Berkeley
+ Santa Maria Ave | [(-122.0773,37),(-122.0773,37.98)] | Oakland
Shattuck Ave | [(-122.2686,37.904),(-122.2686,37.897)] | Berkeley
Sheridan Road | [(-122.2279,37.425),(-122.2253,37.411),(-122.2223,37.377)] | Lafayette
Shoreline Dr | [(-122.2657,37.603),(-122.2648,37.6)] | Berkeley
@@ -317,27 +330,14 @@ SELECT * FROM street;
Welch Creek Road | [(-121.7695,37.386),(-121.7737,37.413)] | Oakland
West Loop Road | [(-122.0576,37.604),(-122.0602,37.586)] | Berkeley
Western Pacific Railroad Spur | [(-122.0394,37.018),(-122.0394,37.961)] | Oakland
- Western Pacific Railroad Spur | [(-122.0394,37.018),(-122.0394,37.961)] | Oakland
Western Pacific Railroad Spur | [(-122.0394,37.018),(-122.0394,37.961)] | Berkeley
+ Western Pacific Railroad Spur | [(-122.0394,37.018),(-122.0394,37.961)] | Oakland
Whitlock Creek | [(-121.74683,37.91276),(-121.733107,37)] | Oakland
Whitlock Creek | [(-121.74683,37.91276),(-121.733107,37)] | Oakland
Willimet Way | [(-122.0964,37.517),(-122.0949,37.493)] | Oakland
Wisconsin St | [(-122.1994,37.017),(-122.1975,37.998),(-122.1971,37.994)] | Oakland
Wisconsin St | [(-122.1994,37.017),(-122.1975,37.998),(-122.1971,37.994)] | Berkeley
Wp Railroad | [(-122.254,37.902),(-122.2506,37.891)] | Berkeley
- 100th Ave | [(-122.1657,37.429),(-122.1647,37.432)] | Oakland
- 107th Ave | [(-122.1555,37.403),(-122.1531,37.41)] | Oakland
- 14th St | [(-122.299,37.147),(-122.3,37.148)] | Lafayette
- 19th Ave | [(-122.2366,37.897),(-122.2359,37.905)] | Berkeley
- 1st St | [(-121.75508,37.89294),(-121.753581,37.90031)] | Oakland
- 5th St | [(-122.278,37),(-122.2792,37.005),(-122.2803,37.009)] | Lafayette
- 5th St | [(-122.296,37.615),(-122.2953,37.598)] | Berkeley
- 82nd Ave | [(-122.1695,37.596),(-122.1681,37.603)] | Berkeley
- 85th Ave | [(-122.1877,37.466),(-122.186,37.476)] | Oakland
- 89th Ave | [(-122.1822,37.459),(-122.1803,37.471)] | Oakland
- 98th Ave | [(-122.1568,37.498),(-122.1558,37.502)] | Oakland
- 98th Ave | [(-122.1693,37.438),(-122.1682,37.444)] | Oakland
- 98th Ave | [(-122.2001,37.258),(-122.1974,37.27)] | Lafayette
(333 rows)
SELECT name, #thepath FROM iexit ORDER BY name COLLATE "C", 2;
diff --git a/src/test/regress/expected/txid.out b/src/test/regress/expected/txid.out
index 95ba66e95ee..2ea4434f513 100644
--- a/src/test/regress/expected/txid.out
+++ b/src/test/regress/expected/txid.out
@@ -238,9 +238,11 @@ SELECT txid_snapshot '1:9223372036854775807:3';
(1 row)
SELECT txid_snapshot '1:9223372036854775808:3';
-ERROR: invalid input syntax for type pg_snapshot: "1:9223372036854775808:3"
-LINE 1: SELECT txid_snapshot '1:9223372036854775808:3';
- ^
+ txid_snapshot
+-------------------------
+ 1:9223372036854775808:3
+(1 row)
+
-- test txid_current_if_assigned
BEGIN;
SELECT txid_current_if_assigned() IS NULL;
diff --git a/src/test/regress/expected/xid.out b/src/test/regress/expected/xid.out
index b7a1ed0f9ee..a26aa0c47bc 100644
--- a/src/test/regress/expected/xid.out
+++ b/src/test/regress/expected/xid.out
@@ -8,9 +8,9 @@ select '010'::xid,
'42'::xid8,
'0xffffffffffffffff'::xid8,
'-1'::xid8;
- xid | xid | xid | xid | xid8 | xid8 | xid8 | xid8
------+-----+------------+------------+------+------+----------------------+----------------------
- 8 | 42 | 4294967295 | 4294967295 | 8 | 42 | 18446744073709551615 | 18446744073709551615
+ xid | xid | xid | xid | xid8 | xid8 | xid8 | xid8
+-----+-----+------------+----------------------+------+------+----------------------+----------------------
+ 8 | 42 | 4294967295 | 18446744073709551615 | 8 | 42 | 18446744073709551615 | 18446744073709551615
(1 row)
-- garbage values are not yet rejected (perhaps they should be)
@@ -373,9 +373,11 @@ SELECT pg_snapshot '1:9223372036854775807:3';
(1 row)
SELECT pg_snapshot '1:9223372036854775808:3';
-ERROR: invalid input syntax for type pg_snapshot: "1:9223372036854775808:3"
-LINE 1: SELECT pg_snapshot '1:9223372036854775808:3';
- ^
+ pg_snapshot
+-------------------------
+ 1:9223372036854775808:3
+(1 row)
+
-- test pg_current_xact_id_if_assigned
BEGIN;
SELECT pg_current_xact_id_if_assigned() IS NULL;
diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c
index e6f71c7582e..fad1ae80827 100644
--- a/src/test/regress/pg_regress.c
+++ b/src/test/regress/pg_regress.c
@@ -2261,7 +2261,7 @@ regression_main(int argc, char *argv[],
/* initdb */
header(_("initializing database system"));
snprintf(buf, sizeof(buf),
- "\"%s%sinitdb\" -D \"%s/data\" --no-clean --no-sync%s%s > \"%s/log/initdb.log\" 2>&1",
+ "\"%s%sinitdb\" -D \"%s/data\" -x 1249835483136 -m 2422361554944 -o 3594887626752 --no-clean --no-sync%s%s > \"%s/log/initdb.log\" 2>&1",
bindir ? bindir : "",
bindir ? "/" : "",
temp_instance,
diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c
index eefbd0f0dfd..b27fea8d23d 100644
--- a/src/test/regress/regress.c
+++ b/src/test/regress/regress.c
@@ -561,6 +561,7 @@ make_tuple_indirect(PG_FUNCTION_ARGS)
tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
ItemPointerSetInvalid(&(tuple.t_self));
tuple.t_tableOid = InvalidOid;
+ HeapTupleSetZeroBase(&tuple);
tuple.t_data = rec;
values = (Datum *) palloc(ncolumns * sizeof(Datum));
diff --git a/src/test/regress/sql/indirect_toast.sql b/src/test/regress/sql/indirect_toast.sql
index 9156a44b7d9..c99c944cadb 100644
--- a/src/test/regress/sql/indirect_toast.sql
+++ b/src/test/regress/sql/indirect_toast.sql
@@ -65,7 +65,18 @@ SELECT substring(indtoasttest::text, 1, 200) FROM indtoasttest;
VACUUM FREEZE indtoasttest;
SELECT substring(indtoasttest::text, 1, 200) FROM indtoasttest;
+create or replace function random_string(len integer) returns text as $$
+select substr((select string_agg(r,'') from (select random()::text as r from generate_series(1,(len+15)/16)) s1), 1, len);
+$$ language sql;
+
+create table toasttest_main(t text);
+alter table toasttest_main alter column t set storage main;
+
+insert into toasttest_main (select random_string(len) from generate_series(8000,9000) len);
+
DROP TABLE indtoasttest;
+DROP TABLE toasttest_main;
DROP FUNCTION update_using_indirect();
+DROP FUNCTION random_string(integer);
RESET default_toast_compression;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index bfaa8a3b277..589a5dcdc67 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -55,7 +55,7 @@ INSERT INTO large_tuple_test (select 3, NULL);
-- now this tuple won't fit on the second page, but the insert should
-- still succeed by extending the relation
-INSERT INTO large_tuple_test (select 4, repeat('a', 8126));
+INSERT INTO large_tuple_test (select 4, repeat('a', 8112));
DROP TABLE large_tuple_test;
@@ -624,3 +624,18 @@ alter table returningwrtest2 drop c;
alter table returningwrtest attach partition returningwrtest2 for values in (2);
insert into returningwrtest values (2, 'foo') returning returningwrtest;
drop table returningwrtest;
+
+-- Check for MaxHeapTupleSize
+create table maxheaptuplesize_test(value text);
+alter table maxheaptuplesize_test alter column value set storage external;
+insert into maxheaptuplesize_test values (repeat('x', 8104));
+insert into maxheaptuplesize_test values (repeat('x', 8112));
+insert into maxheaptuplesize_test values (repeat('x', 8120));
+insert into maxheaptuplesize_test values (repeat('x', 8128));
+insert into maxheaptuplesize_test values (repeat('x', 8136));
+insert into maxheaptuplesize_test values (repeat('x', 8144));
+insert into maxheaptuplesize_test values (repeat('x', 8152));
+insert into maxheaptuplesize_test values (repeat('x', 8160));
+insert into maxheaptuplesize_test values (repeat('x', 8168));
+insert into maxheaptuplesize_test values (repeat('x', 8176));
+drop table maxheaptuplesize_test;
diff --git a/src/test/regress/sql/select_views.sql b/src/test/regress/sql/select_views.sql
index e742f136990..70e663e350c 100644
--- a/src/test/regress/sql/select_views.sql
+++ b/src/test/regress/sql/select_views.sql
@@ -3,7 +3,7 @@
-- test the views defined in CREATE_VIEWS
--
-SELECT * FROM street;
+SELECT * FROM street ORDER BY name COLLATE "C", thepath::text COLLATE "C";
SELECT name, #thepath FROM iexit ORDER BY name COLLATE "C", 2;
diff --git a/src/test/xid-64/Makefile b/src/test/xid-64/Makefile
new file mode 100644
index 00000000000..3b1e50dfc0d
--- /dev/null
+++ b/src/test/xid-64/Makefile
@@ -0,0 +1,22 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/test/xid-64
+#
+# Copyright (c) 2018, Postgres Professional
+#
+# src/test/xid-64/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/test/xid-64
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+check:
+ $(prove_check)
+
+installcheck:
+ $(prove_installcheck)
+
+clean distclean maintainer-clean:
+ rm -rf tmp_check
diff --git a/src/test/xid-64/README b/src/test/xid-64/README
new file mode 100644
index 00000000000..01c0a1a1f74
--- /dev/null
+++ b/src/test/xid-64/README
@@ -0,0 +1,16 @@
+src/test/xid-64/README
+
+Regression tests for 64-bit XIDs
+=============================================
+
+This directory contains a test suite for 64-bit xids.
+
+Running the tests
+=================
+
+ make check
+
+NOTE: This creates a temporary installation, and some tests may
+create one or multiple nodes.
+
+NOTE: This requires the --enable-tap-tests argument to configure.
diff --git a/src/test/xid-64/t/001_test_large_xids.pl b/src/test/xid-64/t/001_test_large_xids.pl
new file mode 100644
index 00000000000..6a039d60d08
--- /dev/null
+++ b/src/test/xid-64/t/001_test_large_xids.pl
@@ -0,0 +1,53 @@
+# Tests for large xid values
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More tests => 9;
+
+use bigint;
+
+sub command_output
+{
+ my ($cmd) = @_;
+ my ($stdout, $stderr);
+ print("# Running: " . join(" ", @{$cmd}) . "\n");
+ my $result = IPC::Run::run $cmd, '>', \$stdout, '2>', \$stderr;
+ ok($result, "@$cmd exit code 0");
+ is($stderr, '', "@$cmd no stderr");
+ return $stdout;
+}
+
+my $START_VAL = 2**32;
+my $MAX_VAL = 2**62;
+
+my $ixid = $START_VAL + int(rand($MAX_VAL - $START_VAL));
+my $imxid = $START_VAL + int(rand($MAX_VAL - $START_VAL));
+my $imoff = $START_VAL + int(rand($MAX_VAL - $START_VAL));
+
+# Initialize master node with the random xid-related parameters
+my $node = PostgreSQL::Test::Cluster->new('master');
+$node->init(extra => [ "--xid=$ixid", "--multixact-id=$imxid", "--multixact-offset=$imoff" ]);
+$node->start;
+
+# Initialize master node and check the xid-related parameters
+my $pgcd_output = command_output(
+ [ 'pg_controldata', '-D', $node->data_dir ] );
+print($pgcd_output); print('\n');
+ok($pgcd_output =~ qr/Latest checkpoint's NextXID:\s*(\d+)/, "XID found");
+my ($nextxid) = ($1);
+ok($nextxid >= $ixid && $nextxid < $ixid + 1000,
+ "Latest checkpoint's NextXID ($nextxid) is close to the initial xid ($ixid).");
+ok($pgcd_output =~ qr/Latest checkpoint's NextMultiXactId:\s*(\d+)/, "MultiXactId found");
+my ($nextmxid) = ($1);
+ok($nextmxid >= $imxid && $nextmxid < $imxid + 1000,
+ "Latest checkpoint's NextMultiXactId ($nextmxid) is close to the initial multiXactId ($imxid).");
+ok($pgcd_output =~ qr/Latest checkpoint's NextMultiOffset:\s*(\d+)/, "MultiOffset found");
+my ($nextmoff) = ($1);
+ok($nextmoff >= $imoff && $nextmoff < $imoff + 1000,
+ "Latest checkpoint's NextMultiOffset ($nextmoff) is close to the initial multiOffset ($imoff).");
+
+# Run pgbench to check whether the database is working properly
+$node->command_ok(
+ [ qw(pgbench --initialize --no-vacuum --scale=10) ],
+ 'pgbench finished without errors');
diff --git a/src/test/xid-64/t/002_test_gucs.pl b/src/test/xid-64/t/002_test_gucs.pl
new file mode 100644
index 00000000000..2b76374f012
--- /dev/null
+++ b/src/test/xid-64/t/002_test_gucs.pl
@@ -0,0 +1,78 @@
+# Tests for guc boundary values
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More tests => 19;
+
+use bigint;
+
+sub command_output
+{
+ my ($cmd) = @_;
+ my ($stdout, $stderr);
+ print("# Running: " . join(" ", @{$cmd}) . "\n");
+ my $result = IPC::Run::run $cmd, '>', \$stdout, '2>', \$stderr;
+ ok($result, "@$cmd exit code 0");
+ is($stderr, '', "@$cmd no stderr");
+ return $stdout;
+}
+
+sub set_guc
+{
+ my ($node, $guc, $val) = @_;
+ print("SET $guc = $val\n");
+ $node->safe_psql('postgres', "ALTER SYSTEM SET $guc = $val");
+ $node->restart();
+}
+
+sub test_pgbench
+{
+ my ($node) = @_;
+ $node->command_ok(
+ [ qw(pgbench --progress=5 --transactions=1000 --jobs=5 --client=5) ],
+ 'pgbench finished without errors');
+}
+
+my @guc_vals = (
+ [ "autovacuum_freeze_max_age", 100000, 2**63 - 1 ],
+ [ "autovacuum_multixact_freeze_max_age", 10000, 2**63 - 1 ],
+ [ "vacuum_freeze_min_age", 0, 2**63 - 1 ],
+ [ "vacuum_freeze_table_age", 0, 2**63 - 1 ],
+ [ "vacuum_multixact_freeze_min_age", 0, 2**63 - 1 ],
+ [ "vacuum_multixact_freeze_table_age", 0, 2**63 -1 ]
+);
+
+my $START_VAL = 2**32;
+my $MAX_VAL = 2**62;
+
+my $ixid = $START_VAL + int(rand($MAX_VAL - $START_VAL));
+my $imxid = $START_VAL + int(rand($MAX_VAL - $START_VAL));
+my $imoff = $START_VAL + int(rand($MAX_VAL - $START_VAL));
+
+# Initialize master node
+my $node = PostgreSQL::Test::Cluster->new('master');
+$node->init(extra => [ "--xid=$ixid", "--multixact-id=$imxid", "--multixact-offset=$imoff" ]);
+# Disable logging of all statements to avoid log bloat during pgbench
+$node->append_conf('postgresql.conf', "log_statement = none");
+$node->start;
+
+# Fill the test database with the pgbench data
+$node->command_ok(
+ [ qw(pgbench --initialize --scale=10) ],
+ 'pgbench finished without errors');
+
+# Test all GUCs with minimum, maximum and random value inbetween
+# (run pgbench for every configuration setting)
+foreach my $gi (0 .. $#guc_vals) {
+ print($guc_vals[$gi][0]); print("\n");
+ my $guc = $guc_vals[$gi][0];
+ my $minval = $guc_vals[$gi][1];
+ my $maxval = $guc_vals[$gi][2];
+ set_guc($node, $guc, $minval);
+ test_pgbench($node);
+ set_guc($node, $guc, $maxval);
+ test_pgbench($node);
+ set_guc($node, $guc, $minval + int(rand($maxval - $minval)));
+ test_pgbench($node);
+}
diff --git a/src/test/xid-64/t/003_test_integrity.pl b/src/test/xid-64/t/003_test_integrity.pl
new file mode 100644
index 00000000000..d984907aee8
--- /dev/null
+++ b/src/test/xid-64/t/003_test_integrity.pl
@@ -0,0 +1,56 @@
+# Check integrity after dump/restore with different xids
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More tests => 5;
+use File::Compare;
+
+my $tempdir = PostgreSQL::Test::Utils::tempdir;
+use bigint;
+
+my $START_VAL = 2**32;
+my $MAX_VAL = 2**62;
+
+my $ixid = $START_VAL + int(rand($MAX_VAL - $START_VAL));
+my $imxid = $START_VAL + int(rand($MAX_VAL - $START_VAL));
+my $imoff = $START_VAL + int(rand($MAX_VAL - $START_VAL));
+
+# Initialize master node
+my $node = PostgreSQL::Test::Cluster->new('master');
+$node->init();
+$node->start;
+
+# Create a database and fill it with the pgbench data
+$node->safe_psql('postgres', "CREATE DATABASE pgbench_db");
+$node->command_ok(
+ [ qw(pgbench --initialize --scale=2 pgbench_db) ],
+ 'pgbench finished without errors');
+# Dump the database (cluster the main table to put data in a determined order)
+$node->safe_psql('pgbench_db', qq(
+ CREATE INDEX pa_aid_idx ON pgbench_accounts (aid);
+ CLUSTER pgbench_accounts USING pa_aid_idx));
+$node->command_ok(
+ [ "pg_dump", "-w", "--inserts", "--file=$tempdir/pgbench.sql", "pgbench_db" ],
+ 'pgdump finished without errors');
+$node->stop('fast');
+
+# Initialize second node
+my $node2 = PostgreSQL::Test::Cluster->new('master2');
+$node2->init(extra => [ "--xid=$ixid", "--multixact-id=$imxid", "--multixact-offset=$imoff" ]);
+# Disable logging of all statements to avoid log bloat during restore
+$node2->append_conf('postgresql.conf', "log_statement = none");
+$node2->start;
+
+# Create a database and restore the previous dump
+$node2->safe_psql('postgres', "CREATE DATABASE pgbench_db");
+my $txid0 = $node2->safe_psql('pgbench_db', 'SELECT txid_current()');
+print("# Initial txid_current: $txid0\n");
+$node2->command_ok(["psql", "-q", "-f", "$tempdir/pgbench.sql", "pgbench_db"]);
+
+# Dump the database and compare the dumped content with the previous one
+$node2->safe_psql('pgbench_db', 'CLUSTER pgbench_accounts');
+$node2->command_ok(
+ [ "pg_dump", "-w", "--inserts", "--file=$tempdir/pgbench2.sql", "pgbench_db" ],
+ 'pgdump finished without errors');
+ok(File::Compare::compare_text("$tempdir/pgbench.sql", "$tempdir/pgbench2.sql") == 0, "no differences detected");
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index e47c2d648cb..c74e1b9f3f7 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -460,6 +460,7 @@ sub GenerateFiles
PACKAGE_TARNAME => lc qq{"$package_name"},
PACKAGE_URL => qq{"$package_url"},
PACKAGE_VERSION => qq{"$package_version"},
+ XID_IS_64BIT => 1,
PG_INT128_TYPE => undef,
PG_INT64_TYPE => 'long long int',
PG_KRB_SRVNAM => qq{"postgres"},
--
2.24.3 (Apple Git-128)
^ permalink raw reply [nested|flat] 7+ messages in thread
* Re: Add 64-bit XIDs into PostgreSQL 15
2022-02-02 15:10 Re: Add 64-bit XIDs into PostgreSQL 15 Pavel Borisov <[email protected]>
@ 2022-03-02 13:25 ` Aleksander Alekseev <[email protected]>
2022-03-02 14:43 ` Re: Add 64-bit XIDs into PostgreSQL 15 Pavel Borisov <[email protected]>
2022-03-02 21:22 ` Re: Add 64-bit XIDs into PostgreSQL 15 Andres Freund <[email protected]>
0 siblings, 2 replies; 7+ messages in thread
From: Aleksander Alekseev @ 2022-03-02 13:25 UTC (permalink / raw)
To: Pavel Borisov <[email protected]>; +Cc: Andres Freund <[email protected]>; Ilya Anfimov <[email protected]>; Postgres hackers <[email protected]>; pgsql-hackers
Hi hackers,
> In this part, I suppose you've found a definite bug. Thanks! There are a couple
> of ways how it could be fixed:
>
> 1. If we enforce checkpoint at replica promotion then we force full-page writes after each page modification afterward.
>
> 2. Maybe it's worth using BufferDesc bit to mark the page as converted to 64xid but not yet written to disk? For example, one of four bits from BUF_USAGECOUNT.
> BM_MAX_USAGE_COUNT = 5 so it will be enough 3 bits to store it. This will change in-memory page representation but will not need WAL-logging which is impossible on a replica.
>
> What do you think about it?
I'm having difficulties merging and/or testing
v8-0002-Add-64bit-xid.patch since I'm not 100% sure which commit this
patch was targeting. Could you please submit a rebased patch and/or
share your development branch on GitHub?
I agree with Bruce it would be great to deliver this in PG15. Please
let me know if you believe it's unrealistic for any reason so I will
focus on testing and reviewing other patches.
For now, I'm changing the status of the patch to "Waiting on Author".
--
Best regards,
Aleksander Alekseev
^ permalink raw reply [nested|flat] 7+ messages in thread
* Re: Add 64-bit XIDs into PostgreSQL 15
2022-02-02 15:10 Re: Add 64-bit XIDs into PostgreSQL 15 Pavel Borisov <[email protected]>
2022-03-02 13:25 ` Re: Add 64-bit XIDs into PostgreSQL 15 Aleksander Alekseev <[email protected]>
@ 2022-03-02 14:43 ` Pavel Borisov <[email protected]>
2022-03-02 16:35 ` Re: Add 64-bit XIDs into PostgreSQL 15 Justin Pryzby <[email protected]>
1 sibling, 1 reply; 7+ messages in thread
From: Pavel Borisov @ 2022-03-02 14:43 UTC (permalink / raw)
To: Maxim Orlov <[email protected]>; +Cc: Aleksander Alekseev <[email protected]>; Andres Freund <[email protected]>; Ilya Anfimov <[email protected]>; Postgres hackers <[email protected]>; pgsql-hackers
Hi hackers!
Hi! Here is the rebased version.
>
I'd like to add a description of what was done in v9:
- The patch is rebased on current master branch
- In-memory tuple storage format was refactored as promised to have
pre-calculated 64bit xmin and xmax, not just copies of pd_xid_base and
pd_multi_base.
- Fixed bug reported by Andres Freund, with lazy conversion of pages
upgraded from 32 to 64 xid when first tuple read (and therefore lazy
conversion) is done in read-only state (read-only xact or on replica). In
this case now in memory buffer descriptor will be marked
with REGBUF_CONVERTED flag. When cluster comes to read-write state this
will lead to emitting full page write xlog instruction for this page.
Relevant changes in README are also done.
We'd very much appreciate enthusiasm to have 64 bit xid's in PG15 and any
effort to review and test this feature.
Alexander, thanks for your attention to the patchset. Your questions and
review are very much welcome!
The participation of other hackers is highly appreciated as always!
--
Best regards,
Pavel Borisov
Postgres Professional: http://postgrespro.com <http://www.postgrespro.com;
^ permalink raw reply [nested|flat] 7+ messages in thread
* Re: Add 64-bit XIDs into PostgreSQL 15
2022-02-02 15:10 Re: Add 64-bit XIDs into PostgreSQL 15 Pavel Borisov <[email protected]>
2022-03-02 13:25 ` Re: Add 64-bit XIDs into PostgreSQL 15 Aleksander Alekseev <[email protected]>
2022-03-02 14:43 ` Re: Add 64-bit XIDs into PostgreSQL 15 Pavel Borisov <[email protected]>
@ 2022-03-02 16:35 ` Justin Pryzby <[email protected]>
0 siblings, 0 replies; 7+ messages in thread
From: Justin Pryzby @ 2022-03-02 16:35 UTC (permalink / raw)
To: Pavel Borisov <[email protected]>; +Cc: Maxim Orlov <[email protected]>; Aleksander Alekseev <[email protected]>; Andres Freund <[email protected]>; Ilya Anfimov <[email protected]>; pgsql-hackers
On Wed, Mar 02, 2022 at 06:43:11PM +0400, Pavel Borisov wrote:
> Hi hackers!
>
> Hi! Here is the rebased version.
The patch doesn't apply - I suppose the patch is relative a forked postgres
which already has other patches.
http://cfbot.cputube.org/pavel-borisov.html
Note also that I mentioned an issue with pg_upgrade. Handling that that well
is probably the most important part of the patch.
--
Justin
^ permalink raw reply [nested|flat] 7+ messages in thread
* Re: Add 64-bit XIDs into PostgreSQL 15
2022-02-02 15:10 Re: Add 64-bit XIDs into PostgreSQL 15 Pavel Borisov <[email protected]>
2022-03-02 13:25 ` Re: Add 64-bit XIDs into PostgreSQL 15 Aleksander Alekseev <[email protected]>
@ 2022-03-02 21:22 ` Andres Freund <[email protected]>
2022-03-03 11:07 ` Re: Add 64-bit XIDs into PostgreSQL 15 Aleksander Alekseev <[email protected]>
1 sibling, 1 reply; 7+ messages in thread
From: Andres Freund @ 2022-03-02 21:22 UTC (permalink / raw)
To: Aleksander Alekseev <[email protected]>; +Cc: Pavel Borisov <[email protected]>; Ilya Anfimov <[email protected]>; Postgres hackers <[email protected]>; pgsql-hackers
Hi,
On 2022-03-02 16:25:33 +0300, Aleksander Alekseev wrote:
> I agree with Bruce it would be great to deliver this in PG15.
> Please let me know if you believe it's unrealistic for any reason so I will
> focus on testing and reviewing other patches.
I don't see 15 as a realistic target for this patch. There's huge amounts of
work left, it has gotten very little review.
I encourage trying to break down the patch into smaller incrementally useful
pieces. E.g. making all the SLRUs 64bit would be a substantial and
independently committable piece.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 7+ messages in thread
* Re: Add 64-bit XIDs into PostgreSQL 15
2022-02-02 15:10 Re: Add 64-bit XIDs into PostgreSQL 15 Pavel Borisov <[email protected]>
2022-03-02 13:25 ` Re: Add 64-bit XIDs into PostgreSQL 15 Aleksander Alekseev <[email protected]>
2022-03-02 21:22 ` Re: Add 64-bit XIDs into PostgreSQL 15 Andres Freund <[email protected]>
@ 2022-03-03 11:07 ` Aleksander Alekseev <[email protected]>
0 siblings, 0 replies; 7+ messages in thread
From: Aleksander Alekseev @ 2022-03-03 11:07 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Pavel Borisov <[email protected]>; Ilya Anfimov <[email protected]>; Postgres hackers <[email protected]>; pgsql-hackers
Hi hackers,
> The patch doesn't apply - I suppose the patch is relative a forked postgres
No, the authors just used a little outdated `master` branch. I
successfully applied it against 31d8d474 and then rebased to the
latest master (62ce0c75). The new version is attached.
Not 100% sure if my rebase is correct since I didn't invest too much
time into reviewing the code. But at least it passes `make
installcheck` locally. Let's see what cfbot will tell us.
> I encourage trying to break down the patch into smaller incrementally useful
> pieces. E.g. making all the SLRUs 64bit would be a substantial and
> independently committable piece.
Completely agree. And the changes like:
+#if 0 /* XXX remove unit tests */
... suggest that the patch is pretty raw in its current state.
Pavel, Maxim, don't you mind me splitting the patchset, or would you
like to do it yourself and/or maybe include more changes? I don't know
how actively you are working on this.
--
Best regards,
Aleksander Alekseev
Attachments:
[application/octet-stream] v10-0001-64-bit-GUCs.patch (25.6K, ../../CAJ7c6TNA8rd=Pqfc4yDpug60VHwrBA59qS8ETW_qwDZigDfPqw@mail.gmail.com/2-v10-0001-64-bit-GUCs.patch)
download | inline diff:
From 8ca0634431fb34bb6945568520cd7f40fe71d0e5 Mon Sep 17 00:00:00 2001
From: Maxim Orlov <[email protected]>
Date: Thu, 24 Feb 2022 16:56:21 +0300
Subject: [PATCH v10 1/3] 64-bit GUCs
Authors:
- Alexander Korotkov <[email protected]>
- Teodor Sigaev <[email protected]>
- Nikita Glukhov <[email protected]>
- Maxim Orlov <[email protected]>
- Pavel Borisov <[email protected]>
- Yura Sokolov <[email protected]> <[email protected]>
---
src/backend/access/common/reloptions.c | 67 ++++
src/backend/utils/misc/guc.c | 432 +++++++++++++++++++++++++
src/include/access/reloptions.h | 13 +
src/include/utils/guc.h | 17 +
src/include/utils/guc_tables.h | 18 ++
5 files changed, 547 insertions(+)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index d592655258..e7062d4da3 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -373,7 +373,12 @@ static relopt_int intRelOpts[] =
},
-1, 0, 1024
},
+ /* list terminator */
+ {{NULL}}
+};
+static relopt_int64 int64RelOpts[] =
+{
/* list terminator */
{{NULL}}
};
@@ -588,6 +593,12 @@ initialize_reloptions(void)
intRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; int64RelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(int64RelOpts[i].gen.lockmode,
+ int64RelOpts[i].gen.lockmode));
+ j++;
+ }
for (i = 0; realRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(realRelOpts[i].gen.lockmode,
@@ -630,6 +641,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; int64RelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &int64RelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_INT64;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; realRelOpts[i].gen.name; i++)
{
relOpts[j] = &realRelOpts[i].gen;
@@ -785,6 +804,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
+ case RELOPT_TYPE_INT64:
+ size = sizeof(relopt_int64);
+ break;
case RELOPT_TYPE_REAL:
size = sizeof(relopt_real);
break;
@@ -939,6 +961,25 @@ init_real_reloption(bits32 kinds, const char *name, const char *desc,
return newoption;
}
+/*
+ * add_int64_reloption
+ * Add a new 64-bit integer reloption
+ */
+void
+add_int64_reloption(bits32 kinds, char *name, char *desc, int64 default_val,
+ int64 min_val, int64 max_val, LOCKMODE lockmode)
+{
+ relopt_int64 *newoption;
+
+ newoption = (relopt_int64 *) allocate_reloption(kinds, RELOPT_TYPE_INT64,
+ name, desc, lockmode);
+ newoption->default_val = default_val;
+ newoption->min = min_val;
+ newoption->max = max_val;
+
+ add_reloption((relopt_gen *) newoption);
+}
+
/*
* add_real_reloption
* Add a new float reloption
@@ -1613,6 +1654,27 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
optint->min, optint->max)));
}
break;
+ case RELOPT_TYPE_INT64:
+ {
+ relopt_int64 *optint = (relopt_int64 *) option->gen;
+
+ parsed = parse_int64(value, &option->values.int64_val, 0, NULL);
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for 64-bit integer option \"%s\": %s",
+ option->gen->name, value)));
+ if (validate && (option->values.int64_val < optint->min ||
+ option->values.int64_val > optint->max))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("value %s out of bounds for option \"%s\"",
+ value, option->gen->name),
+ errdetail("Valid values are between \"" INT64_FORMAT
+ "\" and \"" INT64_FORMAT "\".",
+ optint->min, optint->max)));
+ }
+ break;
case RELOPT_TYPE_REAL:
{
relopt_real *optreal = (relopt_real *) option->gen;
@@ -1768,6 +1830,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.int_val :
((relopt_int *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_INT64:
+ *(int64 *) itempos = options[i].isset ?
+ options[i].values.int64_val :
+ ((relopt_int64 *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_REAL:
*(double *) itempos = options[i].isset ?
options[i].values.real_val :
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 1e3650184b..3fe2b28c6b 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -165,6 +165,8 @@ static bool call_bool_check_hook(struct config_bool *conf, bool *newval,
void **extra, GucSource source, int elevel);
static bool call_int_check_hook(struct config_int *conf, int *newval,
void **extra, GucSource source, int elevel);
+static bool call_int64_check_hook(struct config_int64 *conf, int64 *newval,
+ void **extra, GucSource source, int elevel);
static bool call_real_check_hook(struct config_real *conf, double *newval,
void **extra, GucSource source, int elevel);
static bool call_string_check_hook(struct config_string *conf, char **newval,
@@ -838,6 +840,7 @@ const char *const config_type_names[] =
{
/* PGC_BOOL */ "bool",
/* PGC_INT */ "integer",
+ /* PGC_INT64 */ "int64",
/* PGC_REAL */ "real",
/* PGC_STRING */ "string",
/* PGC_ENUM */ "enum"
@@ -944,6 +947,8 @@ static const unit_conversion time_unit_conversion_table[] =
{""} /* end of table marker */
};
+static const char *int64_unit_overflow_hint = gettext_noop("Int64 value with units should be positive number < 2^53");
+
/*
* Contents of GUC tables
*
@@ -3880,6 +3885,15 @@ static struct config_real ConfigureNamesReal[] =
};
+static struct config_int64 ConfigureNamesInt64[] =
+{
+ /* End-of-list marker */
+ {
+ {NULL, 0, 0, NULL, NULL}, NULL, 0.0, 0.0, 0.0, NULL, NULL, NULL
+ }
+};
+
+
static struct config_string ConfigureNamesString[] =
{
{
@@ -5211,6 +5225,10 @@ extra_field_used(struct config_generic *gconf, void *extra)
if (extra == ((struct config_int *) gconf)->reset_extra)
return true;
break;
+ case PGC_INT64:
+ if (extra == ((struct config_int64 *) gconf)->reset_extra)
+ return true;
+ break;
case PGC_REAL:
if (extra == ((struct config_real *) gconf)->reset_extra)
return true;
@@ -5272,6 +5290,10 @@ set_stack_value(struct config_generic *gconf, config_var_value *val)
val->val.intval =
*((struct config_int *) gconf)->variable;
break;
+ case PGC_INT64:
+ val->val.int64val =
+ *((struct config_int64 *) gconf)->variable;
+ break;
case PGC_REAL:
val->val.realval =
*((struct config_real *) gconf)->variable;
@@ -5300,6 +5322,7 @@ discard_stack_value(struct config_generic *gconf, config_var_value *val)
{
case PGC_BOOL:
case PGC_INT:
+ case PGC_INT64:
case PGC_REAL:
case PGC_ENUM:
/* no need to do anything */
@@ -5354,6 +5377,14 @@ build_guc_variables(void)
num_vars++;
}
+ for (i = 0; ConfigureNamesInt64[i].gen.name; i++)
+ {
+ struct config_int64 *conf = &ConfigureNamesInt64[i];
+
+ conf->gen.vartype = PGC_INT64;
+ num_vars++;
+ }
+
for (i = 0; ConfigureNamesReal[i].gen.name; i++)
{
struct config_real *conf = &ConfigureNamesReal[i];
@@ -5394,6 +5425,9 @@ build_guc_variables(void)
for (i = 0; ConfigureNamesInt[i].gen.name; i++)
guc_vars[num_vars++] = &ConfigureNamesInt[i].gen;
+ for (i = 0; ConfigureNamesInt64[i].gen.name; i++)
+ guc_vars[num_vars++] = &ConfigureNamesInt64[i].gen;
+
for (i = 0; ConfigureNamesReal[i].gen.name; i++)
guc_vars[num_vars++] = &ConfigureNamesReal[i].gen;
@@ -5845,6 +5879,24 @@ InitializeOneGUCOption(struct config_generic *gconf)
conf->gen.extra = conf->reset_extra = extra;
break;
}
+ case PGC_INT64:
+ {
+ struct config_int64 *conf = (struct config_int64 *) gconf;
+ int64 newval = conf->boot_val;
+ void *extra = NULL;
+
+ Assert(newval >= conf->min);
+ Assert(newval <= conf->max);
+ if (!call_int64_check_hook(conf, &newval, &extra,
+ PGC_S_DEFAULT, LOG))
+ elog(FATAL, "failed to initialize %s to " INT64_FORMAT,
+ conf->gen.name, newval);
+ if (conf->assign_hook)
+ (*conf->assign_hook) (newval, extra);
+ *conf->variable = conf->reset_val = newval;
+ conf->gen.extra = conf->reset_extra = extra;
+ break;
+ }
case PGC_REAL:
{
struct config_real *conf = (struct config_real *) gconf;
@@ -6132,6 +6184,18 @@ ResetAllOptions(void)
{
struct config_int *conf = (struct config_int *) gconf;
+ if (conf->assign_hook)
+ (*conf->assign_hook) (conf->reset_val,
+ conf->reset_extra);
+ *conf->variable = conf->reset_val;
+ set_extra_field(&conf->gen, &conf->gen.extra,
+ conf->reset_extra);
+ break;
+ }
+ case PGC_INT64:
+ {
+ struct config_int64 *conf = (struct config_int64 *) gconf;
+
if (conf->assign_hook)
conf->assign_hook(conf->reset_val,
conf->reset_extra);
@@ -6478,6 +6542,24 @@ AtEOXact_GUC(bool isCommit, int nestLevel)
int newval = newvalue.val.intval;
void *newextra = newvalue.extra;
+ if (*conf->variable != newval ||
+ conf->gen.extra != newextra)
+ {
+ if (conf->assign_hook)
+ (*conf->assign_hook) (newval, newextra);
+ *conf->variable = newval;
+ set_extra_field(&conf->gen, &conf->gen.extra,
+ newextra);
+ changed = true;
+ }
+ break;
+ }
+ case PGC_INT64:
+ {
+ struct config_int64 *conf = (struct config_int64 *) gconf;
+ int64 newval = newvalue.val.int64val;
+ void *newextra = newvalue.extra;
+
if (*conf->variable != newval ||
conf->gen.extra != newextra)
{
@@ -7006,6 +7088,79 @@ parse_int(const char *value, int *result, int flags, const char **hintmsg)
return true;
}
+/*
+ * Try to parse value as an 64-bit integer. The accepted format is
+ * decimal number.
+ *
+ * If the string parses okay, return true, else false.
+ * If okay and result is not NULL, return the value in *result.
+ * If not okay and hintmsg is not NULL, *hintmsg is set to a suitable
+ * HINT message, or NULL if no hint provided.
+ */
+bool
+parse_int64(const char *value, int64 *result, int flags, const char **hintmsg)
+{
+ int64 val;
+ char *endptr;
+
+ /* To suppress compiler warnings, always set output params */
+ if (result)
+ *result = 0;
+ if (hintmsg)
+ *hintmsg = NULL;
+
+ /* We assume here that int64 is at least as wide as long */
+ errno = 0;
+#ifdef _MSC_VER /* MSVC only */
+ val = _strtoi64(value, &endptr, 0);
+#elif defined(HAVE_STRTOLL) && SIZEOF_LONG < 8
+ val = strtoll(value, &endptr, 0);
+#else
+ val = strtol(value, &endptr, 0);
+#endif
+
+ if (endptr == value)
+ return false; /* no HINT for integer syntax error */
+
+ if (errno == ERANGE)
+ {
+ if (hintmsg)
+ *hintmsg = gettext_noop("Value exceeds 64-bit integer range.");
+ return false;
+ }
+
+ /*
+ * got double format and/or units.
+ * For now we attempts parse it as double and throw error on 53bit overflow
+ */
+ if (*endptr != '\0')
+ {
+ double dval;
+ bool ok;
+
+ ok = parse_real(value, &dval, flags, hintmsg);
+ if (!ok)
+ return false;
+
+ dval = rint(val);
+
+ if (fabs(dval) >= (double)((uint64)1<<53))
+ {
+ *hintmsg = int64_unit_overflow_hint;
+ return false;
+ }
+
+ val = (int64) dval;
+ }
+
+
+ if (result)
+ *result = val;
+ return true;
+}
+
+
+
/*
* Try to parse value as a floating point number in the usual format.
* Optionally, the value can be followed by a unit name if "flags" indicates
@@ -7247,6 +7402,37 @@ parse_and_validate_value(struct config_generic *record,
return false;
}
break;
+ case PGC_INT64:
+ {
+ struct config_int64 *conf = (struct config_int64 *) record;
+ const char *hintmsg;
+
+ if (!parse_int64(value, &newval->int64val, conf->gen.flags, &hintmsg))
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for parameter \"%s\": \"%s\"",
+ name, value),
+ hintmsg ? errhint("%s", _(hintmsg)) : 0));
+ return false;
+ }
+
+ if (newval->int64val < conf->min || newval->int64val > conf->max)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg(INT64_FORMAT " is outside the valid range for parameter \"%s\" ("
+ INT64_FORMAT " .. " INT64_FORMAT ")",
+ newval->int64val, name,
+ conf->min, conf->max)));
+ return false;
+ }
+
+ if (!call_int64_check_hook(conf, &newval->int64val, newextra,
+ source, elevel))
+ return false;
+ }
+ break;
case PGC_REAL:
{
struct config_real *conf = (struct config_real *) record;
@@ -7813,6 +7999,96 @@ set_config_option(const char *name, const char *value,
free(newextra);
break;
+#undef newval
+ }
+
+ case PGC_INT64:
+ {
+ struct config_int64 *conf = (struct config_int64 *) record;
+
+#define newval (newval_union.int64val)
+
+ if (value)
+ {
+ if (!parse_and_validate_value(record, name, value,
+ source, elevel,
+ &newval_union, &newextra))
+ return 0;
+ }
+ else if (source == PGC_S_DEFAULT)
+ {
+ newval = conf->boot_val;
+ if (!call_int64_check_hook(conf, &newval, &newextra,
+ source, elevel))
+ return 0;
+ }
+ else
+ {
+ newval = conf->reset_val;
+ newextra = conf->reset_extra;
+ source = conf->gen.reset_source;
+ context = conf->gen.reset_scontext;
+ }
+
+ if (prohibitValueChange)
+ {
+ if (*conf->variable != newval)
+ {
+ record->status |= GUC_PENDING_RESTART;
+ ereport(elevel,
+ (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM),
+ errmsg("parameter \"%s\" cannot be changed without restarting the server",
+ name)));
+ return 0;
+ }
+ record->status &= ~GUC_PENDING_RESTART;
+ return -1;
+ }
+
+ if (changeVal)
+ {
+ /* Save old value to support transaction abort */
+ if (!makeDefault)
+ push_old_value(&conf->gen, action);
+
+ if (conf->assign_hook)
+ (*conf->assign_hook) (newval, newextra);
+ *conf->variable = newval;
+ set_extra_field(&conf->gen, &conf->gen.extra,
+ newextra);
+ conf->gen.source = source;
+ conf->gen.scontext = context;
+ }
+ if (makeDefault)
+ {
+ GucStack *stack;
+
+ if (conf->gen.reset_source <= source)
+ {
+ conf->reset_val = newval;
+ set_extra_field(&conf->gen, &conf->reset_extra,
+ newextra);
+ conf->gen.reset_source = source;
+ conf->gen.reset_scontext = context;
+ }
+ for (stack = conf->gen.stack; stack; stack = stack->prev)
+ {
+ if (stack->source <= source)
+ {
+ stack->prior.val.intval = newval;
+ set_extra_field(&conf->gen, &stack->prior.extra,
+ newextra);
+ stack->source = source;
+ stack->scontext = context;
+ }
+ }
+ }
+
+ /* Perhaps we didn't install newextra anywhere */
+ if (newextra && !extra_field_used(&conf->gen, newextra))
+ free(newextra);
+ break;
+
#undef newval
}
@@ -8228,6 +8504,11 @@ GetConfigOption(const char *name, bool missing_ok, bool restrict_privileged)
*((struct config_int *) record)->variable);
return buffer;
+ case PGC_INT64:
+ snprintf(buffer, sizeof(buffer), INT64_FORMAT,
+ *((struct config_int64 *) record)->variable);
+ return buffer;
+
case PGC_REAL:
snprintf(buffer, sizeof(buffer), "%g",
*((struct config_real *) record)->variable);
@@ -8275,6 +8556,11 @@ GetConfigOptionResetString(const char *name)
((struct config_int *) record)->reset_val);
return buffer;
+ case PGC_INT64:
+ snprintf(buffer, sizeof(buffer), INT64_FORMAT,
+ ((struct config_int64 *) record)->reset_val);
+ return buffer;
+
case PGC_REAL:
snprintf(buffer, sizeof(buffer), "%g",
((struct config_real *) record)->reset_val);
@@ -9300,6 +9586,36 @@ DefineCustomIntVariable(const char *name,
define_custom_variable(&var->gen);
}
+void
+DefineCustomInt64Variable(const char *name,
+ const char *short_desc,
+ const char *long_desc,
+ int64 *valueAddr,
+ int64 bootValue,
+ int64 minValue,
+ int64 maxValue,
+ GucContext context,
+ int flags,
+ GucInt64CheckHook check_hook,
+ GucInt64AssignHook assign_hook,
+ GucShowHook show_hook)
+{
+ struct config_int64 *var;
+
+ var = (struct config_int64 *)
+ init_custom_variable(name, short_desc, long_desc, context, flags,
+ PGC_INT64, sizeof(struct config_int64));
+ var->variable = valueAddr;
+ var->boot_val = bootValue;
+ var->reset_val = bootValue;
+ var->min = minValue;
+ var->max = maxValue;
+ var->check_hook = check_hook;
+ var->assign_hook = assign_hook;
+ var->show_hook = show_hook;
+ define_custom_variable(&var->gen);
+}
+
void
DefineCustomRealVariable(const char *name,
const char *short_desc,
@@ -9837,6 +10153,31 @@ GetConfigOptionByNum(int varnum, const char **values, bool *noshow)
}
break;
+ case PGC_INT64:
+ {
+ struct config_int64 *lconf = (struct config_int64 *) conf;
+
+ /* min_val */
+ snprintf(buffer, sizeof(buffer), INT64_FORMAT, lconf->min);
+ values[9] = pstrdup(buffer);
+
+ /* max_val */
+ snprintf(buffer, sizeof(buffer), INT64_FORMAT, lconf->max);
+ values[10] = pstrdup(buffer);
+
+ /* enumvals */
+ values[11] = NULL;
+
+ /* boot_val */
+ snprintf(buffer, sizeof(buffer), INT64_FORMAT, lconf->boot_val);
+ values[12] = pstrdup(buffer);
+
+ /* reset_val */
+ snprintf(buffer, sizeof(buffer), INT64_FORMAT, lconf->reset_val);
+ values[13] = pstrdup(buffer);
+ }
+ break;
+
case PGC_REAL:
{
struct config_real *lconf = (struct config_real *) conf;
@@ -10293,6 +10634,21 @@ _ShowOption(struct config_generic *record, bool use_units)
}
break;
+ case PGC_INT64:
+ {
+ struct config_int64 *conf = (struct config_int64 *) record;
+
+ if (conf->show_hook)
+ val = (*conf->show_hook) ();
+ else
+ {
+ snprintf(buffer, sizeof(buffer), INT64_FORMAT,
+ *conf->variable);
+ val = buffer;
+ }
+ }
+ break;
+
case PGC_REAL:
{
struct config_real *conf = (struct config_real *) record;
@@ -10395,6 +10751,14 @@ write_one_nondefault_variable(FILE *fp, struct config_generic *gconf)
}
break;
+ case PGC_INT64:
+ {
+ struct config_int64 *conf = (struct config_int64 *) gconf;
+
+ fprintf(fp, INT64_FORMAT, *conf->variable);
+ }
+ break;
+
case PGC_REAL:
{
struct config_real *conf = (struct config_real *) gconf;
@@ -10666,6 +11030,24 @@ estimate_variable_size(struct config_generic *gconf)
}
break;
+ case PGC_INT64:
+ {
+ struct config_int64 *conf = (struct config_int64 *) gconf;
+
+ /*
+ * Instead of getting the exact display length, use max
+ * length. Also reduce the max length for typical ranges of
+ * small values. Maximum value is 2^63, i.e. 20 chars.
+ * Include one byte for sign.
+ */
+ if (Abs(*conf->variable) < 1000)
+ valsize = 3 + 1;
+ else
+ valsize = 20 + 1;
+ }
+ break;
+
+
case PGC_REAL:
{
/*
@@ -10823,6 +11205,14 @@ serialize_variable(char **destptr, Size *maxbytes,
}
break;
+ case PGC_INT64:
+ {
+ struct config_int64 *conf = (struct config_int64 *) gconf;
+
+ do_serialize(destptr, maxbytes, INT64_FORMAT, *conf->variable);
+ }
+ break;
+
case PGC_REAL:
{
struct config_real *conf = (struct config_real *) gconf;
@@ -11028,6 +11418,14 @@ RestoreGUCState(void *gucstate)
{
struct config_int *conf = (struct config_int *) gconf;
+ if (conf->reset_extra && conf->reset_extra != gconf->extra)
+ free(conf->reset_extra);
+ break;
+ }
+ case PGC_INT64:
+ {
+ struct config_int64 *conf = (struct config_int64 *) gconf;
+
if (conf->reset_extra && conf->reset_extra != gconf->extra)
free(conf->reset_extra);
break;
@@ -11602,6 +12000,40 @@ call_int_check_hook(struct config_int *conf, int *newval, void **extra,
return true;
}
+static bool
+call_int64_check_hook(struct config_int64 * conf, int64 *newval, void **extra,
+ GucSource source, int elevel)
+{
+ /* Quick success if no hook */
+ if (!conf->check_hook)
+ return true;
+
+ /* Reset variables that might be set by hook */
+ GUC_check_errcode_value = ERRCODE_INVALID_PARAMETER_VALUE;
+ GUC_check_errmsg_string = NULL;
+ GUC_check_errdetail_string = NULL;
+ GUC_check_errhint_string = NULL;
+
+ if (!(*conf->check_hook) (newval, extra, source))
+ {
+ ereport(elevel,
+ (errcode(GUC_check_errcode_value),
+ GUC_check_errmsg_string ?
+ errmsg_internal("%s", GUC_check_errmsg_string) :
+ errmsg("invalid value for parameter \"%s\": " INT64_FORMAT,
+ conf->gen.name, *newval),
+ GUC_check_errdetail_string ?
+ errdetail_internal("%s", GUC_check_errdetail_string) : 0,
+ GUC_check_errhint_string ?
+ errhint("%s", GUC_check_errhint_string) : 0));
+ /* Flush any strings created in ErrorContext */
+ FlushErrorState();
+ return false;
+ }
+
+ return true;
+}
+
static bool
call_real_check_hook(struct config_real *conf, double *newval, void **extra,
GucSource source, int elevel)
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index f740513303..08a3c1a28e 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -30,6 +30,7 @@ typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
RELOPT_TYPE_INT,
+ RELOPT_TYPE_INT64,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
RELOPT_TYPE_STRING
@@ -81,6 +82,7 @@ typedef struct relopt_value
{
bool bool_val;
int int_val;
+ int64 int64_val;
double real_val;
int enum_val;
char *string_val; /* allocated separately */
@@ -102,6 +104,14 @@ typedef struct relopt_int
int max;
} relopt_int;
+typedef struct relopt_int64
+{
+ relopt_gen gen;
+ int64 default_val;
+ int64 min;
+ int64 max;
+} relopt_int64;
+
typedef struct relopt_real
{
relopt_gen gen;
@@ -185,6 +195,9 @@ extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
+extern void add_int64_reloption(bits32 kinds, char *name, char *desc,
+ int64 default_val, int64 min_val, int64 max_val,
+ LOCKMODE lockmode);
extern void add_real_reloption(bits32 kinds, const char *name, const char *desc,
double default_val, double min_val, double max_val,
LOCKMODE lockmode);
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ea774968f0..210ee0bc55 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -173,12 +173,14 @@ struct config_enum_entry
*/
typedef bool (*GucBoolCheckHook) (bool *newval, void **extra, GucSource source);
typedef bool (*GucIntCheckHook) (int *newval, void **extra, GucSource source);
+typedef bool (*GucInt64CheckHook) (int64 *newval, void **extra, GucSource source);
typedef bool (*GucRealCheckHook) (double *newval, void **extra, GucSource source);
typedef bool (*GucStringCheckHook) (char **newval, void **extra, GucSource source);
typedef bool (*GucEnumCheckHook) (int *newval, void **extra, GucSource source);
typedef void (*GucBoolAssignHook) (bool newval, void *extra);
typedef void (*GucIntAssignHook) (int newval, void *extra);
+typedef void (*GucInt64AssignHook) (int64 newval, void *extra);
typedef void (*GucRealAssignHook) (double newval, void *extra);
typedef void (*GucStringAssignHook) (const char *newval, void *extra);
typedef void (*GucEnumAssignHook) (int newval, void *extra);
@@ -318,6 +320,19 @@ extern void DefineCustomIntVariable(const char *name,
GucIntAssignHook assign_hook,
GucShowHook show_hook);
+extern void DefineCustomInt64Variable(const char *name,
+ const char *short_desc,
+ const char *long_desc,
+ int64 *valueAddr,
+ int64 bootValue,
+ int64 minValue,
+ int64 maxValue,
+ GucContext context,
+ int flags,
+ GucInt64CheckHook check_hook,
+ GucInt64AssignHook assign_hook,
+ GucShowHook show_hook);
+
extern void DefineCustomRealVariable(const char *name,
const char *short_desc,
const char *long_desc,
@@ -375,6 +390,8 @@ extern void ReportChangedGUCOptions(void);
extern void ParseLongOption(const char *string, char **name, char **value);
extern bool parse_int(const char *value, int *result, int flags,
const char **hintmsg);
+extern bool parse_int64(const char *value, int64 *result, int flags,
+ const char **hintmsg);
extern bool parse_real(const char *value, double *result, int flags,
const char **hintmsg);
extern int set_config_option(const char *name, const char *value,
diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h
index f7e54a87b7..e59e2739ac 100644
--- a/src/include/utils/guc_tables.h
+++ b/src/include/utils/guc_tables.h
@@ -23,6 +23,7 @@ enum config_type
{
PGC_BOOL,
PGC_INT,
+ PGC_INT64,
PGC_REAL,
PGC_STRING,
PGC_ENUM
@@ -32,6 +33,7 @@ union config_var_val
{
bool boolval;
int intval;
+ int64 int64val;
double realval;
char *stringval;
int enumval;
@@ -202,6 +204,22 @@ struct config_int
void *reset_extra;
};
+struct config_int64
+{
+ struct config_generic gen;
+ /* constant fields, must be set correctly in initial value: */
+ int64 *variable;
+ int64 boot_val;
+ int64 min;
+ int64 max;
+ GucInt64CheckHook check_hook;
+ GucInt64AssignHook assign_hook;
+ GucShowHook show_hook;
+ /* variable fields, initialized at runtime: */
+ int64 reset_val;
+ void *reset_extra;
+};
+
struct config_real
{
struct config_generic gen;
--
2.35.1
[application/octet-stream] v10-0002-README.XID64.patch (7.0K, ../../CAJ7c6TNA8rd=Pqfc4yDpug60VHwrBA59qS8ETW_qwDZigDfPqw@mail.gmail.com/3-v10-0002-README.XID64.patch)
download | inline diff:
From 48de016fbf7b736530b2e30b61c92f35972c892f Mon Sep 17 00:00:00 2001
From: Maxim Orlov <[email protected]>
Date: Thu, 24 Feb 2022 16:59:26 +0300
Subject: [PATCH v10 2/3] README.XID64
Authors:
- Alexander Korotkov <[email protected]>
- Teodor Sigaev <[email protected]>
- Nikita Glukhov <[email protected]>
- Maxim Orlov <[email protected]>
- Pavel Borisov <[email protected]>
- Yura Sokolov<[email protected]> <[email protected]>
---
src/backend/access/heap/README.XID64 | 134 +++++++++++++++++++++++++++
1 file changed, 134 insertions(+)
create mode 100644 src/backend/access/heap/README.XID64
diff --git a/src/backend/access/heap/README.XID64 b/src/backend/access/heap/README.XID64
new file mode 100644
index 0000000000..74fc4eaae1
--- /dev/null
+++ b/src/backend/access/heap/README.XID64
@@ -0,0 +1,134 @@
+src/backend/access/heap/README.XID64
+
+64-bit Transaction ID's (XID)
+=============================
+
+A limited number (N = 2^32) of XID's required to do vacuum freeze to prevent
+wraparound every N/2 transactions. This causes performance degradation due
+to the need to read and rewrite all not yet frozen pages tables while being
+vacuumed. In each wraparound cycle, SLRU buffers are also being cut.
+
+With 64-bit XID's wraparound is effectively postponed to a very distant
+future. Even in highly loaded systems that had 2^32 transactions per day
+it will take huge 2^31 days before the first enforced "vacuum to prevent
+wraparound"). Buffers cutting and routine vacuum are not enforced, and DBA
+can plan them independently at the time with the least system load and least
+critical for database performance. Also, it can be done less frequently
+(several times a year vs every several days) on systems with transaction rates
+similar to those mentioned above.
+
+On-disk tuple and page format
+-----------------------------
+
+On-disk tuple format remains unchanged. 32-bit t_xmin and t_xmax store the
+lower parts of 64-bit XMIN and XMAX values. Each heap page has additional
+64-bit pd_xid_base and pd_multi_base which are common for all tuples on a page.
+They are placed into a pd_special area - 16 bytes in the end of a heap page.
+Actual XMIN/XMAX for a tuple are calculated upon reading a tuple from a page
+as follows:
+
+XMIN = t_xmin + pd_xid_base. (1)
+XMAX = t_xmax + pd_xid_base/pd_multi_base. (2)
+
+"Double XMAX" page format
+---------------------------------
+
+At first read of a heap page after pg_upgrade from 32-bit XID PostgreSQL
+version pd_special area with a size of 16 bytes should be added to a page.
+Though a page may not have space for this. Then it can be converted to a
+temporary format called "double XMAX".
+
+All tuples after pg-upgrade doesn't need t_xmin anymore as no older transactions
+could be running. So we don't need tuple header t_xmin field and we reuse
+t_xmin to store higher 32 bits of its XMAX.
+
+Double XMAX format is only for full pages that don't have 16 bytes for
+pd_special. So it neither has a place for a single tuple. Insert and HOT update
+for double XMAX pages is impossible and not supported. We can only read or
+delete tuples from it.
+
+When we are able to prune page double XMAX it will be converted from it to
+general 64-bit XID page format with all operations on its tuples supported.
+
+In-memory tuple format
+----------------------
+
+In-memory tuple representation consists of two parts:
+- HeapTupleHeader from disk page (contains all heap tuple contents, not only
+header)
+- HeapTuple with additional in-memory fields
+
+HeapTuple for each tuple in memory stores 64bit XMIN/XMAX. They are
+precalculated on tuple read from page with (1) and (2).
+
+The filling of XMIN and XMAX in HeapTuple is done in the same way as the other
+fields of HeapTuple struct. It is done in all cases of HeapTuple manipulation.
+
+Update/delete with 64-bit XIDs and 32-bit t_xmin/t_xmax
+--------------------------------------------------------------
+
+When we try to delete/update a tuple, we check that XMAX for a page fits (2).
+I.e. that t_xmax will not be over MaxShortTransactionId relative to
+pd_xid_base/pd_multi_base of a its page.
+
+If the current XID doesn't fit a range
+(pd_xid_base, pd_xid_base + MaxShortTransactionId) (3):
+
+- heap_page_prepare_for_xid() will try to increase pd_xid_base/pd_multi_base on
+a page and update all t_xmin/t_xmax of the other tuples on the page to
+correspond new pd_xid_base/pd_multi_base.
+
+- If it was impossible, it will try to prune and freeze tuples on a page.
+
+- If this is unsuccessful it will throw an error. Normally this is very
+unlikely but if there is a very old living transaction with an age of around
+2^32 this can arise. Basically, this is a behavior similar to one during the
+vacuum to prevent wraparound when XID was 32-bit. Dba should take care and
+avoid very-long-living transactions with an age close to 2^32. So long-living
+transactions often they are most likely defunct.
+
+Insert with 64-bit XIDs and 32-bit t_xmin/t_xmax
+------------------------------------------------
+
+On insert we check if current XID fits a range (3). Otherwise:
+
+- heap_page_prepare_for_xid() will try to increase pd_xid_base for t_xmin will
+not be over MaxShortTransactionId.
+
+- If it is impossible, then it will try to prune and freeze tuples on a page.
+
+Known issue: if pd_xid_base could not be shifted to accommodate a tuple being
+inserted due to a very long-running transaction, we just throw an error. We
+neither try to insert a tuple into another page nor mark the current page as
+full. So, in this (unlikely) case we will get regular insert errors on the next
+tries to insert to the page 'locked' by this very long-running transaction.
+
+Upgrade from 32-bit XID versions
+--------------------------------
+
+pg_upgrade doesn't change pages format itself. It is done lazily after.
+
+1. At first heap page read, tuples on a page are repacked to free 16 bytes
+at the end of a page, possibly freeing space from dead tuples.
+
+2A. 16 bytes of pd_special is added if there is a place for it
+
+2B. Page is converted to "Double XMAX" format if there is no place for
+pd_special
+
+3. If a page is in double XMAX format after its first read, and vacuum (or
+micro-vacuum at select query) could prune some tuples and free space for
+pd_special, prune_page will add pd_special and convert page from double XMAX
+to general 64-bit XID page format.
+
+This lazy conversion is called only on pages being read. This can slow down
+performance after upgrade, but just for a short period of time while "hot"
+pages are read (and therefore converted to 64-bit format).
+
+There is a special case when the first read of a tuple is done in read-only
+state (in read-only transaction or on replica). This tuples are to be converted
+"in memory", but not sync "to disk", unless cluster or transaction changed to
+read-write state (e.g. replica is promoted). In order to support this, we mark
+"in memory" pages with converted tuples with bit REGBUF_CONVERTED in buffer
+descriptor. When in read-write state this will trigger full page write xlog
+record.
--
2.35.1
[application/octet-stream] v10-0003-Add-64bit-xid.patch (744.4K, ../../CAJ7c6TNA8rd=Pqfc4yDpug60VHwrBA59qS8ETW_qwDZigDfPqw@mail.gmail.com/4-v10-0003-Add-64bit-xid.patch)
download | inline diff:
From e6a7abedf3857a96c8cd6d98cd84029ecac4b1ba Mon Sep 17 00:00:00 2001
From: Maxim Orlov <[email protected]>
Date: Thu, 24 Feb 2022 16:59:03 +0300
Subject: [PATCH v10 3/3] Add 64bit xid.
- change TransactionId to 64bit
- disk tuple format (HeapTupleHeader) is (almost) unchanged: xmin and xmax remains 32bit
-- now 32bit xid is named ShortTransactionId
- heap page format is changed to contain xid and multixact base value, tuple's
xmin and xmax are offsets from.
-- xid_base and multi_base are stored as a page special data. PageHeader remains unmodified.
- in-memory tuple (HeapTuple) were enriched with precalulated 64bit xmin/xmax.
Authors:
- Alexander Korotkov <[email protected]>
- Teodor Sigaev <[email protected]>
- Nikita Glukhov <[email protected]>
- Maxim Orlov <[email protected]>
- Pavel Borisov <[email protected]>
- Yura Sokolov <[email protected]> <[email protected]>
---
contrib/amcheck/verify_heapam.c | 91 +-
contrib/amcheck/verify_nbtree.c | 2 +-
contrib/hstore/hstore_io.c | 2 +
contrib/pageinspect/Makefile | 3 +-
contrib/pageinspect/brinfuncs.c | 10 +-
contrib/pageinspect/btreefuncs.c | 25 +-
contrib/pageinspect/expected/hash_1.out | 166 +++
.../pageinspect/expected/oldextversions.out | 10 +-
contrib/pageinspect/expected/page.out | 28 +-
contrib/pageinspect/fsmfuncs.c | 2 +-
contrib/pageinspect/heapfuncs.c | 9 +-
.../pageinspect/pageinspect--1.10--1.11.sql | 145 +++
contrib/pageinspect/pageinspect--1.5.sql | 2 +
contrib/pageinspect/pageinspect.control | 2 +-
contrib/pageinspect/rawpage.c | 26 +-
contrib/pg_surgery/heap_surgery.c | 12 +-
.../pg_visibility/expected/pg_visibility.out | 17 +
contrib/pg_visibility/pg_visibility.c | 5 +-
contrib/pg_visibility/sql/pg_visibility.sql | 18 +
contrib/pgrowlocks/pgrowlocks.c | 8 +-
contrib/pgstattuple/pgstatapprox.c | 1 +
contrib/pgstattuple/pgstatindex.c | 2 +-
.../postgres_fdw/expected/postgres_fdw.out | 55 +-
contrib/postgres_fdw/postgres_fdw.c | 9 +-
contrib/postgres_fdw/sql/postgres_fdw.sql | 15 +-
contrib/test_decoding/test_decoding.c | 24 +-
src/backend/access/common/heaptuple.c | 8 +-
src/backend/access/common/reloptions.c | 118 +-
src/backend/access/hash/hashvalidate.c | 5 +-
src/backend/access/heap/heapam.c | 1098 +++++++++++++++--
src/backend/access/heap/heapam_handler.c | 30 +-
src/backend/access/heap/heapam_visibility.c | 173 +--
src/backend/access/heap/heaptoast.c | 3 +
src/backend/access/heap/hio.c | 21 +-
src/backend/access/heap/pruneheap.c | 60 +-
src/backend/access/heap/rewriteheap.c | 57 +-
src/backend/access/heap/vacuumlazy.c | 130 +-
src/backend/access/nbtree/nbtpage.c | 2 +
src/backend/access/nbtree/nbtsplitloc.c | 13 +-
src/backend/access/nbtree/nbtxlog.c | 2 +
src/backend/access/rmgrdesc/clogdesc.c | 8 +-
src/backend/access/rmgrdesc/committsdesc.c | 8 +-
src/backend/access/rmgrdesc/gistdesc.c | 8 +-
src/backend/access/rmgrdesc/hashdesc.c | 2 +-
src/backend/access/rmgrdesc/heapdesc.c | 50 +-
src/backend/access/rmgrdesc/mxactdesc.c | 12 +-
src/backend/access/rmgrdesc/nbtdesc.c | 8 +-
src/backend/access/rmgrdesc/spgdesc.c | 2 +-
src/backend/access/rmgrdesc/standbydesc.c | 6 +-
src/backend/access/rmgrdesc/xactdesc.c | 16 +-
src/backend/access/rmgrdesc/xlogdesc.c | 9 +-
src/backend/access/transam/clog.c | 92 +-
src/backend/access/transam/commit_ts.c | 70 +-
src/backend/access/transam/multixact.c | 885 +++----------
src/backend/access/transam/slru.c | 125 +-
src/backend/access/transam/subtrans.c | 51 +-
src/backend/access/transam/transam.c | 68 +-
src/backend/access/transam/twophase.c | 31 +-
src/backend/access/transam/varsup.c | 153 +--
src/backend/access/transam/xact.c | 50 +-
src/backend/access/transam/xlog.c | 20 +-
src/backend/access/transam/xloginsert.c | 7 +
src/backend/access/transam/xlogreader.c | 34 -
src/backend/access/transam/xlogrecovery.c | 20 +-
src/backend/bootstrap/bootstrap.c | 29 +-
src/backend/catalog/heap.c | 8 +-
src/backend/catalog/pg_inherits.c | 2 +-
src/backend/commands/async.c | 16 +-
src/backend/commands/sequence.c | 47 +-
src/backend/commands/vacuum.c | 74 +-
src/backend/executor/execExprInterp.c | 1 +
src/backend/executor/execUtils.c | 1 +
src/backend/executor/nodeModifyTable.c | 1 +
src/backend/executor/spi.c | 1 +
src/backend/nodes/outfuncs.c | 4 +-
src/backend/optimizer/util/plancat.c | 2 +-
src/backend/postmaster/autovacuum.c | 72 +-
src/backend/replication/logical/decode.c | 18 +-
src/backend/replication/logical/logical.c | 2 +-
src/backend/replication/logical/proto.c | 4 +-
.../replication/logical/reorderbuffer.c | 29 +-
src/backend/replication/logical/snapbuild.c | 18 +-
src/backend/replication/logical/worker.c | 12 +-
src/backend/replication/walreceiver.c | 27 +-
src/backend/replication/walsender.c | 63 +-
src/backend/statistics/extended_stats.c | 1 +
src/backend/storage/buffer/Makefile | 3 +-
src/backend/storage/buffer/bufmgr.c | 88 +-
src/backend/storage/buffer/heap_convert.c | 510 ++++++++
src/backend/storage/ipc/procarray.c | 120 +-
src/backend/storage/ipc/standby.c | 10 +-
src/backend/storage/lmgr/lmgr.c | 13 +-
src/backend/storage/lmgr/predicate.c | 28 +-
src/backend/storage/page/bufpage.c | 199 ++-
src/backend/tcop/postgres.c | 1 -
src/backend/utils/adt/enum.c | 2 +-
src/backend/utils/adt/jsonfuncs.c | 1 +
src/backend/utils/adt/lockfuncs.c | 11 +-
src/backend/utils/adt/numutils.c | 22 +
src/backend/utils/adt/pgstatfuncs.c | 1 +
src/backend/utils/adt/rowtypes.c | 12 +
src/backend/utils/adt/xid.c | 27 +-
src/backend/utils/adt/xid8funcs.c | 57 +-
src/backend/utils/cache/catcache.c | 1 +
src/backend/utils/cache/relcache.c | 3 +-
src/backend/utils/error/csvlog.c | 4 +-
src/backend/utils/error/elog.c | 8 +-
src/backend/utils/error/jsonlog.c | 4 +-
src/backend/utils/fmgr/fmgr.c | 4 +-
src/backend/utils/misc/guc.c | 184 +--
src/backend/utils/misc/help_config.c | 8 +-
src/backend/utils/misc/pg_controldata.c | 3 +-
src/backend/utils/misc/postgresql.conf.sample | 4 +-
src/backend/utils/sort/tuplesort.c | 8 +-
src/backend/utils/time/combocid.c | 18 +-
src/backend/utils/time/snapmgr.c | 23 +-
src/bin/initdb/initdb.c | 67 +-
src/bin/pg_amcheck/t/004_verify_heapam.pl | 184 ++-
src/bin/pg_controldata/pg_controldata.c | 17 +-
src/bin/pg_dump/pg_dump.c | 51 +-
src/bin/pg_dump/pg_dump.h | 8 +-
src/bin/pg_resetwal/pg_resetwal.c | 143 +--
src/bin/pg_upgrade/Makefile | 1 +
src/bin/pg_upgrade/check.c | 114 ++
src/bin/pg_upgrade/controldata.c | 28 +-
src/bin/pg_upgrade/file.c | 172 ++-
src/bin/pg_upgrade/pg_upgrade.c | 132 +-
src/bin/pg_upgrade/pg_upgrade.h | 24 +-
src/bin/pg_upgrade/segresize.c | 571 +++++++++
src/bin/pg_upgrade/test.sh | 19 +-
src/bin/pg_upgrade/util.c | 17 +
src/bin/pg_upgrade/version.c | 134 +-
src/bin/pg_verifybackup/t/003_corruption.pl | 2 +-
src/bin/pg_waldump/pg_waldump.c | 4 +-
src/include/access/clog.h | 2 +-
src/include/access/commit_ts.h | 2 +-
src/include/access/ginblock.h | 11 +-
src/include/access/gist.h | 2 +-
src/include/access/heapam.h | 18 +-
src/include/access/heapam_xlog.h | 22 +-
src/include/access/heaptoast.h | 2 +-
src/include/access/htup.h | 24 +-
src/include/access/htup_details.h | 151 ++-
src/include/access/multixact.h | 14 +-
src/include/access/nbtree.h | 10 +
src/include/access/rewriteheap.h | 2 +-
src/include/access/rmgrlist.h | 1 +
src/include/access/slru.h | 28 +-
src/include/access/tableam.h | 2 +-
src/include/access/transam.h | 76 +-
src/include/access/xact.h | 13 +-
src/include/access/xlog.h | 8 +
src/include/access/xloginsert.h | 1 +
src/include/access/xlogreader.h | 4 -
src/include/c.h | 44 +-
src/include/catalog/pg_amproc.dat | 4 +-
src/include/catalog/pg_control.h | 6 +
src/include/catalog/pg_operator.dat | 8 +-
src/include/catalog/pg_proc.dat | 12 +-
src/include/catalog/pg_type.dat | 4 +-
src/include/commands/vacuum.h | 28 +-
src/include/fmgr.h | 2 +
src/include/pg_config.h.in | 3 +
src/include/postgres.h | 6 +-
src/include/postmaster/autovacuum.h | 4 +-
src/include/storage/buf_internals.h | 5 +-
src/include/storage/bufmgr.h | 4 +
src/include/storage/bufpage.h | 105 +-
src/include/storage/itemid.h | 2 +
src/include/storage/lock.h | 14 +-
src/include/storage/proc.h | 2 +-
src/include/storage/standby.h | 2 +-
src/include/storage/sync.h | 2 +-
src/include/utils/builtins.h | 1 +
src/include/utils/combocid.h | 2 +-
src/include/utils/rel.h | 12 +-
src/pl/plperl/plperl.c | 4 +-
src/pl/plpgsql/src/pl_comp.c | 4 +-
src/pl/plpgsql/src/pl_exec.c | 2 +
src/pl/plpython/plpy_procedure.c | 4 +-
src/pl/tcl/pltcl.c | 4 +-
src/test/Makefile | 3 +-
src/test/perl/PostgreSQL/Test/Cluster.pm | 1 +
src/test/recovery/t/003_recovery_targets.pl | 2 +-
src/test/regress/expected/indirect_toast.out | 8 +
src/test/regress/expected/insert.out | 16 +-
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/expected/select_views.out | 70 +-
src/test/regress/expected/txid.out | 8 +-
src/test/regress/expected/xid.out | 14 +-
src/test/regress/pg_regress.c | 2 +-
src/test/regress/sql/indirect_toast.sql | 11 +
src/test/regress/sql/insert.sql | 17 +-
src/test/regress/sql/select_views.sql | 2 +-
src/test/xid-64/Makefile | 22 +
src/test/xid-64/README | 16 +
src/test/xid-64/t/001_test_large_xids.pl | 53 +
src/test/xid-64/t/002_test_gucs.pl | 78 ++
src/test/xid-64/t/003_test_integrity.pl | 56 +
src/tools/msvc/Solution.pm | 1 +
200 files changed, 5796 insertions(+), 2914 deletions(-)
create mode 100644 contrib/pageinspect/expected/hash_1.out
create mode 100644 contrib/pageinspect/pageinspect--1.10--1.11.sql
create mode 100644 src/backend/storage/buffer/heap_convert.c
create mode 100644 src/bin/pg_upgrade/segresize.c
create mode 100644 src/test/xid-64/Makefile
create mode 100644 src/test/xid-64/README
create mode 100644 src/test/xid-64/t/001_test_large_xids.pl
create mode 100644 src/test/xid-64/t/002_test_gucs.pl
create mode 100644 src/test/xid-64/t/003_test_integrity.pl
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index f996f9a572..95ea09b931 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -126,6 +126,7 @@ typedef struct HeapCheckContext
uint16 lp_len;
uint16 lp_off;
HeapTupleHeader tuphdr;
+ HeapTupleData tuple;
int natts;
/* Values for iterating over attributes within the tuple */
@@ -523,6 +524,11 @@ verify_heapam(PG_FUNCTION_ARGS)
ctx.tuphdr = (HeapTupleHeader) PageGetItem(ctx.page, ctx.itemid);
ctx.natts = HeapTupleHeaderGetNatts(ctx.tuphdr);
+ ctx.tuple.t_data = ctx.tuphdr;
+ ctx.tuple.t_len = ItemIdGetLength(ctx.itemid);
+ ctx.tuple.t_tableOid = RelationGetRelid(ctx.rel);
+ HeapTupleCopyBaseFromPage(&ctx.tuple, ctx.page);
+
/* Ok, ready to check this next tuple */
check_tuple(&ctx);
}
@@ -768,12 +774,13 @@ check_tuple_visibility(HeapCheckContext *ctx)
XidCommitStatus xmin_status;
XidCommitStatus xvac_status;
XidCommitStatus xmax_status;
+ HeapTuple tuple = &ctx->tuple;
HeapTupleHeader tuphdr = ctx->tuphdr;
ctx->tuple_could_be_pruned = true; /* have not yet proven otherwise */
/* If xmin is normal, it should be within valid range */
- xmin = HeapTupleHeaderGetXmin(tuphdr);
+ xmin = HeapTupleGetXmin(tuple);
switch (get_xid_status(xmin, ctx, &xmin_status))
{
case XID_INVALID:
@@ -781,23 +788,20 @@ check_tuple_visibility(HeapCheckContext *ctx)
break;
case XID_IN_FUTURE:
report_corruption(ctx,
- psprintf("xmin %u equals or exceeds next valid transaction ID %u:%u",
+ psprintf("xmin " XID_FMT " equals or exceeds next valid transaction ID " XID_FMT,
xmin,
- EpochFromFullTransactionId(ctx->next_fxid),
XidFromFullTransactionId(ctx->next_fxid)));
return false;
case XID_PRECEDES_CLUSTERMIN:
report_corruption(ctx,
- psprintf("xmin %u precedes oldest valid transaction ID %u:%u",
+ psprintf("xmin " XID_FMT " precedes oldest valid transaction ID " XID_FMT,
xmin,
- EpochFromFullTransactionId(ctx->oldest_fxid),
XidFromFullTransactionId(ctx->oldest_fxid)));
return false;
case XID_PRECEDES_RELMIN:
report_corruption(ctx,
- psprintf("xmin %u precedes relation freeze threshold %u:%u",
+ psprintf("xmin " XID_FMT " precedes relation freeze threshold " XID_FMT,
xmin,
- EpochFromFullTransactionId(ctx->relfrozenfxid),
XidFromFullTransactionId(ctx->relfrozenfxid)));
return false;
}
@@ -822,23 +826,20 @@ check_tuple_visibility(HeapCheckContext *ctx)
return false;
case XID_IN_FUTURE:
report_corruption(ctx,
- psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple equals or exceeds next valid transaction ID %u:%u",
+ psprintf("old-style VACUUM FULL transaction ID " XID_FMT " for moved off tuple equals or exceeds next valid transaction ID " XID_FMT,
xvac,
- EpochFromFullTransactionId(ctx->next_fxid),
XidFromFullTransactionId(ctx->next_fxid)));
return false;
case XID_PRECEDES_RELMIN:
report_corruption(ctx,
- psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple precedes relation freeze threshold %u:%u",
+ psprintf("old-style VACUUM FULL transaction ID " XID_FMT " for moved off tuple precedes relation freeze threshold " XID_FMT,
xvac,
- EpochFromFullTransactionId(ctx->relfrozenfxid),
XidFromFullTransactionId(ctx->relfrozenfxid)));
return false;
case XID_PRECEDES_CLUSTERMIN:
report_corruption(ctx,
- psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple precedes oldest valid transaction ID %u:%u",
+ psprintf("old-style VACUUM FULL transaction ID " XID_FMT " for moved off tuple precedes oldest valid transaction ID " XID_FMT,
xvac,
- EpochFromFullTransactionId(ctx->oldest_fxid),
XidFromFullTransactionId(ctx->oldest_fxid)));
return false;
case XID_BOUNDS_OK:
@@ -849,12 +850,12 @@ check_tuple_visibility(HeapCheckContext *ctx)
{
case XID_IS_CURRENT_XID:
report_corruption(ctx,
- psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple matches our current transaction ID",
+ psprintf("old-style VACUUM FULL transaction ID " XID_FMT " for moved off tuple matches our current transaction ID",
xvac));
return false;
case XID_IN_PROGRESS:
report_corruption(ctx,
- psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple appears to be in progress",
+ psprintf("old-style VACUUM FULL transaction ID " XID_FMT " for moved off tuple appears to be in progress",
xvac));
return false;
@@ -891,23 +892,20 @@ check_tuple_visibility(HeapCheckContext *ctx)
return false;
case XID_IN_FUTURE:
report_corruption(ctx,
- psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple equals or exceeds next valid transaction ID %u:%u",
+ psprintf("old-style VACUUM FULL transaction ID " XID_FMT " for moved in tuple equals or exceeds next valid transaction ID " XID_FMT,
xvac,
- EpochFromFullTransactionId(ctx->next_fxid),
XidFromFullTransactionId(ctx->next_fxid)));
return false;
case XID_PRECEDES_RELMIN:
report_corruption(ctx,
- psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple precedes relation freeze threshold %u:%u",
+ psprintf("old-style VACUUM FULL transaction ID " XID_FMT " for moved in tuple precedes relation freeze threshold " XID_FMT,
xvac,
- EpochFromFullTransactionId(ctx->relfrozenfxid),
XidFromFullTransactionId(ctx->relfrozenfxid)));
return false;
case XID_PRECEDES_CLUSTERMIN:
report_corruption(ctx,
- psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple precedes oldest valid transaction ID %u:%u",
+ psprintf("old-style VACUUM FULL transaction ID " XID_FMT " for moved in tuple precedes oldest valid transaction ID " XID_FMT,
xvac,
- EpochFromFullTransactionId(ctx->oldest_fxid),
XidFromFullTransactionId(ctx->oldest_fxid)));
return false;
case XID_BOUNDS_OK:
@@ -918,12 +916,12 @@ check_tuple_visibility(HeapCheckContext *ctx)
{
case XID_IS_CURRENT_XID:
report_corruption(ctx,
- psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple matches our current transaction ID",
+ psprintf("old-style VACUUM FULL transaction ID " XID_FMT " for moved in tuple matches our current transaction ID",
xvac));
return false;
case XID_IN_PROGRESS:
report_corruption(ctx,
- psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple appears to be in progress",
+ psprintf("old-style VACUUM FULL transaction ID " XID_FMT " for moved in tuple appears to be in progress",
xvac));
return false;
@@ -985,7 +983,7 @@ check_tuple_visibility(HeapCheckContext *ctx)
* HEAP_XMAX_IS_LOCKED_ONLY is true, but for now we err on the side of
* avoiding possibly-bogus complaints about missing TOAST entries.
*/
- xmax = HeapTupleHeaderGetRawXmax(tuphdr);
+ xmax = HeapTupleGetRawXmax(tuple);
switch (check_mxid_valid_in_rel(xmax, ctx))
{
case XID_INVALID:
@@ -994,17 +992,17 @@ check_tuple_visibility(HeapCheckContext *ctx)
return true;
case XID_PRECEDES_RELMIN:
report_corruption(ctx,
- psprintf("multitransaction ID %u precedes relation minimum multitransaction ID threshold %u",
+ psprintf("multitransaction ID " XID_FMT " precedes relation minimum multitransaction ID threshold " XID_FMT,
xmax, ctx->relminmxid));
return true;
case XID_PRECEDES_CLUSTERMIN:
report_corruption(ctx,
- psprintf("multitransaction ID %u precedes oldest valid multitransaction ID threshold %u",
+ psprintf("multitransaction ID " XID_FMT " precedes oldest valid multitransaction ID threshold " XID_FMT,
xmax, ctx->oldest_mxact));
return true;
case XID_IN_FUTURE:
report_corruption(ctx,
- psprintf("multitransaction ID %u equals or exceeds next valid multitransaction ID %u",
+ psprintf("multitransaction ID " XID_FMT " equals or exceeds next valid multitransaction ID " XID_FMT,
xmax,
ctx->next_mxact));
return true;
@@ -1042,7 +1040,7 @@ check_tuple_visibility(HeapCheckContext *ctx)
* We already checked above that this multixact is within limits for
* this table. Now check the update xid from this multixact.
*/
- xmax = HeapTupleGetUpdateXid(tuphdr);
+ xmax = HeapTupleGetUpdateXid(tuple);
switch (get_xid_status(xmax, ctx, &xmax_status))
{
case XID_INVALID:
@@ -1052,23 +1050,20 @@ check_tuple_visibility(HeapCheckContext *ctx)
return true;
case XID_IN_FUTURE:
report_corruption(ctx,
- psprintf("update xid %u equals or exceeds next valid transaction ID %u:%u",
+ psprintf("update xid " XID_FMT " equals or exceeds next valid transaction ID " XID_FMT,
xmax,
- EpochFromFullTransactionId(ctx->next_fxid),
XidFromFullTransactionId(ctx->next_fxid)));
return true;
case XID_PRECEDES_RELMIN:
report_corruption(ctx,
- psprintf("update xid %u precedes relation freeze threshold %u:%u",
+ psprintf("update xid " XID_FMT " precedes relation freeze threshold " XID_FMT,
xmax,
- EpochFromFullTransactionId(ctx->relfrozenfxid),
XidFromFullTransactionId(ctx->relfrozenfxid)));
return true;
case XID_PRECEDES_CLUSTERMIN:
report_corruption(ctx,
- psprintf("update xid %u precedes oldest valid transaction ID %u:%u",
+ psprintf("update xid " XID_FMT " precedes oldest valid transaction ID " XID_FMT,
xmax,
- EpochFromFullTransactionId(ctx->oldest_fxid),
XidFromFullTransactionId(ctx->oldest_fxid)));
return true;
case XID_BOUNDS_OK:
@@ -1109,28 +1104,25 @@ check_tuple_visibility(HeapCheckContext *ctx)
}
/* xmax is an XID, not a MXID. Sanity check it. */
- xmax = HeapTupleHeaderGetRawXmax(tuphdr);
+ xmax = HeapTupleGetRawXmax(tuple);
switch (get_xid_status(xmax, ctx, &xmax_status))
{
case XID_IN_FUTURE:
report_corruption(ctx,
- psprintf("xmax %u equals or exceeds next valid transaction ID %u:%u",
+ psprintf("xmax " XID_FMT " equals or exceeds next valid transaction ID " XID_FMT,
xmax,
- EpochFromFullTransactionId(ctx->next_fxid),
XidFromFullTransactionId(ctx->next_fxid)));
return false; /* corrupt */
case XID_PRECEDES_RELMIN:
report_corruption(ctx,
- psprintf("xmax %u precedes relation freeze threshold %u:%u",
+ psprintf("xmax " XID_FMT " precedes relation freeze threshold " XID_FMT,
xmax,
- EpochFromFullTransactionId(ctx->relfrozenfxid),
XidFromFullTransactionId(ctx->relfrozenfxid)));
return false; /* corrupt */
case XID_PRECEDES_CLUSTERMIN:
report_corruption(ctx,
- psprintf("xmax %u precedes oldest valid transaction ID %u:%u",
+ psprintf("xmax " XID_FMT " precedes oldest valid transaction ID " XID_FMT,
xmax,
- EpochFromFullTransactionId(ctx->oldest_fxid),
XidFromFullTransactionId(ctx->oldest_fxid)));
return false; /* corrupt */
case XID_BOUNDS_OK:
@@ -1615,21 +1607,13 @@ check_tuple(HeapCheckContext *ctx)
}
/*
- * Convert a TransactionId into a FullTransactionId using our cached values of
- * the valid transaction ID range. It is the caller's responsibility to have
- * already updated the cached values, if necessary.
+ * With 64xid this function is just wrapper for FullTransactionIdFromXid,
+ * but we left it in order for its calls remain "vanilla" like.
*/
static FullTransactionId
-FullTransactionIdFromXidAndCtx(TransactionId xid, const HeapCheckContext *ctx)
+FullTransactionIdFromXidAndCtx(TransactionId xid, const HeapCheckContext *ctx pg_attribute_unused())
{
- uint32 epoch;
-
- if (!TransactionIdIsNormal(xid))
- return FullTransactionIdFromEpochAndXid(0, xid);
- epoch = EpochFromFullTransactionId(ctx->next_fxid);
- if (xid > ctx->next_xid)
- epoch--;
- return FullTransactionIdFromEpochAndXid(epoch, xid);
+ return FullTransactionIdFromXid(xid);
}
/*
@@ -1754,7 +1738,6 @@ get_xid_status(TransactionId xid, HeapCheckContext *ctx,
* performed the full xid conversion, reconvert.
*/
update_cached_xid_range(ctx);
- fxid = FullTransactionIdFromXidAndCtx(xid, ctx);
}
if (FullTransactionIdPrecedesOrEquals(ctx->next_fxid, fxid))
diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c
index d2510ee648..e44e192dce 100644
--- a/contrib/amcheck/verify_nbtree.c
+++ b/contrib/amcheck/verify_nbtree.c
@@ -498,7 +498,7 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
* avoid this.
*/
if (IsolationUsesXactSnapshot() && rel->rd_index->indcheckxmin &&
- !TransactionIdPrecedes(HeapTupleHeaderGetXmin(rel->rd_indextuple->t_data),
+ !TransactionIdPrecedes(HeapTupleGetXmin(rel->rd_indextuple),
snapshot->xmin))
ereport(ERROR,
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
diff --git a/contrib/hstore/hstore_io.c b/contrib/hstore/hstore_io.c
index b3304ff844..f54741330c 100644
--- a/contrib/hstore/hstore_io.c
+++ b/contrib/hstore/hstore_io.c
@@ -859,6 +859,7 @@ hstore_from_record(PG_FUNCTION_ARGS)
ItemPointerSetInvalid(&(tuple.t_self));
tuple.t_tableOid = InvalidOid;
tuple.t_data = rec;
+ HeapTupleSetZeroBase(&tuple);
values = (Datum *) palloc(ncolumns * sizeof(Datum));
nulls = (bool *) palloc(ncolumns * sizeof(bool));
@@ -1012,6 +1013,7 @@ hstore_populate_record(PG_FUNCTION_ARGS)
ItemPointerSetInvalid(&(tuple.t_self));
tuple.t_tableOid = InvalidOid;
tuple.t_data = rec;
+ HeapTupleSetZeroBase(&tuple);
}
/*
diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index 5c0736564a..5ca80c9d76 100644
--- a/contrib/pageinspect/Makefile
+++ b/contrib/pageinspect/Makefile
@@ -13,7 +13,8 @@ OBJS = \
rawpage.o
EXTENSION = pageinspect
-DATA = pageinspect--1.9--1.10.sql pageinspect--1.8--1.9.sql \
+DATA = pageinspect--1.10--1.11.sql \
+ pageinspect--1.9--1.10.sql pageinspect--1.8--1.9.sql \
pageinspect--1.7--1.8.sql pageinspect--1.6--1.7.sql \
pageinspect--1.5.sql pageinspect--1.5--1.6.sql \
pageinspect--1.4--1.5.sql pageinspect--1.3--1.4.sql \
diff --git a/contrib/pageinspect/brinfuncs.c b/contrib/pageinspect/brinfuncs.c
index 683749a150..22944fc144 100644
--- a/contrib/pageinspect/brinfuncs.c
+++ b/contrib/pageinspect/brinfuncs.c
@@ -45,7 +45,7 @@ Datum
brin_page_type(PG_FUNCTION_ARGS)
{
bytea *raw_page = PG_GETARG_BYTEA_P(0);
- Page page = VARDATA(raw_page);
+ Page page = get_page_from_raw(raw_page);
int raw_page_size;
char *type;
@@ -101,7 +101,7 @@ verify_brin_page(bytea *raw_page, uint16 type, const char *strtype)
errdetail("Expected size %d, got %d",
BLCKSZ, raw_page_size)));
- page = VARDATA(raw_page);
+ page = get_page_from_raw(raw_page);
/* verify the special space says this page is what we want */
if (BrinPageType(page) != type)
@@ -390,15 +390,15 @@ brin_revmap_data(PG_FUNCTION_ARGS)
MemoryContext mctx;
Page page;
- /* minimally verify the page we got */
- page = verify_brin_page(raw_page, BRIN_PAGETYPE_REVMAP, "revmap");
-
/* create a function context for cross-call persistence */
fctx = SRF_FIRSTCALL_INIT();
/* switch to memory context appropriate for multiple function calls */
mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
+ /* minimally verify the page we got */
+ page = verify_brin_page(raw_page, BRIN_PAGETYPE_REVMAP, "revmap");
+
state = palloc(sizeof(*state));
state->tids = ((RevmapContents *) PageGetContents(page))->rm_tids;
state->idx = 0;
diff --git a/contrib/pageinspect/btreefuncs.c b/contrib/pageinspect/btreefuncs.c
index 03debe336b..21463cf65c 100644
--- a/contrib/pageinspect/btreefuncs.c
+++ b/contrib/pageinspect/btreefuncs.c
@@ -105,9 +105,14 @@ GetBTPageStatistics(BlockNumber blkno, Buffer buffer, BTPageStat *stat)
stat->page_size = PageGetPageSize(page);
+ stat->btpo_prev = opaque->btpo_prev;
+ stat->btpo_level = opaque->btpo_level;
+
/* page type (flags) */
if (P_ISDELETED(opaque))
{
+ TransactionId safexid;
+
/* We divide deleted pages into leaf ('d') or internal ('D') */
if (P_ISLEAF(opaque) || !P_HAS_FULLXID(opaque))
stat->type = 'd';
@@ -122,16 +127,16 @@ GetBTPageStatistics(BlockNumber blkno, Buffer buffer, BTPageStat *stat)
* called "bpto").
*/
if (P_HAS_FULLXID(opaque))
+ safexid = XidFromFullTransactionId(BTPageGetDeleteXid(page));
+ else
{
- FullTransactionId safexid = BTPageGetDeleteXid(page);
-
- elog(DEBUG2, "deleted page from block %u has safexid %u:%u",
- blkno, EpochFromFullTransactionId(safexid),
- XidFromFullTransactionId(safexid));
+ safexid = BTP_GET_XACT(opaque);
+ stat->btpo_prev = 0;
+ stat->btpo_level = 0;
}
- else
- elog(DEBUG2, "deleted page from block %u has safexid %u",
- blkno, opaque->btpo_level);
+
+ elog(DEBUG2, "deleted page from block %u has safexid " XID_FMT,
+ blkno, safexid);
/* Don't interpret BTDeletedPageData as index tuples */
maxoff = InvalidOffsetNumber;
@@ -146,9 +151,7 @@ GetBTPageStatistics(BlockNumber blkno, Buffer buffer, BTPageStat *stat)
stat->type = 'i';
/* btpage opaque data */
- stat->btpo_prev = opaque->btpo_prev;
stat->btpo_next = opaque->btpo_next;
- stat->btpo_level = opaque->btpo_level;
stat->btpo_flags = opaque->btpo_flags;
stat->btpo_cycleid = opaque->btpo_cycleid;
@@ -613,7 +616,7 @@ bt_page_items_bytea(PG_FUNCTION_ARGS)
uargs = palloc(sizeof(struct user_args));
- uargs->page = VARDATA(raw_page);
+ uargs->page = get_page_from_raw(raw_page);
uargs->offset = FirstOffsetNumber;
diff --git a/contrib/pageinspect/expected/hash_1.out b/contrib/pageinspect/expected/hash_1.out
new file mode 100644
index 0000000000..5e64eb9260
--- /dev/null
+++ b/contrib/pageinspect/expected/hash_1.out
@@ -0,0 +1,166 @@
+CREATE TABLE test_hash (a int, b text);
+INSERT INTO test_hash VALUES (1, 'one');
+CREATE INDEX test_hash_a_idx ON test_hash USING hash (a);
+\x
+SELECT hash_page_type(get_raw_page('test_hash_a_idx', 0));
+-[ RECORD 1 ]--+---------
+hash_page_type | metapage
+
+SELECT hash_page_type(get_raw_page('test_hash_a_idx', 1));
+-[ RECORD 1 ]--+-------
+hash_page_type | bucket
+
+SELECT hash_page_type(get_raw_page('test_hash_a_idx', 2));
+-[ RECORD 1 ]--+-------
+hash_page_type | bucket
+
+SELECT hash_page_type(get_raw_page('test_hash_a_idx', 3));
+-[ RECORD 1 ]--+-------
+hash_page_type | bucket
+
+SELECT hash_page_type(get_raw_page('test_hash_a_idx', 4));
+-[ RECORD 1 ]--+-------
+hash_page_type | bucket
+
+SELECT hash_page_type(get_raw_page('test_hash_a_idx', 5));
+-[ RECORD 1 ]--+-------
+hash_page_type | bitmap
+
+SELECT hash_page_type(get_raw_page('test_hash_a_idx', 6));
+ERROR: block number 6 is out of range for relation "test_hash_a_idx"
+SELECT * FROM hash_bitmap_info('test_hash_a_idx', -1);
+ERROR: invalid block number
+SELECT * FROM hash_bitmap_info('test_hash_a_idx', 0);
+ERROR: invalid overflow block number 0
+SELECT * FROM hash_bitmap_info('test_hash_a_idx', 1);
+ERROR: invalid overflow block number 1
+SELECT * FROM hash_bitmap_info('test_hash_a_idx', 2);
+ERROR: invalid overflow block number 2
+SELECT * FROM hash_bitmap_info('test_hash_a_idx', 3);
+ERROR: invalid overflow block number 3
+SELECT * FROM hash_bitmap_info('test_hash_a_idx', 4);
+ERROR: invalid overflow block number 4
+SELECT * FROM hash_bitmap_info('test_hash_a_idx', 5);
+ERROR: invalid overflow block number 5
+SELECT * FROM hash_bitmap_info('test_hash_a_idx', 6);
+ERROR: block number 6 is out of range for relation "test_hash_a_idx"
+SELECT magic, version, ntuples, bsize, bmsize, bmshift, maxbucket, highmask,
+lowmask, ovflpoint, firstfree, nmaps, procid, spares, mapp FROM
+hash_metapage_info(get_raw_page('test_hash_a_idx', 0));
+-[ RECORD 1 ]--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+magic | 105121344
+version | 4
+ntuples | 1
+bsize | 8156
+bmsize | 4096
+bmshift | 15
+maxbucket | 3
+highmask | 7
+lowmask | 3
+ovflpoint | 2
+firstfree | 0
+nmaps | 1
+procid | 450
+spares | {0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+mapp | {5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+
+SELECT magic, version, ntuples, bsize, bmsize, bmshift, maxbucket, highmask,
+lowmask, ovflpoint, firstfree, nmaps, procid, spares, mapp FROM
+hash_metapage_info(get_raw_page('test_hash_a_idx', 1));
+ERROR: page is not a hash meta page
+SELECT magic, version, ntuples, bsize, bmsize, bmshift, maxbucket, highmask,
+lowmask, ovflpoint, firstfree, nmaps, procid, spares, mapp FROM
+hash_metapage_info(get_raw_page('test_hash_a_idx', 2));
+ERROR: page is not a hash meta page
+SELECT magic, version, ntuples, bsize, bmsize, bmshift, maxbucket, highmask,
+lowmask, ovflpoint, firstfree, nmaps, procid, spares, mapp FROM
+hash_metapage_info(get_raw_page('test_hash_a_idx', 3));
+ERROR: page is not a hash meta page
+SELECT magic, version, ntuples, bsize, bmsize, bmshift, maxbucket, highmask,
+lowmask, ovflpoint, firstfree, nmaps, procid, spares, mapp FROM
+hash_metapage_info(get_raw_page('test_hash_a_idx', 4));
+ERROR: page is not a hash meta page
+SELECT magic, version, ntuples, bsize, bmsize, bmshift, maxbucket, highmask,
+lowmask, ovflpoint, firstfree, nmaps, procid, spares, mapp FROM
+hash_metapage_info(get_raw_page('test_hash_a_idx', 5));
+ERROR: page is not a hash meta page
+SELECT live_items, dead_items, page_size, hasho_prevblkno, hasho_nextblkno,
+hasho_bucket, hasho_flag, hasho_page_id FROM
+hash_page_stats(get_raw_page('test_hash_a_idx', 0));
+ERROR: page is not a hash bucket or overflow page
+SELECT live_items, dead_items, page_size, hasho_prevblkno, hasho_nextblkno,
+hasho_bucket, hasho_flag, hasho_page_id FROM
+hash_page_stats(get_raw_page('test_hash_a_idx', 1));
+-[ RECORD 1 ]---+-----------
+live_items | 0
+dead_items | 0
+page_size | 8192
+hasho_prevblkno | 3
+hasho_nextblkno | 4294967295
+hasho_bucket | 0
+hasho_flag | 2
+hasho_page_id | 65408
+
+SELECT live_items, dead_items, page_size, hasho_prevblkno, hasho_nextblkno,
+hasho_bucket, hasho_flag, hasho_page_id FROM
+hash_page_stats(get_raw_page('test_hash_a_idx', 2));
+-[ RECORD 1 ]---+-----------
+live_items | 0
+dead_items | 0
+page_size | 8192
+hasho_prevblkno | 3
+hasho_nextblkno | 4294967295
+hasho_bucket | 1
+hasho_flag | 2
+hasho_page_id | 65408
+
+SELECT live_items, dead_items, page_size, hasho_prevblkno, hasho_nextblkno,
+hasho_bucket, hasho_flag, hasho_page_id FROM
+hash_page_stats(get_raw_page('test_hash_a_idx', 3));
+-[ RECORD 1 ]---+-----------
+live_items | 1
+dead_items | 0
+page_size | 8192
+hasho_prevblkno | 3
+hasho_nextblkno | 4294967295
+hasho_bucket | 2
+hasho_flag | 2
+hasho_page_id | 65408
+
+SELECT live_items, dead_items, page_size, hasho_prevblkno, hasho_nextblkno,
+hasho_bucket, hasho_flag, hasho_page_id FROM
+hash_page_stats(get_raw_page('test_hash_a_idx', 4));
+-[ RECORD 1 ]---+-----------
+live_items | 0
+dead_items | 0
+page_size | 8192
+hasho_prevblkno | 3
+hasho_nextblkno | 4294967295
+hasho_bucket | 3
+hasho_flag | 2
+hasho_page_id | 65408
+
+SELECT live_items, dead_items, page_size, hasho_prevblkno, hasho_nextblkno,
+hasho_bucket, hasho_flag, hasho_page_id FROM
+hash_page_stats(get_raw_page('test_hash_a_idx', 5));
+ERROR: page is not a hash bucket or overflow page
+SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 0));
+ERROR: page is not a hash bucket or overflow page
+SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 1));
+(0 rows)
+
+SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 2));
+(0 rows)
+
+SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 3));
+-[ RECORD 1 ]----------
+itemoffset | 1
+ctid | (0,1)
+data | 2389907270
+
+SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 4));
+(0 rows)
+
+SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 5));
+ERROR: page is not a hash bucket or overflow page
+DROP TABLE test_hash;
diff --git a/contrib/pageinspect/expected/oldextversions.out b/contrib/pageinspect/expected/oldextversions.out
index f5c4b61bd7..00323d392d 100644
--- a/contrib/pageinspect/expected/oldextversions.out
+++ b/contrib/pageinspect/expected/oldextversions.out
@@ -40,16 +40,16 @@ SELECT * FROM bt_page_items('test1_a_idx', 1);
-- pagesize in pageinspect >= 1.10.
ALTER EXTENSION pageinspect UPDATE TO '1.9';
\df page_header
- List of functions
- Schema | Name | Result data type | Argument data types | Type
---------+-------------+------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------
- public | page_header | record | page bytea, OUT lsn pg_lsn, OUT checksum smallint, OUT flags smallint, OUT lower smallint, OUT upper smallint, OUT special smallint, OUT pagesize smallint, OUT version smallint, OUT prune_xid xid | func
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------------+------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------
+ public | page_header | record | page bytea, OUT lsn pg_lsn, OUT checksum smallint, OUT flags smallint, OUT lower smallint, OUT upper smallint, OUT special smallint, OUT pagesize smallint, OUT version smallint, OUT xid_base xid, OUT multi_base xid, OUT prune_xid xid | func
(1 row)
SELECT pagesize, version FROM page_header(get_raw_page('test1', 0));
pagesize | version
----------+---------
- 8192 | 4
+ 8192 | 5
(1 row)
DROP TABLE test1;
diff --git a/contrib/pageinspect/expected/page.out b/contrib/pageinspect/expected/page.out
index 4e325ae56d..804be53c6d 100644
--- a/contrib/pageinspect/expected/page.out
+++ b/contrib/pageinspect/expected/page.out
@@ -48,7 +48,7 @@ SELECT get_raw_page('test1', 0) = get_raw_page('test1', 'main', 0);
SELECT pagesize, version FROM page_header(get_raw_page('test1', 0));
pagesize | version
----------+---------
- 8192 | 4
+ 8192 | 5
(1 row)
SELECT page_checksum(get_raw_page('test1', 0), 0) IS NOT NULL AS silly_checksum_test;
@@ -69,19 +69,19 @@ SELECT tuple_data_split('test1'::regclass, t_data, t_infomask, t_infomask2, t_bi
SELECT * FROM fsm_page_contents(get_raw_page('test1', 'fsm', 0));
fsm_page_contents
-------------------
- 0: 254 +
- 1: 254 +
- 3: 254 +
- 7: 254 +
- 15: 254 +
- 31: 254 +
- 63: 254 +
- 127: 254 +
- 255: 254 +
- 511: 254 +
- 1023: 254 +
- 2047: 254 +
- 4095: 254 +
+ 0: 253 +
+ 1: 253 +
+ 3: 253 +
+ 7: 253 +
+ 15: 253 +
+ 31: 253 +
+ 63: 253 +
+ 127: 253 +
+ 255: 253 +
+ 511: 253 +
+ 1023: 253 +
+ 2047: 253 +
+ 4095: 253 +
fp_next_slot: 0 +
(1 row)
diff --git a/contrib/pageinspect/fsmfuncs.c b/contrib/pageinspect/fsmfuncs.c
index dadd62aa20..7aa1a0558f 100644
--- a/contrib/pageinspect/fsmfuncs.c
+++ b/contrib/pageinspect/fsmfuncs.c
@@ -44,7 +44,7 @@ fsm_page_contents(PG_FUNCTION_ARGS)
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to use raw page functions")));
- fsmpage = (FSMPage) PageGetContents(VARDATA(raw_page));
+ fsmpage = (FSMPage) PageGetContents(get_page_from_raw(raw_page));
initStringInfo(&sinfo);
diff --git a/contrib/pageinspect/heapfuncs.c b/contrib/pageinspect/heapfuncs.c
index 3dd1a9bc2a..3a22c4794d 100644
--- a/contrib/pageinspect/heapfuncs.c
+++ b/contrib/pageinspect/heapfuncs.c
@@ -163,7 +163,7 @@ heap_page_items(PG_FUNCTION_ARGS)
inter_call_data->tupd = tupdesc;
inter_call_data->offset = FirstOffsetNumber;
- inter_call_data->page = VARDATA(raw_page);
+ inter_call_data->page = get_page_from_raw(raw_page);
fctx->max_calls = PageGetMaxOffsetNumber(inter_call_data->page);
fctx->user_fctx = inter_call_data;
@@ -211,6 +211,7 @@ heap_page_items(PG_FUNCTION_ARGS)
lp_offset == MAXALIGN(lp_offset) &&
lp_offset + lp_len <= raw_page_size)
{
+ HeapTupleData tup;
HeapTupleHeader tuphdr;
bytea *tuple_data_bytea;
int tuple_data_len;
@@ -218,9 +219,11 @@ heap_page_items(PG_FUNCTION_ARGS)
/* Extract information from the tuple header */
tuphdr = (HeapTupleHeader) PageGetItem(page, id);
+ tup.t_data = tuphdr;
+ HeapTupleCopyBaseFromPage(&tup, page);
- values[4] = UInt32GetDatum(HeapTupleHeaderGetRawXmin(tuphdr));
- values[5] = UInt32GetDatum(HeapTupleHeaderGetRawXmax(tuphdr));
+ values[4] = TransactionIdGetDatum(HeapTupleGetXmin(&tup));
+ values[5] = TransactionIdGetDatum(HeapTupleGetRawXmax(&tup));
/* shared with xvac */
values[6] = UInt32GetDatum(HeapTupleHeaderGetRawCommandId(tuphdr));
values[7] = PointerGetDatum(&tuphdr->t_ctid);
diff --git a/contrib/pageinspect/pageinspect--1.10--1.11.sql b/contrib/pageinspect/pageinspect--1.10--1.11.sql
new file mode 100644
index 0000000000..236f18aa2f
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.10--1.11.sql
@@ -0,0 +1,145 @@
+/* contrib/pageinspect/pageinspect--1.10--1.11.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION pageinspect UPDATE TO '1.11'" to load this file. \quit
+
+--
+-- gist_page_opaque_info()
+--
+DROP FUNCTION gist_page_opaque_info(bytea);
+CREATE FUNCTION gist_page_opaque_info(IN page bytea,
+ OUT lsn pg_lsn,
+ OUT nsn pg_lsn,
+ OUT rightlink bigint,
+ OUT flags text[])
+AS 'MODULE_PATHNAME', 'gist_page_opaque_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+
+--
+-- gist_page_items_bytea()
+--
+DROP FUNCTION gist_page_items_bytea(bytea);
+CREATE FUNCTION gist_page_items_bytea(IN page bytea,
+ OUT itemoffset smallint,
+ OUT ctid tid,
+ OUT itemlen smallint,
+ OUT dead boolean,
+ OUT key_data bytea)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'gist_page_items_bytea'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- gist_page_items()
+--
+DROP FUNCTION gist_page_items(bytea, regclass);
+CREATE FUNCTION gist_page_items(IN page bytea,
+ IN index_oid regclass,
+ OUT itemoffset smallint,
+ OUT ctid tid,
+ OUT itemlen smallint,
+ OUT dead boolean,
+ OUT keys text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'gist_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- get_raw_page()
+--
+DROP FUNCTION get_raw_page(text, int8);
+DROP FUNCTION IF EXISTS get_raw_page(text, int4);
+CREATE FUNCTION get_raw_page(text, int8)
+RETURNS bytea
+AS 'MODULE_PATHNAME', 'get_raw_page_1_9'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+DROP FUNCTION get_raw_page(text, text, int8);
+DROP FUNCTION IF EXISTS get_raw_page(text, text, int4);
+CREATE FUNCTION get_raw_page(text, text, int8)
+RETURNS bytea
+AS 'MODULE_PATHNAME', 'get_raw_page_fork_1_9'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- page_checksum()
+--
+DROP FUNCTION page_checksum(IN page bytea, IN blkno int8);
+DROP FUNCTION IF EXISTS page_checksum(IN page bytea, IN blkno int4);
+CREATE FUNCTION page_checksum(IN page bytea, IN blkno int8)
+RETURNS smallint
+AS 'MODULE_PATHNAME', 'page_checksum_1_9'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- bt_metap()
+--
+DROP FUNCTION bt_metap(text);
+CREATE FUNCTION bt_metap(IN relname text,
+ OUT magic int4,
+ OUT version int4,
+ OUT root int8,
+ OUT level int8,
+ OUT fastroot int8,
+ OUT fastlevel int8,
+ OUT last_cleanup_num_delpages int8,
+ OUT last_cleanup_num_tuples float8,
+ OUT allequalimage boolean)
+AS 'MODULE_PATHNAME', 'bt_metap'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- bt_page_stats()
+--
+DROP FUNCTION bt_page_stats(text, int8);
+DROP FUNCTION IF EXISTS bt_page_stats(text, int4);
+CREATE FUNCTION bt_page_stats(IN relname text, IN blkno int8,
+ OUT blkno int8,
+ OUT type "char",
+ OUT live_items int4,
+ OUT dead_items int4,
+ OUT avg_item_size int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT btpo_prev int8,
+ OUT btpo_next int8,
+ OUT btpo_level int8,
+ OUT btpo_flags int4)
+AS 'MODULE_PATHNAME', 'bt_page_stats_1_9'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- bt_page_items()
+--
+DROP FUNCTION bt_page_items(text, int8);
+DROP FUNCTION IF EXISTS bt_page_items(text, int4);
+CREATE FUNCTION bt_page_items(IN relname text, IN blkno int8,
+ OUT itemoffset smallint,
+ OUT ctid tid,
+ OUT itemlen smallint,
+ OUT nulls bool,
+ OUT vars bool,
+ OUT data text,
+ OUT dead boolean,
+ OUT htid tid,
+ OUT tids tid[])
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'bt_page_items_1_9'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_page_items()
+--
+DROP FUNCTION brin_page_items(IN page bytea, IN index_oid regclass);
+CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass,
+ OUT itemoffset int,
+ OUT blknum int8,
+ OUT attnum int,
+ OUT allnulls bool,
+ OUT hasnulls bool,
+ OUT placeholder bool,
+ OUT value text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'brin_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect--1.5.sql b/contrib/pageinspect/pageinspect--1.5.sql
index 1e40c3c97e..fdbd2995a2 100644
--- a/contrib/pageinspect/pageinspect--1.5.sql
+++ b/contrib/pageinspect/pageinspect--1.5.sql
@@ -28,6 +28,8 @@ CREATE FUNCTION page_header(IN page bytea,
OUT special smallint,
OUT pagesize smallint,
OUT version smallint,
+ OUT xid_base xid,
+ OUT multi_base xid,
OUT prune_xid xid)
AS 'MODULE_PATHNAME', 'page_header'
LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control
index 7cdf37913d..f277413dd8 100644
--- a/contrib/pageinspect/pageinspect.control
+++ b/contrib/pageinspect/pageinspect.control
@@ -1,5 +1,5 @@
# pageinspect extension
comment = 'inspect the contents of database pages at a low level'
-default_version = '1.10'
+default_version = '1.11'
module_pathname = '$libdir/pageinspect'
relocatable = true
diff --git a/contrib/pageinspect/rawpage.c b/contrib/pageinspect/rawpage.c
index 7e41af045f..5caa9c100a 100644
--- a/contrib/pageinspect/rawpage.c
+++ b/contrib/pageinspect/rawpage.c
@@ -17,6 +17,7 @@
#include "access/htup_details.h"
#include "access/relation.h"
+#include "commands/sequence.h"
#include "catalog/namespace.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
@@ -252,8 +253,8 @@ page_header(PG_FUNCTION_ARGS)
Datum result;
HeapTuple tuple;
- Datum values[9];
- bool nulls[9];
+ Datum values[11];
+ bool nulls[11];
PageHeader page;
XLogRecPtr lsn;
@@ -274,7 +275,7 @@ page_header(PG_FUNCTION_ARGS)
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("input page too small (%d bytes)", raw_page_size)));
- page = (PageHeader) VARDATA(raw_page);
+ page = (PageHeader) get_page_from_raw(raw_page);
/* Build a tuple descriptor for our result type */
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
@@ -324,12 +325,29 @@ page_header(PG_FUNCTION_ARGS)
}
values[7] = UInt16GetDatum(PageGetPageLayoutVersion(page));
- values[8] = TransactionIdGetDatum(page->pd_prune_xid);
/* Build and return the tuple. */
memset(nulls, 0, sizeof(nulls));
+ if (PageGetSpecialSize(page) == MAXALIGN(sizeof(HeapPageSpecialData)))
+ {
+ HeapPageSpecial pageSpecial = HeapPageGetSpecial(page);
+
+ values[8] = TransactionIdGetDatum(pageSpecial->pd_xid_base);
+ values[9] = TransactionIdGetDatum(pageSpecial->pd_multi_base);
+ values[10] = TransactionIdGetDatum(HeapPageGetPruneXid(page));
+ nulls[8] = false;
+ nulls[9] = false;
+ nulls[10] = false;
+ }
+ else
+ {
+ nulls[8] = true;
+ nulls[9] = true;
+ nulls[10] = true;
+ }
+
tuple = heap_form_tuple(tupdesc, values, nulls);
result = HeapTupleGetDatum(tuple);
diff --git a/contrib/pg_surgery/heap_surgery.c b/contrib/pg_surgery/heap_surgery.c
index 3e641aa644..3554f075db 100644
--- a/contrib/pg_surgery/heap_surgery.c
+++ b/contrib/pg_surgery/heap_surgery.c
@@ -271,11 +271,17 @@ heap_force_common(FunctionCallInfo fcinfo, HeapTupleForceOption heap_force_opt)
else
{
HeapTupleHeader htup;
+ HeapTupleData tuple;
Assert(heap_force_opt == HEAP_FORCE_FREEZE);
htup = (HeapTupleHeader) PageGetItem(page, itemid);
+ tuple.t_data = htup;
+ tuple.t_len = ItemIdGetLength(itemid);
+ tuple.t_tableOid = RelationGetRelid(rel);
+ HeapTupleCopyBaseFromPage(&tuple, page);
+
/*
* Reset all visibility-related fields of the tuple. This
* logic should mimic heap_execute_freeze_tuple(), but we
@@ -283,8 +289,10 @@ heap_force_common(FunctionCallInfo fcinfo, HeapTupleForceOption heap_force_opt)
* potentially-garbled data is left behind.
*/
ItemPointerSet(&htup->t_ctid, blkno, curoff);
- HeapTupleHeaderSetXmin(htup, FrozenTransactionId);
- HeapTupleHeaderSetXmax(htup, InvalidTransactionId);
+ HeapTupleSetXmin(&tuple, FrozenTransactionId);
+ HeapTupleHeaderSetXmin(page, &tuple);
+ HeapTupleSetXmax(&tuple, InvalidTransactionId);
+ HeapTupleHeaderSetXmax(page, &tuple);
if (htup->t_infomask & HEAP_MOVED)
{
if (htup->t_infomask & HEAP_MOVED_OFF)
diff --git a/contrib/pg_visibility/expected/pg_visibility.out b/contrib/pg_visibility/expected/pg_visibility.out
index 9de54db2a2..32bfbaa84e 100644
--- a/contrib/pg_visibility/expected/pg_visibility.out
+++ b/contrib/pg_visibility/expected/pg_visibility.out
@@ -267,6 +267,22 @@ select * from pg_check_frozen('copyfreeze');
--------
(0 rows)
+create table vacuum_test as select 42 i;
+vacuum (disable_page_skipping) vacuum_test;
+-- pg_check_visible() can report false positive due to autovacuum activity.
+-- To workaround this issue, repeat the call. See PGPRO-4776.
+do $$
+declare
+ non_visible_count bigint;
+ i integer;
+begin
+ for i in 1 .. 10 loop
+ if i > 1 then perform pg_sleep(1); end if;
+ select count(*) from pg_check_visible('vacuum_test') into non_visible_count;
+ if non_visible_count = 0 then exit; end if;
+ end loop;
+ if non_visible_count > 0 then raise exception 'The visibility map is corrupt.'; end if;
+end $$;
-- cleanup
drop table test_partitioned;
drop view test_view;
@@ -277,3 +293,4 @@ drop foreign data wrapper dummy;
drop materialized view matview_visibility_test;
drop table regular_table;
drop table copyfreeze;
+drop table vacuum_test;
diff --git a/contrib/pg_visibility/pg_visibility.c b/contrib/pg_visibility/pg_visibility.c
index 1853c354e3..4bc80869c0 100644
--- a/contrib/pg_visibility/pg_visibility.c
+++ b/contrib/pg_visibility/pg_visibility.c
@@ -657,6 +657,7 @@ collect_corrupt_items(Oid relid, bool all_visible, bool all_frozen)
tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid);
tuple.t_len = ItemIdGetLength(itemid);
tuple.t_tableOid = relid;
+ HeapTupleCopyBaseFromPage(&tuple, page);
/*
* If we're checking whether the page is all-visible, we expect
@@ -700,7 +701,7 @@ collect_corrupt_items(Oid relid, bool all_visible, bool all_frozen)
*/
if (check_frozen)
{
- if (heap_tuple_needs_eventual_freeze(tuple.t_data))
+ if (heap_tuple_needs_eventual_freeze(&tuple))
record_corrupt_item(items, &tuple.t_self);
}
}
@@ -763,7 +764,7 @@ tuple_all_visible(HeapTuple tup, TransactionId OldestXmin, Buffer buffer)
* be set here. So just check the xmin.
*/
- xmin = HeapTupleHeaderGetXmin(tup->t_data);
+ xmin = HeapTupleGetXmin(tup);
if (!TransactionIdPrecedes(xmin, OldestXmin))
return false; /* xmin not old enough for all to see */
diff --git a/contrib/pg_visibility/sql/pg_visibility.sql b/contrib/pg_visibility/sql/pg_visibility.sql
index ff3538f996..42c4fad36a 100644
--- a/contrib/pg_visibility/sql/pg_visibility.sql
+++ b/contrib/pg_visibility/sql/pg_visibility.sql
@@ -170,6 +170,23 @@ commit;
select * from pg_visibility_map('copyfreeze');
select * from pg_check_frozen('copyfreeze');
+create table vacuum_test as select 42 i;
+vacuum (disable_page_skipping) vacuum_test;
+-- pg_check_visible() can report false positive due to autovacuum activity.
+-- To workaround this issue, repeat the call. See PGPRO-4776.
+do $$
+declare
+ non_visible_count bigint;
+ i integer;
+begin
+ for i in 1 .. 10 loop
+ if i > 1 then perform pg_sleep(1); end if;
+ select count(*) from pg_check_visible('vacuum_test') into non_visible_count;
+ if non_visible_count = 0 then exit; end if;
+ end loop;
+ if non_visible_count > 0 then raise exception 'The visibility map is corrupt.'; end if;
+end $$;
+
-- cleanup
drop table test_partitioned;
drop view test_view;
@@ -180,3 +197,4 @@ drop foreign data wrapper dummy;
drop materialized view matview_visibility_test;
drop table regular_table;
drop table copyfreeze;
+drop table vacuum_test;
diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index d8946dc510..e26451c9d5 100644
--- a/contrib/pgrowlocks/pgrowlocks.c
+++ b/contrib/pgrowlocks/pgrowlocks.c
@@ -156,7 +156,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
htsu = HeapTupleSatisfiesUpdate(tuple,
GetCurrentCommandId(false),
hscan->rs_cbuf);
- xmax = HeapTupleHeaderGetRawXmax(tuple->t_data);
+ xmax = HeapTupleGetRawXmax(tuple);
infomask = tuple->t_data->t_infomask;
/*
@@ -168,7 +168,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
PointerGetDatum(&tuple->t_self));
values[Atnum_xmax] = palloc(NCHARS * sizeof(char));
- snprintf(values[Atnum_xmax], NCHARS, "%u", xmax);
+ snprintf(values[Atnum_xmax], NCHARS, XID_FMT, xmax);
if (infomask & HEAP_XMAX_IS_MULTI)
{
MultiXactMember *members;
@@ -209,7 +209,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
strcat(values[Atnum_modes], ",");
strcat(values[Atnum_pids], ",");
}
- snprintf(buf, NCHARS, "%u", members[j].xid);
+ snprintf(buf, NCHARS, XID_FMT, members[j].xid);
strcat(values[Atnum_xids], buf);
switch (members[j].status)
{
@@ -250,7 +250,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
values[Atnum_ismulti] = pstrdup("false");
values[Atnum_xids] = palloc(NCHARS * sizeof(char));
- snprintf(values[Atnum_xids], NCHARS, "{%u}", xmax);
+ snprintf(values[Atnum_xids], NCHARS, "{" XID_FMT "}", xmax);
values[Atnum_modes] = palloc(NCHARS);
if (infomask & HEAP_XMAX_LOCK_ONLY)
diff --git a/contrib/pgstattuple/pgstatapprox.c b/contrib/pgstattuple/pgstatapprox.c
index 15ddc32239..6d9e481013 100644
--- a/contrib/pgstattuple/pgstatapprox.c
+++ b/contrib/pgstattuple/pgstatapprox.c
@@ -153,6 +153,7 @@ statapprox_heap(Relation rel, output_type *stat)
tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid);
tuple.t_len = ItemIdGetLength(itemid);
tuple.t_tableOid = RelationGetRelid(rel);
+ HeapTupleCopyBaseFromPage(&tuple, page);
/*
* We follow VACUUM's lead in counting INSERT_IN_PROGRESS tuples
diff --git a/contrib/pgstattuple/pgstatindex.c b/contrib/pgstattuple/pgstatindex.c
index 6c4b053dd0..7ed7f1381a 100644
--- a/contrib/pgstattuple/pgstatindex.c
+++ b/contrib/pgstattuple/pgstatindex.c
@@ -605,7 +605,7 @@ pgstathashindex(PG_FUNCTION_ARGS)
metabuf = _hash_getbuf(rel, HASH_METAPAGE, HASH_READ, LH_META_PAGE);
metap = HashPageGetMeta(BufferGetPage(metabuf));
stats.version = metap->hashm_version;
- stats.space_per_page = metap->hashm_bsize;
+ stats.space_per_page = BLCKSZ - SizeOfPageHeaderData - MAXALIGN(sizeof(HashPageOpaqueData));
_hash_relbuf(rel, metabuf);
/* Get the current relation length */
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index f210f91188..d4f9d57a45 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -4447,16 +4447,24 @@ UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3;
UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3;
EXPLAIN (verbose, costs off)
-UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; -- can be pushed down
- QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------
- Update on public.ft2
- Output: c1, c2, c3, c4, c5, c6, c7, c8
- -> Foreign Update on public.ft2
- Remote SQL: UPDATE "S 1"."T 1" SET c2 = (c2 + 400), c3 = (c3 || '_update7') WHERE ((("C 1" % 10) = 7)) RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8
-(4 rows)
+WITH t AS (UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *)
+SELECT * FROM t ORDER BY c1; -- can be pushed down
+ QUERY PLAN
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Sort
+ Output: t.c1, t.c2, t.c3, t.c4, t.c5, t.c6, t.c7, t.c8
+ Sort Key: t.c1
+ CTE t
+ -> Update on public.ft2
+ Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8
+ -> Foreign Update on public.ft2
+ Remote SQL: UPDATE "S 1"."T 1" SET c2 = (c2 + 400), c3 = (c3 || '_update7') WHERE ((("C 1" % 10) = 7)) RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8
+ -> CTE Scan on t
+ Output: t.c1, t.c2, t.c3, t.c4, t.c5, t.c6, t.c7, t.c8
+(10 rows)
-UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *;
+WITH t AS (UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *)
+SELECT * FROM t ORDER BY c1;
c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
------+-----+--------------------+------------------------------+--------------------------+----+------------+-----
7 | 407 | 00007_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo
@@ -4576,16 +4584,24 @@ UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;
EXPLAIN (verbose, costs off)
- DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; -- can be pushed down
- QUERY PLAN
---------------------------------------------------------------------------------------------
- Delete on public.ft2
- Output: c1, c4
- -> Foreign Delete on public.ft2
- Remote SQL: DELETE FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 5)) RETURNING "C 1", c4
-(4 rows)
+ WITH t AS (DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4)
+ SELECT * FROM t ORDER BY c1; -- can be pushed down
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------
+ Sort
+ Output: t.c1, t.c4
+ Sort Key: t.c1
+ CTE t
+ -> Delete on public.ft2
+ Output: ft2.c1, ft2.c4
+ -> Foreign Delete on public.ft2
+ Remote SQL: DELETE FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 5)) RETURNING "C 1", c4
+ -> CTE Scan on t
+ Output: t.c1, t.c4
+(10 rows)
-DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;
+WITH t AS (DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4)
+SELECT * FROM t ORDER BY c1;
c1 | c4
------+------------------------------
5 | Tue Jan 06 00:00:00 1970 PST
@@ -5846,7 +5862,8 @@ INSERT INTO ft2 (c1,c2,c3,c6) VALUES (1218, 818, 'ggg', '(--;') RETURNING *;
1218 | 818 | ggg_trig_update | | | (--; | ft2 |
(1 row)
-UPDATE ft2 SET c2 = c2 + 600 WHERE c1 % 10 = 8 AND c1 < 1200 RETURNING *;
+WITH t AS (UPDATE ft2 SET c2 = c2 + 600 WHERE c1 % 10 = 8 AND c1 < 1200 RETURNING *)
+SELECT * FROM t ORDER BY c1;
c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
------+-----+------------------------+------------------------------+--------------------------+----+------------+-----
8 | 608 | 00008_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 56654844e8..f0651bfaac 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -4803,8 +4803,8 @@ apply_returning_filter(PgFdwDirectModifyState *dmstate,
* Note: no need to care about tableoid here because it will be
* initialized in ExecProcessReturning().
*/
- HeapTupleHeaderSetXmin(resultTup->t_data, InvalidTransactionId);
- HeapTupleHeaderSetXmax(resultTup->t_data, InvalidTransactionId);
+ HeapTupleSetXmin(resultTup, InvalidTransactionId);
+ HeapTupleSetXmax(resultTup, InvalidTransactionId);
HeapTupleHeaderSetCmin(resultTup->t_data, InvalidTransactionId);
}
@@ -7265,6 +7265,7 @@ make_tuple_from_result_row(PGresult *res,
*/
if (ctid)
tuple->t_self = tuple->t_data->t_ctid = *ctid;
+ HeapTupleSetZeroBase(tuple);
/*
* Stomp on the xmin, xmax, and cmin fields from the tuple created by
@@ -7274,8 +7275,8 @@ make_tuple_from_result_row(PGresult *res,
* assumption. If we don't do this then, for example, the tuple length
* ends up in the xmin field, which isn't what we want.
*/
- HeapTupleHeaderSetXmax(tuple->t_data, InvalidTransactionId);
- HeapTupleHeaderSetXmin(tuple->t_data, InvalidTransactionId);
+ HeapTupleSetXmax(tuple, InvalidTransactionId);
+ HeapTupleSetXmin(tuple, InvalidTransactionId);
HeapTupleHeaderSetCmin(tuple->t_data, InvalidTransactionId);
/* Clean up */
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 95b6b7192e..77130541e7 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -1243,16 +1243,20 @@ EXPLAIN (verbose, costs off)
UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3; -- can be pushed down
UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3;
EXPLAIN (verbose, costs off)
-UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; -- can be pushed down
-UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *;
+WITH t AS (UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *)
+SELECT * FROM t ORDER BY c1; -- can be pushed down
+WITH t AS (UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *)
+SELECT * FROM t ORDER BY c1;
EXPLAIN (verbose, costs off)
UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; -- can be pushed down
UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;
EXPLAIN (verbose, costs off)
- DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; -- can be pushed down
-DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;
+ WITH t AS (DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4)
+ SELECT * FROM t ORDER BY c1; -- can be pushed down
+WITH t AS (DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4)
+SELECT * FROM t ORDER BY c1;
EXPLAIN (verbose, costs off)
DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; -- can be pushed down
DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
@@ -1359,7 +1363,8 @@ CREATE TRIGGER t1_br_insert BEFORE INSERT OR UPDATE
INSERT INTO ft2 (c1,c2,c3) VALUES (1208, 818, 'fff') RETURNING *;
INSERT INTO ft2 (c1,c2,c3,c6) VALUES (1218, 818, 'ggg', '(--;') RETURNING *;
-UPDATE ft2 SET c2 = c2 + 600 WHERE c1 % 10 = 8 AND c1 < 1200 RETURNING *;
+WITH t AS (UPDATE ft2 SET c2 = c2 + 600 WHERE c1 % 10 = 8 AND c1 < 1200 RETURNING *)
+SELECT * FROM t ORDER BY c1;
-- Test errors thrown on remote side during update
ALTER TABLE "S 1"."T 1" ADD CONSTRAINT c2positive CHECK (c2 >= 0);
diff --git a/contrib/test_decoding/test_decoding.c b/contrib/test_decoding/test_decoding.c
index ea22649e41..523ed6ef64 100644
--- a/contrib/test_decoding/test_decoding.c
+++ b/contrib/test_decoding/test_decoding.c
@@ -333,7 +333,7 @@ pg_output_begin(LogicalDecodingContext *ctx, TestDecodingData *data, ReorderBuff
{
OutputPluginPrepareWrite(ctx, last_write);
if (data->include_xids)
- appendStringInfo(ctx->out, "BEGIN %u", txn->xid);
+ appendStringInfo(ctx->out, "BEGIN " XID_FMT, txn->xid);
else
appendStringInfoString(ctx->out, "BEGIN");
OutputPluginWrite(ctx, last_write);
@@ -356,7 +356,7 @@ pg_decode_commit_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
OutputPluginPrepareWrite(ctx, true);
if (data->include_xids)
- appendStringInfo(ctx->out, "COMMIT %u", txn->xid);
+ appendStringInfo(ctx->out, "COMMIT " XID_FMT, txn->xid);
else
appendStringInfoString(ctx->out, "COMMIT");
@@ -401,7 +401,7 @@ pg_decode_prepare_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
quote_literal_cstr(txn->gid));
if (data->include_xids)
- appendStringInfo(ctx->out, ", txid %u", txn->xid);
+ appendStringInfo(ctx->out, ", txid " XID_FMT, txn->xid);
if (data->include_timestamp)
appendStringInfo(ctx->out, " (at %s)",
@@ -423,7 +423,7 @@ pg_decode_commit_prepared_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn
quote_literal_cstr(txn->gid));
if (data->include_xids)
- appendStringInfo(ctx->out, ", txid %u", txn->xid);
+ appendStringInfo(ctx->out, ", txid " XID_FMT, txn->xid);
if (data->include_timestamp)
appendStringInfo(ctx->out, " (at %s)",
@@ -447,7 +447,7 @@ pg_decode_rollback_prepared_txn(LogicalDecodingContext *ctx,
quote_literal_cstr(txn->gid));
if (data->include_xids)
- appendStringInfo(ctx->out, ", txid %u", txn->xid);
+ appendStringInfo(ctx->out, ", txid " XID_FMT, txn->xid);
if (data->include_timestamp)
appendStringInfo(ctx->out, " (at %s)",
@@ -828,7 +828,7 @@ pg_output_stream_start(LogicalDecodingContext *ctx, TestDecodingData *data, Reor
{
OutputPluginPrepareWrite(ctx, last_write);
if (data->include_xids)
- appendStringInfo(ctx->out, "opening a streamed block for transaction TXN %u", txn->xid);
+ appendStringInfo(ctx->out, "opening a streamed block for transaction TXN " XID_FMT, txn->xid);
else
appendStringInfoString(ctx->out, "opening a streamed block for transaction");
OutputPluginWrite(ctx, last_write);
@@ -846,7 +846,7 @@ pg_decode_stream_stop(LogicalDecodingContext *ctx,
OutputPluginPrepareWrite(ctx, true);
if (data->include_xids)
- appendStringInfo(ctx->out, "closing a streamed block for transaction TXN %u", txn->xid);
+ appendStringInfo(ctx->out, "closing a streamed block for transaction TXN " XID_FMT, txn->xid);
else
appendStringInfoString(ctx->out, "closing a streamed block for transaction");
OutputPluginWrite(ctx, true);
@@ -880,7 +880,7 @@ pg_decode_stream_abort(LogicalDecodingContext *ctx,
OutputPluginPrepareWrite(ctx, true);
if (data->include_xids)
- appendStringInfo(ctx->out, "aborting streamed (sub)transaction TXN %u", txn->xid);
+ appendStringInfo(ctx->out, "aborting streamed (sub)transaction TXN " XID_FMT, txn->xid);
else
appendStringInfoString(ctx->out, "aborting streamed (sub)transaction");
OutputPluginWrite(ctx, true);
@@ -900,7 +900,7 @@ pg_decode_stream_prepare(LogicalDecodingContext *ctx,
OutputPluginPrepareWrite(ctx, true);
if (data->include_xids)
- appendStringInfo(ctx->out, "preparing streamed transaction TXN %s, txid %u",
+ appendStringInfo(ctx->out, "preparing streamed transaction TXN %s, txid " XID_FMT,
quote_literal_cstr(txn->gid), txn->xid);
else
appendStringInfo(ctx->out, "preparing streamed transaction %s",
@@ -931,7 +931,7 @@ pg_decode_stream_commit(LogicalDecodingContext *ctx,
OutputPluginPrepareWrite(ctx, true);
if (data->include_xids)
- appendStringInfo(ctx->out, "committing streamed transaction TXN %u", txn->xid);
+ appendStringInfo(ctx->out, "committing streamed transaction TXN " XID_FMT, txn->xid);
else
appendStringInfoString(ctx->out, "committing streamed transaction");
@@ -965,7 +965,7 @@ pg_decode_stream_change(LogicalDecodingContext *ctx,
OutputPluginPrepareWrite(ctx, true);
if (data->include_xids)
- appendStringInfo(ctx->out, "streaming change for TXN %u", txn->xid);
+ appendStringInfo(ctx->out, "streaming change for TXN " XID_FMT, txn->xid);
else
appendStringInfoString(ctx->out, "streaming change for transaction");
OutputPluginWrite(ctx, true);
@@ -1050,7 +1050,7 @@ pg_decode_stream_truncate(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
OutputPluginPrepareWrite(ctx, true);
if (data->include_xids)
- appendStringInfo(ctx->out, "streaming truncate for TXN %u", txn->xid);
+ appendStringInfo(ctx->out, "streaming truncate for TXN " XID_FMT, txn->xid);
else
appendStringInfoString(ctx->out, "streaming truncate for transaction");
OutputPluginWrite(ctx, true);
diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 503cda46ef..4e9a884903 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -640,10 +640,10 @@ heap_getsysattr(HeapTuple tup, int attnum, TupleDesc tupleDesc, bool *isnull)
result = PointerGetDatum(&(tup->t_self));
break;
case MinTransactionIdAttributeNumber:
- result = TransactionIdGetDatum(HeapTupleHeaderGetRawXmin(tup->t_data));
+ result = TransactionIdGetDatum(HeapTupleGetRawXmin(tup));
break;
case MaxTransactionIdAttributeNumber:
- result = TransactionIdGetDatum(HeapTupleHeaderGetRawXmax(tup->t_data));
+ result = TransactionIdGetDatum(HeapTupleGetRawXmax(tup));
break;
case MinCommandIdAttributeNumber:
case MaxCommandIdAttributeNumber:
@@ -688,6 +688,7 @@ heap_copytuple(HeapTuple tuple)
newTuple->t_len = tuple->t_len;
newTuple->t_self = tuple->t_self;
newTuple->t_tableOid = tuple->t_tableOid;
+ HeapTupleCopyBase(newTuple, tuple);
newTuple->t_data = (HeapTupleHeader) ((char *) newTuple + HEAPTUPLESIZE);
memcpy((char *) newTuple->t_data, (char *) tuple->t_data, tuple->t_len);
return newTuple;
@@ -714,6 +715,7 @@ heap_copytuple_with_tuple(HeapTuple src, HeapTuple dest)
dest->t_len = src->t_len;
dest->t_self = src->t_self;
dest->t_tableOid = src->t_tableOid;
+ HeapTupleCopyBase(dest, src);
dest->t_data = (HeapTupleHeader) palloc(src->t_len);
memcpy((char *) dest->t_data, (char *) src->t_data, src->t_len);
}
@@ -1161,6 +1163,7 @@ heap_modify_tuple(HeapTuple tuple,
newTuple->t_data->t_ctid = tuple->t_data->t_ctid;
newTuple->t_self = tuple->t_self;
newTuple->t_tableOid = tuple->t_tableOid;
+ HeapTupleCopyBase(newTuple, tuple);
return newTuple;
}
@@ -1224,6 +1227,7 @@ heap_modify_tuple_by_cols(HeapTuple tuple,
newTuple->t_data->t_ctid = tuple->t_data->t_ctid;
newTuple->t_self = tuple->t_self;
newTuple->t_tableOid = tuple->t_tableOid;
+ HeapTupleCopyBase(newTuple, tuple);
return newTuple;
}
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index e7062d4da3..4e8553b403 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -251,58 +251,6 @@ static relopt_int intRelOpts[] =
},
-1, 1, 10000
},
- {
- {
- "autovacuum_freeze_min_age",
- "Minimum age at which VACUUM should freeze a table row, for autovacuum",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- -1, 0, 1000000000
- },
- {
- {
- "autovacuum_multixact_freeze_min_age",
- "Minimum multixact age at which VACUUM should freeze a row multixact's, for autovacuum",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- -1, 0, 1000000000
- },
- {
- {
- "autovacuum_freeze_max_age",
- "Age at which to autovacuum a table to prevent transaction ID wraparound",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- -1, 100000, 2000000000
- },
- {
- {
- "autovacuum_multixact_freeze_max_age",
- "Multixact age at which to autovacuum a table to prevent multixact wraparound",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- -1, 10000, 2000000000
- },
- {
- {
- "autovacuum_freeze_table_age",
- "Age at which VACUUM should perform a full table sweep to freeze row versions",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- }, -1, 0, 2000000000
- },
- {
- {
- "autovacuum_multixact_freeze_table_age",
- "Age of multixact at which VACUUM should perform a full table sweep to freeze row versions",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- }, -1, 0, 2000000000
- },
{
{
"log_autovacuum_min_duration",
@@ -379,6 +327,60 @@ static relopt_int intRelOpts[] =
static relopt_int64 int64RelOpts[] =
{
+ {
+ {
+ "autovacuum_freeze_min_age",
+ "Minimum age at which VACUUM should freeze a table row, for autovacuum",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ INT64CONST(-1), INT64CONST(0), INT64CONST(1000000000)
+ },
+ {
+ {
+ "autovacuum_multixact_freeze_min_age",
+ "Minimum multixact age at which VACUUM should freeze a row multixact's, for autovacuum",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ INT64CONST(-1), INT64CONST(0), INT64CONST(1000000000)
+ },
+ {
+ {
+ "autovacuum_freeze_max_age",
+ "Age at which to autovacuum a table to prevent transaction ID wraparound",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ INT64CONST(-1), INT64CONST(100000), INT64CONST(2000000000)
+ },
+ {
+ {
+ "autovacuum_multixact_freeze_max_age",
+ "Multixact age at which to autovacuum a table to prevent multixact wraparound",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ INT64CONST(-1), INT64CONST(10000), INT64CONST(2000000000)
+ },
+ {
+ {
+ "autovacuum_freeze_table_age",
+ "Age at which VACUUM should perform a full table sweep to freeze row versions",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ INT64CONST(-1), INT64CONST(0), INT64CONST(2000000000)
+ },
+ {
+ {
+ "autovacuum_multixact_freeze_table_age",
+ "Age of multixact at which VACUUM should perform a full table sweep to freeze row versions",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ INT64CONST(-1), INT64CONST(0), INT64CONST(2000000000)
+ },
/* list terminator */
{{NULL}}
};
@@ -1912,17 +1914,17 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, analyze_threshold)},
{"autovacuum_vacuum_cost_limit", RELOPT_TYPE_INT,
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_cost_limit)},
- {"autovacuum_freeze_min_age", RELOPT_TYPE_INT,
+ {"autovacuum_freeze_min_age", RELOPT_TYPE_INT64,
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, freeze_min_age)},
- {"autovacuum_freeze_max_age", RELOPT_TYPE_INT,
+ {"autovacuum_freeze_max_age", RELOPT_TYPE_INT64,
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, freeze_max_age)},
- {"autovacuum_freeze_table_age", RELOPT_TYPE_INT,
+ {"autovacuum_freeze_table_age", RELOPT_TYPE_INT64,
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, freeze_table_age)},
- {"autovacuum_multixact_freeze_min_age", RELOPT_TYPE_INT,
+ {"autovacuum_multixact_freeze_min_age", RELOPT_TYPE_INT64,
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, multixact_freeze_min_age)},
- {"autovacuum_multixact_freeze_max_age", RELOPT_TYPE_INT,
+ {"autovacuum_multixact_freeze_max_age", RELOPT_TYPE_INT64,
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, multixact_freeze_max_age)},
- {"autovacuum_multixact_freeze_table_age", RELOPT_TYPE_INT,
+ {"autovacuum_multixact_freeze_table_age", RELOPT_TYPE_INT64,
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, multixact_freeze_table_age)},
{"log_autovacuum_min_duration", RELOPT_TYPE_INT,
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, log_min_duration)},
diff --git a/src/backend/access/hash/hashvalidate.c b/src/backend/access/hash/hashvalidate.c
index 10bf26ce7c..83e033b93c 100644
--- a/src/backend/access/hash/hashvalidate.c
+++ b/src/backend/access/hash/hashvalidate.c
@@ -317,11 +317,10 @@ check_hash_func_signature(Oid funcid, int16 amprocnum, Oid argtype)
* INTERNAL and allowing any such function seems too scary.
*/
if ((funcid == F_HASHINT4 || funcid == F_HASHINT4EXTENDED) &&
- (argtype == DATEOID ||
- argtype == XIDOID || argtype == CIDOID))
+ (argtype == DATEOID || argtype == CIDOID))
/* okay, allowed use of hashint4() */ ;
else if ((funcid == F_HASHINT8 || funcid == F_HASHINT8EXTENDED) &&
- (argtype == XID8OID))
+ (argtype == XID8OID || argtype == XIDOID))
/* okay, allowed use of hashint8() */ ;
else if ((funcid == F_TIMESTAMP_HASH ||
funcid == F_TIMESTAMP_HASH_EXTENDED) &&
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 59d43e2ba9..0b1a60b2c6 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -52,6 +52,9 @@
#include "access/xloginsert.h"
#include "access/xlogutils.h"
#include "catalog/catalog.h"
+#include "catalog/index.h"
+#include "catalog/namespace.h"
+#include "commands/vacuum.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "port/atomics.h"
@@ -73,7 +76,7 @@
static HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup,
- TransactionId xid, CommandId cid, int options);
+ CommandId cid, int options);
static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
Buffer newbuf, HeapTuple oldtup,
HeapTuple newtup, HeapTuple old_key_tuple,
@@ -216,6 +219,20 @@ static const int MultiXactStatusLock[MaxMultiXactStatus + 1] =
#define TUPLOCK_from_mxstatus(status) \
(MultiXactStatusLock[(status)])
+ShortTransactionId
+HeapPageSetPruneXidInternal(Page page, TransactionId xid)
+{
+ if (TransactionIdIsNormal(xid) &&
+ /* abuse unsigned underflow */
+ xid - HeapPageGetSpecial(page)->pd_xid_base - FirstNormalTransactionId >
+ MaxShortTransactionId)
+ elog(WARNING, "pd_prune_xid is out of range");
+ ((PageHeader) (page))->pd_prune_xid =
+ NormalTransactionIdToShort(HeapPageGetSpecial(page)->pd_xid_base, xid);
+
+ return ((PageHeader) (page))->pd_prune_xid;
+}
+
/* ----------------------------------------------------------------
* heap support routines
* ----------------------------------------------------------------
@@ -460,6 +477,7 @@ heapgetpage(TableScanDesc sscan, BlockNumber page)
loctup.t_tableOid = RelationGetRelid(scan->rs_base.rs_rd);
loctup.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
loctup.t_len = ItemIdGetLength(lpp);
+ HeapTupleCopyBaseFromPage(&loctup, dp);
ItemPointerSet(&(loctup.t_self), page, lineoff);
if (all_visible)
@@ -676,6 +694,7 @@ heapgettup(HeapScanDesc scan,
tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
tuple->t_len = ItemIdGetLength(lpp);
+ HeapTupleCopyBaseFromPage(tuple, dp);
return;
}
@@ -702,6 +721,7 @@ heapgettup(HeapScanDesc scan,
tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
tuple->t_len = ItemIdGetLength(lpp);
+ HeapTupleCopyBaseFromPage(tuple, dp);
ItemPointerSet(&(tuple->t_self), page, lineoff);
/*
@@ -1001,6 +1021,7 @@ heapgettup_pagemode(HeapScanDesc scan,
tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
tuple->t_len = ItemIdGetLength(lpp);
+ HeapTupleCopyBaseFromPage(tuple, dp);
/* check that rs_cindex is in sync */
Assert(scan->rs_cindex < scan->rs_ntuples);
@@ -1023,6 +1044,7 @@ heapgettup_pagemode(HeapScanDesc scan,
tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
tuple->t_len = ItemIdGetLength(lpp);
+ HeapTupleCopyBaseFromPage(tuple, dp);
ItemPointerSet(&(tuple->t_self), page, lineoff);
/*
@@ -1655,6 +1677,7 @@ heap_fetch(Relation relation,
tuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
tuple->t_len = ItemIdGetLength(lp);
tuple->t_tableOid = RelationGetRelid(relation);
+ HeapTupleCopyBaseFromPage(tuple, page);
/*
* check tuple visibility, then release lock
@@ -1663,7 +1686,7 @@ heap_fetch(Relation relation,
if (valid)
PredicateLockTID(relation, &(tuple->t_self), snapshot,
- HeapTupleHeaderGetXmin(tuple->t_data));
+ HeapTupleGetXmin(tuple));
HeapCheckForSerializableConflictOut(valid, relation, tuple, buffer, snapshot);
@@ -1735,6 +1758,8 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
Assert(TransactionIdIsValid(RecentXmin));
Assert(BufferGetBlockNumber(buffer) == blkno);
+ heapTuple->t_self = *tid;
+
/* Scan through possible multiple members of HOT-chain */
for (;;)
{
@@ -1770,6 +1795,7 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
heapTuple->t_data = (HeapTupleHeader) PageGetItem(dp, lp);
heapTuple->t_len = ItemIdGetLength(lp);
heapTuple->t_tableOid = RelationGetRelid(relation);
+ HeapTupleCopyBaseFromPage(heapTuple, dp);
ItemPointerSet(&heapTuple->t_self, blkno, offnum);
/*
@@ -1784,7 +1810,7 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
*/
if (TransactionIdIsValid(prev_xmax) &&
!TransactionIdEquals(prev_xmax,
- HeapTupleHeaderGetXmin(heapTuple->t_data)))
+ HeapTupleGetXmin(heapTuple)))
break;
/*
@@ -1805,7 +1831,7 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
{
ItemPointerSetOffsetNumber(tid, offnum);
PredicateLockTID(relation, &heapTuple->t_self, snapshot,
- HeapTupleHeaderGetXmin(heapTuple->t_data));
+ HeapTupleGetXmin(heapTuple));
if (all_dead)
*all_dead = false;
return true;
@@ -1840,7 +1866,7 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
blkno);
offnum = ItemPointerGetOffsetNumber(&heapTuple->t_data->t_ctid);
at_chain_start = false;
- prev_xmax = HeapTupleHeaderGetUpdateXid(heapTuple->t_data);
+ prev_xmax = HeapTupleGetUpdateXidAny(heapTuple);
}
else
break; /* end of chain */
@@ -1927,13 +1953,14 @@ heap_get_latest_tid(TableScanDesc sscan,
tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
tp.t_len = ItemIdGetLength(lp);
tp.t_tableOid = RelationGetRelid(relation);
+ HeapTupleCopyBaseFromPage(&tp, page);
/*
* After following a t_ctid link, we might arrive at an unrelated
* tuple. Check for XMIN match.
*/
if (TransactionIdIsValid(priorXmax) &&
- !TransactionIdEquals(priorXmax, HeapTupleHeaderGetXmin(tp.t_data)))
+ !TransactionIdEquals(priorXmax, HeapTupleGetXmin(&tp)))
{
UnlockReleaseBuffer(buffer);
break;
@@ -1952,7 +1979,7 @@ heap_get_latest_tid(TableScanDesc sscan,
* If there's a valid t_ctid link, follow it, else we're done.
*/
if ((tp.t_data->t_infomask & HEAP_XMAX_INVALID) ||
- HeapTupleHeaderIsOnlyLocked(tp.t_data) ||
+ HeapTupleIsOnlyLocked(&tp) ||
HeapTupleHeaderIndicatesMovedPartitions(tp.t_data) ||
ItemPointerEquals(&tp.t_self, &tp.t_data->t_ctid))
{
@@ -1961,7 +1988,7 @@ heap_get_latest_tid(TableScanDesc sscan,
}
ctid = tp.t_data->t_ctid;
- priorXmax = HeapTupleHeaderGetUpdateXid(tp.t_data);
+ priorXmax = HeapTupleGetUpdateXidAny(&tp);
UnlockReleaseBuffer(buffer);
} /* end of loop */
}
@@ -1986,7 +2013,7 @@ heap_get_latest_tid(TableScanDesc sscan,
static void
UpdateXmaxHintBits(HeapTupleHeader tuple, Buffer buffer, TransactionId xid)
{
- Assert(TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple), xid));
+ Assert(TransactionIdEquals(HeapTupleHeaderGetRawXmax(BufferGetPage(buffer), tuple), xid));
Assert(!(tuple->t_infomask & HEAP_XMAX_IS_MULTI));
if (!(tuple->t_infomask & (HEAP_XMAX_COMMITTED | HEAP_XMAX_INVALID)))
@@ -2078,7 +2105,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
* Note: below this point, heaptup is the data we actually intend to store
* into the relation; tup is the caller's original untoasted data.
*/
- heaptup = heap_prepare_insert(relation, tup, xid, cid, options);
+ heaptup = heap_prepare_insert(relation, tup, cid, options);
/*
* Find buffer to insert this tuple into. If the page is all visible,
@@ -2105,6 +2132,9 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
*/
CheckForSerializableConflictIn(relation, NULL, InvalidBlockNumber);
+ heap_page_prepare_for_xid(relation, buffer, xid, false);
+ HeapTupleSetXmin(heaptup, xid);
+
/* NO EREPORT(ERROR) from here till changes are logged */
START_CRIT_SECTION();
@@ -2186,6 +2216,10 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
}
XLogBeginInsert();
+ if (info & XLOG_HEAP_INIT_PAGE)
+ {
+ XLogRegisterData((char *) &HeapPageGetSpecial(page)->pd_xid_base, sizeof(TransactionId));
+ }
XLogRegisterData((char *) &xlrec, SizeOfHeapInsert);
xlhdr.t_infomask2 = heaptup->t_data->t_infomask2;
@@ -2240,6 +2274,543 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
}
}
+/*
+ * Find minimum and maximum short transaction ids which occurs in the page.
+ */
+static bool
+heap_page_xid_min_max(Page page, bool multi,
+ ShortTransactionId *min,
+ ShortTransactionId *max)
+{
+ TransactionId xid_base = HeapPageGetSpecial(page)->pd_xid_base;
+ bool found = false;
+ OffsetNumber offnum,
+ maxoff;
+
+ maxoff = PageGetMaxOffsetNumber(page);
+
+ for (offnum = FirstOffsetNumber;
+ offnum <= maxoff;
+ offnum = OffsetNumberNext(offnum))
+ {
+ ItemId itemid;
+ HeapTupleHeader htup;
+
+ itemid = PageGetItemId(page, offnum);
+
+ if (!ItemIdIsNormal(itemid))
+ continue;
+
+ htup = (HeapTupleHeader) PageGetItem(page, itemid);
+
+ if (!multi)
+ {
+ if (!HeapTupleHeaderXminFrozen(htup) &&
+ TransactionIdIsNormal(htup->t_choice.t_heap.t_xmin))
+ {
+ Assert(htup->t_choice.t_heap.t_xmin >= FirstNormalTransactionId);
+ Assert(htup->t_choice.t_heap.t_xmin <= MaxShortTransactionId);
+
+ if (!found)
+ {
+ *min = *max = htup->t_choice.t_heap.t_xmin;
+ found = true;
+ }
+ else
+ {
+ *min = Min(*min, htup->t_choice.t_heap.t_xmin);
+ *max = Max(*max, htup->t_choice.t_heap.t_xmin);
+ }
+ }
+
+ if (htup->t_infomask & HEAP_XMAX_IS_MULTI)
+ {
+ if (!(htup->t_infomask & (HEAP_XMAX_INVALID | HEAP_XMAX_LOCK_ONLY)))
+ {
+ TransactionId update_xid =
+ MultiXactIdGetUpdateXid(HeapTupleHeaderGetRawXmax(page, htup),
+ htup->t_infomask);
+ ShortTransactionId xid = NormalTransactionIdToShort(xid_base, update_xid);
+
+ Assert(xid >= FirstNormalTransactionId);
+ Assert(xid <= MaxShortTransactionId);
+
+ if (!found)
+ {
+ *min = *max = xid;
+ found = true;
+ }
+ else
+ {
+ *min = Min(*min, xid);
+ *max = Max(*max, xid);
+ }
+ }
+ }
+ else if (!(htup->t_infomask & HEAP_XMAX_INVALID) &&
+ TransactionIdIsNormal(htup->t_choice.t_heap.t_xmax))
+ {
+ Assert(htup->t_choice.t_heap.t_xmax >= FirstNormalTransactionId);
+ Assert(htup->t_choice.t_heap.t_xmax <= MaxShortTransactionId);
+
+ if (!found)
+ {
+ *min = *max = htup->t_choice.t_heap.t_xmax;
+ found = true;
+ }
+ else
+ {
+ *min = Min(*min, htup->t_choice.t_heap.t_xmax);
+ *max = Max(*max, htup->t_choice.t_heap.t_xmax);
+ }
+ }
+ }
+ else
+ {
+ if (TransactionIdIsNormal(htup->t_choice.t_heap.t_xmax) &&
+ (htup->t_infomask & HEAP_XMAX_IS_MULTI))
+ {
+ Assert(htup->t_choice.t_heap.t_xmax >= FirstNormalTransactionId);
+ Assert(htup->t_choice.t_heap.t_xmax <= MaxShortTransactionId);
+
+ if (!found)
+ {
+ *min = *max = htup->t_choice.t_heap.t_xmax;
+ found = true;
+ }
+ else
+ {
+ *min = Min(*min, htup->t_choice.t_heap.t_xmax);
+ *max = Max(*max, htup->t_choice.t_heap.t_xmax);
+ }
+ }
+ }
+ }
+ return found;
+}
+
+/*
+ * Shift xid base in the page. WAL-logged if buffer is specified.
+ */
+static void
+heap_page_shift_base(Relation relation, Buffer buffer, Page page, bool multi, int64 delta)
+{
+ HeapPageSpecial pageSpecial = HeapPageGetSpecial(page);
+ OffsetNumber offnum,
+ maxoff;
+
+ START_CRIT_SECTION();
+
+ /* Iterate over page items */
+ maxoff = PageGetMaxOffsetNumber(page);
+ for (offnum = FirstOffsetNumber;
+ offnum <= maxoff;
+ offnum = OffsetNumberNext(offnum))
+ {
+ ItemId itemid;
+ HeapTupleHeader htup;
+
+ itemid = PageGetItemId(page, offnum);
+
+ if (!ItemIdIsNormal(itemid))
+ continue;
+
+ htup = (HeapTupleHeader) PageGetItem(page, itemid);
+
+ /* Apply xid shift to heap tuple */
+ if (!multi)
+ {
+ if (!HeapTupleHeaderXminFrozen(htup) &&
+ TransactionIdIsNormal(htup->t_choice.t_heap.t_xmin))
+ {
+ Assert(htup->t_choice.t_heap.t_xmin - delta >= FirstNormalTransactionId);
+ Assert(htup->t_choice.t_heap.t_xmin - delta <= MaxShortTransactionId);
+ htup->t_choice.t_heap.t_xmin -= delta;
+ }
+
+ if (TransactionIdIsNormal(htup->t_choice.t_heap.t_xmax) &&
+ !(htup->t_infomask & HEAP_XMAX_IS_MULTI))
+ {
+ Assert(htup->t_choice.t_heap.t_xmax - delta >= FirstNormalTransactionId);
+ Assert(htup->t_choice.t_heap.t_xmax - delta <= MaxShortTransactionId);
+ htup->t_choice.t_heap.t_xmax -= delta;
+ }
+ }
+ else
+ {
+ if (TransactionIdIsNormal(htup->t_choice.t_heap.t_xmax) &&
+ (htup->t_infomask & HEAP_XMAX_IS_MULTI))
+ {
+ Assert(htup->t_choice.t_heap.t_xmax - delta >= FirstNormalTransactionId);
+ Assert(htup->t_choice.t_heap.t_xmax - delta <= MaxShortTransactionId);
+ htup->t_choice.t_heap.t_xmax -= delta;
+ }
+ }
+ }
+
+ /* Apply xid shift to base as well */
+ if (!multi)
+ pageSpecial->pd_xid_base += delta;
+ else
+ pageSpecial->pd_multi_base += delta;
+
+ if (BufferIsValid(buffer))
+ MarkBufferDirty(buffer);
+
+ /* Write WAL record if needed */
+ if (relation && RelationNeedsWAL(relation))
+ {
+ XLogRecPtr recptr;
+ xl_heap_base_shift xlrec;
+
+ xlrec.multi = multi;
+ xlrec.delta = delta;
+
+ XLogBeginInsert();
+ XLogRegisterData((char *) &xlrec, SizeOfHeapBaseShift);
+
+ XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
+
+ recptr = XLogInsert(RM_HEAP3_ID, XLOG_HEAP3_BASE_SHIFT);
+
+ PageSetLSN(page, recptr);
+ }
+
+ END_CRIT_SECTION();
+}
+
+/*
+ * Freeze xids in the single heap page. Useful when we can't fit new xid even
+ * with base shift.
+ */
+static void
+freeze_single_heap_page(Relation relation, Buffer buffer)
+{
+ Page page = BufferGetPage(buffer);
+ OffsetNumber offnum,
+ maxoff;
+ HeapTupleData tuple;
+ int nfrozen = 0;
+ xl_heap_freeze_tuple *frozen;
+ TransactionId OldestXmin,
+ FreezeXid;
+ MultiXactId MultiXactCutoff;
+ GlobalVisState *vistest;
+
+ vacuum_set_xid_limits(relation, 0, 0, 0, 0,
+ &OldestXmin, &FreezeXid, &MultiXactCutoff);
+
+ vistest = GlobalVisTestFor(relation);
+
+ heap_page_prune(relation, buffer, vistest,
+ InvalidTransactionId, 0,
+ false, &offnum, false);
+
+ /*
+ * Now scan the page to collect vacuumable items and check for tuples
+ * requiring freezing.
+ */
+ maxoff = PageGetMaxOffsetNumber(page);
+ frozen = palloc(sizeof(xl_heap_freeze_tuple) * MaxHeapTuplesPerPage);
+
+ /*
+ * Note: If you change anything in the loop below, also look at
+ * heap_page_is_all_visible to see if that needs to be changed.
+ */
+ for (offnum = FirstOffsetNumber;
+ offnum <= maxoff;
+ offnum = OffsetNumberNext(offnum))
+ {
+ ItemId itemid;
+ bool tuple_totally_frozen;
+
+ itemid = PageGetItemId(page, offnum);
+
+ if (!ItemIdIsNormal(itemid))
+ continue;
+
+ Assert(ItemIdIsNormal(itemid));
+
+ tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid);
+ tuple.t_len = ItemIdGetLength(itemid);
+ tuple.t_tableOid = RelationGetRelid(relation);
+ HeapTupleCopyBaseFromPage(&tuple, page);
+
+ /*
+ * Each non-removable tuple must be checked to see if it needs
+ * freezing. Note we already have exclusive buffer lock.
+ */
+ if (heap_prepare_freeze_tuple(&tuple,
+ relation->rd_rel->relfrozenxid,
+ relation->rd_rel->relminmxid,
+ FreezeXid, MultiXactCutoff,
+ &frozen[nfrozen], &tuple_totally_frozen))
+ frozen[nfrozen++].offset = offnum;
+ } /* scan along page */
+
+ /*
+ * If we froze any tuples, mark the buffer dirty, and write a WAL
+ * record recording the changes. We must log the changes to be
+ * crash-safe against future truncation of CLOG.
+ */
+ if (nfrozen > 0)
+ {
+ int i;
+
+ START_CRIT_SECTION();
+
+ MarkBufferDirty(buffer);
+
+ /* execute collected freezes */
+ for (i = 0; i < nfrozen; i++)
+ {
+ ItemId itemid;
+ HeapTupleHeader htup;
+
+ itemid = PageGetItemId(page, frozen[i].offset);
+ htup = (HeapTupleHeader) PageGetItem(page, itemid);
+
+ heap_execute_freeze_tuple_page(page, htup, &frozen[i]);
+ }
+
+ /* Now WAL-log freezing if necessary */
+ if (RelationNeedsWAL(relation))
+ {
+ XLogRecPtr recptr;
+
+ recptr = log_heap_freeze(relation, buffer, FreezeXid,
+ frozen, nfrozen);
+ PageSetLSN(page, recptr);
+ }
+
+ END_CRIT_SECTION();
+ }
+
+ return;
+}
+
+/*
+ * Ensure that given xid fits base of given page.
+ */
+bool
+heap_page_prepare_for_xid(Relation relation, Buffer buffer,
+ TransactionId xid, bool multi)
+{
+ Page page = BufferGetPage(buffer);
+ HeapPageSpecial pageSpecial = HeapPageGetSpecial(page);
+ TransactionId base;
+ bool found;
+ ShortTransactionId min,
+ max;
+ int i;
+
+ /* "Double xmax" page format doesn't require any preparation */
+ if (HeapPageIsDoubleXmax(page))
+ return false;
+
+ if (!TransactionIdIsNormal(xid))
+ return false;
+
+ for (i = 0; i < 2; i++)
+ {
+ base = multi ? pageSpecial->pd_multi_base : pageSpecial->pd_xid_base;
+
+ /* Can we already store this xid? */
+ if (xid >= base + FirstNormalTransactionId && xid <= base + MaxShortTransactionId)
+ return false;
+
+ /* Find minimum and maximum xids in the page */
+ found = heap_page_xid_min_max(page, multi, &min, &max);
+
+ /* No items on the page? */
+ if (!found)
+ {
+ int64 delta;
+
+ delta = (int64) (xid - FirstNormalTransactionId) - (int64) base;
+
+ if (xid < base + delta + FirstNormalTransactionId ||
+ xid > base + delta + MaxShortTransactionId)
+ {
+ elog(FATAL, "Fatal xid base calculation error: "
+ "xid = " XID_FMT ", "
+ "base = " XID_FMT ", "
+ "min = %u, "
+ "max = %u, "
+ "delta = " INT64_FORMAT,
+ xid, base, min, max, delta
+ );
+ }
+
+ Assert(xid >= base + delta + FirstNormalTransactionId);
+ Assert(xid <= base + delta + MaxShortTransactionId);
+
+ heap_page_shift_base(relation, buffer, page, multi, delta);
+
+ base = multi ? pageSpecial->pd_multi_base : pageSpecial->pd_xid_base;
+ Assert(xid >= base + FirstNormalTransactionId);
+ Assert(xid <= base + MaxShortTransactionId);
+ return false;
+ }
+
+ /* Can we just shift base on the page */
+ if (xid < base + FirstNormalTransactionId)
+ {
+ int64 freeDelta = MaxShortTransactionId - max,
+ requiredDelta = (base + FirstNormalTransactionId) - xid;
+
+ /* Shouldn't consider setting base less than 0 */
+ freeDelta = Min(freeDelta, base);
+
+ if (requiredDelta <= freeDelta)
+ {
+ int64 delta = - (freeDelta + requiredDelta) / 2;
+
+ if (xid < base + delta + FirstNormalTransactionId ||
+ xid > base + delta + MaxShortTransactionId)
+ {
+ elog(FATAL, "Fatal xid base calculation error: "
+ "xid = " XID_FMT ", "
+ "base = " XID_FMT ", "
+ "min = %u, "
+ "max = %u, "
+ "freeDelta = " INT64_FORMAT ", "
+ "requiredDelta = " INT64_FORMAT ", "
+ "delta = " INT64_FORMAT,
+ xid, base, min, max, freeDelta, requiredDelta, delta
+ );
+ }
+
+ Assert(xid >= base + delta + FirstNormalTransactionId);
+ Assert(xid <= base + delta + MaxShortTransactionId);
+
+ heap_page_shift_base(relation, buffer, page, multi, delta);
+
+ base = multi ? pageSpecial->pd_multi_base : pageSpecial->pd_xid_base;
+ Assert(xid >= base + FirstNormalTransactionId);
+ Assert(xid <= base + MaxShortTransactionId);
+ return true;
+ }
+ }
+ else
+ {
+ int64 freeDelta = min - FirstNormalTransactionId,
+ requiredDelta = xid - (base + MaxShortTransactionId);
+
+ Assert(xid > base + MaxShortTransactionId);
+
+ if (requiredDelta <= freeDelta)
+ {
+ int64 delta = (freeDelta + requiredDelta) / 2;
+
+ if (xid < base + delta + FirstNormalTransactionId ||
+ xid > base + delta + MaxShortTransactionId)
+ {
+ elog(FATAL, "Fatal xid base calculation error: "
+ "xid = " XID_FMT ", "
+ "base = " XID_FMT ", "
+ "min = %u, "
+ "max = %u, "
+ "freeDelta = " INT64_FORMAT ", "
+ "requiredDelta = " INT64_FORMAT ", "
+ "delta = " INT64_FORMAT,
+ xid, base, min, max, freeDelta, requiredDelta, delta
+ );
+ }
+
+ Assert(xid >= base + delta + FirstNormalTransactionId);
+ Assert(xid <= base + delta + MaxShortTransactionId);
+
+ heap_page_shift_base(relation, buffer, page, multi, delta);
+
+ base = multi ? pageSpecial->pd_multi_base : pageSpecial->pd_xid_base;
+ Assert(xid >= base + FirstNormalTransactionId);
+ Assert(xid <= base + MaxShortTransactionId);
+ return true;
+ }
+ }
+
+ if (i == 1)
+ {
+ break;
+ }
+
+ /* Have to try freeing the page... */
+ freeze_single_heap_page(relation, buffer);
+ }
+
+ elog(ERROR, "Can't fit xid into page.");
+ return false;
+}
+
+/*
+ * Ensure that given xid fits base of given page.
+ */
+bool
+rewrite_page_prepare_for_xid(Page page, TransactionId xid, bool multi)
+{
+ HeapPageSpecial pageSpecial = HeapPageGetSpecial(page);
+ TransactionId base;
+ bool found;
+ ShortTransactionId min,
+ max;
+
+ if (!TransactionIdIsNormal(xid))
+ return false;
+
+ if (!multi)
+ base = pageSpecial->pd_xid_base;
+ else
+ base = pageSpecial->pd_multi_base;
+
+ /* Can we already store this xid? */
+ if (xid >= base + FirstNormalTransactionId && xid <= base + MaxShortTransactionId)
+ return false;
+
+ /* Find minimum and maximum xids in the page */
+ found = heap_page_xid_min_max(page, multi, &min, &max);
+
+ /* No items on the page? */
+ if (!found)
+ {
+ if (!multi)
+ pageSpecial->pd_xid_base = xid - FirstNormalTransactionId;
+ else
+ pageSpecial->pd_multi_base = xid - FirstNormalTransactionId;
+ return false;
+ }
+
+ /* Can we just shift base on the page */
+ if (xid < base + FirstNormalTransactionId)
+ {
+ int64 freeDelta = MaxShortTransactionId - max,
+ requiredDelta = (base + FirstNormalTransactionId) - xid;
+
+ if (requiredDelta <= freeDelta)
+ {
+ heap_page_shift_base(NULL, InvalidBuffer,
+ page, multi, - (freeDelta + requiredDelta) / 2);
+ return true;
+ }
+ }
+ else
+ {
+ int64 freeDelta = min - FirstNormalTransactionId,
+ requiredDelta = xid - (base + MaxShortTransactionId);
+
+ if (requiredDelta <= freeDelta)
+ {
+ heap_page_shift_base(NULL, InvalidBuffer,
+ page, multi, (freeDelta + requiredDelta) / 2);
+ return true;
+ }
+ }
+
+ elog(ERROR, "Can't fit xid into page.");
+ return false;
+}
+
+
/*
* Subroutine for heap_insert(). Prepares a tuple for insertion. This sets the
* tuple header fields and toasts the tuple if necessary. Returns a toasted
@@ -2247,7 +2818,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
* that in any case, the header fields are also set in the original tuple.
*/
static HeapTuple
-heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
+heap_prepare_insert(Relation relation, HeapTuple tup,
CommandId cid, int options)
{
/*
@@ -2264,12 +2835,12 @@ heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
tup->t_data->t_infomask &= ~(HEAP_XACT_MASK);
tup->t_data->t_infomask2 &= ~(HEAP2_XACT_MASK);
tup->t_data->t_infomask |= HEAP_XMAX_INVALID;
- HeapTupleHeaderSetXmin(tup->t_data, xid);
+ HeapTupleSetXmin(tup, InvalidTransactionId);
if (options & HEAP_INSERT_FROZEN)
HeapTupleHeaderSetXminFrozen(tup->t_data);
HeapTupleHeaderSetCmin(tup->t_data, cid);
- HeapTupleHeaderSetXmax(tup->t_data, 0); /* for cleanliness */
+ HeapTupleSetXmax(tup, 0); /* for cleanliness */
tup->t_tableOid = RelationGetRelid(relation);
/*
@@ -2332,8 +2903,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
tuple = ExecFetchSlotHeapTuple(slots[i], true, NULL);
slots[i]->tts_tableOid = RelationGetRelid(relation);
tuple->t_tableOid = slots[i]->tts_tableOid;
- heaptuples[i] = heap_prepare_insert(relation, tuple, xid, cid,
- options);
+ heaptuples[i] = heap_prepare_insert(relation, tuple, cid, options);
}
/*
@@ -2389,6 +2959,8 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
if (starting_with_empty_page && (options & HEAP_INSERT_FROZEN))
all_frozen_set = true;
+ heap_page_prepare_for_xid(relation, buffer, xid, false);
+
/* NO EREPORT(ERROR) from here till changes are logged */
START_CRIT_SECTION();
@@ -2396,6 +2968,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
* RelationGetBufferForTuple has ensured that the first tuple fits.
* Put that on the page, and then as many other tuples as fit.
*/
+ HeapTupleSetXmin(heaptuples[ndone], xid);
RelationPutHeapTuple(relation, buffer, heaptuples[ndone], false);
/*
@@ -2412,6 +2985,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
if (PageGetHeapFreeSpace(page) < MAXALIGN(heaptup->t_len) + saveFreeSpace)
break;
+ HeapTupleSetXmin(heaptup, xid);
RelationPutHeapTuple(relation, buffer, heaptup, false);
/*
@@ -2547,6 +3121,10 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
bufflags |= REGBUF_KEEP_DATA;
XLogBeginInsert();
+ if (info & XLOG_HEAP_INIT_PAGE)
+ {
+ XLogRegisterData((char *) &HeapPageGetSpecial(page)->pd_xid_base, sizeof(TransactionId));
+ }
XLogRegisterData((char *) xlrec, tupledata - scratch.data);
XLogRegisterBuffer(0, buffer, REGBUF_STANDARD | bufflags);
@@ -2767,6 +3345,7 @@ heap_delete(Relation relation, ItemPointer tid,
tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
tp.t_len = ItemIdGetLength(lp);
tp.t_self = *tid;
+ HeapTupleCopyBaseFromPage(&tp, page);
l1:
result = HeapTupleSatisfiesUpdate(&tp, cid, buffer);
@@ -2784,7 +3363,7 @@ l1:
uint16 infomask;
/* must copy state data before unlocking buffer */
- xwait = HeapTupleHeaderGetRawXmax(tp.t_data);
+ xwait = HeapTupleGetRawXmax(&tp);
infomask = tp.t_data->t_infomask;
/*
@@ -2823,13 +3402,16 @@ l1:
NULL);
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+ /* Copy possibly updated xid base after relocking */
+ HeapTupleCopyBaseFromPage(&tp, page);
+
/*
* If xwait had just locked the tuple then some other xact
* could update this tuple before we get to this point. Check
* for xmax change, and start over if so.
*/
if (xmax_infomask_changed(tp.t_data->t_infomask, infomask) ||
- !TransactionIdEquals(HeapTupleHeaderGetRawXmax(tp.t_data),
+ !TransactionIdEquals(HeapTupleGetRawXmax(&tp),
xwait))
goto l1;
}
@@ -2856,13 +3438,16 @@ l1:
XactLockTableWait(xwait, relation, &(tp.t_self), XLTW_Delete);
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+ /* Copy possibly updated xid base after relocking */
+ HeapTupleCopyBaseFromPage(&tp, page);
+
/*
* xwait is done, but if xwait had just locked the tuple then some
* other xact could update this tuple before we get to this point.
* Check for xmax change, and start over if so.
*/
if (xmax_infomask_changed(tp.t_data->t_infomask, infomask) ||
- !TransactionIdEquals(HeapTupleHeaderGetRawXmax(tp.t_data),
+ !TransactionIdEquals(HeapTupleGetRawXmax(&tp),
xwait))
goto l1;
@@ -2876,7 +3461,7 @@ l1:
*/
if ((tp.t_data->t_infomask & HEAP_XMAX_INVALID) ||
HEAP_XMAX_IS_LOCKED_ONLY(tp.t_data->t_infomask) ||
- HeapTupleHeaderIsOnlyLocked(tp.t_data))
+ HeapTupleIsOnlyLocked(&tp))
result = TM_Ok;
else if (!ItemPointerEquals(&tp.t_self, &tp.t_data->t_ctid))
result = TM_Updated;
@@ -2901,9 +3486,9 @@ l1:
Assert(result != TM_Updated ||
!ItemPointerEquals(&tp.t_self, &tp.t_data->t_ctid));
tmfd->ctid = tp.t_data->t_ctid;
- tmfd->xmax = HeapTupleHeaderGetUpdateXid(tp.t_data);
+ tmfd->xmax = HeapTupleGetUpdateXidAny(&tp);
if (result == TM_SelfModified)
- tmfd->cmax = HeapTupleHeaderGetCmax(tp.t_data);
+ tmfd->cmax = HeapTupleGetCmax(&tp);
else
tmfd->cmax = InvalidCommandId;
UnlockReleaseBuffer(buffer);
@@ -2926,7 +3511,7 @@ l1:
CheckForSerializableConflictIn(relation, tid, BufferGetBlockNumber(buffer));
/* replace cid with a combo CID if necessary */
- HeapTupleHeaderAdjustCmax(tp.t_data, &cid, &iscombo);
+ HeapTupleHeaderAdjustCmax(&tp, &cid, &iscombo);
/*
* Compute replica identity tuple before entering the critical section so
@@ -2944,11 +3529,15 @@ l1:
*/
MultiXactIdSetOldestMember();
- compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(tp.t_data),
+ compute_new_xmax_infomask(HeapTupleGetRawXmax(&tp),
tp.t_data->t_infomask, tp.t_data->t_infomask2,
xid, LockTupleExclusive, true,
&new_xmax, &new_infomask, &new_infomask2);
+ heap_page_prepare_for_xid(relation, buffer, new_xmax,
+ (new_infomask & HEAP_XMAX_IS_MULTI) ? true : false);
+ HeapTupleCopyBaseFromPage(&tp, page);
+
START_CRIT_SECTION();
/*
@@ -2974,10 +3563,12 @@ l1:
tp.t_data->t_infomask |= new_infomask;
tp.t_data->t_infomask2 |= new_infomask2;
HeapTupleHeaderClearHotUpdated(tp.t_data);
- HeapTupleHeaderSetXmax(tp.t_data, new_xmax);
+ HeapTupleSetXmax(&tp, new_xmax);
+ HeapTupleHeaderSetXmax(page, &tp);
HeapTupleHeaderSetCmax(tp.t_data, cid, iscombo);
/* Make sure there is no forward chain link in t_ctid */
tp.t_data->t_ctid = tp.t_self;
+ HeapTupleCopyBaseFromPage(&tp, page);
/* Signal that this is actually a move into another partition */
if (changingPart)
@@ -3168,7 +3759,8 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
HeapTuple heaptup;
HeapTuple old_key_tuple = NULL;
bool old_key_copied = false;
- Page page;
+ Page page,
+ newpage;
BlockNumber block;
MultiXactStatus mxact_status;
Buffer buffer,
@@ -3261,6 +3853,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
oldtup.t_len = ItemIdGetLength(lp);
oldtup.t_self = *otid;
+ HeapTupleCopyBaseFromPage(&oldtup, page);
/* the new tuple is ready, except for this: */
newtup->t_tableOid = RelationGetRelid(relation);
@@ -3354,7 +3947,7 @@ l2:
*/
/* must copy state data before unlocking buffer */
- xwait = HeapTupleHeaderGetRawXmax(oldtup.t_data);
+ xwait = HeapTupleGetRawXmax(&oldtup);
infomask = oldtup.t_data->t_infomask;
/*
@@ -3405,6 +3998,7 @@ l2:
checked_lockers = true;
locker_remains = remain != 0;
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(&oldtup, page);
/*
* If xwait had just locked the tuple then some other xact
@@ -3413,7 +4007,7 @@ l2:
*/
if (xmax_infomask_changed(oldtup.t_data->t_infomask,
infomask) ||
- !TransactionIdEquals(HeapTupleHeaderGetRawXmax(oldtup.t_data),
+ !TransactionIdEquals(HeapTupleGetRawXmax(&oldtup),
xwait))
goto l2;
}
@@ -3439,7 +4033,7 @@ l2:
* subxact aborts.
*/
if (!HEAP_XMAX_IS_LOCKED_ONLY(oldtup.t_data->t_infomask))
- update_xact = HeapTupleGetUpdateXid(oldtup.t_data);
+ update_xact = HeapTupleGetUpdateXid(&oldtup);
else
update_xact = InvalidTransactionId;
@@ -3487,6 +4081,8 @@ l2:
checked_lockers = true;
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(&oldtup, page);
+
/*
* xwait is done, but if xwait had just locked the tuple then some
* other xact could update this tuple before we get to this point.
@@ -3494,7 +4090,7 @@ l2:
*/
if (xmax_infomask_changed(oldtup.t_data->t_infomask, infomask) ||
!TransactionIdEquals(xwait,
- HeapTupleHeaderGetRawXmax(oldtup.t_data)))
+ HeapTupleGetRawXmax(&oldtup)))
goto l2;
/* Otherwise check if it committed or aborted */
@@ -3531,9 +4127,9 @@ l2:
Assert(result != TM_Updated ||
!ItemPointerEquals(&oldtup.t_self, &oldtup.t_data->t_ctid));
tmfd->ctid = oldtup.t_data->t_ctid;
- tmfd->xmax = HeapTupleHeaderGetUpdateXid(oldtup.t_data);
+ tmfd->xmax = HeapTupleGetUpdateXidAny(&oldtup);
if (result == TM_SelfModified)
- tmfd->cmax = HeapTupleHeaderGetCmax(oldtup.t_data);
+ tmfd->cmax = HeapTupleGetCmax(&oldtup);
else
tmfd->cmax = InvalidCommandId;
UnlockReleaseBuffer(buffer);
@@ -3563,6 +4159,7 @@ l2:
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
visibilitymap_pin(relation, block, &vmbuffer);
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(&oldtup, page);
goto l2;
}
@@ -3572,7 +4169,7 @@ l2:
* If the tuple we're updating is locked, we need to preserve the locking
* info in the old tuple's Xmax. Prepare a new Xmax value for this.
*/
- compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup.t_data),
+ compute_new_xmax_infomask(HeapTupleGetRawXmax(&oldtup),
oldtup.t_data->t_infomask,
oldtup.t_data->t_infomask2,
xid, *lockmode, true,
@@ -3591,7 +4188,7 @@ l2:
(checked_lockers && !locker_remains))
xmax_new_tuple = InvalidTransactionId;
else
- xmax_new_tuple = HeapTupleHeaderGetRawXmax(oldtup.t_data);
+ xmax_new_tuple = HeapTupleGetRawXmax(&oldtup);
if (!TransactionIdIsValid(xmax_new_tuple))
{
@@ -3624,17 +4221,15 @@ l2:
*/
newtup->t_data->t_infomask &= ~(HEAP_XACT_MASK);
newtup->t_data->t_infomask2 &= ~(HEAP2_XACT_MASK);
- HeapTupleHeaderSetXmin(newtup->t_data, xid);
HeapTupleHeaderSetCmin(newtup->t_data, cid);
newtup->t_data->t_infomask |= HEAP_UPDATED | infomask_new_tuple;
newtup->t_data->t_infomask2 |= infomask2_new_tuple;
- HeapTupleHeaderSetXmax(newtup->t_data, xmax_new_tuple);
/*
* Replace cid with a combo CID if necessary. Note that we already put
* the plain cid into the new tuple.
*/
- HeapTupleHeaderAdjustCmax(oldtup.t_data, &cid, &iscombo);
+ HeapTupleHeaderAdjustCmax(&oldtup, &cid, &iscombo);
/*
* If the toaster needs to be activated, OR if the new tuple will not fit
@@ -3664,7 +4259,7 @@ l2:
newtupsize = MAXALIGN(newtup->t_len);
- if (need_toast || newtupsize > pagefree)
+ if (need_toast || newtupsize > pagefree || HeapPageIsDoubleXmax(page))
{
TransactionId xmax_lock_old_tuple;
uint16 infomask_lock_old_tuple,
@@ -3689,7 +4284,7 @@ l2:
* updating, because the potentially created multixact would otherwise
* be wrong.
*/
- compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup.t_data),
+ compute_new_xmax_infomask(HeapTupleGetRawXmax(&oldtup),
oldtup.t_data->t_infomask,
oldtup.t_data->t_infomask2,
xid, *lockmode, false,
@@ -3698,6 +4293,10 @@ l2:
Assert(HEAP_XMAX_IS_LOCKED_ONLY(infomask_lock_old_tuple));
+ heap_page_prepare_for_xid(relation, buffer, xmax_lock_old_tuple,
+ (infomask_lock_old_tuple & HEAP_XMAX_IS_MULTI) ? true : false);
+ HeapTupleCopyBaseFromPage(&oldtup, page);
+
START_CRIT_SECTION();
/* Clear obsolete visibility flags ... */
@@ -3706,10 +4305,12 @@ l2:
HeapTupleClearHotUpdated(&oldtup);
/* ... and store info about transaction updating this tuple */
Assert(TransactionIdIsValid(xmax_lock_old_tuple));
- HeapTupleHeaderSetXmax(oldtup.t_data, xmax_lock_old_tuple);
oldtup.t_data->t_infomask |= infomask_lock_old_tuple;
oldtup.t_data->t_infomask2 |= infomask2_lock_old_tuple;
+ HeapTupleSetXmax(&oldtup, xmax_lock_old_tuple);
+ HeapTupleHeaderSetXmax(page, &oldtup);
HeapTupleHeaderSetCmax(oldtup.t_data, cid, iscombo);
+ HeapTupleCopyBaseFromPage(&oldtup, page);
/* temporarily make it look not-updated, but locked */
oldtup.t_data->t_ctid = oldtup.t_self;
@@ -3791,7 +4392,11 @@ l2:
*/
for (;;)
{
- if (newtupsize > pagefree)
+ /*
+ * We can't fit new tuple to "double xmax" page, since it's impossible to
+ * set xmin there.
+ */
+ if (newtupsize > pagefree || HeapPageIsDoubleXmax(page))
{
/* It doesn't fit, must use RelationGetBufferForTuple. */
newbuf = RelationGetBufferForTuple(relation, heaptup->t_len,
@@ -3824,6 +4429,9 @@ l2:
break;
}
}
+
+ /* Copy possibly updated xid base to old tuple after relocking */
+ HeapTupleCopyBaseFromPage(&oldtup, page);
}
else
{
@@ -3883,6 +4491,34 @@ l2:
id_has_external,
&old_key_copied);
+ newpage = BufferGetPage(newbuf);
+
+ /*
+ * Prepare pages for the current xid, that witten to the new tuple's
+ * Xmax and old page's pd_prune_xid.
+ */
+ heap_page_prepare_for_xid(relation, buffer, xid, false);
+ if (newbuf != buffer)
+ heap_page_prepare_for_xid(relation, newbuf, xid, false);
+
+ /* Prepare pages for tuple's Xmax */
+ heap_page_prepare_for_xid(relation, buffer, xmax_old_tuple,
+ (infomask_old_tuple & HEAP_XMAX_IS_MULTI) ? true : false);
+ heap_page_prepare_for_xid(relation, newbuf, xmax_new_tuple,
+ (heaptup->t_data->t_infomask & HEAP_XMAX_IS_MULTI) ? true : false);
+
+ /* Copy possibly updated Xid bases to the both tuples. */
+ HeapTupleCopyBaseFromPage(&oldtup, page);
+
+ /*
+ * Set new tuple's Xmin/Xmax, old tuple's Xmin/Xmax were already
+ * shifted.
+ */
+ HeapTupleSetXmin(heaptup, xid);
+ HeapTupleHeaderSetXmin(page, heaptup);
+ HeapTupleSetXmax(heaptup, xmax_new_tuple);
+ HeapTupleHeaderSetXmax(page, heaptup);
+
/* NO EREPORT(ERROR) from here till changes are logged */
START_CRIT_SECTION();
@@ -3925,10 +4561,12 @@ l2:
oldtup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
/* ... and store info about transaction updating this tuple */
Assert(TransactionIdIsValid(xmax_old_tuple));
- HeapTupleHeaderSetXmax(oldtup.t_data, xmax_old_tuple);
oldtup.t_data->t_infomask |= infomask_old_tuple;
oldtup.t_data->t_infomask2 |= infomask2_old_tuple;
+ HeapTupleSetXmax(&oldtup, xmax_old_tuple);
+ HeapTupleHeaderSetXmax(page, &oldtup);
HeapTupleHeaderSetCmax(oldtup.t_data, cid, iscombo);
+ HeapTupleCopyBaseFromPage(&oldtup, page);
/* record address of new tuple in t_ctid of old one */
oldtup.t_data->t_ctid = heaptup->t_self;
@@ -3982,6 +4620,20 @@ l2:
END_CRIT_SECTION();
+ if (newtup != heaptup)
+ {
+ /*
+ * Set new tuple's Xmin/Xmax only after both xid base preparations.
+ * Old tuple's Xmin/Xmax were already shifted because old tuple is
+ * on the page.
+ */
+ HeapTupleCopyBase(newtup, heaptup);
+ HeapTupleSetXmin(newtup, xid);
+ HeapTupleHeaderSetXmin(newpage, newtup);
+ HeapTupleSetXmax(newtup, xmax_new_tuple);
+ HeapTupleHeaderSetXmax(newpage, newtup);
+ }
+
if (newbuf != buffer)
LockBuffer(newbuf, BUFFER_LOCK_UNLOCK);
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
@@ -4320,6 +4972,7 @@ heap_lock_tuple(Relation relation, HeapTuple tuple,
tuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
tuple->t_len = ItemIdGetLength(lp);
tuple->t_tableOid = RelationGetRelid(relation);
+ HeapTupleCopyBaseFromPage(tuple, page);
l3:
result = HeapTupleSatisfiesUpdate(tuple, cid, *buffer);
@@ -4346,7 +4999,7 @@ l3:
ItemPointerData t_ctid;
/* must copy state data before unlocking buffer */
- xwait = HeapTupleHeaderGetRawXmax(tuple->t_data);
+ xwait = HeapTupleGetRawXmax(tuple);
infomask = tuple->t_data->t_infomask;
infomask2 = tuple->t_data->t_infomask2;
ItemPointerCopy(&tuple->t_data->t_ctid, &t_ctid);
@@ -4504,11 +5157,13 @@ l3:
result = res;
/* recovery code expects to have buffer lock held */
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(tuple, page);
goto failed;
}
}
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(tuple, page);
/*
* Make sure it's still an appropriate lock, else start over.
@@ -4517,7 +5172,7 @@ l3:
* now need to follow the update chain to lock the new
* versions.
*/
- if (!HeapTupleHeaderIsOnlyLocked(tuple->t_data) &&
+ if (!HeapTupleIsOnlyLocked(tuple) &&
((tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED) ||
!updated))
goto l3;
@@ -4544,6 +5199,7 @@ l3:
!HEAP_XMAX_IS_EXCL_LOCKED(infomask))
{
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(tuple, page);
/*
* Make sure it's still an appropriate lock, else start over.
@@ -4572,8 +5228,10 @@ l3:
* meantime, start over.
*/
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(tuple, page);
+
if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
- !TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple->t_data),
+ !TransactionIdEquals(HeapTupleGetRawXmax(tuple),
xwait))
goto l3;
@@ -4584,10 +5242,11 @@ l3:
else if (HEAP_XMAX_IS_KEYSHR_LOCKED(infomask))
{
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(tuple, page);
/* if the xmax changed in the meantime, start over */
if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
- !TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple->t_data),
+ !TransactionIdEquals(HeapTupleGetRawXmax(tuple),
xwait))
goto l3;
/* otherwise, we're good */
@@ -4612,8 +5271,10 @@ l3:
{
/* ... but if the xmax changed in the meantime, start over */
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(tuple, page);
+
if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
- !TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple->t_data),
+ !TransactionIdEquals(HeapTupleGetRawXmax(tuple),
xwait))
goto l3;
Assert(HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_data->t_infomask));
@@ -4634,6 +5295,7 @@ l3:
if (require_sleep && (result == TM_Updated || result == TM_Deleted))
{
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(tuple, page);
goto failed;
}
else if (require_sleep)
@@ -4659,6 +5321,7 @@ l3:
result = TM_WouldBlock;
/* recovery code expects to have buffer lock held */
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(tuple, page);
goto failed;
}
@@ -4685,6 +5348,7 @@ l3:
result = TM_WouldBlock;
/* recovery code expects to have buffer lock held */
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(tuple, page);
goto failed;
}
break;
@@ -4725,6 +5389,7 @@ l3:
result = TM_WouldBlock;
/* recovery code expects to have buffer lock held */
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(tuple, page);
goto failed;
}
break;
@@ -4751,11 +5416,13 @@ l3:
result = res;
/* recovery code expects to have buffer lock held */
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(tuple, page);
goto failed;
}
}
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(tuple, page);
/*
* xwait is done, but if xwait had just locked the tuple then some
@@ -4763,7 +5430,7 @@ l3:
* Check for xmax change, and start over if so.
*/
if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
- !TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple->t_data),
+ !TransactionIdEquals(HeapTupleGetRawXmax(tuple),
xwait))
goto l3;
@@ -4791,7 +5458,7 @@ l3:
if (!require_sleep ||
(tuple->t_data->t_infomask & HEAP_XMAX_INVALID) ||
HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_data->t_infomask) ||
- HeapTupleHeaderIsOnlyLocked(tuple->t_data))
+ HeapTupleIsOnlyLocked(tuple))
result = TM_Ok;
else if (!ItemPointerEquals(&tuple->t_self, &tuple->t_data->t_ctid))
result = TM_Updated;
@@ -4817,9 +5484,9 @@ failed:
Assert(result != TM_Updated ||
!ItemPointerEquals(&tuple->t_self, &tuple->t_data->t_ctid));
tmfd->ctid = tuple->t_data->t_ctid;
- tmfd->xmax = HeapTupleHeaderGetUpdateXid(tuple->t_data);
+ tmfd->xmax = HeapTupleGetUpdateXidAny(tuple);
if (result == TM_SelfModified)
- tmfd->cmax = HeapTupleHeaderGetCmax(tuple->t_data);
+ tmfd->cmax = HeapTupleGetCmax(tuple);
else
tmfd->cmax = InvalidCommandId;
goto out_locked;
@@ -4839,10 +5506,11 @@ failed:
LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
visibilitymap_pin(relation, block, &vmbuffer);
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+ HeapTupleCopyBaseFromPage(tuple, page);
goto l3;
}
- xmax = HeapTupleHeaderGetRawXmax(tuple->t_data);
+ xmax = HeapTupleGetRawXmax(tuple);
old_infomask = tuple->t_data->t_infomask;
/*
@@ -4864,6 +5532,10 @@ failed:
GetCurrentTransactionId(), mode, false,
&xid, &new_infomask, &new_infomask2);
+ heap_page_prepare_for_xid(relation, *buffer, xid,
+ (new_infomask & HEAP_XMAX_IS_MULTI) ? true : false);
+ HeapTupleCopyBaseFromPage(tuple, page);
+
START_CRIT_SECTION();
/*
@@ -4882,7 +5554,8 @@ failed:
tuple->t_data->t_infomask2 |= new_infomask2;
if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
HeapTupleHeaderClearHotUpdated(tuple->t_data);
- HeapTupleHeaderSetXmax(tuple->t_data, xid);
+ HeapTupleSetXmax(tuple, xid);
+ HeapTupleHeaderSetXmax(page, tuple);
/*
* Make sure there is no forward chain link in t_ctid. Note that in the
@@ -5198,7 +5871,7 @@ l5:
* TransactionIdIsInProgress() should have returned false. We
* assume it's no longer locked in this case.
*/
- elog(WARNING, "LOCK_ONLY found for Xid in progress %u", xmax);
+ elog(WARNING, "LOCK_ONLY found for Xid in progress " XID_FMT, xmax);
old_infomask |= HEAP_XMAX_INVALID;
old_infomask &= ~HEAP_XMAX_LOCK_ONLY;
goto l5;
@@ -5475,12 +6148,18 @@ l4:
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
}
+ /*
+ * Copy xid base after buffer relocking, it could have changed
+ * since heap_fetch().
+ */
+ HeapTupleCopyBaseFromPage(&mytup, BufferGetPage(buf));
+
/*
* Check the tuple XMIN against prior XMAX, if any. If we reached the
* end of the chain, we're done, so return success.
*/
if (TransactionIdIsValid(priorXmax) &&
- !TransactionIdEquals(HeapTupleHeaderGetXmin(mytup.t_data),
+ !TransactionIdEquals(HeapTupleGetXmin(&mytup),
priorXmax))
{
result = TM_Ok;
@@ -5492,7 +6171,7 @@ l4:
* (sub)transaction, then we already locked the last live one in the
* chain, thus we're done, so return success.
*/
- if (TransactionIdDidAbort(HeapTupleHeaderGetXmin(mytup.t_data)))
+ if (TransactionIdDidAbort(HeapTupleGetXmin(&mytup)))
{
result = TM_Ok;
goto out_locked;
@@ -5500,7 +6179,7 @@ l4:
old_infomask = mytup.t_data->t_infomask;
old_infomask2 = mytup.t_data->t_infomask2;
- xmax = HeapTupleHeaderGetRawXmax(mytup.t_data);
+ xmax = HeapTupleGetRawXmax(&mytup);
/*
* If this tuple version has been updated or locked by some concurrent
@@ -5513,7 +6192,7 @@ l4:
TransactionId rawxmax;
bool needwait;
- rawxmax = HeapTupleHeaderGetRawXmax(mytup.t_data);
+ rawxmax = HeapTupleGetRawXmax(&mytup);
if (old_infomask & HEAP_XMAX_IS_MULTI)
{
int nmembers;
@@ -5654,14 +6333,19 @@ l4:
VISIBILITYMAP_ALL_FROZEN))
cleared_all_frozen = true;
+ heap_page_prepare_for_xid(rel, buf, new_xmax,
+ (new_infomask & HEAP_XMAX_IS_MULTI) ? true : false);
+ HeapTupleCopyBaseFromPage(&mytup, BufferGetPage(buf));
+
START_CRIT_SECTION();
/* ... and set them */
- HeapTupleHeaderSetXmax(mytup.t_data, new_xmax);
mytup.t_data->t_infomask &= ~HEAP_XMAX_BITS;
mytup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
mytup.t_data->t_infomask |= new_infomask;
mytup.t_data->t_infomask2 |= new_infomask2;
+ HeapTupleSetXmax(&mytup, new_xmax);
+ HeapTupleHeaderSetXmax(BufferGetPage(buf), &mytup);
MarkBufferDirty(buf);
@@ -5695,14 +6379,14 @@ next:
if (mytup.t_data->t_infomask & HEAP_XMAX_INVALID ||
HeapTupleHeaderIndicatesMovedPartitions(mytup.t_data) ||
ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_ctid) ||
- HeapTupleHeaderIsOnlyLocked(mytup.t_data))
+ HeapTupleIsOnlyLocked(&mytup))
{
result = TM_Ok;
goto out_locked;
}
/* tail recursion */
- priorXmax = HeapTupleHeaderGetUpdateXid(mytup.t_data);
+ priorXmax = HeapTupleGetUpdateXidAny(&mytup);
ItemPointerCopy(&(mytup.t_data->t_ctid), &tupid);
UnlockReleaseBuffer(buf);
}
@@ -5909,12 +6593,13 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
tp.t_len = ItemIdGetLength(lp);
tp.t_self = *tid;
+ HeapTupleCopyBaseFromPage(&tp, page);
/*
* Sanity check that the tuple really is a speculatively inserted tuple,
* inserted by us.
*/
- if (tp.t_data->t_choice.t_heap.t_xmin != xid)
+ if (HeapTupleGetRawXmin(&tp) != xid)
elog(ERROR, "attempted to kill a tuple inserted by another transaction");
if (!(IsToastRelation(relation) || HeapTupleHeaderIsSpeculative(tp.t_data)))
elog(ERROR, "attempted to kill a non-speculative tuple");
@@ -5943,6 +6628,8 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
prune_xid = relation->rd_rel->relfrozenxid;
else
prune_xid = TransactionXmin;
+ Assert(TransactionIdIsValid(prune_xid));
+ heap_page_prepare_for_xid(relation, buffer, prune_xid, false);
PageSetPrunable(page, prune_xid);
/* store transaction information of xact deleting the tuple */
@@ -5953,8 +6640,11 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
* Set the tuple header xmin to InvalidTransactionId. This makes the
* tuple immediately invisible everyone. (In particular, to any
* transactions waiting on the speculative token, woken up later.)
+ * Don't need to reload xid base from page because InvalidTransactionId
+ * doesn't require xid base to be valid.
*/
- HeapTupleHeaderSetXmin(tp.t_data, InvalidTransactionId);
+ HeapTupleSetXmin(&tp, InvalidTransactionId);
+ HeapTupleHeaderSetXmin(page, &tp);
/* Clear the speculative insertion token too */
tp.t_data->t_ctid = tp.t_self;
@@ -6173,7 +6863,7 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
else if (MultiXactIdPrecedes(multi, relminmxid))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
- errmsg_internal("found multixact %u from before relminmxid %u",
+ errmsg_internal("found multixact " XID_FMT " from before relminmxid " XID_FMT,
multi, relminmxid)));
else if (MultiXactIdPrecedes(multi, cutoff_multi))
{
@@ -6187,7 +6877,7 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
HEAP_XMAX_IS_LOCKED_ONLY(t_infomask)))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
- errmsg_internal("multixact %u from before cutoff %u found to be still running",
+ errmsg_internal("multixact " XID_FMT " from before cutoff " XID_FMT " found to be still running",
multi, cutoff_multi)));
if (HEAP_XMAX_IS_LOCKED_ONLY(t_infomask))
@@ -6206,7 +6896,7 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
if (TransactionIdPrecedes(xid, relfrozenxid))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
- errmsg_internal("found update xid %u from before relfrozenxid %u",
+ errmsg_internal("found update xid " XID_FMT " from before relfrozenxid " XID_FMT,
xid, relfrozenxid)));
/*
@@ -6218,7 +6908,7 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
if (TransactionIdDidCommit(xid))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
- errmsg_internal("cannot freeze committed update xid %u", xid)));
+ errmsg_internal("cannot freeze committed update xid " XID_FMT, xid)));
*flags |= FRM_INVALIDATE_XMAX;
xid = InvalidTransactionId; /* not strictly necessary */
}
@@ -6276,7 +6966,7 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
* to keep.
*/
nnewmembers = 0;
- newmembers = palloc(sizeof(MultiXactMember) * nmembers);
+ newmembers = palloc0(sizeof(MultiXactMember) * nmembers);
has_lockers = false;
update_xid = InvalidTransactionId;
update_committed = false;
@@ -6294,7 +6984,7 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
if (TransactionIdPrecedes(xid, relfrozenxid))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
- errmsg_internal("found update xid %u from before relfrozenxid %u",
+ errmsg_internal("found update xid " XID_FMT " from before relfrozenxid " XID_FMT,
xid, relfrozenxid)));
/*
@@ -6344,7 +7034,7 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
TransactionIdPrecedes(update_xid, cutoff_xid))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
- errmsg_internal("found update xid %u from before xid cutoff %u",
+ errmsg_internal("found update xid " XID_FMT " from before xid cutoff " XID_FMT,
update_xid, cutoff_xid)));
/*
@@ -6442,7 +7132,7 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
* recovery. We really need to remove old xids.
*/
bool
-heap_prepare_freeze_tuple(HeapTupleHeader tuple,
+heap_prepare_freeze_tuple(HeapTuple htup,
TransactionId relfrozenxid, TransactionId relminmxid,
TransactionId cutoff_xid, TransactionId cutoff_multi,
xl_heap_freeze_tuple *frz, bool *totally_frozen_p)
@@ -6452,11 +7142,12 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple,
bool xmin_frozen;
bool freeze_xmax;
TransactionId xid;
+ HeapTupleHeader tuple = htup->t_data;
frz->frzflags = 0;
frz->t_infomask2 = tuple->t_infomask2;
frz->t_infomask = tuple->t_infomask;
- frz->xmax = HeapTupleHeaderGetRawXmax(tuple);
+ frz->xmax = HeapTupleGetRawXmax(htup);
/*
* Process xmin. xmin_frozen has two slightly different meanings: in the
@@ -6466,7 +7157,7 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple,
* otherwise). In both cases we're allowed to set totally_frozen, as far
* as xmin is concerned.
*/
- xid = HeapTupleHeaderGetXmin(tuple);
+ xid = HeapTupleGetXmin(htup);
if (!TransactionIdIsNormal(xid))
xmin_frozen = true;
else
@@ -6474,7 +7165,7 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple,
if (TransactionIdPrecedes(xid, relfrozenxid))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
- errmsg_internal("found xmin %u from before relfrozenxid %u",
+ errmsg_internal("found xmin " XID_FMT " from before relfrozenxid " XID_FMT,
xid, relfrozenxid)));
xmin_frozen = TransactionIdPrecedes(xid, cutoff_xid);
@@ -6483,7 +7174,7 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple,
if (!TransactionIdDidCommit(xid))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
- errmsg_internal("uncommitted xmin %u from before xid cutoff %u needs to be frozen",
+ errmsg_internal("uncommitted xmin " XID_FMT " from before xid cutoff " XID_FMT " needs to be frozen",
xid, cutoff_xid)));
frz->t_infomask |= HEAP_XMIN_FROZEN;
@@ -6500,7 +7191,7 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple,
*
* Make sure to keep heap_tuple_needs_freeze in sync with this.
*/
- xid = HeapTupleHeaderGetRawXmax(tuple);
+ xid = HeapTupleGetRawXmax(htup);
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
@@ -6555,7 +7246,7 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple,
if (TransactionIdPrecedes(xid, relfrozenxid))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
- errmsg_internal("found xmax %u from before relfrozenxid %u",
+ errmsg_internal("found xmax " XID_FMT " from before relfrozenxid " XID_FMT,
xid, relfrozenxid)));
if (TransactionIdPrecedes(xid, cutoff_xid))
@@ -6570,7 +7261,7 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple,
TransactionIdDidCommit(xid))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
- errmsg_internal("cannot freeze committed xmax %u",
+ errmsg_internal("cannot freeze committed xmax " XID_FMT,
xid)));
freeze_xmax = true;
}
@@ -6578,7 +7269,7 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple,
freeze_xmax = false;
}
else if ((tuple->t_infomask & HEAP_XMAX_INVALID) ||
- !TransactionIdIsValid(HeapTupleHeaderGetRawXmax(tuple)))
+ !TransactionIdIsValid(HeapTupleGetRawXmax(htup)))
{
freeze_xmax = false;
xmax_already_frozen = true;
@@ -6586,7 +7277,7 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple,
else
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
- errmsg_internal("found xmax %u (infomask 0x%04x) not frozen, not multi, not normal",
+ errmsg_internal("found xmax " XID_FMT " (infomask 0x%04x) not frozen, not multi, not normal",
xid, tuple->t_infomask)));
if (freeze_xmax)
@@ -6671,18 +7362,30 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple,
* NB: All code in here must be safe to execute during crash recovery!
*/
void
-heap_execute_freeze_tuple(HeapTupleHeader tuple, xl_heap_freeze_tuple *frz)
+heap_execute_freeze_tuple(HeapTuple htup, xl_heap_freeze_tuple *frz)
{
- HeapTupleHeaderSetXmax(tuple, frz->xmax);
+ HeapTupleHeader tuple = htup->t_data;
+
+ tuple->t_infomask = frz->t_infomask;
+ tuple->t_infomask2 = frz->t_infomask2;
+
+ HeapTupleSetXmax(htup, frz->xmax);
if (frz->frzflags & XLH_FREEZE_XVAC)
HeapTupleHeaderSetXvac(tuple, FrozenTransactionId);
if (frz->frzflags & XLH_INVALID_XVAC)
HeapTupleHeaderSetXvac(tuple, InvalidTransactionId);
+}
- tuple->t_infomask = frz->t_infomask;
- tuple->t_infomask2 = frz->t_infomask2;
+void
+heap_execute_freeze_tuple_page(Page page, HeapTupleHeader htup, xl_heap_freeze_tuple *frz)
+{
+ HeapTupleData tuple;
+
+ tuple.t_data = htup;
+ heap_execute_freeze_tuple(&tuple, frz);
+ HeapTupleHeaderSetXmax(page, &tuple);
}
/*
@@ -6692,7 +7395,7 @@ heap_execute_freeze_tuple(HeapTupleHeader tuple, xl_heap_freeze_tuple *frz)
* Useful for callers like CLUSTER that perform their own WAL logging.
*/
bool
-heap_freeze_tuple(HeapTupleHeader tuple,
+heap_freeze_tuple(HeapTuple tuple,
TransactionId relfrozenxid, TransactionId relminmxid,
TransactionId cutoff_xid, TransactionId cutoff_multi)
{
@@ -6856,10 +7559,10 @@ MultiXactIdGetUpdateXid(TransactionId xmax, uint16 t_infomask)
* checking the hint bits.
*/
TransactionId
-HeapTupleGetUpdateXid(HeapTupleHeader tuple)
+HeapTupleGetUpdateXid(HeapTuple tuple)
{
- return MultiXactIdGetUpdateXid(HeapTupleHeaderGetRawXmax(tuple),
- tuple->t_infomask);
+ return MultiXactIdGetUpdateXid(HeapTupleGetRawXmax(tuple),
+ tuple->t_data->t_infomask);
}
/*
@@ -7087,15 +7790,18 @@ ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status,
* will ever be needed, not whether it's needed now.
*/
bool
-heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple)
+heap_tuple_needs_eventual_freeze(HeapTuple htup)
{
TransactionId xid;
+ HeapTupleHeader tuple;
+
+ tuple = htup->t_data;
/*
* If xmin is a normal transaction ID, this tuple is definitely not
* frozen.
*/
- xid = HeapTupleHeaderGetXmin(tuple);
+ xid = HeapTupleGetXmin(htup);
if (TransactionIdIsNormal(xid))
return true;
@@ -7106,13 +7812,13 @@ heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple)
{
MultiXactId multi;
- multi = HeapTupleHeaderGetRawXmax(tuple);
+ multi = HeapTupleGetRawXmax(htup);
if (MultiXactIdIsValid(multi))
return true;
}
else
{
- xid = HeapTupleHeaderGetRawXmax(tuple);
+ xid = HeapTupleGetRawXmax(htup);
if (TransactionIdIsNormal(xid))
return true;
}
@@ -7140,12 +7846,13 @@ heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple)
* on a standby.
*/
bool
-heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
+heap_tuple_needs_freeze(HeapTuple htup, TransactionId cutoff_xid,
MultiXactId cutoff_multi)
{
TransactionId xid;
+ HeapTupleHeader tuple = htup->t_data;
- xid = HeapTupleHeaderGetXmin(tuple);
+ xid = HeapTupleGetXmin(htup);
if (TransactionIdIsNormal(xid) &&
TransactionIdPrecedes(xid, cutoff_xid))
return true;
@@ -7159,7 +7866,7 @@ heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
{
MultiXactId multi;
- multi = HeapTupleHeaderGetRawXmax(tuple);
+ multi = HeapTupleGetRawXmax(htup);
if (!MultiXactIdIsValid(multi))
{
/* no xmax set, ignore */
@@ -7194,7 +7901,7 @@ heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
}
else
{
- xid = HeapTupleHeaderGetRawXmax(tuple);
+ xid = HeapTupleGetRawXmax(htup);
if (TransactionIdIsNormal(xid) &&
TransactionIdPrecedes(xid, cutoff_xid))
return true;
@@ -7219,14 +7926,14 @@ heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
* with queries.
*/
void
-HeapTupleHeaderAdvanceLatestRemovedXid(HeapTupleHeader tuple,
+HeapTupleHeaderAdvanceLatestRemovedXid(HeapTuple tuple,
TransactionId *latestRemovedXid)
{
- TransactionId xmin = HeapTupleHeaderGetXmin(tuple);
- TransactionId xmax = HeapTupleHeaderGetUpdateXid(tuple);
- TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+ TransactionId xmin = HeapTupleGetXmin(tuple);
+ TransactionId xmax = HeapTupleGetUpdateXidAny(tuple);
+ TransactionId xvac = HeapTupleHeaderGetXvac(tuple->t_data);
- if (tuple->t_infomask & HEAP_MOVED)
+ if (tuple->t_data->t_infomask & HEAP_MOVED)
{
if (TransactionIdPrecedes(*latestRemovedXid, xvac))
*latestRemovedXid = xvac;
@@ -7238,8 +7945,8 @@ HeapTupleHeaderAdvanceLatestRemovedXid(HeapTupleHeader tuple,
*
* Look for a committed hint bit, or if no xmin bit is set, check clog.
*/
- if (HeapTupleHeaderXminCommitted(tuple) ||
- (!HeapTupleHeaderXminInvalid(tuple) && TransactionIdDidCommit(xmin)))
+ if (HeapTupleHeaderXminCommitted(tuple->t_data) ||
+ (!HeapTupleHeaderXminInvalid(tuple->t_data) && TransactionIdDidCommit(xmin)))
{
if (xmax != xmin &&
TransactionIdFollows(xmax, *latestRemovedXid))
@@ -7589,7 +8296,7 @@ heap_index_delete_tuples(Relation rel, TM_IndexDeleteOp *delstate)
for (;;)
{
ItemId lp;
- HeapTupleHeader htup;
+ HeapTupleData htup;
/* Sanity check (pure paranoia) */
if (offnum < FirstOffsetNumber)
@@ -7626,16 +8333,18 @@ heap_index_delete_tuples(Relation rel, TM_IndexDeleteOp *delstate)
if (!ItemIdIsNormal(lp))
break;
- htup = (HeapTupleHeader) PageGetItem(page, lp);
+ htup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+ htup.t_len = ItemIdGetLength(lp);
+ HeapTupleCopyBaseFromPage(&htup, page);
/*
* Check the tuple XMIN against prior XMAX, if any
*/
if (TransactionIdIsValid(priorXmax) &&
- !TransactionIdEquals(HeapTupleHeaderGetXmin(htup), priorXmax))
+ !TransactionIdEquals(HeapTupleGetXmin(&htup), priorXmax))
break;
- HeapTupleHeaderAdvanceLatestRemovedXid(htup, &latestRemovedXid);
+ HeapTupleHeaderAdvanceLatestRemovedXid(&htup, &latestRemovedXid);
/*
* If the tuple is not HOT-updated, then we are at the end of this
@@ -7643,13 +8352,13 @@ heap_index_delete_tuples(Relation rel, TM_IndexDeleteOp *delstate)
* chain (they get their own index entries) -- just move on to
* next htid from index AM caller.
*/
- if (!HeapTupleHeaderIsHotUpdated(htup))
+ if (!HeapTupleHeaderIsHotUpdated(htup.t_data))
break;
/* Advance to next HOT chain member */
- Assert(ItemPointerGetBlockNumber(&htup->t_ctid) == blkno);
- offnum = ItemPointerGetOffsetNumber(&htup->t_ctid);
- priorXmax = HeapTupleHeaderGetUpdateXid(htup);
+ Assert(ItemPointerGetBlockNumber(&htup.t_data->t_ctid) == blkno);
+ offnum = ItemPointerGetOffsetNumber(&htup.t_data->t_ctid);
+ priorXmax = HeapTupleGetUpdateXidAny(&htup);
}
/* Enable further/final shrinking of deltids for caller */
@@ -8229,13 +8938,13 @@ log_heap_update(Relation reln, Buffer oldbuf,
/* Prepare WAL data for the old page */
xlrec.old_offnum = ItemPointerGetOffsetNumber(&oldtup->t_self);
- xlrec.old_xmax = HeapTupleHeaderGetRawXmax(oldtup->t_data);
+ xlrec.old_xmax = HeapTupleGetRawXmax(oldtup);
xlrec.old_infobits_set = compute_infobits(oldtup->t_data->t_infomask,
oldtup->t_data->t_infomask2);
/* Prepare WAL data for the new page */
xlrec.new_offnum = ItemPointerGetOffsetNumber(&newtup->t_self);
- xlrec.new_xmax = HeapTupleHeaderGetRawXmax(newtup->t_data);
+ xlrec.new_xmax = HeapTupleGetRawXmax(newtup);
bufflags = REGBUF_STANDARD;
if (init)
@@ -8247,6 +8956,10 @@ log_heap_update(Relation reln, Buffer oldbuf,
if (oldbuf != newbuf)
XLogRegisterBuffer(1, oldbuf, REGBUF_STANDARD);
+ if (info & XLOG_HEAP_INIT_PAGE)
+ {
+ XLogRegisterData((char *) &HeapPageGetSpecial(page)->pd_xid_base, sizeof(TransactionId));
+ }
XLogRegisterData((char *) &xlrec, SizeOfHeapUpdate);
/*
@@ -8359,8 +9072,8 @@ log_heap_new_cid(Relation relation, HeapTuple tup)
{
Assert(!(hdr->t_infomask & HEAP_XMAX_INVALID));
Assert(!HeapTupleHeaderXminInvalid(hdr));
- xlrec.cmin = HeapTupleHeaderGetCmin(hdr);
- xlrec.cmax = HeapTupleHeaderGetCmax(hdr);
+ xlrec.cmin = HeapTupleGetCmin(tup);
+ xlrec.cmax = HeapTupleGetCmax(tup);
xlrec.combocid = HeapTupleHeaderGetRawCommandId(hdr);
}
/* No combo CID, so only cmin or cmax can be set by this TX */
@@ -8563,7 +9276,8 @@ heap_xlog_prune(XLogReaderState *record)
heap_page_prune_execute(buffer,
redirected, nredirected,
nowdead, ndead,
- nowunused, nunused);
+ nowunused, nunused,
+ true);
/*
* Note: we don't worry about updating the page's prunability hints.
@@ -8855,7 +9569,7 @@ heap_xlog_freeze_page(XLogReaderState *record)
lp = PageGetItemId(page, xlrec_tp->offset); /* offsets are one-based */
tuple = (HeapTupleHeader) PageGetItem(page, lp);
- heap_execute_freeze_tuple(tuple, xlrec_tp);
+ heap_execute_freeze_tuple_page(page, tuple, xlrec_tp);
}
PageSetLSN(page, lsn);
@@ -8926,6 +9640,8 @@ heap_xlog_delete(XLogReaderState *record)
if (XLogReadBufferForRedo(record, 0, &buffer) == BLK_NEEDS_REDO)
{
+ HeapTupleData tuple;
+
page = BufferGetPage(buffer);
if (PageGetMaxOffsetNumber(page) >= xlrec->offnum)
@@ -8941,10 +9657,18 @@ heap_xlog_delete(XLogReaderState *record)
HeapTupleHeaderClearHotUpdated(htup);
fix_infomask_from_infobits(xlrec->infobits_set,
&htup->t_infomask, &htup->t_infomask2);
+ tuple.t_data = htup;
+
if (!(xlrec->flags & XLH_DELETE_IS_SUPER))
- HeapTupleHeaderSetXmax(htup, xlrec->xmax);
+ {
+ HeapTupleSetXmax(&tuple, xlrec->xmax);
+ HeapTupleHeaderSetXmax(page, &tuple);
+ }
else
- HeapTupleHeaderSetXmin(htup, InvalidTransactionId);
+ {
+ HeapTupleSetXmin(&tuple, InvalidTransactionId);
+ HeapTupleHeaderSetXmin(page, &tuple);
+ }
HeapTupleHeaderSetCmax(htup, FirstCommandId, false);
/* Mark the page as a candidate for pruning */
@@ -8969,7 +9693,7 @@ static void
heap_xlog_insert(XLogReaderState *record)
{
XLogRecPtr lsn = record->EndRecPtr;
- xl_heap_insert *xlrec = (xl_heap_insert *) XLogRecGetData(record);
+ xl_heap_insert *xlrec;
Buffer buffer;
Page page;
union
@@ -8985,6 +9709,17 @@ heap_xlog_insert(XLogReaderState *record)
BlockNumber blkno;
ItemPointerData target_tid;
XLogRedoAction action;
+ bool isinit = (XLogRecGetInfo(record) & XLOG_HEAP_INIT_PAGE) != 0;
+ TransactionId pd_xid_base = InvalidTransactionId;
+ Pointer rec_data = (Pointer) XLogRecGetData(record);
+
+ if (isinit)
+ {
+ pd_xid_base = *((TransactionId *) rec_data);
+ rec_data += sizeof(TransactionId);
+ }
+
+ xlrec = (xl_heap_insert *) rec_data;
XLogRecGetBlockTag(record, 0, &target_node, NULL, &blkno);
ItemPointerSetBlockNumber(&target_tid, blkno);
@@ -9009,11 +9744,12 @@ heap_xlog_insert(XLogReaderState *record)
* If we inserted the first and only tuple on the page, re-initialize the
* page from scratch.
*/
- if (XLogRecGetInfo(record) & XLOG_HEAP_INIT_PAGE)
+ if (isinit)
{
buffer = XLogInitBufferForRedo(record, 0);
page = BufferGetPage(buffer);
- PageInit(page, BufferGetPageSize(buffer), 0);
+ PageInit(page, BufferGetPageSize(buffer), sizeof(HeapPageSpecialData));
+ HeapPageGetSpecial(page)->pd_xid_base = pd_xid_base;
action = BLK_NEEDS_REDO;
}
else
@@ -9022,6 +9758,7 @@ heap_xlog_insert(XLogReaderState *record)
{
Size datalen;
char *data;
+ HeapTupleData tuple;
page = BufferGetPage(buffer);
@@ -9045,7 +9782,9 @@ heap_xlog_insert(XLogReaderState *record)
htup->t_infomask2 = xlhdr.t_infomask2;
htup->t_infomask = xlhdr.t_infomask;
htup->t_hoff = xlhdr.t_hoff;
- HeapTupleHeaderSetXmin(htup, XLogRecGetXid(record));
+ tuple.t_data = htup;
+ HeapTupleSetXmin(&tuple, XLogRecGetXid(record));
+ HeapTupleHeaderSetXmin(page, &tuple);
HeapTupleHeaderSetCmin(htup, FirstCommandId);
htup->t_ctid = target_tid;
@@ -9105,12 +9844,19 @@ heap_xlog_multi_insert(XLogReaderState *record)
int i;
bool isinit = (XLogRecGetInfo(record) & XLOG_HEAP_INIT_PAGE) != 0;
XLogRedoAction action;
+ TransactionId pd_xid_base = InvalidTransactionId;
+ Pointer rec_data = (Pointer) XLogRecGetData(record);
/*
* Insertion doesn't overwrite MVCC data, so no conflict processing is
* required.
*/
- xlrec = (xl_heap_multi_insert *) XLogRecGetData(record);
+ if (isinit)
+ {
+ pd_xid_base = *((TransactionId *) rec_data);
+ rec_data += sizeof(TransactionId);
+ }
+ xlrec = (xl_heap_multi_insert *) rec_data;
XLogRecGetBlockTag(record, 0, &rnode, NULL, &blkno);
@@ -9137,7 +9883,8 @@ heap_xlog_multi_insert(XLogReaderState *record)
{
buffer = XLogInitBufferForRedo(record, 0);
page = BufferGetPage(buffer);
- PageInit(page, BufferGetPageSize(buffer), 0);
+ PageInit(page, BufferGetPageSize(buffer), sizeof(HeapPageSpecialData));
+ HeapPageGetSpecial(page)->pd_xid_base = pd_xid_base;
action = BLK_NEEDS_REDO;
}
else
@@ -9158,6 +9905,7 @@ heap_xlog_multi_insert(XLogReaderState *record)
{
OffsetNumber offnum;
xl_multi_insert_tuple *xlhdr;
+ HeapTupleData tuple;
/*
* If we're reinitializing the page, the tuples are stored in
@@ -9188,7 +9936,9 @@ heap_xlog_multi_insert(XLogReaderState *record)
htup->t_infomask2 = xlhdr->t_infomask2;
htup->t_infomask = xlhdr->t_infomask;
htup->t_hoff = xlhdr->t_hoff;
- HeapTupleHeaderSetXmin(htup, XLogRecGetXid(record));
+ tuple.t_data = htup;
+ HeapTupleSetXmin(&tuple, XLogRecGetXid(record));
+ HeapTupleHeaderSetXmin(page, &tuple);
HeapTupleHeaderSetCmin(htup, FirstCommandId);
ItemPointerSetBlockNumber(&htup->t_ctid, blkno);
ItemPointerSetOffsetNumber(&htup->t_ctid, offnum);
@@ -9236,7 +9986,7 @@ static void
heap_xlog_update(XLogReaderState *record, bool hot_update)
{
XLogRecPtr lsn = record->EndRecPtr;
- xl_heap_update *xlrec = (xl_heap_update *) XLogRecGetData(record);
+ xl_heap_update *xlrec;
RelFileNode rnode;
BlockNumber oldblk;
BlockNumber newblk;
@@ -9261,6 +10011,17 @@ heap_xlog_update(XLogReaderState *record, bool hot_update)
Size freespace = 0;
XLogRedoAction oldaction;
XLogRedoAction newaction;
+ bool isinit = (XLogRecGetInfo(record) & XLOG_HEAP_INIT_PAGE) != 0;
+ TransactionId pd_xid_base = InvalidTransactionId;
+ Pointer rec_data = (Pointer) XLogRecGetData(record);
+
+ if (isinit)
+ {
+ pd_xid_base = *((TransactionId *) rec_data);
+ rec_data += sizeof(TransactionId);
+ }
+
+ xlrec = (xl_heap_update *) rec_data;
/* initialize to keep the compiler quiet */
oldtup.t_data = NULL;
@@ -9307,6 +10068,8 @@ heap_xlog_update(XLogReaderState *record, bool hot_update)
&obuffer);
if (oldaction == BLK_NEEDS_REDO)
{
+ HeapTupleData tuple;
+
page = BufferGetPage(obuffer);
offnum = xlrec->old_offnum;
if (PageGetMaxOffsetNumber(page) >= offnum)
@@ -9319,6 +10082,7 @@ heap_xlog_update(XLogReaderState *record, bool hot_update)
oldtup.t_data = htup;
oldtup.t_len = ItemIdGetLength(lp);
+ HeapTupleCopyBaseFromPage(&oldtup, page);
htup->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
htup->t_infomask2 &= ~HEAP_KEYS_UPDATED;
@@ -9328,7 +10092,9 @@ heap_xlog_update(XLogReaderState *record, bool hot_update)
HeapTupleHeaderClearHotUpdated(htup);
fix_infomask_from_infobits(xlrec->old_infobits_set, &htup->t_infomask,
&htup->t_infomask2);
- HeapTupleHeaderSetXmax(htup, xlrec->old_xmax);
+ tuple.t_data = htup;
+ HeapTupleSetXmax(&tuple, xlrec->old_xmax);
+ HeapTupleHeaderSetXmax(page, &tuple);
HeapTupleHeaderSetCmax(htup, FirstCommandId, false);
/* Set forward chain link in t_ctid */
htup->t_ctid = newtid;
@@ -9351,11 +10117,12 @@ heap_xlog_update(XLogReaderState *record, bool hot_update)
nbuffer = obuffer;
newaction = oldaction;
}
- else if (XLogRecGetInfo(record) & XLOG_HEAP_INIT_PAGE)
+ else if (isinit)
{
nbuffer = XLogInitBufferForRedo(record, 0);
page = (Page) BufferGetPage(nbuffer);
- PageInit(page, BufferGetPageSize(nbuffer), 0);
+ PageInit(page, BufferGetPageSize(nbuffer), sizeof(HeapPageSpecialData));
+ HeapPageGetSpecial(page)->pd_xid_base = pd_xid_base;
newaction = BLK_NEEDS_REDO;
}
else
@@ -9383,6 +10150,7 @@ heap_xlog_update(XLogReaderState *record, bool hot_update)
char *recdata_end;
Size datalen;
Size tuplen;
+ HeapTupleData tuple;
recdata = XLogRecGetBlockData(record, 0, &datalen);
recdata_end = recdata + datalen;
@@ -9461,9 +10229,12 @@ heap_xlog_update(XLogReaderState *record, bool hot_update)
htup->t_infomask = xlhdr.t_infomask;
htup->t_hoff = xlhdr.t_hoff;
- HeapTupleHeaderSetXmin(htup, XLogRecGetXid(record));
+ tuple.t_data = htup;
+ HeapTupleSetXmin(&tuple, XLogRecGetXid(record));
+ HeapTupleHeaderSetXmin(page, &tuple);
HeapTupleHeaderSetCmin(htup, FirstCommandId);
- HeapTupleHeaderSetXmax(htup, xlrec->new_xmax);
+ HeapTupleSetXmax(&tuple, xlrec->new_xmax);
+ HeapTupleHeaderSetXmax(page, &tuple);
/* Make sure there is no forward chain link in t_ctid */
htup->t_ctid = newtid;
@@ -9574,6 +10345,8 @@ heap_xlog_lock(XLogReaderState *record)
if (XLogReadBufferForRedo(record, 0, &buffer) == BLK_NEEDS_REDO)
{
+ HeapTupleData tuple;
+
page = (Page) BufferGetPage(buffer);
offnum = xlrec->offnum;
@@ -9602,7 +10375,10 @@ heap_xlog_lock(XLogReaderState *record)
BufferGetBlockNumber(buffer),
offnum);
}
- HeapTupleHeaderSetXmax(htup, xlrec->locking_xid);
+
+ tuple.t_data = htup;
+ HeapTupleSetXmax(&tuple, xlrec->locking_xid);
+ HeapTupleHeaderSetXmax(page, &tuple);
HeapTupleHeaderSetCmax(htup, FirstCommandId, false);
PageSetLSN(page, lsn);
MarkBufferDirty(buffer);
@@ -9647,6 +10423,8 @@ heap_xlog_lock_updated(XLogReaderState *record)
if (XLogReadBufferForRedo(record, 0, &buffer) == BLK_NEEDS_REDO)
{
+ HeapTupleData tuple;
+
page = BufferGetPage(buffer);
offnum = xlrec->offnum;
@@ -9662,7 +10440,9 @@ heap_xlog_lock_updated(XLogReaderState *record)
htup->t_infomask2 &= ~HEAP_KEYS_UPDATED;
fix_infomask_from_infobits(xlrec->infobits_set, &htup->t_infomask,
&htup->t_infomask2);
- HeapTupleHeaderSetXmax(htup, xlrec->xmax);
+ tuple.t_data = htup;
+ HeapTupleSetXmax(&tuple, xlrec->xmax);
+ HeapTupleHeaderSetXmax(page, &tuple);
PageSetLSN(page, lsn);
MarkBufferDirty(buffer);
@@ -9712,6 +10492,31 @@ heap_xlog_inplace(XLogReaderState *record)
UnlockReleaseBuffer(buffer);
}
+static void
+heap_xlog_base_shift(XLogReaderState *record)
+{
+ XLogRecPtr lsn = record->EndRecPtr;
+ xl_heap_base_shift *xlrec = (xl_heap_base_shift *) XLogRecGetData(record);
+ Buffer buffer;
+ Page page;
+ BlockNumber blkno;
+ RelFileNode target_node;
+
+ XLogRecGetBlockTag(record, 0, &target_node, NULL, &blkno);
+
+ if (XLogReadBufferForRedo(record, 0, &buffer) == BLK_NEEDS_REDO)
+ {
+ page = BufferGetPage(buffer);
+ heap_page_shift_base(NULL, InvalidBuffer, page, xlrec->multi, xlrec->delta);
+ PageSetLSN(page, lsn);
+ MarkBufferDirty(buffer);
+ }
+
+ if (BufferIsValid(buffer))
+ UnlockReleaseBuffer(buffer);
+}
+
+
void
heap_redo(XLogReaderState *record)
{
@@ -9798,6 +10603,21 @@ heap2_redo(XLogReaderState *record)
}
}
+void
+heap3_redo(XLogReaderState *record)
+{
+ uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+
+ switch (info & XLOG_HEAP_OPMASK)
+ {
+ case XLOG_HEAP3_BASE_SHIFT:
+ heap_xlog_base_shift(record);
+ break;
+ default:
+ elog(PANIC, "heap3_redo: unknown op code %u", info);
+ }
+}
+
/*
* Mask a heap page before performing consistency checks on it.
*/
@@ -9810,6 +10630,10 @@ heap_mask(char *pagedata, BlockNumber blkno)
mask_page_lsn_and_checksum(page);
mask_page_hint_bits(page);
+
+ /* Ignore prune_xid (it's like a hint-bit) */
+ HeapPageSetPruneXid(page, InvalidTransactionId);
+
mask_unused_space(page);
for (off = 1; off <= PageGetMaxOffsetNumber(page); off++)
@@ -9925,14 +10749,14 @@ HeapCheckForSerializableConflictOut(bool visible, Relation relation,
case HEAPTUPLE_LIVE:
if (visible)
return;
- xid = HeapTupleHeaderGetXmin(tuple->t_data);
+ xid = HeapTupleGetXmin(tuple);
break;
case HEAPTUPLE_RECENTLY_DEAD:
case HEAPTUPLE_DELETE_IN_PROGRESS:
if (visible)
- xid = HeapTupleHeaderGetUpdateXid(tuple->t_data);
+ xid = HeapTupleGetUpdateXidAny(tuple);
else
- xid = HeapTupleHeaderGetXmin(tuple->t_data);
+ xid = HeapTupleGetXmin(tuple);
if (TransactionIdPrecedes(xid, TransactionXmin))
{
@@ -9942,7 +10766,7 @@ HeapCheckForSerializableConflictOut(bool visible, Relation relation,
}
break;
case HEAPTUPLE_INSERT_IN_PROGRESS:
- xid = HeapTupleHeaderGetXmin(tuple->t_data);
+ xid = HeapTupleGetXmin(tuple);
break;
case HEAPTUPLE_DEAD:
Assert(!visible);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 39ef8a0b77..93e153773a 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -413,7 +413,7 @@ tuple_lock_retry:
* changes in an existing tuple, except to invalid or
* frozen, and neither of those can match priorXmax.)
*/
- if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple->t_data),
+ if (!TransactionIdEquals(HeapTupleGetXmin(tuple),
priorXmax))
{
ReleaseBuffer(buffer);
@@ -424,7 +424,7 @@ tuple_lock_retry:
if (TransactionIdIsValid(SnapshotDirty.xmin))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
- errmsg_internal("t_xmin %u is uncommitted in tuple (%u,%u) to be updated in table \"%s\"",
+ errmsg_internal("t_xmin " XID_FMT " is uncommitted in tuple (%u,%u) to be updated in table \"%s\"",
SnapshotDirty.xmin,
ItemPointerGetBlockNumber(&tuple->t_self),
ItemPointerGetOffsetNumber(&tuple->t_self),
@@ -473,7 +473,7 @@ tuple_lock_retry:
* variable instead of doing HeapTupleHeaderGetXmin again.
*/
if (TransactionIdIsCurrentTransactionId(priorXmax) &&
- HeapTupleHeaderGetCmin(tuple->t_data) >= cid)
+ HeapTupleGetCmin(tuple) >= cid)
{
tmfd->xmax = priorXmax;
@@ -481,7 +481,7 @@ tuple_lock_retry:
* Cmin is the problematic value, so store that. See
* above.
*/
- tmfd->cmax = HeapTupleHeaderGetCmin(tuple->t_data);
+ tmfd->cmax = HeapTupleGetCmin(tuple);
ReleaseBuffer(buffer);
return TM_SelfModified;
}
@@ -506,7 +506,7 @@ tuple_lock_retry:
/*
* As above, if xmin isn't what we're expecting, do nothing.
*/
- if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple->t_data),
+ if (!TransactionIdEquals(HeapTupleGetXmin(tuple),
priorXmax))
{
if (BufferIsValid(buffer))
@@ -539,7 +539,7 @@ tuple_lock_retry:
/* updated, so look at the updated row */
*tid = tuple->t_data->t_ctid;
/* updated row should have xmin matching this xmax */
- priorXmax = HeapTupleHeaderGetUpdateXid(tuple->t_data);
+ priorXmax = HeapTupleGetUpdateXidAny(tuple);
if (BufferIsValid(buffer))
ReleaseBuffer(buffer);
/* loop back to fetch next in chain */
@@ -860,7 +860,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
* case we had better copy it.
*/
if (!is_system_catalog &&
- !TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple->t_data)))
+ !TransactionIdIsCurrentTransactionId(HeapTupleGetXmin(tuple)))
elog(WARNING, "concurrent insert in progress within table \"%s\"",
RelationGetRelationName(OldHeap));
/* treat as live */
@@ -872,7 +872,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
* Similar situation to INSERT_IN_PROGRESS case.
*/
if (!is_system_catalog &&
- !TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple->t_data)))
+ !TransactionIdIsCurrentTransactionId(HeapTupleGetUpdateXidAny(tuple)))
elog(WARNING, "concurrent delete in progress within table \"%s\"",
RelationGetRelationName(OldHeap));
/* treat as recently dead */
@@ -1057,6 +1057,7 @@ heapam_scan_analyze_next_tuple(TableScanDesc scan, TransactionId OldestXmin,
targtuple->t_tableOid = RelationGetRelid(scan->rs_rd);
targtuple->t_data = (HeapTupleHeader) PageGetItem(targpage, itemid);
targtuple->t_len = ItemIdGetLength(itemid);
+ HeapTupleCopyBaseFromPage(targtuple, targpage);
switch (HeapTupleSatisfiesVacuum(targtuple, OldestXmin,
hscan->rs_cbuf))
@@ -1092,7 +1093,7 @@ heapam_scan_analyze_next_tuple(TableScanDesc scan, TransactionId OldestXmin,
* numbers we send to the stats collector to make this come
* out right.)
*/
- if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(targtuple->t_data)))
+ if (TransactionIdIsCurrentTransactionId(HeapTupleGetXmin(targtuple)))
{
sample_it = true;
*liverows += 1;
@@ -1123,7 +1124,7 @@ heapam_scan_analyze_next_tuple(TableScanDesc scan, TransactionId OldestXmin,
* but not the post-image. We also get sane results if the
* concurrent transaction never commits.
*/
- if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(targtuple->t_data)))
+ if (TransactionIdIsCurrentTransactionId(HeapTupleGetUpdateXidAny(targtuple)))
*deadrows += 1;
else
{
@@ -1465,7 +1466,7 @@ heapam_index_build_range_scan(Relation heapRelation,
* before commit there. Give a warning if neither case
* applies.
*/
- xwait = HeapTupleHeaderGetXmin(heapTuple->t_data);
+ xwait = HeapTupleGetXmin(heapTuple);
if (!TransactionIdIsCurrentTransactionId(xwait))
{
if (!is_system_catalog)
@@ -1524,7 +1525,7 @@ heapam_index_build_range_scan(Relation heapRelation,
break;
}
- xwait = HeapTupleHeaderGetUpdateXid(heapTuple->t_data);
+ xwait = HeapTupleGetUpdateXidAny(heapTuple);
if (!TransactionIdIsCurrentTransactionId(xwait))
{
if (!is_system_catalog)
@@ -2202,13 +2203,14 @@ heapam_scan_bitmap_next_block(TableScanDesc scan,
loctup.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lp);
loctup.t_len = ItemIdGetLength(lp);
loctup.t_tableOid = scan->rs_rd->rd_id;
+ HeapTupleCopyBaseFromPage(&loctup, dp);
ItemPointerSet(&loctup.t_self, page, offnum);
valid = HeapTupleSatisfiesVisibility(&loctup, snapshot, buffer);
if (valid)
{
hscan->rs_vistuples[ntup++] = offnum;
PredicateLockTID(scan->rs_rd, &loctup.t_self, snapshot,
- HeapTupleHeaderGetXmin(loctup.t_data));
+ HeapTupleGetXmin(&loctup));
}
HeapCheckForSerializableConflictOut(valid, scan->rs_rd, &loctup,
buffer, snapshot);
@@ -2247,6 +2249,7 @@ heapam_scan_bitmap_next_tuple(TableScanDesc scan,
hscan->rs_ctup.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lp);
hscan->rs_ctup.t_len = ItemIdGetLength(lp);
hscan->rs_ctup.t_tableOid = scan->rs_rd->rd_id;
+ HeapTupleCopyBaseFromPage(&hscan->rs_ctup, dp);
ItemPointerSet(&hscan->rs_ctup.t_self, hscan->rs_cblock, targoffset);
pgstat_count_heap_fetch(scan->rs_rd);
@@ -2387,6 +2390,7 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
tuple->t_data = (HeapTupleHeader) PageGetItem(page, itemid);
tuple->t_len = ItemIdGetLength(itemid);
+ HeapTupleCopyBaseFromPage(tuple, page);
ItemPointerSet(&(tuple->t_self), blockno, tupoffset);
diff --git a/src/backend/access/heap/heapam_visibility.c b/src/backend/access/heap/heapam_visibility.c
index ceadac70d5..5adfb9d12c 100644
--- a/src/backend/access/heap/heapam_visibility.c
+++ b/src/backend/access/heap/heapam_visibility.c
@@ -217,7 +217,7 @@ HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
}
}
}
- else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+ else if (TransactionIdIsCurrentTransactionId(HeapTupleGetRawXmin(htup)))
{
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
return true;
@@ -229,7 +229,7 @@ HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
{
TransactionId xmax;
- xmax = HeapTupleGetUpdateXid(tuple);
+ xmax = HeapTupleGetUpdateXid(htup);
/* not LOCKED_ONLY, so it has to have an xmax */
Assert(TransactionIdIsValid(xmax));
@@ -241,7 +241,7 @@ HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
return false;
}
- if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+ if (!TransactionIdIsCurrentTransactionId(HeapTupleGetRawXmax(htup)))
{
/* deleting subtransaction must have aborted */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
@@ -251,11 +251,11 @@ HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
return false;
}
- else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+ else if (TransactionIdIsInProgress(HeapTupleGetRawXmin(htup)))
return false;
- else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+ else if (TransactionIdDidCommit(HeapTupleGetRawXmin(htup)))
SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
- HeapTupleHeaderGetRawXmin(tuple));
+ HeapTupleGetRawXmin(htup));
else
{
/* it must have aborted or crashed */
@@ -284,7 +284,7 @@ HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
return true;
- xmax = HeapTupleGetUpdateXid(tuple);
+ xmax = HeapTupleGetUpdateXid(htup);
/* not LOCKED_ONLY, so it has to have an xmax */
Assert(TransactionIdIsValid(xmax));
@@ -299,17 +299,17 @@ HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
return true;
}
- if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+ if (TransactionIdIsCurrentTransactionId(HeapTupleGetRawXmax(htup)))
{
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
return true;
return false;
}
- if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+ if (TransactionIdIsInProgress(HeapTupleGetRawXmax(htup)))
return true;
- if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+ if (!TransactionIdDidCommit(HeapTupleGetRawXmax(htup)))
{
/* it must have aborted or crashed */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
@@ -327,7 +327,7 @@ HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
}
SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
- HeapTupleHeaderGetRawXmax(tuple));
+ HeapTupleGetRawXmax(htup));
return false;
}
@@ -416,7 +416,7 @@ HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot,
* is canceled by super-deleting the tuple. This also applies to
* TOAST tuples created during speculative insertion.
*/
- else if (!TransactionIdIsValid(HeapTupleHeaderGetXmin(tuple)))
+ else if (!TransactionIdIsValid(HeapTupleGetXmin(htup)))
return false;
}
@@ -506,9 +506,9 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
}
}
}
- else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+ else if (TransactionIdIsCurrentTransactionId(HeapTupleGetRawXmin(htup)))
{
- if (HeapTupleHeaderGetCmin(tuple) >= curcid)
+ if (HeapTupleGetCmin(htup) >= curcid)
return TM_Invisible; /* inserted after scan started */
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
@@ -518,7 +518,7 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
{
TransactionId xmax;
- xmax = HeapTupleHeaderGetRawXmax(tuple);
+ xmax = HeapTupleGetRawXmax(htup);
/*
* Careful here: even though this tuple was created by our own
@@ -549,7 +549,7 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
{
TransactionId xmax;
- xmax = HeapTupleGetUpdateXid(tuple);
+ xmax = HeapTupleGetUpdateXid(htup);
/* not LOCKED_ONLY, so it has to have an xmax */
Assert(TransactionIdIsValid(xmax));
@@ -557,21 +557,21 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
/* deleting subtransaction must have aborted */
if (!TransactionIdIsCurrentTransactionId(xmax))
{
- if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple),
+ if (MultiXactIdIsRunning(HeapTupleGetRawXmax(htup),
false))
return TM_BeingModified;
return TM_Ok;
}
else
{
- if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+ if (HeapTupleGetCmax(htup) >= curcid)
return TM_SelfModified; /* updated after scan started */
else
return TM_Invisible; /* updated before scan started */
}
}
- if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+ if (!TransactionIdIsCurrentTransactionId(HeapTupleGetRawXmax(htup)))
{
/* deleting subtransaction must have aborted */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
@@ -579,16 +579,16 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
return TM_Ok;
}
- if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+ if (HeapTupleGetCmax(htup) >= curcid)
return TM_SelfModified; /* updated after scan started */
else
return TM_Invisible; /* updated before scan started */
}
- else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+ else if (TransactionIdIsInProgress(HeapTupleGetRawXmin(htup)))
return TM_Invisible;
- else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+ else if (TransactionIdDidCommit(HeapTupleGetRawXmin(htup)))
SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
- HeapTupleHeaderGetRawXmin(tuple));
+ HeapTupleGetRawXmin(htup));
else
{
/* it must have aborted or crashed */
@@ -622,17 +622,17 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
{
- if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), true))
+ if (MultiXactIdIsRunning(HeapTupleGetRawXmax(htup), true))
return TM_BeingModified;
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
return TM_Ok;
}
- xmax = HeapTupleGetUpdateXid(tuple);
+ xmax = HeapTupleGetUpdateXid(htup);
if (!TransactionIdIsValid(xmax))
{
- if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+ if (MultiXactIdIsRunning(HeapTupleGetRawXmax(htup), false))
return TM_BeingModified;
}
@@ -641,13 +641,13 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
if (TransactionIdIsCurrentTransactionId(xmax))
{
- if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+ if (HeapTupleGetCmax(htup) >= curcid)
return TM_SelfModified; /* updated after scan started */
else
return TM_Invisible; /* updated before scan started */
}
- if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+ if (MultiXactIdIsRunning(HeapTupleGetRawXmax(htup), false))
return TM_BeingModified;
if (TransactionIdDidCommit(xmax))
@@ -663,7 +663,7 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
* what about the other members?
*/
- if (!MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+ if (!MultiXactIdIsRunning(HeapTupleGetRawXmax(htup), false))
{
/*
* There's no member, even just a locker, alive anymore, so we can
@@ -680,20 +680,20 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
}
}
- if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+ if (TransactionIdIsCurrentTransactionId(HeapTupleGetRawXmax(htup)))
{
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
return TM_BeingModified;
- if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+ if (HeapTupleGetCmax(htup) >= curcid)
return TM_SelfModified; /* updated after scan started */
else
return TM_Invisible; /* updated before scan started */
}
- if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+ if (TransactionIdIsInProgress(HeapTupleGetRawXmax(htup)))
return TM_BeingModified;
- if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+ if (!TransactionIdDidCommit(HeapTupleGetRawXmax(htup)))
{
/* it must have aborted or crashed */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
@@ -711,7 +711,7 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
}
SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
- HeapTupleHeaderGetRawXmax(tuple));
+ HeapTupleGetRawXmax(htup));
if (!ItemPointerEquals(&htup->t_self, &tuple->t_ctid))
return TM_Updated; /* updated by other */
else
@@ -794,7 +794,7 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
}
}
}
- else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+ else if (TransactionIdIsCurrentTransactionId(HeapTupleGetRawXmin(htup)))
{
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
return true;
@@ -806,7 +806,7 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
{
TransactionId xmax;
- xmax = HeapTupleGetUpdateXid(tuple);
+ xmax = HeapTupleGetUpdateXid(htup);
/* not LOCKED_ONLY, so it has to have an xmax */
Assert(TransactionIdIsValid(xmax));
@@ -818,7 +818,7 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
return false;
}
- if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+ if (!TransactionIdIsCurrentTransactionId(HeapTupleGetRawXmax(htup)))
{
/* deleting subtransaction must have aborted */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
@@ -828,7 +828,7 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
return false;
}
- else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+ else if (TransactionIdIsInProgress(HeapTupleGetRawXmin(htup)))
{
/*
* Return the speculative token to caller. Caller can worry about
@@ -844,13 +844,13 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
Assert(snapshot->speculativeToken != 0);
}
- snapshot->xmin = HeapTupleHeaderGetRawXmin(tuple);
+ snapshot->xmin = HeapTupleGetRawXmin(htup);
/* XXX shouldn't we fall through to look at xmax? */
return true; /* in insertion by other */
}
- else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+ else if (TransactionIdDidCommit(HeapTupleGetRawXmin(htup)))
SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
- HeapTupleHeaderGetRawXmin(tuple));
+ HeapTupleGetRawXmin(htup));
else
{
/* it must have aborted or crashed */
@@ -879,7 +879,7 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
return true;
- xmax = HeapTupleGetUpdateXid(tuple);
+ xmax = HeapTupleGetUpdateXid(htup);
/* not LOCKED_ONLY, so it has to have an xmax */
Assert(TransactionIdIsValid(xmax));
@@ -897,21 +897,21 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
return true;
}
- if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+ if (TransactionIdIsCurrentTransactionId(HeapTupleGetRawXmax(htup)))
{
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
return true;
return false;
}
- if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+ if (TransactionIdIsInProgress(HeapTupleGetRawXmax(htup)))
{
if (!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
- snapshot->xmax = HeapTupleHeaderGetRawXmax(tuple);
+ snapshot->xmax = HeapTupleGetRawXmax(htup);
return true;
}
- if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+ if (!TransactionIdDidCommit(HeapTupleGetRawXmax(htup)))
{
/* it must have aborted or crashed */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
@@ -929,7 +929,7 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
}
SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
- HeapTupleHeaderGetRawXmax(tuple));
+ HeapTupleGetRawXmax(htup));
return false; /* updated by other */
}
@@ -1008,9 +1008,9 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
}
}
}
- else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+ else if (TransactionIdIsCurrentTransactionId(HeapTupleGetRawXmin(htup)))
{
- if (HeapTupleHeaderGetCmin(tuple) >= snapshot->curcid)
+ if (HeapTupleGetCmin(htup) >= snapshot->curcid)
return false; /* inserted after scan started */
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
@@ -1023,7 +1023,7 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
{
TransactionId xmax;
- xmax = HeapTupleGetUpdateXid(tuple);
+ xmax = HeapTupleGetUpdateXid(htup);
/* not LOCKED_ONLY, so it has to have an xmax */
Assert(TransactionIdIsValid(xmax));
@@ -1031,13 +1031,13 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
/* updating subtransaction must have aborted */
if (!TransactionIdIsCurrentTransactionId(xmax))
return true;
- else if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+ else if (HeapTupleGetCmax(htup) >= snapshot->curcid)
return true; /* updated after scan started */
else
return false; /* updated before scan started */
}
- if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+ if (!TransactionIdIsCurrentTransactionId(HeapTupleGetRawXmax(htup)))
{
/* deleting subtransaction must have aborted */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
@@ -1045,16 +1045,16 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
return true;
}
- if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+ if (HeapTupleGetCmax(htup) >= snapshot->curcid)
return true; /* deleted after scan started */
else
return false; /* deleted before scan started */
}
- else if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
+ else if (XidInMVCCSnapshot(HeapTupleGetRawXmin(htup), snapshot))
return false;
- else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+ else if (TransactionIdDidCommit(HeapTupleGetRawXmin(htup)))
SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
- HeapTupleHeaderGetRawXmin(tuple));
+ HeapTupleGetRawXmin(htup));
else
{
/* it must have aborted or crashed */
@@ -1067,7 +1067,7 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
{
/* xmin is committed, but maybe not according to our snapshot */
if (!HeapTupleHeaderXminFrozen(tuple) &&
- XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
+ XidInMVCCSnapshot(HeapTupleGetRawXmin(htup), snapshot))
return false; /* treat as still in progress */
}
@@ -1086,14 +1086,14 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
/* already checked above */
Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
- xmax = HeapTupleGetUpdateXid(tuple);
+ xmax = HeapTupleGetUpdateXid(htup);
/* not LOCKED_ONLY, so it has to have an xmax */
Assert(TransactionIdIsValid(xmax));
if (TransactionIdIsCurrentTransactionId(xmax))
{
- if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+ if (HeapTupleGetCmax(htup) >= snapshot->curcid)
return true; /* deleted after scan started */
else
return false; /* deleted before scan started */
@@ -1108,18 +1108,18 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
{
- if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+ if (TransactionIdIsCurrentTransactionId(HeapTupleGetRawXmax(htup)))
{
- if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+ if (HeapTupleGetCmax(htup) >= snapshot->curcid)
return true; /* deleted after scan started */
else
return false; /* deleted before scan started */
}
- if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
+ if (XidInMVCCSnapshot(HeapTupleGetRawXmax(htup), snapshot))
return true;
- if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+ if (!TransactionIdDidCommit(HeapTupleGetRawXmax(htup)))
{
/* it must have aborted or crashed */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
@@ -1129,12 +1129,12 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
/* xmax transaction committed */
SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
- HeapTupleHeaderGetRawXmax(tuple));
+ HeapTupleGetRawXmax(htup));
}
else
{
/* xmax is committed, but maybe not according to our snapshot */
- if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
+ if (XidInMVCCSnapshot(HeapTupleGetRawXmax(htup), snapshot))
return true; /* treat as still in progress */
}
@@ -1249,21 +1249,21 @@ HeapTupleSatisfiesVacuumHorizon(HeapTuple htup, Buffer buffer, TransactionId *de
return HEAPTUPLE_DEAD;
}
}
- else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+ else if (TransactionIdIsCurrentTransactionId(HeapTupleGetRawXmin(htup)))
{
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
return HEAPTUPLE_INSERT_IN_PROGRESS;
/* only locked? run infomask-only check first, for performance */
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask) ||
- HeapTupleHeaderIsOnlyLocked(tuple))
+ HeapTupleIsOnlyLocked(htup))
return HEAPTUPLE_INSERT_IN_PROGRESS;
/* inserted and then deleted by same xact */
- if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple)))
+ if (TransactionIdIsCurrentTransactionId(HeapTupleGetUpdateXidAny(htup)))
return HEAPTUPLE_DELETE_IN_PROGRESS;
/* deleting subtransaction must have aborted */
return HEAPTUPLE_INSERT_IN_PROGRESS;
}
- else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+ else if (TransactionIdIsInProgress(HeapTupleGetRawXmin(htup)))
{
/*
* It'd be possible to discern between INSERT/DELETE in progress
@@ -1275,9 +1275,9 @@ HeapTupleSatisfiesVacuumHorizon(HeapTuple htup, Buffer buffer, TransactionId *de
*/
return HEAPTUPLE_INSERT_IN_PROGRESS;
}
- else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+ else if (TransactionIdDidCommit(HeapTupleGetRawXmin(htup)))
SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
- HeapTupleHeaderGetRawXmin(tuple));
+ HeapTupleGetRawXmin(htup));
else
{
/*
@@ -1319,14 +1319,14 @@ HeapTupleSatisfiesVacuumHorizon(HeapTuple htup, Buffer buffer, TransactionId *de
* possibly be running; otherwise have to check.
*/
if (!HEAP_LOCKED_UPGRADED(tuple->t_infomask) &&
- MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple),
+ MultiXactIdIsRunning(HeapTupleGetRawXmax(htup),
true))
return HEAPTUPLE_LIVE;
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
}
else
{
- if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+ if (TransactionIdIsInProgress(HeapTupleGetRawXmax(htup)))
return HEAPTUPLE_LIVE;
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
InvalidTransactionId);
@@ -1344,7 +1344,7 @@ HeapTupleSatisfiesVacuumHorizon(HeapTuple htup, Buffer buffer, TransactionId *de
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
- TransactionId xmax = HeapTupleGetUpdateXid(tuple);
+ TransactionId xmax = HeapTupleGetUpdateXid(htup);
/* already checked above */
Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
@@ -1367,7 +1367,7 @@ HeapTupleSatisfiesVacuumHorizon(HeapTuple htup, Buffer buffer, TransactionId *de
*dead_after = xmax;
return HEAPTUPLE_RECENTLY_DEAD;
}
- else if (!MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+ else if (!MultiXactIdIsRunning(HeapTupleGetRawXmax(htup), false))
{
/*
* Not in Progress, Not Committed, so either Aborted or crashed.
@@ -1381,11 +1381,11 @@ HeapTupleSatisfiesVacuumHorizon(HeapTuple htup, Buffer buffer, TransactionId *de
if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
{
- if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+ if (TransactionIdIsInProgress(HeapTupleGetRawXmax(htup)))
return HEAPTUPLE_DELETE_IN_PROGRESS;
- else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+ else if (TransactionIdDidCommit(HeapTupleGetRawXmax(htup)))
SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
- HeapTupleHeaderGetRawXmax(tuple));
+ HeapTupleGetRawXmax(htup));
else
{
/*
@@ -1407,7 +1407,7 @@ HeapTupleSatisfiesVacuumHorizon(HeapTuple htup, Buffer buffer, TransactionId *de
* Deleter committed, allow caller to check if it was recent enough that
* some open transactions could still see the tuple.
*/
- *dead_after = HeapTupleHeaderGetRawXmax(tuple);
+ *dead_after = HeapTupleGetRawXmax(htup);
return HEAPTUPLE_RECENTLY_DEAD;
}
@@ -1503,7 +1503,7 @@ HeapTupleIsSurelyDead(HeapTuple htup, GlobalVisState *vistest)
/* Deleter committed, so tuple is dead if the XID is old enough. */
return GlobalVisTestIsRemovableXid(vistest,
- HeapTupleHeaderGetRawXmax(tuple));
+ HeapTupleGetRawXmax(htup));
}
/*
@@ -1516,8 +1516,9 @@ HeapTupleIsSurelyDead(HeapTuple htup, GlobalVisState *vistest)
* at the top of this file.
*/
bool
-HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple)
+HeapTupleIsOnlyLocked(HeapTuple htup)
{
+ HeapTupleHeader tuple = htup->t_data;
TransactionId xmax;
/* if there's no valid Xmax, then there's obviously no update either */
@@ -1528,7 +1529,7 @@ HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple)
return true;
/* invalid xmax means no update */
- if (!TransactionIdIsValid(HeapTupleHeaderGetRawXmax(tuple)))
+ if (!TransactionIdIsValid(HeapTupleGetRawXmax(htup)))
return true;
/*
@@ -1539,7 +1540,7 @@ HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple)
return false;
/* ... but if it's a multi, then perhaps the updating Xid aborted. */
- xmax = HeapTupleGetUpdateXid(tuple);
+ xmax = HeapTupleGetUpdateXid(htup);
/* not LOCKED_ONLY, so it has to have an xmax */
Assert(TransactionIdIsValid(xmax));
@@ -1587,8 +1588,8 @@ HeapTupleSatisfiesHistoricMVCC(HeapTuple htup, Snapshot snapshot,
Buffer buffer)
{
HeapTupleHeader tuple = htup->t_data;
- TransactionId xmin = HeapTupleHeaderGetXmin(tuple);
- TransactionId xmax = HeapTupleHeaderGetRawXmax(tuple);
+ TransactionId xmin = HeapTupleGetXmin(htup);
+ TransactionId xmax = HeapTupleGetRawXmax(htup);
Assert(ItemPointerIsValid(&htup->t_self));
Assert(htup->t_tableOid != InvalidOid);
@@ -1688,7 +1689,7 @@ HeapTupleSatisfiesHistoricMVCC(HeapTuple htup, Snapshot snapshot,
*/
else if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
- xmax = HeapTupleGetUpdateXid(tuple);
+ xmax = HeapTupleGetUpdateXid(htup);
}
/* check if it's one of our txids, toplevel is also in there */
diff --git a/src/backend/access/heap/heaptoast.c b/src/backend/access/heap/heaptoast.c
index 1575a81b01..252e57cc1d 100644
--- a/src/backend/access/heap/heaptoast.c
+++ b/src/backend/access/heap/heaptoast.c
@@ -307,6 +307,7 @@ heap_toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
result_tuple->t_len = new_tuple_len;
result_tuple->t_self = newtup->t_self;
result_tuple->t_tableOid = newtup->t_tableOid;
+ HeapTupleCopyBase(result_tuple, newtup);
new_data = (HeapTupleHeader) ((char *) result_tuple + HEAPTUPLESIZE);
result_tuple->t_data = new_data;
@@ -395,6 +396,7 @@ toast_flatten_tuple(HeapTuple tup, TupleDesc tupleDesc)
*/
new_tuple->t_self = tup->t_self;
new_tuple->t_tableOid = tup->t_tableOid;
+ HeapTupleCopyBase(new_tuple, tup);
new_tuple->t_data->t_choice = tup->t_data->t_choice;
new_tuple->t_data->t_ctid = tup->t_data->t_ctid;
@@ -467,6 +469,7 @@ toast_flatten_tuple_to_datum(HeapTupleHeader tup,
ItemPointerSetInvalid(&(tmptup.t_self));
tmptup.t_tableOid = InvalidOid;
tmptup.t_data = tup;
+ HeapTupleSetZeroBase(&tmptup);
/*
* Break down the tuple into fields.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index ae2e2ce37a..81cc9c7e45 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -59,6 +59,11 @@ RelationPutHeapTuple(Relation relation,
/* Add the tuple to the page */
pageHeader = BufferGetPage(buffer);
+ HeapTupleSetXmin(tuple, tuple->t_xmin);
+ HeapTupleHeaderSetXmin(pageHeader, tuple);
+ HeapTupleSetXmax(tuple, tuple->t_xmax);
+ HeapTupleHeaderSetXmax(pageHeader, tuple);
+
offnum = PageAddItem(pageHeader, (Item) tuple->t_data,
tuple->t_len, InvalidOffsetNumber, false, true);
@@ -232,6 +237,9 @@ RelationAddExtraBlocks(Relation relation, BulkInsertState bistate)
BufferGetBlockNumber(buffer),
RelationGetRelationName(relation));
+ PageInit(page, BufferGetPageSize(buffer), sizeof(HeapPageSpecialData));
+ HeapPageGetSpecial(page)->pd_xid_base = RecentXmin - FirstNormalTransactionId;
+
/*
* Add the page to the FSM without initializing. If we were to
* initialize here, the page would potentially get flushed out to disk
@@ -243,7 +251,7 @@ RelationAddExtraBlocks(Relation relation, BulkInsertState bistate)
/* we'll need this info below */
blockNum = BufferGetBlockNumber(buffer);
- freespace = BufferGetPageSize(buffer) - SizeOfPageHeaderData;
+ freespace = BufferGetPageSize(buffer) - SizeOfPageHeaderData - MAXALIGN(sizeof(HeapPageSpecialData));
UnlockReleaseBuffer(buffer);
@@ -514,6 +522,9 @@ loop:
/*
* Now we can check to see if there's enough free space here. If so,
* we're done.
+ *
+ * "Double xmax" page is not suitable for any new tuple, since xmin
+ * can't be set there.
*/
page = BufferGetPage(buffer);
@@ -525,12 +536,13 @@ loop:
*/
if (PageIsNew(page))
{
- PageInit(page, BufferGetPageSize(buffer), 0);
+ PageInit(page, BufferGetPageSize(buffer), sizeof(HeapPageSpecialData));
MarkBufferDirty(buffer);
}
pageFreeSpace = PageGetHeapFreeSpace(page);
- if (targetFreeSpace <= pageFreeSpace)
+ if (targetFreeSpace <= pageFreeSpace &&
+ !HeapPageIsDoubleXmax(page))
{
/* use this page as future insert target, too */
RelationSetTargetBlock(relation, targetBlock);
@@ -635,7 +647,8 @@ loop:
BufferGetBlockNumber(buffer),
RelationGetRelationName(relation));
- PageInit(page, BufferGetPageSize(buffer), 0);
+ PageInit(page, BufferGetPageSize(buffer), sizeof(HeapPageSpecialData));
+ HeapPageGetSpecial(page)->pd_xid_base = RecentXmin - FirstNormalTransactionId;
MarkBufferDirty(buffer);
/*
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index b6500763a5..e825dca8d2 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -136,7 +136,7 @@ heap_page_prune_opt(Relation relation, Buffer buffer)
* determining the appropriate horizon is a waste if there's no prune_xid
* (i.e. no updates/deletes left potentially dead tuples around).
*/
- prune_xid = ((PageHeader) page)->pd_prune_xid;
+ prune_xid = HeapPageGetPruneXidNoAssert(page);
if (!TransactionIdIsValid(prune_xid))
return;
@@ -207,7 +207,7 @@ heap_page_prune_opt(Relation relation, Buffer buffer)
nnewlpdead;
ndeleted = heap_page_prune(relation, buffer, vistest, limited_xmin,
- limited_ts, &nnewlpdead, NULL);
+ limited_ts, &nnewlpdead, NULL, true);
/*
* Report the number of tuples reclaimed to pgstats. This is
@@ -266,7 +266,8 @@ heap_page_prune(Relation relation, Buffer buffer,
TransactionId old_snap_xmin,
TimestampTz old_snap_ts,
int *nnewlpdead,
- OffsetNumber *off_loc)
+ OffsetNumber *off_loc,
+ bool repairFragmentation)
{
int ndeleted = 0;
Page page = BufferGetPage(buffer);
@@ -337,6 +338,7 @@ heap_page_prune(Relation relation, Buffer buffer,
htup = (HeapTupleHeader) PageGetItem(page, itemid);
tup.t_data = htup;
tup.t_len = ItemIdGetLength(itemid);
+ HeapTupleCopyBaseFromPage(&tup, page);
ItemPointerSet(&(tup.t_self), blockno, offnum);
/*
@@ -391,13 +393,15 @@ heap_page_prune(Relation relation, Buffer buffer,
heap_page_prune_execute(buffer,
prstate.redirected, prstate.nredirected,
prstate.nowdead, prstate.ndead,
- prstate.nowunused, prstate.nunused);
+ prstate.nowunused, prstate.nunused,
+ repairFragmentation);
/*
* Update the page's pd_prune_xid field to either zero, or the lowest
* XID of any soon-prunable tuple.
*/
- ((PageHeader) page)->pd_prune_xid = prstate.new_prune_xid;
+ if (XidFitsPage(page, prstate.new_prune_xid))
+ HeapPageSetPruneXid(page, prstate.new_prune_xid);
/*
* Also clear the "page is full" flag, since there's no point in
@@ -459,10 +463,10 @@ heap_page_prune(Relation relation, Buffer buffer,
* point in repeating the prune/defrag process until something else
* happens to the page.
*/
- if (((PageHeader) page)->pd_prune_xid != prstate.new_prune_xid ||
+ if (HeapPageGetPruneXid(page) != prstate.new_prune_xid ||
PageIsFull(page))
{
- ((PageHeader) page)->pd_prune_xid = prstate.new_prune_xid;
+ HeapPageSetPruneXid(page, prstate.new_prune_xid);
PageClearFull(page);
MarkBufferDirtyHint(buffer, true);
}
@@ -599,6 +603,9 @@ heap_prune_chain(Buffer buffer, OffsetNumber rootoffnum, PruneState *prstate)
OffsetNumber chainitems[MaxHeapTuplesPerPage];
int nchain = 0,
i;
+ HeapTupleData tup;
+
+ tup.t_tableOid = RelationGetRelid(prstate->rel);
rootlp = PageGetItemId(dp, rootoffnum);
@@ -610,6 +617,11 @@ heap_prune_chain(Buffer buffer, OffsetNumber rootoffnum, PruneState *prstate)
Assert(prstate->htsv[rootoffnum] != -1);
htup = (HeapTupleHeader) PageGetItem(dp, rootlp);
+ tup.t_data = htup;
+ tup.t_len = ItemIdGetLength(rootlp);
+ ItemPointerSet(&(tup.t_self), BufferGetBlockNumber(buffer), rootoffnum);
+ HeapTupleCopyBaseFromPage(&tup, dp);
+
if (HeapTupleHeaderIsHeapOnly(htup))
{
/*
@@ -634,7 +646,7 @@ heap_prune_chain(Buffer buffer, OffsetNumber rootoffnum, PruneState *prstate)
!HeapTupleHeaderIsHotUpdated(htup))
{
heap_prune_record_unused(prstate, rootoffnum);
- HeapTupleHeaderAdvanceLatestRemovedXid(htup,
+ HeapTupleHeaderAdvanceLatestRemovedXid(&tup,
&prstate->latestRemovedXid);
ndeleted++;
}
@@ -701,11 +713,16 @@ heap_prune_chain(Buffer buffer, OffsetNumber rootoffnum, PruneState *prstate)
Assert(prstate->htsv[offnum] != -1);
htup = (HeapTupleHeader) PageGetItem(dp, lp);
+ tup.t_data = htup;
+ tup.t_len = ItemIdGetLength(lp);
+ HeapTupleCopyBaseFromPage(&tup, dp);
+ ItemPointerSet(&(tup.t_self), BufferGetBlockNumber(buffer), offnum);
+
/*
* Check the tuple XMIN against prior XMAX, if any
*/
if (TransactionIdIsValid(priorXmax) &&
- !TransactionIdEquals(HeapTupleHeaderGetXmin(htup), priorXmax))
+ !TransactionIdEquals(HeapTupleGetXmin(&tup), priorXmax))
break;
/*
@@ -732,7 +749,7 @@ heap_prune_chain(Buffer buffer, OffsetNumber rootoffnum, PruneState *prstate)
* that the page is reconsidered for pruning in future.
*/
heap_prune_record_prunable(prstate,
- HeapTupleHeaderGetUpdateXid(htup));
+ HeapTupleGetUpdateXidAny(&tup));
break;
case HEAPTUPLE_DELETE_IN_PROGRESS:
@@ -742,7 +759,7 @@ heap_prune_chain(Buffer buffer, OffsetNumber rootoffnum, PruneState *prstate)
* that the page is reconsidered for pruning in future.
*/
heap_prune_record_prunable(prstate,
- HeapTupleHeaderGetUpdateXid(htup));
+ HeapTupleGetUpdateXidAny(&tup));
break;
case HEAPTUPLE_LIVE:
@@ -771,7 +788,7 @@ heap_prune_chain(Buffer buffer, OffsetNumber rootoffnum, PruneState *prstate)
if (tupdead)
{
latestdead = offnum;
- HeapTupleHeaderAdvanceLatestRemovedXid(htup,
+ HeapTupleHeaderAdvanceLatestRemovedXid(&tup,
&prstate->latestRemovedXid);
}
else if (!recent_dead)
@@ -793,7 +810,7 @@ heap_prune_chain(Buffer buffer, OffsetNumber rootoffnum, PruneState *prstate)
Assert(ItemPointerGetBlockNumber(&htup->t_ctid) ==
BufferGetBlockNumber(buffer));
offnum = ItemPointerGetOffsetNumber(&htup->t_ctid);
- priorXmax = HeapTupleHeaderGetUpdateXid(htup);
+ priorXmax = HeapTupleGetUpdateXidAny(&tup);
}
/*
@@ -910,7 +927,8 @@ void
heap_page_prune_execute(Buffer buffer,
OffsetNumber *redirected, int nredirected,
OffsetNumber *nowdead, int ndead,
- OffsetNumber *nowunused, int nunused)
+ OffsetNumber *nowunused, int nunused,
+ bool repairFragmentation)
{
Page page = (Page) BufferGetPage(buffer);
OffsetNumber *offnum;
@@ -1034,7 +1052,8 @@ heap_page_prune_execute(Buffer buffer,
* Finally, repair any fragmentation, and update the page's hint bit about
* whether it has free pointers.
*/
- PageRepairFragmentation(page);
+ if (repairFragmentation)
+ PageRepairFragmentation(page);
/*
* Now that the page has been modified, assert that redirect items still
@@ -1121,6 +1140,7 @@ heap_get_root_tuples(Page page, OffsetNumber *root_offsets)
HeapTupleHeader htup;
OffsetNumber nextoffnum;
TransactionId priorXmax;
+ HeapTupleData tup;
/* skip unused and dead items */
if (!ItemIdIsUsed(lp) || ItemIdIsDead(lp))
@@ -1129,6 +1149,8 @@ heap_get_root_tuples(Page page, OffsetNumber *root_offsets)
if (ItemIdIsNormal(lp))
{
htup = (HeapTupleHeader) PageGetItem(page, lp);
+ tup.t_data = htup;
+ HeapTupleCopyBaseFromPage(&tup, page);
/*
* Check if this tuple is part of a HOT-chain rooted at some other
@@ -1150,7 +1172,7 @@ heap_get_root_tuples(Page page, OffsetNumber *root_offsets)
/* Set up to scan the HOT-chain */
nextoffnum = ItemPointerGetOffsetNumber(&htup->t_ctid);
- priorXmax = HeapTupleHeaderGetUpdateXid(htup);
+ priorXmax = HeapTupleGetUpdateXidAny(&tup);
}
else
{
@@ -1189,9 +1211,11 @@ heap_get_root_tuples(Page page, OffsetNumber *root_offsets)
break;
htup = (HeapTupleHeader) PageGetItem(page, lp);
+ tup.t_data = htup;
+ HeapTupleCopyBaseFromPage(&tup, page);
if (TransactionIdIsValid(priorXmax) &&
- !TransactionIdEquals(priorXmax, HeapTupleHeaderGetXmin(htup)))
+ !TransactionIdEquals(priorXmax, HeapTupleGetXmin(&tup)))
break;
/* Remember the root line pointer for this item */
@@ -1205,7 +1229,7 @@ heap_get_root_tuples(Page page, OffsetNumber *root_offsets)
Assert(!HeapTupleHeaderIndicatesMovedPartitions(htup));
nextoffnum = ItemPointerGetOffsetNumber(&htup->t_ctid);
- priorXmax = HeapTupleHeaderGetUpdateXid(htup);
+ priorXmax = HeapTupleGetUpdateXidAny(&tup);
}
}
}
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 2a53826736..42124de009 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -379,6 +379,7 @@ rewrite_heap_tuple(RewriteState state,
&old_tuple->t_data->t_choice.t_heap,
sizeof(HeapTupleFields));
+ HeapTupleCopyBase(new_tuple, old_tuple);
new_tuple->t_data->t_infomask &= ~HEAP_XACT_MASK;
new_tuple->t_data->t_infomask2 &= ~HEAP2_XACT_MASK;
new_tuple->t_data->t_infomask |=
@@ -388,7 +389,7 @@ rewrite_heap_tuple(RewriteState state,
* While we have our hands on the tuple, we may as well freeze any
* eligible xmin or xmax, so that future VACUUM effort can be saved.
*/
- heap_freeze_tuple(new_tuple->t_data,
+ heap_freeze_tuple(new_tuple,
state->rs_old_rel->rd_rel->relfrozenxid,
state->rs_old_rel->rd_rel->relminmxid,
state->rs_freeze_xid,
@@ -404,7 +405,7 @@ rewrite_heap_tuple(RewriteState state,
* If the tuple has been updated, check the old-to-new mapping hash table.
*/
if (!((old_tuple->t_data->t_infomask & HEAP_XMAX_INVALID) ||
- HeapTupleHeaderIsOnlyLocked(old_tuple->t_data)) &&
+ HeapTupleIsOnlyLocked(old_tuple)) &&
!HeapTupleHeaderIndicatesMovedPartitions(old_tuple->t_data) &&
!(ItemPointerEquals(&(old_tuple->t_self),
&(old_tuple->t_data->t_ctid))))
@@ -412,7 +413,7 @@ rewrite_heap_tuple(RewriteState state,
OldToNewMapping mapping;
memset(&hashkey, 0, sizeof(hashkey));
- hashkey.xmin = HeapTupleHeaderGetUpdateXid(old_tuple->t_data);
+ hashkey.xmin = HeapTupleGetUpdateXidAny(old_tuple);
hashkey.tid = old_tuple->t_data->t_ctid;
mapping = (OldToNewMapping)
@@ -485,7 +486,7 @@ rewrite_heap_tuple(RewriteState state,
* RECENTLY_DEAD if and only if the xmin is not before OldestXmin.
*/
if ((new_tuple->t_data->t_infomask & HEAP_UPDATED) &&
- !TransactionIdPrecedes(HeapTupleHeaderGetXmin(new_tuple->t_data),
+ !TransactionIdPrecedes(HeapTupleGetXmin(new_tuple),
state->rs_oldest_xmin))
{
/*
@@ -494,7 +495,7 @@ rewrite_heap_tuple(RewriteState state,
UnresolvedTup unresolved;
memset(&hashkey, 0, sizeof(hashkey));
- hashkey.xmin = HeapTupleHeaderGetXmin(new_tuple->t_data);
+ hashkey.xmin = HeapTupleGetXmin(new_tuple);
hashkey.tid = old_tid;
unresolved = hash_search(state->rs_unresolved_tups, &hashkey,
@@ -582,7 +583,7 @@ rewrite_heap_dead_tuple(RewriteState state, HeapTuple old_tuple)
bool found;
memset(&hashkey, 0, sizeof(hashkey));
- hashkey.xmin = HeapTupleHeaderGetXmin(old_tuple->t_data);
+ hashkey.xmin = HeapTupleGetXmin(old_tuple);
hashkey.tid = old_tuple->t_self;
unresolved = hash_search(state->rs_unresolved_tups, &hashkey,
@@ -618,6 +619,7 @@ raw_heap_insert(RewriteState state, HeapTuple tup)
Size len;
OffsetNumber newoff;
HeapTuple heaptup;
+ TransactionId xmin, xmax;
/*
* If the new tuple is too big for storage or contains already toasted
@@ -703,10 +705,21 @@ raw_heap_insert(RewriteState state, HeapTuple tup)
if (!state->rs_buffer_valid)
{
/* Initialize a new empty page */
- PageInit(page, BLCKSZ, 0);
+ PageInit(page, BLCKSZ, sizeof(HeapPageSpecialData));
state->rs_buffer_valid = true;
}
+ rewrite_page_prepare_for_xid(page, HeapTupleGetXmin(heaptup), false);
+ rewrite_page_prepare_for_xid(page, HeapTupleGetRawXmax(heaptup),
+ (heaptup->t_data->t_infomask & HEAP_XMAX_IS_MULTI) ? true : false);
+
+ xmin = HeapTupleGetXmin(heaptup);
+ xmax = HeapTupleGetRawXmax(heaptup);
+ HeapTupleSetXmin(heaptup, xmin);
+ HeapTupleHeaderSetXmin(page, heaptup);
+ HeapTupleSetXmax(heaptup, xmax);
+ HeapTupleHeaderSetXmax(page, heaptup);
+
/* And now we can insert the tuple into the page */
newoff = PageAddItem(page, (Item) heaptup->t_data, heaptup->t_len,
InvalidOffsetNumber, false, true);
@@ -999,7 +1012,10 @@ logical_rewrite_log_mapping(RewriteState state, TransactionId xid,
"pg_logical/mappings/" LOGICAL_REWRITE_FORMAT,
dboid, relid,
LSN_FORMAT_ARGS(state->rs_begin_lsn),
- xid, GetCurrentTransactionId());
+ (uint32) (xid >> 32),
+ (uint32) xid,
+ (uint32) (GetCurrentTransactionId() >> 32),
+ (uint32) GetCurrentTransactionId());
dlist_init(&src->mappings);
src->num_mappings = 0;
@@ -1048,9 +1064,9 @@ logical_rewrite_heap_tuple(RewriteState state, ItemPointerData old_tid,
if (!state->rs_logical_rewrite)
return;
- xmin = HeapTupleHeaderGetXmin(new_tuple->t_data);
+ xmin = HeapTupleGetXmin(new_tuple);
/* use *GetUpdateXid to correctly deal with multixacts */
- xmax = HeapTupleHeaderGetUpdateXid(new_tuple->t_data);
+ xmax = HeapTupleGetUpdateXidAny(new_tuple);
/*
* Log the mapping iff the tuple has been created recently.
@@ -1121,7 +1137,10 @@ heap_xlog_logical_rewrite(XLogReaderState *r)
"pg_logical/mappings/" LOGICAL_REWRITE_FORMAT,
xlrec->mapped_db, xlrec->mapped_rel,
LSN_FORMAT_ARGS(xlrec->start_lsn),
- xlrec->mapped_xid, XLogRecGetXid(r));
+ (uint32) (xlrec->mapped_xid >> 32),
+ (uint32) xlrec->mapped_xid,
+ (uint32) (XLogRecGetXid(r) >> 32),
+ (uint32) XLogRecGetXid(r));
fd = OpenTransientFile(path,
O_CREAT | O_WRONLY | PG_BINARY);
@@ -1217,10 +1236,12 @@ CheckPointLogicalRewriteHeap(void)
Oid dboid;
Oid relid;
XLogRecPtr lsn;
- TransactionId rewrite_xid;
- TransactionId create_xid;
- uint32 hi,
- lo;
+ uint32 lsn_hi,
+ lsn_lo,
+ rewrite_xid_hi,
+ rewrite_xid_lo,
+ create_xid_hi,
+ create_xid_lo;
if (strcmp(mapping_de->d_name, ".") == 0 ||
strcmp(mapping_de->d_name, "..") == 0)
@@ -1235,10 +1256,12 @@ CheckPointLogicalRewriteHeap(void)
continue;
if (sscanf(mapping_de->d_name, LOGICAL_REWRITE_FORMAT,
- &dboid, &relid, &hi, &lo, &rewrite_xid, &create_xid) != 6)
+ &dboid, &relid, &lsn_hi, &lsn_lo,
+ &rewrite_xid_hi, &rewrite_xid_lo,
+ &create_xid_hi, &create_xid_lo) != 8)
elog(ERROR, "could not parse filename \"%s\"", mapping_de->d_name);
- lsn = ((uint64) hi) << 32 | lo;
+ lsn = ((uint64) lsn_hi) << 32 | lsn_lo;
if (lsn < cutoff || cutoff == InvalidXLogRecPtr)
{
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 40101e0cb8..063ed65d0e 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -262,7 +262,6 @@ static bool lazy_vacuum_all_indexes(LVRelState *vacrel);
static void lazy_vacuum_heap_rel(LVRelState *vacrel);
static int lazy_vacuum_heap_page(LVRelState *vacrel, BlockNumber blkno,
Buffer buffer, int index, Buffer *vmbuffer);
-static bool lazy_check_wraparound_failsafe(LVRelState *vacrel);
static void lazy_cleanup_all_indexes(LVRelState *vacrel);
static IndexBulkDeleteResult *lazy_vacuum_one_index(Relation indrel,
IndexBulkDeleteResult *istat,
@@ -582,7 +581,7 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
{
StringInfoData buf;
char *msgfmt;
- int32 diff;
+ int64 diff;
TimestampDifference(starttime, endtime, &secs, &usecs);
@@ -641,31 +640,33 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
orig_rel_pages == 0 ? 0 :
100.0 * vacrel->scanned_pages / orig_rel_pages);
appendStringInfo(&buf,
- _("tuples: %lld removed, %lld remain, %lld are dead but not yet removable\n"),
+ _("tuples: %lld removed, %lld remain, %lld are dead but not yet removable, oldest xmin: "),
(long long) vacrel->tuples_deleted,
(long long) vacrel->new_rel_tuples,
(long long) vacrel->recently_dead_tuples);
+ appendStringInfo(&buf, XID_FMT "\n", OldestXmin);
+ orig_rel_pages = vacrel->rel_pages + vacrel->removed_pages;
if (vacrel->missed_dead_tuples > 0)
appendStringInfo(&buf,
_("tuples missed: %lld dead from %u pages not removed due to cleanup lock contention\n"),
(long long) vacrel->missed_dead_tuples,
vacrel->missed_dead_pages);
- diff = (int32) (ReadNextTransactionId() - OldestXmin);
+ diff = (int64) (ReadNextTransactionId() - OldestXmin);
appendStringInfo(&buf,
- _("removable cutoff: %u, older by %d xids when operation ended\n"),
+ _("removable cutoff: " XID_FMT ", older by " XID_FMT " xids when operation ended\n"),
OldestXmin, diff);
if (frozenxid_updated)
{
- diff = (int32) (FreezeLimit - vacrel->relfrozenxid);
+ diff = (int64) (FreezeLimit - vacrel->relfrozenxid);
appendStringInfo(&buf,
- _("new relfrozenxid: %u, which is %d xids ahead of previous value\n"),
+ _("new relfrozenxid: " XID_FMT ", which is " XID_FMT " xids ahead of previous value\n"),
FreezeLimit, diff);
}
if (minmulti_updated)
{
- diff = (int32) (MultiXactCutoff - vacrel->relminmxid);
+ diff = (int64) (MultiXactCutoff - vacrel->relminmxid);
appendStringInfo(&buf,
- _("new relminmxid: %u, which is %d mxids ahead of previous value\n"),
+ _("new relminmxid: " XID_FMT ", which is " XID_FMT " mxids ahead of previous value\n"),
MultiXactCutoff, diff);
}
if (orig_rel_pages > 0)
@@ -824,14 +825,6 @@ lazy_scan_heap(LVRelState *vacrel, int nworkers)
vacrel->indstats = (IndexBulkDeleteResult **)
palloc0(vacrel->nindexes * sizeof(IndexBulkDeleteResult *));
- /*
- * Do failsafe precheck before calling dead_items_alloc. This ensures
- * that parallel VACUUM won't be attempted when relfrozenxid is already
- * dangerously old.
- */
- lazy_check_wraparound_failsafe(vacrel);
- next_failsafe_block = 0;
-
/*
* Allocate the space for dead_items. Note that this handles parallel
* VACUUM initialization as part of allocating shared memory space used
@@ -1031,7 +1024,6 @@ lazy_scan_heap(LVRelState *vacrel, int nworkers)
*/
if (blkno - next_failsafe_block >= FAILSAFE_EVERY_PAGES)
{
- lazy_check_wraparound_failsafe(vacrel);
next_failsafe_block = blkno;
}
@@ -1462,7 +1454,9 @@ lazy_scan_new_or_empty(LVRelState *vacrel, Buffer buf, BlockNumber blkno,
if (GetRecordedFreeSpace(vacrel->rel, blkno) == 0)
{
- freespace = BLCKSZ - SizeOfPageHeaderData;
+ freespace = BufferGetPageSize(buf)
+ - SizeOfPageHeaderData
+ - sizeof(HeapPageSpecialData);
RecordPageWithFreeSpace(vacrel->rel, blkno, freespace);
}
@@ -1567,6 +1561,7 @@ lazy_scan_prune(LVRelState *vacrel,
maxoff;
ItemId itemid;
HeapTupleData tuple;
+ HeapTupleHeader htup;
HTSV_Result res;
int tuples_deleted,
lpdead_items,
@@ -1600,7 +1595,7 @@ retry:
*/
tuples_deleted = heap_page_prune(rel, buf, vistest,
InvalidTransactionId, 0, &nnewlpdead,
- &vacrel->offnum);
+ &vacrel->offnum, true);
/*
* Now scan the page to collect LP_DEAD items and check for tuples
@@ -1666,6 +1661,7 @@ retry:
tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid);
tuple.t_len = ItemIdGetLength(itemid);
tuple.t_tableOid = RelationGetRelid(rel);
+ HeapTupleCopyBaseFromPage(&tuple, page);
/*
* DEAD tuples are almost always pruned into LP_DEAD line pointers by
@@ -1729,7 +1725,7 @@ retry:
* The inserter definitely committed. But is it old enough
* that everyone sees it as committed?
*/
- xmin = HeapTupleHeaderGetXmin(tuple.t_data);
+ xmin = HeapTupleGetXmin(&tuple);
if (!TransactionIdPrecedes(xmin, vacrel->OldestXmin))
{
prunestate->all_visible = false;
@@ -1784,8 +1780,8 @@ retry:
* Check tuple left behind after pruning to see if needs to be frozen
* now.
*/
- prunestate->hastup = true; /* page won't be truncatable */
- if (heap_prepare_freeze_tuple(tuple.t_data,
+ prunestate->hastup = true;
+ if (heap_prepare_freeze_tuple(&tuple,
vacrel->relfrozenxid,
vacrel->relminmxid,
vacrel->FreezeLimit,
@@ -1836,12 +1832,9 @@ retry:
/* execute collected freezes */
for (int i = 0; i < nfrozen; i++)
{
- HeapTupleHeader htup;
-
itemid = PageGetItemId(page, frozen[i].offset);
htup = (HeapTupleHeader) PageGetItem(page, itemid);
-
- heap_execute_freeze_tuple(htup, &frozen[i]);
+ heap_execute_freeze_tuple_page(page, htup, &frozen[i]);
}
/* Now WAL-log freezing if necessary */
@@ -2005,7 +1998,13 @@ lazy_scan_noprune(LVRelState *vacrel,
*hastup = true; /* page prevents rel truncation */
tupleheader = (HeapTupleHeader) PageGetItem(page, itemid);
- if (heap_tuple_needs_freeze(tupleheader,
+ ItemPointerSet(&(tuple.t_self), blkno, offnum);
+ tuple.t_data = tupleheader;
+ tuple.t_len = ItemIdGetLength(itemid);
+ tuple.t_tableOid = RelationGetRelid(vacrel->rel);
+ HeapTupleCopyBaseFromPage(&tuple, page);
+
+ if (heap_tuple_needs_freeze(&tuple,
vacrel->FreezeLimit,
vacrel->MultiXactCutoff))
{
@@ -2295,13 +2294,6 @@ lazy_vacuum_all_indexes(LVRelState *vacrel)
Assert(TransactionIdIsNormal(vacrel->relfrozenxid));
Assert(MultiXactIdIsValid(vacrel->relminmxid));
- /* Precheck for XID wraparound emergencies */
- if (lazy_check_wraparound_failsafe(vacrel))
- {
- /* Wraparound emergency -- don't even start an index scan */
- return false;
- }
-
/* Report that we are now vacuuming indexes */
pgstat_progress_update_param(PROGRESS_VACUUM_PHASE,
PROGRESS_VACUUM_PHASE_VACUUM_INDEX);
@@ -2316,13 +2308,6 @@ lazy_vacuum_all_indexes(LVRelState *vacrel)
vacrel->indstats[idx] =
lazy_vacuum_one_index(indrel, istat, vacrel->old_live_tuples,
vacrel);
-
- if (lazy_check_wraparound_failsafe(vacrel))
- {
- /* Wraparound emergency -- end current index scan */
- allindexes = false;
- break;
- }
}
}
else
@@ -2330,13 +2315,6 @@ lazy_vacuum_all_indexes(LVRelState *vacrel)
/* Outsource everything to parallel variant */
parallel_vacuum_bulkdel_all_indexes(vacrel->pvs, vacrel->old_live_tuples,
vacrel->num_index_scans);
-
- /*
- * Do a postcheck to consider applying wraparound failsafe now. Note
- * that parallel VACUUM only gets the precheck and this postcheck.
- */
- if (lazy_check_wraparound_failsafe(vacrel))
- allindexes = false;
}
/*
@@ -2589,55 +2567,6 @@ lazy_vacuum_heap_page(LVRelState *vacrel, BlockNumber blkno, Buffer buffer,
return index;
}
-/*
- * Trigger the failsafe to avoid wraparound failure when vacrel table has a
- * relfrozenxid and/or relminmxid that is dangerously far in the past.
- * Triggering the failsafe makes the ongoing VACUUM bypass any further index
- * vacuuming and heap vacuuming. Truncating the heap is also bypassed.
- *
- * Any remaining work (work that VACUUM cannot just bypass) is typically sped
- * up when the failsafe triggers. VACUUM stops applying any cost-based delay
- * that it started out with.
- *
- * Returns true when failsafe has been triggered.
- */
-static bool
-lazy_check_wraparound_failsafe(LVRelState *vacrel)
-{
- /* Don't warn more than once per VACUUM */
- if (vacrel->failsafe_active)
- return true;
-
- if (unlikely(vacuum_xid_failsafe_check(vacrel->relfrozenxid,
- vacrel->relminmxid)))
- {
- vacrel->failsafe_active = true;
-
- /* Disable index vacuuming, index cleanup, and heap rel truncation */
- vacrel->do_index_vacuuming = false;
- vacrel->do_index_cleanup = false;
- vacrel->do_rel_truncate = false;
-
- ereport(WARNING,
- (errmsg("bypassing nonessential maintenance of table \"%s.%s.%s\" as a failsafe after %d index scans",
- get_database_name(MyDatabaseId),
- vacrel->relnamespace,
- vacrel->relname,
- vacrel->num_index_scans),
- errdetail("The table's relfrozenxid or relminmxid is too far in the past."),
- errhint("Consider increasing configuration parameter \"maintenance_work_mem\" or \"autovacuum_work_mem\".\n"
- "You might also need to consider other ways for VACUUM to keep up with the allocation of transaction IDs.")));
-
- /* Stop applying cost limits from this point on */
- VacuumCostActive = false;
- VacuumCostBalance = 0;
-
- return true;
- }
-
- return false;
-}
-
/*
* lazy_cleanup_all_indexes() -- cleanup all indexes of relation.
*/
@@ -3265,6 +3194,7 @@ heap_page_is_all_visible(LVRelState *vacrel, Buffer buf,
tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid);
tuple.t_len = ItemIdGetLength(itemid);
tuple.t_tableOid = RelationGetRelid(vacrel->rel);
+ HeapTupleCopyBaseFromPage(&tuple, page);
switch (HeapTupleSatisfiesVacuum(&tuple, vacrel->OldestXmin, buf))
{
@@ -3284,7 +3214,7 @@ heap_page_is_all_visible(LVRelState *vacrel, Buffer buf,
* The inserter definitely committed. But is it old enough
* that everyone sees it as committed?
*/
- xmin = HeapTupleHeaderGetXmin(tuple.t_data);
+ xmin = HeapTupleGetXmin(&tuple);
if (!TransactionIdPrecedes(xmin, vacrel->OldestXmin))
{
all_visible = false;
@@ -3298,7 +3228,7 @@ heap_page_is_all_visible(LVRelState *vacrel, Buffer buf,
/* Check whether this tuple is already frozen or not */
if (all_visible && *all_frozen &&
- heap_tuple_needs_eventual_freeze(tuple.t_data))
+ heap_tuple_needs_eventual_freeze(&tuple))
*all_frozen = false;
}
break;
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 6b5f01e1d0..75840325bf 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -546,6 +546,7 @@ _bt_getroot(Relation rel, int access)
rootblkno = rootopaque->btpo_next;
}
+ /* Note: can't check btpo_level on deleted pages */
if (rootopaque->btpo_level != rootlevel)
elog(ERROR, "root page %u of index \"%s\" has level %u, expected %u",
rootblkno, RelationGetRelationName(rel),
@@ -649,6 +650,7 @@ _bt_gettrueroot(Relation rel)
rootblkno = rootopaque->btpo_next;
}
+ /* Note: can't check btpo_level on deleted pages */
if (rootopaque->btpo_level != rootlevel)
elog(ERROR, "root page %u of index \"%s\" has level %u, expected %u",
rootblkno, RelationGetRelationName(rel),
diff --git a/src/backend/access/nbtree/nbtsplitloc.c b/src/backend/access/nbtree/nbtsplitloc.c
index c46594e1a2..c0bcff5755 100644
--- a/src/backend/access/nbtree/nbtsplitloc.c
+++ b/src/backend/access/nbtree/nbtsplitloc.c
@@ -141,6 +141,7 @@ _bt_findsplitloc(Relation rel,
olddataitemstoleft,
perfectpenalty,
leaffillfactor;
+ int maxTupleEnd PG_USED_FOR_ASSERTS_ONLY;
FindSplitData state;
FindSplitStrat strategy;
ItemId itemid;
@@ -154,6 +155,7 @@ _bt_findsplitloc(Relation rel,
opaque = (BTPageOpaque) PageGetSpecialPointer(origpage);
maxoff = PageGetMaxOffsetNumber(origpage);
+ maxTupleEnd = ItemIdGetTupleEnd(PageGetItemId(origpage, P_HIKEY));
/* Total free space available on a btree page, after fixed overhead */
leftspace = rightspace =
@@ -215,6 +217,15 @@ _bt_findsplitloc(Relation rel,
itemid = PageGetItemId(origpage, offnum);
itemsz = MAXALIGN(ItemIdGetLength(itemid)) + sizeof(ItemIdData);
+#ifdef USE_ASSERT_CHECKING
+ /* Ending of rightmost tuple on a page can be shifted relative to
+ * left boundary of BTPageOpaqueData due to conversion from EE96,
+ * which used different BTPageOpaqueData layout. It is only checked
+ * in the assert below.
+ */
+ if (maxTupleEnd < ItemIdGetTupleEnd(itemid))
+ maxTupleEnd = ItemIdGetTupleEnd(itemid);
+#endif
/*
* When item offset number is not newitemoff, neither side of the
* split can be newitem. Record a split after the previous data item
@@ -249,7 +260,7 @@ _bt_findsplitloc(Relation rel,
* (Though only when it's possible that newitem will end up alone on new
* right page.)
*/
- Assert(olddataitemstoleft == olddataitemstotal);
+ Assert(olddataitemstoleft + ((PageHeader)origpage)->pd_special - maxTupleEnd == olddataitemstotal);
if (newitemoff > maxoff)
_bt_recsplitloc(&state, newitemoff, false, olddataitemstotal, 0);
diff --git a/src/backend/access/nbtree/nbtxlog.c b/src/backend/access/nbtree/nbtxlog.c
index 611f412ba8..d8294b429b 100644
--- a/src/backend/access/nbtree/nbtxlog.c
+++ b/src/backend/access/nbtree/nbtxlog.c
@@ -15,6 +15,8 @@
#include "postgres.h"
#include "access/bufmask.h"
+#include "access/heapam_xlog.h"
+#include "access/htup_details.h"
#include "access/nbtree.h"
#include "access/nbtxlog.h"
#include "access/transam.h"
diff --git a/src/backend/access/rmgrdesc/clogdesc.c b/src/backend/access/rmgrdesc/clogdesc.c
index 87513732be..e950dd0bdc 100644
--- a/src/backend/access/rmgrdesc/clogdesc.c
+++ b/src/backend/access/rmgrdesc/clogdesc.c
@@ -25,17 +25,17 @@ clog_desc(StringInfo buf, XLogReaderState *record)
if (info == CLOG_ZEROPAGE)
{
- int pageno;
+ int64 pageno;
- memcpy(&pageno, rec, sizeof(int));
- appendStringInfo(buf, "page %d", pageno);
+ memcpy(&pageno, rec, sizeof(int64));
+ appendStringInfo(buf, "page " INT64_FORMAT, pageno);
}
else if (info == CLOG_TRUNCATE)
{
xl_clog_truncate xlrec;
memcpy(&xlrec, rec, sizeof(xl_clog_truncate));
- appendStringInfo(buf, "page %d; oldestXact %u",
+ appendStringInfo(buf, "page " INT64_FORMAT "; oldestXact " XID_FMT,
xlrec.pageno, xlrec.oldestXact);
}
}
diff --git a/src/backend/access/rmgrdesc/committsdesc.c b/src/backend/access/rmgrdesc/committsdesc.c
index 3a65538bb0..904e13a882 100644
--- a/src/backend/access/rmgrdesc/committsdesc.c
+++ b/src/backend/access/rmgrdesc/committsdesc.c
@@ -26,16 +26,16 @@ commit_ts_desc(StringInfo buf, XLogReaderState *record)
if (info == COMMIT_TS_ZEROPAGE)
{
- int pageno;
+ int64 pageno;
- memcpy(&pageno, rec, sizeof(int));
- appendStringInfo(buf, "%d", pageno);
+ memcpy(&pageno, rec, sizeof(int64));
+ appendStringInfo(buf, INT64_FORMAT, pageno);
}
else if (info == COMMIT_TS_TRUNCATE)
{
xl_commit_ts_truncate *trunc = (xl_commit_ts_truncate *) rec;
- appendStringInfo(buf, "pageno %d, oldestXid %u",
+ appendStringInfo(buf, "pageno " INT64_FORMAT ", oldestXid " XID_FMT,
trunc->pageno, trunc->oldestXid);
}
}
diff --git a/src/backend/access/rmgrdesc/gistdesc.c b/src/backend/access/rmgrdesc/gistdesc.c
index 9cab4fa158..0b1a7c6ad8 100644
--- a/src/backend/access/rmgrdesc/gistdesc.c
+++ b/src/backend/access/rmgrdesc/gistdesc.c
@@ -26,17 +26,16 @@ out_gistxlogPageUpdate(StringInfo buf, gistxlogPageUpdate *xlrec)
static void
out_gistxlogPageReuse(StringInfo buf, gistxlogPageReuse *xlrec)
{
- appendStringInfo(buf, "rel %u/%u/%u; blk %u; latestRemovedXid %u:%u",
+ appendStringInfo(buf, "rel %u/%u/%u; blk %u; latestRemovedXid " XID_FMT,
xlrec->node.spcNode, xlrec->node.dbNode,
xlrec->node.relNode, xlrec->block,
- EpochFromFullTransactionId(xlrec->latestRemovedFullXid),
XidFromFullTransactionId(xlrec->latestRemovedFullXid));
}
static void
out_gistxlogDelete(StringInfo buf, gistxlogDelete *xlrec)
{
- appendStringInfo(buf, "delete: latestRemovedXid %u, nitems: %u",
+ appendStringInfo(buf, "delete: latestRemovedXid " XID_FMT ", nitems: %u",
xlrec->latestRemovedXid, xlrec->ntodelete);
}
@@ -51,8 +50,7 @@ out_gistxlogPageSplit(StringInfo buf, gistxlogPageSplit *xlrec)
static void
out_gistxlogPageDelete(StringInfo buf, gistxlogPageDelete *xlrec)
{
- appendStringInfo(buf, "deleteXid %u:%u; downlink %u",
- EpochFromFullTransactionId(xlrec->deleteXid),
+ appendStringInfo(buf, "deleteXid " XID_FMT "; downlink %u",
XidFromFullTransactionId(xlrec->deleteXid),
xlrec->downlinkOffset);
}
diff --git a/src/backend/access/rmgrdesc/hashdesc.c b/src/backend/access/rmgrdesc/hashdesc.c
index ef443bdb16..d9f0c40fbf 100644
--- a/src/backend/access/rmgrdesc/hashdesc.c
+++ b/src/backend/access/rmgrdesc/hashdesc.c
@@ -113,7 +113,7 @@ hash_desc(StringInfo buf, XLogReaderState *record)
{
xl_hash_vacuum_one_page *xlrec = (xl_hash_vacuum_one_page *) rec;
- appendStringInfo(buf, "ntuples %d, latestRemovedXid %u",
+ appendStringInfo(buf, "ntuples %d, latestRemovedXid " XID_FMT,
xlrec->ntuples,
xlrec->latestRemovedXid);
break;
diff --git a/src/backend/access/rmgrdesc/heapdesc.c b/src/backend/access/rmgrdesc/heapdesc.c
index 6238085d65..12140ba370 100644
--- a/src/backend/access/rmgrdesc/heapdesc.c
+++ b/src/backend/access/rmgrdesc/heapdesc.c
@@ -58,12 +58,12 @@ heap_desc(StringInfo buf, XLogReaderState *record)
{
xl_heap_update *xlrec = (xl_heap_update *) rec;
- appendStringInfo(buf, "off %u xmax %u flags 0x%02X ",
+ appendStringInfo(buf, "off %u xmax " XID_FMT " flags 0x%02X ",
xlrec->old_offnum,
xlrec->old_xmax,
xlrec->flags);
out_infobits(buf, xlrec->old_infobits_set);
- appendStringInfo(buf, "; new off %u xmax %u",
+ appendStringInfo(buf, "; new off %u xmax " XID_FMT,
xlrec->new_offnum,
xlrec->new_xmax);
}
@@ -71,12 +71,12 @@ heap_desc(StringInfo buf, XLogReaderState *record)
{
xl_heap_update *xlrec = (xl_heap_update *) rec;
- appendStringInfo(buf, "off %u xmax %u flags 0x%02X ",
+ appendStringInfo(buf, "off %u xmax " XID_FMT " flags 0x%02X ",
xlrec->old_offnum,
xlrec->old_xmax,
xlrec->flags);
out_infobits(buf, xlrec->old_infobits_set);
- appendStringInfo(buf, "; new off %u xmax %u",
+ appendStringInfo(buf, "; new off %u xmax " XID_FMT,
xlrec->new_offnum,
xlrec->new_xmax);
}
@@ -103,7 +103,7 @@ heap_desc(StringInfo buf, XLogReaderState *record)
{
xl_heap_lock *xlrec = (xl_heap_lock *) rec;
- appendStringInfo(buf, "off %u: xid %u: flags 0x%02X ",
+ appendStringInfo(buf, "off %u: xid " XID_FMT ": flags 0x%02X ",
xlrec->offnum, xlrec->locking_xid, xlrec->flags);
out_infobits(buf, xlrec->infobits_set);
}
@@ -125,7 +125,7 @@ heap2_desc(StringInfo buf, XLogReaderState *record)
{
xl_heap_prune *xlrec = (xl_heap_prune *) rec;
- appendStringInfo(buf, "latestRemovedXid %u nredirected %u ndead %u",
+ appendStringInfo(buf, "latestRemovedXid " XID_FMT " nredirected %u ndead %u",
xlrec->latestRemovedXid,
xlrec->nredirected,
xlrec->ndead);
@@ -140,14 +140,14 @@ heap2_desc(StringInfo buf, XLogReaderState *record)
{
xl_heap_freeze_page *xlrec = (xl_heap_freeze_page *) rec;
- appendStringInfo(buf, "cutoff xid %u ntuples %u",
+ appendStringInfo(buf, "cutoff xid " XID_FMT " ntuples %u",
xlrec->cutoff_xid, xlrec->ntuples);
}
else if (info == XLOG_HEAP2_VISIBLE)
{
xl_heap_visible *xlrec = (xl_heap_visible *) rec;
- appendStringInfo(buf, "cutoff xid %u flags 0x%02X",
+ appendStringInfo(buf, "cutoff xid " XID_FMT " flags 0x%02X",
xlrec->cutoff_xid, xlrec->flags);
}
else if (info == XLOG_HEAP2_MULTI_INSERT)
@@ -161,7 +161,7 @@ heap2_desc(StringInfo buf, XLogReaderState *record)
{
xl_heap_lock_updated *xlrec = (xl_heap_lock_updated *) rec;
- appendStringInfo(buf, "off %u: xmax %u: flags 0x%02X ",
+ appendStringInfo(buf, "off %u: xmax " XID_FMT ": flags 0x%02X ",
xlrec->offnum, xlrec->xmax, xlrec->flags);
out_infobits(buf, xlrec->infobits_set);
}
@@ -180,6 +180,23 @@ heap2_desc(StringInfo buf, XLogReaderState *record)
}
}
+void
+heap3_desc(StringInfo buf, XLogReaderState *record)
+{
+ char *rec = XLogRecGetData(record);
+ uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+
+ info &= XLOG_HEAP_OPMASK;
+ if (info == XLOG_HEAP3_BASE_SHIFT)
+ {
+ xl_heap_base_shift *xlrec = (xl_heap_base_shift *) rec;
+
+ appendStringInfo(buf, "%s delta " INT64_FORMAT " ",
+ xlrec->multi ? "MultiXactId" : "XactId",
+ xlrec->delta);
+ }
+}
+
const char *
heap_identify(uint8 info)
{
@@ -263,3 +280,18 @@ heap2_identify(uint8 info)
return id;
}
+
+const char *
+heap3_identify(uint8 info)
+{
+ const char *id = NULL;
+
+ switch (info & ~XLR_INFO_MASK)
+ {
+ case XLOG_HEAP3_BASE_SHIFT:
+ id = "BASE_SHIFT";
+ break;
+ }
+
+ return id;
+}
diff --git a/src/backend/access/rmgrdesc/mxactdesc.c b/src/backend/access/rmgrdesc/mxactdesc.c
index 7076be2b3f..fba52ac9f4 100644
--- a/src/backend/access/rmgrdesc/mxactdesc.c
+++ b/src/backend/access/rmgrdesc/mxactdesc.c
@@ -19,7 +19,7 @@
static void
out_member(StringInfo buf, MultiXactMember *member)
{
- appendStringInfo(buf, "%u ", member->xid);
+ appendStringInfo(buf, XID_FMT " ", member->xid);
switch (member->status)
{
case MultiXactStatusForKeyShare:
@@ -55,18 +55,18 @@ multixact_desc(StringInfo buf, XLogReaderState *record)
if (info == XLOG_MULTIXACT_ZERO_OFF_PAGE ||
info == XLOG_MULTIXACT_ZERO_MEM_PAGE)
{
- int pageno;
+ int64 pageno;
memcpy(&pageno, rec, sizeof(int));
- appendStringInfo(buf, "%d", pageno);
+ appendStringInfo(buf, INT64_FORMAT, pageno);
}
else if (info == XLOG_MULTIXACT_CREATE_ID)
{
xl_multixact_create *xlrec = (xl_multixact_create *) rec;
int i;
- appendStringInfo(buf, "%u offset %u nmembers %d: ", xlrec->mid,
- xlrec->moff, xlrec->nmembers);
+ appendStringInfo(buf, XID_FMT " offset " INT64_FORMAT " nmembers %d: ",
+ xlrec->mid, xlrec->moff, xlrec->nmembers);
for (i = 0; i < xlrec->nmembers; i++)
out_member(buf, &xlrec->members[i]);
}
@@ -74,7 +74,7 @@ multixact_desc(StringInfo buf, XLogReaderState *record)
{
xl_multixact_truncate *xlrec = (xl_multixact_truncate *) rec;
- appendStringInfo(buf, "offsets [%u, %u), members [%u, %u)",
+ appendStringInfo(buf, "offsets [" XID_FMT ", " XID_FMT "), members [" INT64_FORMAT ", " INT64_FORMAT ")",
xlrec->startTruncOff, xlrec->endTruncOff,
xlrec->startTruncMemb, xlrec->endTruncMemb);
}
diff --git a/src/backend/access/rmgrdesc/nbtdesc.c b/src/backend/access/rmgrdesc/nbtdesc.c
index dfbbf4ebaf..8aea94b873 100644
--- a/src/backend/access/rmgrdesc/nbtdesc.c
+++ b/src/backend/access/rmgrdesc/nbtdesc.c
@@ -63,7 +63,7 @@ btree_desc(StringInfo buf, XLogReaderState *record)
{
xl_btree_delete *xlrec = (xl_btree_delete *) rec;
- appendStringInfo(buf, "latestRemovedXid %u; ndeleted %u; nupdated %u",
+ appendStringInfo(buf, "latestRemovedXid " XID_FMT "; ndeleted %u; nupdated %u",
xlrec->latestRemovedXid, xlrec->ndeleted, xlrec->nupdated);
break;
}
@@ -80,9 +80,8 @@ btree_desc(StringInfo buf, XLogReaderState *record)
{
xl_btree_unlink_page *xlrec = (xl_btree_unlink_page *) rec;
- appendStringInfo(buf, "left %u; right %u; level %u; safexid %u:%u; ",
+ appendStringInfo(buf, "left %u; right %u; level %u; safexid " XID_FMT "; ",
xlrec->leftsib, xlrec->rightsib, xlrec->level,
- EpochFromFullTransactionId(xlrec->safexid),
XidFromFullTransactionId(xlrec->safexid));
appendStringInfo(buf, "leafleft %u; leafright %u; leaftopparent %u",
xlrec->leafleftsib, xlrec->leafrightsib,
@@ -100,10 +99,9 @@ btree_desc(StringInfo buf, XLogReaderState *record)
{
xl_btree_reuse_page *xlrec = (xl_btree_reuse_page *) rec;
- appendStringInfo(buf, "rel %u/%u/%u; latestRemovedXid %u:%u",
+ appendStringInfo(buf, "rel %u/%u/%u; latestRemovedXid " XID_FMT,
xlrec->node.spcNode, xlrec->node.dbNode,
xlrec->node.relNode,
- EpochFromFullTransactionId(xlrec->latestRemovedFullXid),
XidFromFullTransactionId(xlrec->latestRemovedFullXid));
break;
}
diff --git a/src/backend/access/rmgrdesc/spgdesc.c b/src/backend/access/rmgrdesc/spgdesc.c
index d5d921a42a..a094ac738b 100644
--- a/src/backend/access/rmgrdesc/spgdesc.c
+++ b/src/backend/access/rmgrdesc/spgdesc.c
@@ -118,7 +118,7 @@ spg_desc(StringInfo buf, XLogReaderState *record)
{
spgxlogVacuumRedirect *xlrec = (spgxlogVacuumRedirect *) rec;
- appendStringInfo(buf, "ntoplaceholder: %u, firstplaceholder: %u, newestredirectxid: %u",
+ appendStringInfo(buf, "ntoplaceholder: %u, firstplaceholder: %u, newestredirectxid: " XID_FMT,
xlrec->nToPlaceholder,
xlrec->firstPlaceholder,
xlrec->newestRedirectXid);
diff --git a/src/backend/access/rmgrdesc/standbydesc.c b/src/backend/access/rmgrdesc/standbydesc.c
index 2dba39e349..57fa4b729e 100644
--- a/src/backend/access/rmgrdesc/standbydesc.c
+++ b/src/backend/access/rmgrdesc/standbydesc.c
@@ -21,7 +21,7 @@ standby_desc_running_xacts(StringInfo buf, xl_running_xacts *xlrec)
{
int i;
- appendStringInfo(buf, "nextXid %u latestCompletedXid %u oldestRunningXid %u",
+ appendStringInfo(buf, "nextXid " XID_FMT " latestCompletedXid " XID_FMT " oldestRunningXid " XID_FMT,
xlrec->nextXid,
xlrec->latestCompletedXid,
xlrec->oldestRunningXid);
@@ -29,7 +29,7 @@ standby_desc_running_xacts(StringInfo buf, xl_running_xacts *xlrec)
{
appendStringInfo(buf, "; %d xacts:", xlrec->xcnt);
for (i = 0; i < xlrec->xcnt; i++)
- appendStringInfo(buf, " %u", xlrec->xids[i]);
+ appendStringInfo(buf, " " XID_FMT, xlrec->xids[i]);
}
if (xlrec->subxid_overflow)
@@ -48,7 +48,7 @@ standby_desc(StringInfo buf, XLogReaderState *record)
int i;
for (i = 0; i < xlrec->nlocks; i++)
- appendStringInfo(buf, "xid %u db %u rel %u ",
+ appendStringInfo(buf, "xid " XID_FMT " db %u rel %u ",
xlrec->locks[i].xid, xlrec->locks[i].dbOid,
xlrec->locks[i].relOid);
}
diff --git a/src/backend/access/rmgrdesc/xactdesc.c b/src/backend/access/rmgrdesc/xactdesc.c
index 025d556f6c..d15de29185 100644
--- a/src/backend/access/rmgrdesc/xactdesc.c
+++ b/src/backend/access/rmgrdesc/xactdesc.c
@@ -99,7 +99,8 @@ ParseCommitRecord(uint8 info, xl_xact_commit *xlrec, xl_xact_parsed_commit *pars
{
xl_xact_twophase *xl_twophase = (xl_xact_twophase *) data;
- parsed->twophase_xid = xl_twophase->xid;
+ parsed->twophase_xid =
+ ((uint64) xl_twophase->xid_hi << 32) | xl_twophase->xid_lo;
data += sizeof(xl_xact_twophase);
@@ -183,7 +184,8 @@ ParseAbortRecord(uint8 info, xl_xact_abort *xlrec, xl_xact_parsed_abort *parsed)
{
xl_xact_twophase *xl_twophase = (xl_xact_twophase *) data;
- parsed->twophase_xid = xl_twophase->xid;
+ parsed->twophase_xid =
+ ((uint64) xl_twophase->xid_hi << 32) | xl_twophase->xid_lo;
data += sizeof(xl_xact_twophase);
@@ -276,7 +278,7 @@ xact_desc_subxacts(StringInfo buf, int nsubxacts, TransactionId *subxacts)
{
appendStringInfoString(buf, "; subxacts:");
for (i = 0; i < nsubxacts; i++)
- appendStringInfo(buf, " %u", subxacts[i]);
+ appendStringInfo(buf, " " XID_FMT, subxacts[i]);
}
}
@@ -289,7 +291,7 @@ xact_desc_commit(StringInfo buf, uint8 info, xl_xact_commit *xlrec, RepOriginId
/* If this is a prepared xact, show the xid of the original xact */
if (TransactionIdIsValid(parsed.twophase_xid))
- appendStringInfo(buf, "%u: ", parsed.twophase_xid);
+ appendStringInfo(buf, XID_FMT ": ", parsed.twophase_xid);
appendStringInfoString(buf, timestamptz_to_str(xlrec->xact_time));
@@ -324,7 +326,7 @@ xact_desc_abort(StringInfo buf, uint8 info, xl_xact_abort *xlrec, RepOriginId or
/* If this is a prepared xact, show the xid of the original xact */
if (TransactionIdIsValid(parsed.twophase_xid))
- appendStringInfo(buf, "%u: ", parsed.twophase_xid);
+ appendStringInfo(buf, XID_FMT ": ", parsed.twophase_xid);
appendStringInfoString(buf, timestamptz_to_str(xlrec->xact_time));
@@ -377,7 +379,7 @@ xact_desc_assignment(StringInfo buf, xl_xact_assignment *xlrec)
appendStringInfoString(buf, "subxacts:");
for (i = 0; i < xlrec->nsubxacts; i++)
- appendStringInfo(buf, " %u", xlrec->xsub[i]);
+ appendStringInfo(buf, " " XID_FMT, xlrec->xsub[i]);
}
void
@@ -416,7 +418,7 @@ xact_desc(StringInfo buf, XLogReaderState *record)
* interested in the top-level xid that issued the record and which
* xids are being reported here.
*/
- appendStringInfo(buf, "xtop %u: ", xlrec->xtop);
+ appendStringInfo(buf, "xtop " XID_FMT ": ", xlrec->xtop);
xact_desc_assignment(buf, xlrec);
}
else if (info == XLOG_XACT_INVALIDATIONS)
diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c
index e7452af679..ee897a96c7 100644
--- a/src/backend/access/rmgrdesc/xlogdesc.c
+++ b/src/backend/access/rmgrdesc/xlogdesc.c
@@ -45,15 +45,14 @@ xlog_desc(StringInfo buf, XLogReaderState *record)
CheckPoint *checkpoint = (CheckPoint *) rec;
appendStringInfo(buf, "redo %X/%X; "
- "tli %u; prev tli %u; fpw %s; xid %u:%u; oid %u; multi %u; offset %u; "
- "oldest xid %u in DB %u; oldest multi %u in DB %u; "
- "oldest/newest commit timestamp xid: %u/%u; "
- "oldest running xid %u; %s",
+ "tli %u; prev tli %u; fpw %s; xid " XID_FMT "; oid %u; multi " XID_FMT "; offset " INT64_FORMAT "; "
+ "oldest xid " XID_FMT " in DB %u; oldest multi " XID_FMT " in DB %u; "
+ "oldest/newest commit timestamp xid: " XID_FMT "/" XID_FMT "; "
+ "oldest running xid " XID_FMT "; %s",
LSN_FORMAT_ARGS(checkpoint->redo),
checkpoint->ThisTimeLineID,
checkpoint->PrevTimeLineID,
checkpoint->fullPageWrites ? "true" : "false",
- EpochFromFullTransactionId(checkpoint->nextXid),
XidFromFullTransactionId(checkpoint->nextXid),
checkpoint->nextOid,
checkpoint->nextMulti,
diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c
index de787c3d37..6a3dee5738 100644
--- a/src/backend/access/transam/clog.c
+++ b/src/backend/access/transam/clog.c
@@ -89,24 +89,24 @@ static SlruCtlData XactCtlData;
#define XactCtl (&XactCtlData)
-static int ZeroCLOGPage(int pageno, bool writeXlog);
-static bool CLOGPagePrecedes(int page1, int page2);
-static void WriteZeroPageXlogRec(int pageno);
-static void WriteTruncateXlogRec(int pageno, TransactionId oldestXact,
+static int ZeroCLOGPage(int64 pageno, bool writeXlog);
+static bool CLOGPagePrecedes(int64 page1, int64 page2);
+static void WriteZeroPageXlogRec(int64 pageno);
+static void WriteTruncateXlogRec(int64 pageno, TransactionId oldestXact,
Oid oldestXactDb);
static void TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
TransactionId *subxids, XidStatus status,
- XLogRecPtr lsn, int pageno,
+ XLogRecPtr lsn, int64 pageno,
bool all_xact_same_page);
static void TransactionIdSetStatusBit(TransactionId xid, XidStatus status,
XLogRecPtr lsn, int slotno);
static void set_status_by_pages(int nsubxids, TransactionId *subxids,
XidStatus status, XLogRecPtr lsn);
static bool TransactionGroupUpdateXidStatus(TransactionId xid,
- XidStatus status, XLogRecPtr lsn, int pageno);
+ XidStatus status, XLogRecPtr lsn, int64 pageno);
static void TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids,
TransactionId *subxids, XidStatus status,
- XLogRecPtr lsn, int pageno);
+ XLogRecPtr lsn, int64 pageno);
/*
@@ -164,7 +164,7 @@ void
TransactionIdSetTreeStatus(TransactionId xid, int nsubxids,
TransactionId *subxids, XidStatus status, XLogRecPtr lsn)
{
- int pageno = TransactionIdToPage(xid); /* get page of parent */
+ int64 pageno = TransactionIdToPage(xid); /* get page of parent */
int i;
Assert(status == TRANSACTION_STATUS_COMMITTED ||
@@ -238,7 +238,7 @@ static void
set_status_by_pages(int nsubxids, TransactionId *subxids,
XidStatus status, XLogRecPtr lsn)
{
- int pageno = TransactionIdToPage(subxids[0]);
+ int64 pageno = TransactionIdToPage(subxids[0]);
int offset = 0;
int i = 0;
@@ -247,7 +247,7 @@ set_status_by_pages(int nsubxids, TransactionId *subxids,
while (i < nsubxids)
{
int num_on_page = 0;
- int nextpageno;
+ int64 nextpageno;
do
{
@@ -273,7 +273,7 @@ set_status_by_pages(int nsubxids, TransactionId *subxids,
static void
TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
TransactionId *subxids, XidStatus status,
- XLogRecPtr lsn, int pageno,
+ XLogRecPtr lsn, int64 pageno,
bool all_xact_same_page)
{
/* Can't use group update when PGPROC overflows. */
@@ -338,7 +338,7 @@ TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
static void
TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids,
TransactionId *subxids, XidStatus status,
- XLogRecPtr lsn, int pageno)
+ XLogRecPtr lsn, int64 pageno)
{
int slotno;
int i;
@@ -412,7 +412,7 @@ TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids,
*/
static bool
TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
- XLogRecPtr lsn, int pageno)
+ XLogRecPtr lsn, int64 pageno)
{
volatile PROC_HDR *procglobal = ProcGlobal;
PGPROC *proc = MyProc;
@@ -638,7 +638,7 @@ TransactionIdSetStatusBit(TransactionId xid, XidStatus status, XLogRecPtr lsn, i
XidStatus
TransactionIdGetStatus(TransactionId xid, XLogRecPtr *lsn)
{
- int pageno = TransactionIdToPage(xid);
+ int64 pageno = TransactionIdToPage(xid);
int byteno = TransactionIdToByte(xid);
int bshift = TransactionIdToBIndex(xid) * CLOG_BITS_PER_XACT;
int slotno;
@@ -712,6 +712,9 @@ void
BootStrapCLOG(void)
{
int slotno;
+ int64 pageno;
+
+ pageno = TransactionIdToPage(XidFromFullTransactionId(ShmemVariableCache->nextXid));
LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
@@ -722,6 +725,16 @@ BootStrapCLOG(void)
SimpleLruWritePage(XactCtl, slotno);
Assert(!XactCtl->shared->page_dirty[slotno]);
+ if (pageno != 0)
+ {
+ /* Create and zero the first page of the commit log */
+ slotno = ZeroCLOGPage(pageno, false);
+
+ /* Make sure it's written out */
+ SimpleLruWritePage(XactCtl, slotno);
+ Assert(!XactCtl->shared->page_dirty[slotno]);
+ }
+
LWLockRelease(XactSLRULock);
}
@@ -735,7 +748,7 @@ BootStrapCLOG(void)
* Control lock must be held at entry, and will be held at exit.
*/
static int
-ZeroCLOGPage(int pageno, bool writeXlog)
+ZeroCLOGPage(int64 pageno, bool writeXlog)
{
int slotno;
@@ -755,7 +768,7 @@ void
StartupCLOG(void)
{
TransactionId xid = XidFromFullTransactionId(ShmemVariableCache->nextXid);
- int pageno = TransactionIdToPage(xid);
+ int64 pageno = TransactionIdToPage(xid);
LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
@@ -774,7 +787,7 @@ void
TrimCLOG(void)
{
TransactionId xid = XidFromFullTransactionId(ShmemVariableCache->nextXid);
- int pageno = TransactionIdToPage(xid);
+ int64 pageno = TransactionIdToPage(xid);
LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
@@ -839,7 +852,7 @@ CheckPointCLOG(void)
void
ExtendCLOG(TransactionId newestXact)
{
- int pageno;
+ int64 pageno;
/*
* No work except at first XID of a page. But beware: just after
@@ -878,7 +891,7 @@ ExtendCLOG(TransactionId newestXact)
void
TruncateCLOG(TransactionId oldestXact, Oid oldestxid_datoid)
{
- int cutoffPage;
+ int64 cutoffPage;
/*
* The cutoff point is the start of the segment containing oldestXact. We
@@ -911,49 +924,26 @@ TruncateCLOG(TransactionId oldestXact, Oid oldestxid_datoid)
SimpleLruTruncate(XactCtl, cutoffPage);
}
-
/*
* Decide whether a CLOG page number is "older" for truncation purposes.
*
- * We need to use comparison of TransactionIds here in order to do the right
- * thing with wraparound XID arithmetic. However, TransactionIdPrecedes()
- * would get weird about permanent xact IDs. So, offset both such that xid1,
- * xid2, and xid2 + CLOG_XACTS_PER_PAGE - 1 are all normal XIDs; this offset
- * is relevant to page 0 and to the page preceding page 0.
- *
- * The page containing oldestXact-2^31 is the important edge case. The
- * portion of that page equaling or following oldestXact-2^31 is expendable,
- * but the portion preceding oldestXact-2^31 is not. When oldestXact-2^31 is
- * the first XID of a page and segment, the entire page and segment is
- * expendable, and we could truncate the segment. Recognizing that case would
- * require making oldestXact, not just the page containing oldestXact,
- * available to this callback. The benefit would be rare and small, so we
- * don't optimize that edge case.
+ * With 64xid this function is just "<", but we left it as a function in order
+ * for its calls remain "vanilla" like.
*/
static bool
-CLOGPagePrecedes(int page1, int page2)
+CLOGPagePrecedes(int64 page1, int64 page2)
{
- TransactionId xid1;
- TransactionId xid2;
-
- xid1 = ((TransactionId) page1) * CLOG_XACTS_PER_PAGE;
- xid1 += FirstNormalTransactionId + 1;
- xid2 = ((TransactionId) page2) * CLOG_XACTS_PER_PAGE;
- xid2 += FirstNormalTransactionId + 1;
-
- return (TransactionIdPrecedes(xid1, xid2) &&
- TransactionIdPrecedes(xid1, xid2 + CLOG_XACTS_PER_PAGE - 1));
+ return page1 < page2;
}
-
/*
* Write a ZEROPAGE xlog record
*/
static void
-WriteZeroPageXlogRec(int pageno)
+WriteZeroPageXlogRec(int64 pageno)
{
XLogBeginInsert();
- XLogRegisterData((char *) (&pageno), sizeof(int));
+ XLogRegisterData((char *) (&pageno), sizeof(int64));
(void) XLogInsert(RM_CLOG_ID, CLOG_ZEROPAGE);
}
@@ -964,7 +954,7 @@ WriteZeroPageXlogRec(int pageno)
* in TruncateCLOG().
*/
static void
-WriteTruncateXlogRec(int pageno, TransactionId oldestXact, Oid oldestXactDb)
+WriteTruncateXlogRec(int64 pageno, TransactionId oldestXact, Oid oldestXactDb)
{
XLogRecPtr recptr;
xl_clog_truncate xlrec;
@@ -992,10 +982,10 @@ clog_redo(XLogReaderState *record)
if (info == CLOG_ZEROPAGE)
{
- int pageno;
+ int64 pageno;
int slotno;
- memcpy(&pageno, XLogRecGetData(record), sizeof(int));
+ memcpy(&pageno, XLogRecGetData(record), sizeof(int64));
LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
diff --git a/src/backend/access/transam/commit_ts.c b/src/backend/access/transam/commit_ts.c
index 20950eb1e4..5ccc627d7c 100644
--- a/src/backend/access/transam/commit_ts.c
+++ b/src/backend/access/transam/commit_ts.c
@@ -106,16 +106,16 @@ bool track_commit_timestamp;
static void SetXidCommitTsInPage(TransactionId xid, int nsubxids,
TransactionId *subxids, TimestampTz ts,
- RepOriginId nodeid, int pageno);
+ RepOriginId nodeid, int64 pageno);
static void TransactionIdSetCommitTs(TransactionId xid, TimestampTz ts,
RepOriginId nodeid, int slotno);
static void error_commit_ts_disabled(void);
-static int ZeroCommitTsPage(int pageno, bool writeXlog);
-static bool CommitTsPagePrecedes(int page1, int page2);
+static int ZeroCommitTsPage(int64 pageno, bool writeXlog);
+static bool CommitTsPagePrecedes(int64 page1, int64 page2);
static void ActivateCommitTs(void);
static void DeactivateCommitTs(void);
-static void WriteZeroPageXlogRec(int pageno);
-static void WriteTruncateXlogRec(int pageno, TransactionId oldestXid);
+static void WriteZeroPageXlogRec(int64 pageno);
+static void WriteTruncateXlogRec(int64 pageno, TransactionId oldestXid);
/*
* TransactionTreeSetCommitTsData
@@ -173,7 +173,7 @@ TransactionTreeSetCommitTsData(TransactionId xid, int nsubxids,
i = 0;
for (;;)
{
- int pageno = TransactionIdToCTsPage(headxid);
+ int64 pageno = TransactionIdToCTsPage(headxid);
int j;
for (j = i; j < nsubxids; j++)
@@ -217,7 +217,7 @@ TransactionTreeSetCommitTsData(TransactionId xid, int nsubxids,
static void
SetXidCommitTsInPage(TransactionId xid, int nsubxids,
TransactionId *subxids, TimestampTz ts,
- RepOriginId nodeid, int pageno)
+ RepOriginId nodeid, int64 pageno)
{
int slotno;
int i;
@@ -269,7 +269,7 @@ bool
TransactionIdGetCommitTsData(TransactionId xid, TimestampTz *ts,
RepOriginId *nodeid)
{
- int pageno = TransactionIdToCTsPage(xid);
+ int64 pageno = TransactionIdToCTsPage(xid);
int entryno = TransactionIdToCTsEntry(xid);
int slotno;
CommitTimestampEntry entry;
@@ -279,7 +279,7 @@ TransactionIdGetCommitTsData(TransactionId xid, TimestampTz *ts,
if (!TransactionIdIsValid(xid))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("cannot retrieve commit timestamp for transaction %u", xid)));
+ errmsg("cannot retrieve commit timestamp for transaction %" PRIu64, xid)));
else if (!TransactionIdIsNormal(xid))
{
/* frozen and bootstrap xids are always committed far in the past */
@@ -590,7 +590,7 @@ BootStrapCommitTs(void)
* Control lock must be held at entry, and will be held at exit.
*/
static int
-ZeroCommitTsPage(int pageno, bool writeXlog)
+ZeroCommitTsPage(int64 pageno, bool writeXlog)
{
int slotno;
@@ -683,7 +683,7 @@ static void
ActivateCommitTs(void)
{
TransactionId xid;
- int pageno;
+ int64 pageno;
/* If we've done this already, there's nothing to do */
LWLockAcquire(CommitTsLock, LW_EXCLUSIVE);
@@ -816,7 +816,7 @@ CheckPointCommitTs(void)
void
ExtendCommitTs(TransactionId newestXact)
{
- int pageno;
+ int64 pageno;
/*
* Nothing to do if module not enabled. Note we do an unlocked read of
@@ -854,7 +854,7 @@ ExtendCommitTs(TransactionId newestXact)
void
TruncateCommitTs(TransactionId oldestXact)
{
- int cutoffPage;
+ int64 cutoffPage;
/*
* The cutoff point is the start of the segment containing oldestXact. We
@@ -919,49 +919,23 @@ AdvanceOldestCommitTsXid(TransactionId oldestXact)
* Decide whether a commitTS page number is "older" for truncation purposes.
* Analogous to CLOGPagePrecedes().
*
- * At default BLCKSZ, (1 << 31) % COMMIT_TS_XACTS_PER_PAGE == 128. This
- * introduces differences compared to CLOG and the other SLRUs having (1 <<
- * 31) % per_page == 0. This function never tests exactly
- * TransactionIdPrecedes(x-2^31, x). When the system reaches xidStopLimit,
- * there are two possible counts of page boundaries between oldestXact and the
- * latest XID assigned, depending on whether oldestXact is within the first
- * 128 entries of its page. Since this function doesn't know the location of
- * oldestXact within page2, it returns false for one page that actually is
- * expendable. This is a wider (yet still negligible) version of the
- * truncation opportunity that CLOGPagePrecedes() cannot recognize.
- *
- * For the sake of a worked example, number entries with decimal values such
- * that page1==1 entries range from 1.0 to 1.999. Let N+0.15 be the number of
- * pages that 2^31 entries will span (N is an integer). If oldestXact=N+2.1,
- * then the final safe XID assignment leaves newestXact=1.95. We keep page 2,
- * because entry=2.85 is the border that toggles whether entries precede the
- * last entry of the oldestXact page. While page 2 is expendable at
- * oldestXact=N+2.1, it would be precious at oldestXact=N+2.9.
+ * With 64xid this function is just "<", but we left it as a function in order
+ * for its calls remain "vanilla" like.
*/
static bool
-CommitTsPagePrecedes(int page1, int page2)
+CommitTsPagePrecedes(int64 page1, int64 page2)
{
- TransactionId xid1;
- TransactionId xid2;
-
- xid1 = ((TransactionId) page1) * COMMIT_TS_XACTS_PER_PAGE;
- xid1 += FirstNormalTransactionId + 1;
- xid2 = ((TransactionId) page2) * COMMIT_TS_XACTS_PER_PAGE;
- xid2 += FirstNormalTransactionId + 1;
-
- return (TransactionIdPrecedes(xid1, xid2) &&
- TransactionIdPrecedes(xid1, xid2 + COMMIT_TS_XACTS_PER_PAGE - 1));
+ return page1 < page2;
}
-
/*
* Write a ZEROPAGE xlog record
*/
static void
-WriteZeroPageXlogRec(int pageno)
+WriteZeroPageXlogRec(int64 pageno)
{
XLogBeginInsert();
- XLogRegisterData((char *) (&pageno), sizeof(int));
+ XLogRegisterData((char *) (&pageno), sizeof(int64));
(void) XLogInsert(RM_COMMIT_TS_ID, COMMIT_TS_ZEROPAGE);
}
@@ -969,7 +943,7 @@ WriteZeroPageXlogRec(int pageno)
* Write a TRUNCATE xlog record
*/
static void
-WriteTruncateXlogRec(int pageno, TransactionId oldestXid)
+WriteTruncateXlogRec(int64 pageno, TransactionId oldestXid)
{
xl_commit_ts_truncate xlrec;
@@ -994,10 +968,10 @@ commit_ts_redo(XLogReaderState *record)
if (info == COMMIT_TS_ZEROPAGE)
{
- int pageno;
+ int64 pageno;
int slotno;
- memcpy(&pageno, XLogRecGetData(record), sizeof(int));
+ memcpy(&pageno, XLogRecGetData(record), sizeof(int64));
LWLockAcquire(CommitTsSLRULock, LW_EXCLUSIVE);
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 6a70d49738..2e8e88f641 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -112,15 +112,15 @@
((xid) / (MultiXactOffset) MULTIXACT_OFFSETS_PER_PAGE)
#define MultiXactIdToOffsetEntry(xid) \
((xid) % (MultiXactOffset) MULTIXACT_OFFSETS_PER_PAGE)
-#define MultiXactIdToOffsetSegment(xid) (MultiXactIdToOffsetPage(xid) / SLRU_PAGES_PER_SEGMENT)
+#define MultiXactIdToOffsetSegment(xid) ((uint64)(MultiXactIdToOffsetPage(xid) / SLRU_PAGES_PER_SEGMENT))
/*
* The situation for members is a bit more complex: we store one byte of
* additional flag bits for each TransactionId. To do this without getting
- * into alignment issues, we store four bytes of flags, and then the
- * corresponding 4 Xids. Each such 5-word (20-byte) set we call a "group", and
- * are stored as a whole in pages. Thus, with 8kB BLCKSZ, we keep 409 groups
- * per page. This wastes 12 bytes per page, but that's OK -- simplicity (and
+ * into alignment issues, we store eight bytes of flags, and then the
+ * corresponding 8 Xids. Each such 9-word (72-byte) set we call a "group", and
+ * are stored as a whole in pages. Thus, with 8kB BLCKSZ, we keep 113 groups
+ * per page. This wastes 56 bytes per page, but that's OK -- simplicity (and
* performance) trumps space efficiency here.
*
* Note that the "offset" macros work with byte offset, not array indexes, so
@@ -132,7 +132,7 @@
#define MXACT_MEMBER_XACT_BITMASK ((1 << MXACT_MEMBER_BITS_PER_XACT) - 1)
/* how many full bytes of flags are there in a group? */
-#define MULTIXACT_FLAGBYTES_PER_GROUP 4
+#define MULTIXACT_FLAGBYTES_PER_GROUP 8
#define MULTIXACT_MEMBERS_PER_MEMBERGROUP \
(MULTIXACT_FLAGBYTES_PER_GROUP * MXACT_MEMBER_FLAGS_PER_BYTE)
/* size in bytes of a complete group */
@@ -142,22 +142,9 @@
#define MULTIXACT_MEMBERS_PER_PAGE \
(MULTIXACT_MEMBERGROUPS_PER_PAGE * MULTIXACT_MEMBERS_PER_MEMBERGROUP)
-/*
- * Because the number of items per page is not a divisor of the last item
- * number (member 0xFFFFFFFF), the last segment does not use the maximum number
- * of pages, and moreover the last used page therein does not use the same
- * number of items as previous pages. (Another way to say it is that the
- * 0xFFFFFFFF member is somewhere in the middle of the last page, so the page
- * has some empty space after that item.)
- *
- * This constant is the number of members in the last page of the last segment.
- */
-#define MAX_MEMBERS_IN_LAST_MEMBERS_PAGE \
- ((uint32) ((0xFFFFFFFF % MULTIXACT_MEMBERS_PER_PAGE) + 1))
-
/* page in which a member is to be found */
#define MXOffsetToMemberPage(xid) ((xid) / (TransactionId) MULTIXACT_MEMBERS_PER_PAGE)
-#define MXOffsetToMemberSegment(xid) (MXOffsetToMemberPage(xid) / SLRU_PAGES_PER_SEGMENT)
+#define MXOffsetToMemberSegment(xid) ((uint64)(MXOffsetToMemberPage(xid) / SLRU_PAGES_PER_SEGMENT))
/* Location (byte offset within page) of flag word for a given member */
#define MXOffsetToFlagsOffset(xid) \
@@ -216,22 +203,8 @@ typedef struct MultiXactStateData
MultiXactId oldestMultiXactId;
Oid oldestMultiXactDB;
- /*
- * Oldest multixact offset that is potentially referenced by a multixact
- * referenced by a relation. We don't always know this value, so there's
- * a flag here to indicate whether or not we currently do.
- */
- MultiXactOffset oldestOffset;
- bool oldestOffsetKnown;
-
/* support for anti-wraparound measures */
MultiXactId multiVacLimit;
- MultiXactId multiWarnLimit;
- MultiXactId multiStopLimit;
- MultiXactId multiWrapLimit;
-
- /* support for members anti-wraparound measures */
- MultiXactOffset offsetStopLimit; /* known if oldestOffsetKnown */
/*
* Per-backend data starts here. We have two arrays stored in the area
@@ -353,19 +326,16 @@ static void mXactCachePut(MultiXactId multi, int nmembers,
static char *mxstatus_to_string(MultiXactStatus status);
/* management of SLRU infrastructure */
-static int ZeroMultiXactOffsetPage(int pageno, bool writeXlog);
-static int ZeroMultiXactMemberPage(int pageno, bool writeXlog);
-static bool MultiXactOffsetPagePrecedes(int page1, int page2);
-static bool MultiXactMemberPagePrecedes(int page1, int page2);
+static int ZeroMultiXactOffsetPage(int64 pageno, bool writeXlog);
+static int ZeroMultiXactMemberPage(int64 pageno, bool writeXlog);
+static bool MultiXactOffsetPagePrecedes(int64 page1, int64 page2);
+static bool MultiXactMemberPagePrecedes(int64 page1, int64 page2);
static bool MultiXactOffsetPrecedes(MultiXactOffset offset1,
MultiXactOffset offset2);
static void ExtendMultiXactOffset(MultiXactId multi);
static void ExtendMultiXactMember(MultiXactOffset offset, int nmembers);
-static bool MultiXactOffsetWouldWrap(MultiXactOffset boundary,
- MultiXactOffset start, uint32 distance);
-static bool SetOffsetVacuumLimit(bool is_startup);
static bool find_multixact_start(MultiXactId multi, MultiXactOffset *result);
-static void WriteMZeroPageXlogRec(int pageno, uint8 info);
+static void WriteMZeroPageXlogRec(int64 pageno, uint8 info);
static void WriteMTruncateXlogRec(Oid oldestMultiDB,
MultiXactId startTruncOff,
MultiXactId endTruncOff,
@@ -397,6 +367,9 @@ MultiXactIdCreate(TransactionId xid1, MultiXactStatus status1,
/* MultiXactIdSetOldestMember() must have been called already. */
Assert(MultiXactIdIsValid(OldestMemberMXactId[MyBackendId]));
+ /* memset members array because with 64-bit xids it has a padding hole */
+ MemSet(members, 0, sizeof(members));
+
/*
* Note: unlike MultiXactIdExpand, we don't bother to check that both XIDs
* are still running. In typical usage, xid2 will be our own XID and the
@@ -451,7 +424,7 @@ MultiXactIdExpand(MultiXactId multi, TransactionId xid, MultiXactStatus status)
/* MultiXactIdSetOldestMember() must have been called already. */
Assert(MultiXactIdIsValid(OldestMemberMXactId[MyBackendId]));
- debug_elog5(DEBUG2, "Expand: received multi %u, xid %u status %s",
+ debug_elog5(DEBUG2, "Expand: received multi " XID_FMT ", xid " XID_FMT " status %s",
multi, xid, mxstatus_to_string(status));
/*
@@ -476,7 +449,7 @@ MultiXactIdExpand(MultiXactId multi, TransactionId xid, MultiXactStatus status)
member.status = status;
newMulti = MultiXactIdCreateFromMembers(1, &member);
- debug_elog4(DEBUG2, "Expand: %u has no members, create singleton %u",
+ debug_elog4(DEBUG2, "Expand: " XID_FMT " has no members, create singleton " XID_FMT,
multi, newMulti);
return newMulti;
}
@@ -490,7 +463,7 @@ MultiXactIdExpand(MultiXactId multi, TransactionId xid, MultiXactStatus status)
if (TransactionIdEquals(members[i].xid, xid) &&
(members[i].status == status))
{
- debug_elog4(DEBUG2, "Expand: %u is already a member of %u",
+ debug_elog4(DEBUG2, "Expand: " XID_FMT " is already a member of " XID_FMT,
xid, multi);
pfree(members);
return multi;
@@ -511,7 +484,7 @@ MultiXactIdExpand(MultiXactId multi, TransactionId xid, MultiXactStatus status)
* end of the loop.
*/
newMembers = (MultiXactMember *)
- palloc(sizeof(MultiXactMember) * (nmembers + 1));
+ palloc0(sizeof(MultiXactMember) * (nmembers + 1));
for (i = 0, j = 0; i < nmembers; i++)
{
@@ -526,12 +499,13 @@ MultiXactIdExpand(MultiXactId multi, TransactionId xid, MultiXactStatus status)
newMembers[j].xid = xid;
newMembers[j++].status = status;
+
newMulti = MultiXactIdCreateFromMembers(j, newMembers);
pfree(members);
pfree(newMembers);
- debug_elog3(DEBUG2, "Expand: returning new multi %u", newMulti);
+ debug_elog3(DEBUG2, "Expand: returning new multi " XID_FMT, newMulti);
return newMulti;
}
@@ -554,7 +528,7 @@ MultiXactIdIsRunning(MultiXactId multi, bool isLockOnly)
int nmembers;
int i;
- debug_elog3(DEBUG2, "IsRunning %u?", multi);
+ debug_elog3(DEBUG2, "IsRunning " XID_FMT "?", multi);
/*
* "false" here means we assume our callers have checked that the given
@@ -594,7 +568,7 @@ MultiXactIdIsRunning(MultiXactId multi, bool isLockOnly)
{
if (TransactionIdIsInProgress(members[i].xid))
{
- debug_elog4(DEBUG2, "IsRunning: member %d (%u) is running",
+ debug_elog4(DEBUG2, "IsRunning: member %d (" XID_FMT ") is running",
i, members[i].xid);
pfree(members);
return true;
@@ -603,7 +577,7 @@ MultiXactIdIsRunning(MultiXactId multi, bool isLockOnly)
pfree(members);
- debug_elog3(DEBUG2, "IsRunning: %u is not running", multi);
+ debug_elog3(DEBUG2, "IsRunning: " XID_FMT " is not running", multi);
return false;
}
@@ -657,7 +631,7 @@ MultiXactIdSetOldestMember(void)
LWLockRelease(MultiXactGenLock);
- debug_elog4(DEBUG2, "MultiXact: setting OldestMember[%d] = %u",
+ debug_elog4(DEBUG2, "MultiXact: setting OldestMember[%d] = " XID_FMT,
MyBackendId, nextMXact);
}
}
@@ -722,7 +696,7 @@ MultiXactIdSetOldestVisible(void)
LWLockRelease(MultiXactGenLock);
- debug_elog4(DEBUG2, "MultiXact: setting OldestVisible[%d] = %u",
+ debug_elog4(DEBUG2, "MultiXact: setting OldestVisible[%d] = " XID_FMT,
MyBackendId, oldestMXact);
}
}
@@ -741,9 +715,6 @@ ReadNextMultiXactId(void)
mxid = MultiXactState->nextMXact;
LWLockRelease(MultiXactGenLock);
- if (mxid < FirstMultiXactId)
- mxid = FirstMultiXactId;
-
return mxid;
}
@@ -874,8 +845,8 @@ static void
RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset,
int nmembers, MultiXactMember *members)
{
- int pageno;
- int prev_pageno;
+ int64 pageno;
+ int64 prev_pageno;
int entryno;
int slotno;
MultiXactOffset *offptr;
@@ -911,8 +882,8 @@ RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset,
for (i = 0; i < nmembers; i++, offset++)
{
TransactionId *memberptr;
- uint32 *flagsptr;
- uint32 flagsval;
+ uint64 *flagsptr;
+ uint64 flagsval;
int bshift;
int flagsoff;
int memberoff;
@@ -933,14 +904,15 @@ RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset,
memberptr = (TransactionId *)
(MultiXactMemberCtl->shared->page_buffer[slotno] + memberoff);
+
*memberptr = members[i].xid;
- flagsptr = (uint32 *)
+ flagsptr = (uint64 *)
(MultiXactMemberCtl->shared->page_buffer[slotno] + flagsoff);
flagsval = *flagsptr;
- flagsval &= ~(((1 << MXACT_MEMBER_BITS_PER_XACT) - 1) << bshift);
- flagsval |= (members[i].status << bshift);
+ flagsval &= ~((uint64)((1 << MXACT_MEMBER_BITS_PER_XACT) - 1) << bshift);
+ flagsval |= ((uint64)members[i].status << bshift);
*flagsptr = flagsval;
MultiXactMemberCtl->shared->page_dirty[slotno] = true;
@@ -993,8 +965,6 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
* If we're past multiVacLimit or the safe threshold for member storage
* space, or we don't know what the safe threshold for member storage is,
* start trying to force autovacuum cycles.
- * If we're past multiWarnLimit, start issuing warnings.
- * If we're past multiStopLimit, refuse to create new MultiXactIds.
*
* Note these are pretty much the same protections in GetNewTransactionId.
*----------
@@ -1008,41 +978,9 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
* possibility of deadlock while doing get_database_name(). First,
* copy all the shared values we'll need in this path.
*/
- MultiXactId multiWarnLimit = MultiXactState->multiWarnLimit;
- MultiXactId multiStopLimit = MultiXactState->multiStopLimit;
- MultiXactId multiWrapLimit = MultiXactState->multiWrapLimit;
- Oid oldest_datoid = MultiXactState->oldestMultiXactDB;
LWLockRelease(MultiXactGenLock);
- if (IsUnderPostmaster &&
- !MultiXactIdPrecedes(result, multiStopLimit))
- {
- char *oldest_datname = get_database_name(oldest_datoid);
-
- /*
- * Immediately kick autovacuum into action as we're already in
- * ERROR territory.
- */
- SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
-
- /* complain even if that DB has disappeared */
- if (oldest_datname)
- ereport(ERROR,
- (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg("database is not accepting commands that generate new MultiXactIds to avoid wraparound data loss in database \"%s\"",
- oldest_datname),
- errhint("Execute a database-wide VACUUM in that database.\n"
- "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
- else
- ereport(ERROR,
- (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg("database is not accepting commands that generate new MultiXactIds to avoid wraparound data loss in database with OID %u",
- oldest_datoid),
- errhint("Execute a database-wide VACUUM in that database.\n"
- "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
- }
-
/*
* To avoid swamping the postmaster with signals, we issue the autovac
* request only once per 64K multis generated. This still gives
@@ -1051,36 +989,9 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
if (IsUnderPostmaster && (result % 65536) == 0)
SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
- if (!MultiXactIdPrecedes(result, multiWarnLimit))
- {
- char *oldest_datname = get_database_name(oldest_datoid);
-
- /* complain even if that DB has disappeared */
- if (oldest_datname)
- ereport(WARNING,
- (errmsg_plural("database \"%s\" must be vacuumed before %u more MultiXactId is used",
- "database \"%s\" must be vacuumed before %u more MultiXactIds are used",
- multiWrapLimit - result,
- oldest_datname,
- multiWrapLimit - result),
- errhint("Execute a database-wide VACUUM in that database.\n"
- "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
- else
- ereport(WARNING,
- (errmsg_plural("database with OID %u must be vacuumed before %u more MultiXactId is used",
- "database with OID %u must be vacuumed before %u more MultiXactIds are used",
- multiWrapLimit - result,
- oldest_datoid,
- multiWrapLimit - result),
- errhint("Execute a database-wide VACUUM in that database.\n"
- "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
- }
-
/* Re-acquire lock and start over */
LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE);
result = MultiXactState->nextMXact;
- if (result < FirstMultiXactId)
- result = FirstMultiXactId;
}
/* Make sure there is room for the MXID in the file. */
@@ -1100,78 +1011,6 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
else
*offset = nextOffset;
- /*----------
- * Protect against overrun of the members space as well, with the
- * following rules:
- *
- * If we're past offsetStopLimit, refuse to generate more multis.
- * If we're close to offsetStopLimit, emit a warning.
- *
- * Arbitrarily, we start emitting warnings when we're 20 segments or less
- * from offsetStopLimit.
- *
- * Note we haven't updated the shared state yet, so if we fail at this
- * point, the multixact ID we grabbed can still be used by the next guy.
- *
- * Note that there is no point in forcing autovacuum runs here: the
- * multixact freeze settings would have to be reduced for that to have any
- * effect.
- *----------
- */
-#define OFFSET_WARN_SEGMENTS 20
- if (MultiXactState->oldestOffsetKnown &&
- MultiXactOffsetWouldWrap(MultiXactState->offsetStopLimit, nextOffset,
- nmembers))
- {
- /* see comment in the corresponding offsets wraparound case */
- SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
-
- ereport(ERROR,
- (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg("multixact \"members\" limit exceeded"),
- errdetail_plural("This command would create a multixact with %u members, but the remaining space is only enough for %u member.",
- "This command would create a multixact with %u members, but the remaining space is only enough for %u members.",
- MultiXactState->offsetStopLimit - nextOffset - 1,
- nmembers,
- MultiXactState->offsetStopLimit - nextOffset - 1),
- errhint("Execute a database-wide VACUUM in database with OID %u with reduced vacuum_multixact_freeze_min_age and vacuum_multixact_freeze_table_age settings.",
- MultiXactState->oldestMultiXactDB)));
- }
-
- /*
- * Check whether we should kick autovacuum into action, to prevent members
- * wraparound. NB we use a much larger window to trigger autovacuum than
- * just the warning limit. The warning is just a measure of last resort -
- * this is in line with GetNewTransactionId's behaviour.
- */
- if (!MultiXactState->oldestOffsetKnown ||
- (MultiXactState->nextOffset - MultiXactState->oldestOffset
- > MULTIXACT_MEMBER_SAFE_THRESHOLD))
- {
- /*
- * To avoid swamping the postmaster with signals, we issue the autovac
- * request only when crossing a segment boundary. With default
- * compilation settings that's roughly after 50k members. This still
- * gives plenty of chances before we get into real trouble.
- */
- if ((MXOffsetToMemberPage(nextOffset) / SLRU_PAGES_PER_SEGMENT) !=
- (MXOffsetToMemberPage(nextOffset + nmembers) / SLRU_PAGES_PER_SEGMENT))
- SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
- }
-
- if (MultiXactState->oldestOffsetKnown &&
- MultiXactOffsetWouldWrap(MultiXactState->offsetStopLimit,
- nextOffset,
- nmembers + MULTIXACT_MEMBERS_PER_PAGE * SLRU_PAGES_PER_SEGMENT * OFFSET_WARN_SEGMENTS))
- ereport(WARNING,
- (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg_plural("database with OID %u must be vacuumed before %d more multixact member is used",
- "database with OID %u must be vacuumed before %d more multixact members are used",
- MultiXactState->offsetStopLimit - nextOffset + nmembers,
- MultiXactState->oldestMultiXactDB,
- MultiXactState->offsetStopLimit - nextOffset + nmembers),
- errhint("Execute a database-wide VACUUM in that database with reduced vacuum_multixact_freeze_min_age and vacuum_multixact_freeze_table_age settings.")));
-
ExtendMultiXactMember(nextOffset, nmembers);
/*
@@ -1200,7 +1039,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
LWLockRelease(MultiXactGenLock);
- debug_elog4(DEBUG2, "GetNew: returning %u offset %u", result, *offset);
+ debug_elog4(DEBUG2, "GetNew: returning " XID_FMT " offset " INT64_FORMAT, result, *offset);
return result;
}
@@ -1235,8 +1074,8 @@ int
GetMultiXactIdMembers(MultiXactId multi, MultiXactMember **members,
bool from_pgupgrade, bool onlyLock)
{
- int pageno;
- int prev_pageno;
+ int64 pageno;
+ int64 prev_pageno;
int entryno;
int slotno;
MultiXactOffset *offptr;
@@ -1244,13 +1083,12 @@ GetMultiXactIdMembers(MultiXactId multi, MultiXactMember **members,
int length;
int truelength;
int i;
- MultiXactId oldestMXact;
MultiXactId nextMXact;
MultiXactId tmpMXact;
MultiXactOffset nextOffset;
MultiXactMember *ptr;
- debug_elog3(DEBUG2, "GetMembers: asked for %u", multi);
+ debug_elog3(DEBUG2, "GetMembers: asked for " XID_FMT, multi);
if (!MultiXactIdIsValid(multi) || from_pgupgrade)
{
@@ -1300,24 +1138,11 @@ GetMultiXactIdMembers(MultiXactId multi, MultiXactMember **members,
*/
LWLockAcquire(MultiXactGenLock, LW_SHARED);
- oldestMXact = MultiXactState->oldestMultiXactId;
nextMXact = MultiXactState->nextMXact;
nextOffset = MultiXactState->nextOffset;
LWLockRelease(MultiXactGenLock);
- if (MultiXactIdPrecedes(multi, oldestMXact))
- ereport(ERROR,
- (errcode(ERRCODE_INTERNAL_ERROR),
- errmsg("MultiXactId %u does no longer exist -- apparent wraparound",
- multi)));
-
- if (!MultiXactIdPrecedes(multi, nextMXact))
- ereport(ERROR,
- (errcode(ERRCODE_INTERNAL_ERROR),
- errmsg("MultiXactId %u has not been created yet -- apparent wraparound",
- multi)));
-
/*
* Find out the offset at which we need to start reading MultiXactMembers
* and the number of members in the multixact. We determine the latter as
@@ -1362,7 +1187,9 @@ retry:
offptr += entryno;
offset = *offptr;
- Assert(offset != 0);
+ if (offset == 0)
+ ereport(ERROR,
+ (errmsg("found invalid zero offset in multixact " XID_FMT, multi)));
/*
* Use the same increment rule as GetNewMultiXactId(), that is, don't
@@ -1379,10 +1206,6 @@ retry:
{
MultiXactOffset nextMXOffset;
- /* handle wraparound if needed */
- if (tmpMXact < FirstMultiXactId)
- tmpMXact = FirstMultiXactId;
-
prev_pageno = pageno;
pageno = MultiXactIdToOffsetPage(tmpMXact);
@@ -1409,7 +1232,7 @@ retry:
LWLockRelease(MultiXactOffsetSLRULock);
- ptr = (MultiXactMember *) palloc(length * sizeof(MultiXactMember));
+ ptr = (MultiXactMember *) palloc0(length * sizeof(MultiXactMember));
/* Now get the members themselves. */
LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);
@@ -1419,7 +1242,7 @@ retry:
for (i = 0; i < length; i++, offset++)
{
TransactionId *xactptr;
- uint32 *flagsptr;
+ uint64 *flagsptr;
int flagsoff;
int bshift;
int memberoff;
@@ -1445,7 +1268,7 @@ retry:
flagsoff = MXOffsetToFlagsOffset(offset);
bshift = MXOffsetToFlagsBitShift(offset);
- flagsptr = (uint32 *) (MultiXactMemberCtl->shared->page_buffer[slotno] + flagsoff);
+ flagsptr = (uint64 *) (MultiXactMemberCtl->shared->page_buffer[slotno] + flagsoff);
ptr[truelength].xid = *xactptr;
ptr[truelength].status = (*flagsptr >> bshift) & MXACT_MEMBER_XACT_BITMASK;
@@ -1529,7 +1352,7 @@ mXactCacheGetBySet(int nmembers, MultiXactMember *members)
*/
if (memcmp(members, entry->members, nmembers * sizeof(MultiXactMember)) == 0)
{
- debug_elog3(DEBUG2, "CacheGet: found %u", entry->multi);
+ debug_elog3(DEBUG2, "CacheGet: found " XID_FMT, entry->multi);
dlist_move_head(&MXactCache, iter.cur);
return entry->multi;
}
@@ -1552,7 +1375,7 @@ mXactCacheGetById(MultiXactId multi, MultiXactMember **members)
{
dlist_iter iter;
- debug_elog3(DEBUG2, "CacheGet: looking for %u", multi);
+ debug_elog3(DEBUG2, "CacheGet: looking for " XID_FMT, multi);
dlist_foreach(iter, &MXactCache)
{
@@ -1633,7 +1456,7 @@ mXactCachePut(MultiXactId multi, int nmembers, MultiXactMember *members)
MXactCacheMembers--;
entry = dlist_container(mXactCacheEnt, node, node);
- debug_elog3(DEBUG2, "CachePut: pruning cached multi %u",
+ debug_elog3(DEBUG2, "CachePut: pruning cached multi " XID_FMT,
entry->multi);
pfree(entry);
@@ -1675,11 +1498,11 @@ mxid_to_string(MultiXactId multi, int nmembers, MultiXactMember *members)
initStringInfo(&buf);
- appendStringInfo(&buf, "%u %d[%u (%s)", multi, nmembers, members[0].xid,
+ appendStringInfo(&buf, XID_FMT " %d[" XID_FMT " (%s)", multi, nmembers, members[0].xid,
mxstatus_to_string(members[0].status));
for (i = 1; i < nmembers; i++)
- appendStringInfo(&buf, ", %u (%s)", members[i].xid,
+ appendStringInfo(&buf, ", " XID_FMT " (%s)", members[i].xid,
mxstatus_to_string(members[i].status));
appendStringInfoChar(&buf, ']');
@@ -1906,6 +1729,8 @@ void
BootStrapMultiXact(void)
{
int slotno;
+ int64 multiOffsetPageno;
+ int64 multiMemberPageno;
LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
@@ -1916,6 +1741,17 @@ BootStrapMultiXact(void)
SimpleLruWritePage(MultiXactOffsetCtl, slotno);
Assert(!MultiXactOffsetCtl->shared->page_dirty[slotno]);
+ multiOffsetPageno = MultiXactIdToOffsetPage(MultiXactState->nextMXact - 1);
+ if (multiOffsetPageno != 0)
+ {
+ /* Create and zero the first page of the offsets log */
+ slotno = ZeroMultiXactOffsetPage(multiOffsetPageno, false);
+
+ /* Make sure it's written out */
+ SimpleLruWritePage(MultiXactOffsetCtl, slotno);
+ Assert(!MultiXactOffsetCtl->shared->page_dirty[slotno]);
+ }
+
LWLockRelease(MultiXactOffsetSLRULock);
LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);
@@ -1927,7 +1763,32 @@ BootStrapMultiXact(void)
SimpleLruWritePage(MultiXactMemberCtl, slotno);
Assert(!MultiXactMemberCtl->shared->page_dirty[slotno]);
+ multiMemberPageno = MXOffsetToMemberPage(MultiXactState->nextOffset - 1);
+ if (multiMemberPageno != 0)
+ {
+ /* Create and zero the first page of the members log */
+ slotno = ZeroMultiXactMemberPage(multiMemberPageno, false);
+
+ /* Make sure it's written out */
+ SimpleLruWritePage(MultiXactMemberCtl, slotno);
+ Assert(!MultiXactMemberCtl->shared->page_dirty[slotno]);
+ }
+
LWLockRelease(MultiXactMemberSLRULock);
+
+ /*
+ * If we're starting not from zero offset, initilize dummy multixact to
+ * evade too long loop in PerformMembersTruncation().
+ */
+ if (MultiXactState->nextOffset > 0 && MultiXactState->nextMXact > 0)
+ {
+ RecordNewMultiXact(FirstMultiXactId,
+ MultiXactState->nextOffset,
+ 0, NULL);
+ RecordNewMultiXact(MultiXactState->nextMXact - 1,
+ MultiXactState->nextOffset,
+ 0, NULL);
+ }
}
/*
@@ -1940,7 +1801,7 @@ BootStrapMultiXact(void)
* Control lock must be held at entry, and will be held at exit.
*/
static int
-ZeroMultiXactOffsetPage(int pageno, bool writeXlog)
+ZeroMultiXactOffsetPage(int64 pageno, bool writeXlog)
{
int slotno;
@@ -1956,7 +1817,7 @@ ZeroMultiXactOffsetPage(int pageno, bool writeXlog)
* Ditto, for MultiXactMember
*/
static int
-ZeroMultiXactMemberPage(int pageno, bool writeXlog)
+ZeroMultiXactMemberPage(int64 pageno, bool writeXlog)
{
int slotno;
@@ -1986,7 +1847,7 @@ ZeroMultiXactMemberPage(int pageno, bool writeXlog)
static void
MaybeExtendOffsetSlru(void)
{
- int pageno;
+ int64 pageno;
pageno = MultiXactIdToOffsetPage(MultiXactState->nextMXact);
@@ -2021,7 +1882,7 @@ StartupMultiXact(void)
{
MultiXactId multi = MultiXactState->nextMXact;
MultiXactOffset offset = MultiXactState->nextOffset;
- int pageno;
+ int64 pageno;
/*
* Initialize offset's idea of the latest page number.
@@ -2046,7 +1907,7 @@ TrimMultiXact(void)
MultiXactOffset offset;
MultiXactId oldestMXact;
Oid oldestMXactDB;
- int pageno;
+ int64 pageno;
int entryno;
int flagsoff;
@@ -2156,7 +2017,7 @@ MultiXactGetCheckptMulti(bool is_shutdown,
LWLockRelease(MultiXactGenLock);
debug_elog6(DEBUG2,
- "MultiXact: checkpoint is nextMulti %u, nextOffset %u, oldestMulti %u in DB %u",
+ "MultiXact: checkpoint is nextMulti " XID_FMT ", nextOffset " INT64_FORMAT ", oldestMulti " XID_FMT " in DB %u",
*nextMulti, *nextMultiOffset, *oldestMulti, *oldestMultiDB);
}
@@ -2191,7 +2052,7 @@ void
MultiXactSetNextMXact(MultiXactId nextMulti,
MultiXactOffset nextMultiOffset)
{
- debug_elog4(DEBUG2, "MultiXact: setting next multi to %u offset %u",
+ debug_elog4(DEBUG2, "MultiXact: setting next multi to " XID_FMT " offset " INT64_FORMAT,
nextMulti, nextMultiOffset);
LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE);
MultiXactState->nextMXact = nextMulti;
@@ -2226,47 +2087,9 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid,
bool is_startup)
{
MultiXactId multiVacLimit;
- MultiXactId multiWarnLimit;
- MultiXactId multiStopLimit;
- MultiXactId multiWrapLimit;
- MultiXactId curMulti;
- bool needs_offset_vacuum;
Assert(MultiXactIdIsValid(oldest_datminmxid));
- /*
- * We pretend that a wrap will happen halfway through the multixact ID
- * space, but that's not really true, because multixacts wrap differently
- * from transaction IDs. Note that, separately from any concern about
- * multixact IDs wrapping, we must ensure that multixact members do not
- * wrap. Limits for that are set in SetOffsetVacuumLimit, not here.
- */
- multiWrapLimit = oldest_datminmxid + (MaxMultiXactId >> 1);
- if (multiWrapLimit < FirstMultiXactId)
- multiWrapLimit += FirstMultiXactId;
-
- /*
- * We'll refuse to continue assigning MultiXactIds once we get within 3M
- * multi of data loss. See SetTransactionIdLimit.
- */
- multiStopLimit = multiWrapLimit - 3000000;
- if (multiStopLimit < FirstMultiXactId)
- multiStopLimit -= FirstMultiXactId;
-
- /*
- * We'll start complaining loudly when we get within 40M multis of data
- * loss. This is kind of arbitrary, but if you let your gas gauge get
- * down to 2% of full, would you be looking for the next gas station? We
- * need to be fairly liberal about this number because there are lots of
- * scenarios where most transactions are done by automatic clients that
- * won't pay attention to warnings. (No, we're not gonna make this
- * configurable. If you know enough to configure it, you know enough to
- * not get in this kind of trouble in the first place.)
- */
- multiWarnLimit = multiWrapLimit - 40000000;
- if (multiWarnLimit < FirstMultiXactId)
- multiWarnLimit -= FirstMultiXactId;
-
/*
* We'll start trying to force autovacuums when oldest_datminmxid gets to
* be more than autovacuum_multixact_freeze_max_age mxids old.
@@ -2276,25 +2099,14 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid,
* its value. See SetTransactionIdLimit.
*/
multiVacLimit = oldest_datminmxid + autovacuum_multixact_freeze_max_age;
- if (multiVacLimit < FirstMultiXactId)
- multiVacLimit += FirstMultiXactId;
/* Grab lock for just long enough to set the new limit values */
LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE);
MultiXactState->oldestMultiXactId = oldest_datminmxid;
MultiXactState->oldestMultiXactDB = oldest_datoid;
MultiXactState->multiVacLimit = multiVacLimit;
- MultiXactState->multiWarnLimit = multiWarnLimit;
- MultiXactState->multiStopLimit = multiStopLimit;
- MultiXactState->multiWrapLimit = multiWrapLimit;
- curMulti = MultiXactState->nextMXact;
LWLockRelease(MultiXactGenLock);
- /* Log the info */
- ereport(DEBUG1,
- (errmsg_internal("MultiXactId wrap limit is %u, limited by database with OID %u",
- multiWrapLimit, oldest_datoid)));
-
/*
* Computing the actual limits is only possible once the data directory is
* in a consistent state. There's no need to compute the limits while
@@ -2306,59 +2118,6 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid,
return;
Assert(!InRecovery);
-
- /* Set limits for offset vacuum. */
- needs_offset_vacuum = SetOffsetVacuumLimit(is_startup);
-
- /*
- * If past the autovacuum force point, immediately signal an autovac
- * request. The reason for this is that autovac only processes one
- * database per invocation. Once it's finished cleaning up the oldest
- * database, it'll call here, and we'll signal the postmaster to start
- * another iteration immediately if there are still any old databases.
- */
- if ((MultiXactIdPrecedes(multiVacLimit, curMulti) ||
- needs_offset_vacuum) && IsUnderPostmaster)
- SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
-
- /* Give an immediate warning if past the wrap warn point */
- if (MultiXactIdPrecedes(multiWarnLimit, curMulti))
- {
- char *oldest_datname;
-
- /*
- * We can be called when not inside a transaction, for example during
- * StartupXLOG(). In such a case we cannot do database access, so we
- * must just report the oldest DB's OID.
- *
- * Note: it's also possible that get_database_name fails and returns
- * NULL, for example because the database just got dropped. We'll
- * still warn, even though the warning might now be unnecessary.
- */
- if (IsTransactionState())
- oldest_datname = get_database_name(oldest_datoid);
- else
- oldest_datname = NULL;
-
- if (oldest_datname)
- ereport(WARNING,
- (errmsg_plural("database \"%s\" must be vacuumed before %u more MultiXactId is used",
- "database \"%s\" must be vacuumed before %u more MultiXactIds are used",
- multiWrapLimit - curMulti,
- oldest_datname,
- multiWrapLimit - curMulti),
- errhint("To avoid a database shutdown, execute a database-wide VACUUM in that database.\n"
- "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
- else
- ereport(WARNING,
- (errmsg_plural("database with OID %u must be vacuumed before %u more MultiXactId is used",
- "database with OID %u must be vacuumed before %u more MultiXactIds are used",
- multiWrapLimit - curMulti,
- oldest_datoid,
- multiWrapLimit - curMulti),
- errhint("To avoid a database shutdown, execute a database-wide VACUUM in that database.\n"
- "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
- }
}
/*
@@ -2377,12 +2136,12 @@ MultiXactAdvanceNextMXact(MultiXactId minMulti,
LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE);
if (MultiXactIdPrecedes(MultiXactState->nextMXact, minMulti))
{
- debug_elog3(DEBUG2, "MultiXact: setting next multi to %u", minMulti);
+ debug_elog3(DEBUG2, "MultiXact: setting next multi to " XID_FMT, minMulti);
MultiXactState->nextMXact = minMulti;
}
if (MultiXactOffsetPrecedes(MultiXactState->nextOffset, minMultiOffset))
{
- debug_elog3(DEBUG2, "MultiXact: setting next offset to %u",
+ debug_elog3(DEBUG2, "MultiXact: setting next offset to " INT64_FORMAT,
minMultiOffset);
MultiXactState->nextOffset = minMultiOffset;
}
@@ -2415,7 +2174,7 @@ MultiXactAdvanceOldest(MultiXactId oldestMulti, Oid oldestMultiDB)
static void
ExtendMultiXactOffset(MultiXactId multi)
{
- int pageno;
+ int64 pageno;
/*
* No work except at first MultiXactId of a page. But beware: just after
@@ -2464,7 +2223,7 @@ ExtendMultiXactMember(MultiXactOffset offset, int nmembers)
flagsbit = MXOffsetToFlagsBitShift(offset);
if (flagsoff == 0 && flagsbit == 0)
{
- int pageno;
+ int64 pageno;
pageno = MXOffsetToMemberPage(offset);
@@ -2476,23 +2235,7 @@ ExtendMultiXactMember(MultiXactOffset offset, int nmembers)
LWLockRelease(MultiXactMemberSLRULock);
}
- /*
- * Compute the number of items till end of current page. Careful: if
- * addition of unsigned ints wraps around, we're at the last page of
- * the last segment; since that page holds a different number of items
- * than other pages, we need to do it differently.
- */
- if (offset + MAX_MEMBERS_IN_LAST_MEMBERS_PAGE < offset)
- {
- /*
- * This is the last page of the last segment; we can compute the
- * number of items left to allocate in it without modulo
- * arithmetic.
- */
- difference = MaxMultiXactOffset - offset + 1;
- }
- else
- difference = MULTIXACT_MEMBERS_PER_PAGE - offset % MULTIXACT_MEMBERS_PER_PAGE;
+ difference = MULTIXACT_MEMBERS_PER_PAGE - offset % MULTIXACT_MEMBERS_PER_PAGE;
/*
* Advance to next page, taking care to properly handle the wraparound
@@ -2557,183 +2300,6 @@ GetOldestMultiXactId(void)
return oldestMXact;
}
-/*
- * Determine how aggressively we need to vacuum in order to prevent member
- * wraparound.
- *
- * To do so determine what's the oldest member offset and install the limit
- * info in MultiXactState, where it can be used to prevent overrun of old data
- * in the members SLRU area.
- *
- * The return value is true if emergency autovacuum is required and false
- * otherwise.
- */
-static bool
-SetOffsetVacuumLimit(bool is_startup)
-{
- MultiXactId oldestMultiXactId;
- MultiXactId nextMXact;
- MultiXactOffset oldestOffset = 0; /* placate compiler */
- MultiXactOffset prevOldestOffset;
- MultiXactOffset nextOffset;
- bool oldestOffsetKnown = false;
- bool prevOldestOffsetKnown;
- MultiXactOffset offsetStopLimit = 0;
- MultiXactOffset prevOffsetStopLimit;
-
- /*
- * NB: Have to prevent concurrent truncation, we might otherwise try to
- * lookup an oldestMulti that's concurrently getting truncated away.
- */
- LWLockAcquire(MultiXactTruncationLock, LW_SHARED);
-
- /* Read relevant fields from shared memory. */
- LWLockAcquire(MultiXactGenLock, LW_SHARED);
- oldestMultiXactId = MultiXactState->oldestMultiXactId;
- nextMXact = MultiXactState->nextMXact;
- nextOffset = MultiXactState->nextOffset;
- prevOldestOffsetKnown = MultiXactState->oldestOffsetKnown;
- prevOldestOffset = MultiXactState->oldestOffset;
- prevOffsetStopLimit = MultiXactState->offsetStopLimit;
- Assert(MultiXactState->finishedStartup);
- LWLockRelease(MultiXactGenLock);
-
- /*
- * Determine the offset of the oldest multixact. Normally, we can read
- * the offset from the multixact itself, but there's an important special
- * case: if there are no multixacts in existence at all, oldestMXact
- * obviously can't point to one. It will instead point to the multixact
- * ID that will be assigned the next time one is needed.
- */
- if (oldestMultiXactId == nextMXact)
- {
- /*
- * When the next multixact gets created, it will be stored at the next
- * offset.
- */
- oldestOffset = nextOffset;
- oldestOffsetKnown = true;
- }
- else
- {
- /*
- * Figure out where the oldest existing multixact's offsets are
- * stored. Due to bugs in early release of PostgreSQL 9.3.X and 9.4.X,
- * the supposedly-earliest multixact might not really exist. We are
- * careful not to fail in that case.
- */
- oldestOffsetKnown =
- find_multixact_start(oldestMultiXactId, &oldestOffset);
-
- if (oldestOffsetKnown)
- ereport(DEBUG1,
- (errmsg_internal("oldest MultiXactId member is at offset %u",
- oldestOffset)));
- else
- ereport(LOG,
- (errmsg("MultiXact member wraparound protections are disabled because oldest checkpointed MultiXact %u does not exist on disk",
- oldestMultiXactId)));
- }
-
- LWLockRelease(MultiXactTruncationLock);
-
- /*
- * If we can, compute limits (and install them MultiXactState) to prevent
- * overrun of old data in the members SLRU area. We can only do so if the
- * oldest offset is known though.
- */
- if (oldestOffsetKnown)
- {
- /* move back to start of the corresponding segment */
- offsetStopLimit = oldestOffset - (oldestOffset %
- (MULTIXACT_MEMBERS_PER_PAGE * SLRU_PAGES_PER_SEGMENT));
-
- /* always leave one segment before the wraparound point */
- offsetStopLimit -= (MULTIXACT_MEMBERS_PER_PAGE * SLRU_PAGES_PER_SEGMENT);
-
- if (!prevOldestOffsetKnown && !is_startup)
- ereport(LOG,
- (errmsg("MultiXact member wraparound protections are now enabled")));
-
- ereport(DEBUG1,
- (errmsg_internal("MultiXact member stop limit is now %u based on MultiXact %u",
- offsetStopLimit, oldestMultiXactId)));
- }
- else if (prevOldestOffsetKnown)
- {
- /*
- * If we failed to get the oldest offset this time, but we have a
- * value from a previous pass through this function, use the old
- * values rather than automatically forcing an emergency autovacuum
- * cycle again.
- */
- oldestOffset = prevOldestOffset;
- oldestOffsetKnown = true;
- offsetStopLimit = prevOffsetStopLimit;
- }
-
- /* Install the computed values */
- LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE);
- MultiXactState->oldestOffset = oldestOffset;
- MultiXactState->oldestOffsetKnown = oldestOffsetKnown;
- MultiXactState->offsetStopLimit = offsetStopLimit;
- LWLockRelease(MultiXactGenLock);
-
- /*
- * Do we need an emergency autovacuum? If we're not sure, assume yes.
- */
- return !oldestOffsetKnown ||
- (nextOffset - oldestOffset > MULTIXACT_MEMBER_SAFE_THRESHOLD);
-}
-
-/*
- * Return whether adding "distance" to "start" would move past "boundary".
- *
- * We use this to determine whether the addition is "wrapping around" the
- * boundary point, hence the name. The reason we don't want to use the regular
- * 2^31-modulo arithmetic here is that we want to be able to use the whole of
- * the 2^32-1 space here, allowing for more multixacts than would fit
- * otherwise.
- */
-static bool
-MultiXactOffsetWouldWrap(MultiXactOffset boundary, MultiXactOffset start,
- uint32 distance)
-{
- MultiXactOffset finish;
-
- /*
- * Note that offset number 0 is not used (see GetMultiXactIdMembers), so
- * if the addition wraps around the UINT_MAX boundary, skip that value.
- */
- finish = start + distance;
- if (finish < start)
- finish++;
-
- /*-----------------------------------------------------------------------
- * When the boundary is numerically greater than the starting point, any
- * value numerically between the two is not wrapped:
- *
- * <----S----B---->
- * [---) = F wrapped past B (and UINT_MAX)
- * [---) = F not wrapped
- * [----] = F wrapped past B
- *
- * When the boundary is numerically less than the starting point (i.e. the
- * UINT_MAX wraparound occurs somewhere in between) then all values in
- * between are wrapped:
- *
- * <----B----S---->
- * [---) = F not wrapped past B (but wrapped past UINT_MAX)
- * [---) = F wrapped past B (and UINT_MAX)
- * [----] = F not wrapped
- *-----------------------------------------------------------------------
- */
- if (start < boundary)
- return finish >= boundary || finish < start;
- else
- return finish >= boundary && finish < start;
-}
-
/*
* Find the starting offset of the given MultiXactId.
*
@@ -2747,7 +2313,7 @@ static bool
find_multixact_start(MultiXactId multi, MultiXactOffset *result)
{
MultiXactOffset offset;
- int pageno;
+ int64 pageno;
int entryno;
int slotno;
MultiXactOffset *offptr;
@@ -2777,97 +2343,6 @@ find_multixact_start(MultiXactId multi, MultiXactOffset *result)
return true;
}
-/*
- * Determine how many multixacts, and how many multixact members, currently
- * exist. Return false if unable to determine.
- */
-static bool
-ReadMultiXactCounts(uint32 *multixacts, MultiXactOffset *members)
-{
- MultiXactOffset nextOffset;
- MultiXactOffset oldestOffset;
- MultiXactId oldestMultiXactId;
- MultiXactId nextMultiXactId;
- bool oldestOffsetKnown;
-
- LWLockAcquire(MultiXactGenLock, LW_SHARED);
- nextOffset = MultiXactState->nextOffset;
- oldestMultiXactId = MultiXactState->oldestMultiXactId;
- nextMultiXactId = MultiXactState->nextMXact;
- oldestOffset = MultiXactState->oldestOffset;
- oldestOffsetKnown = MultiXactState->oldestOffsetKnown;
- LWLockRelease(MultiXactGenLock);
-
- if (!oldestOffsetKnown)
- return false;
-
- *members = nextOffset - oldestOffset;
- *multixacts = nextMultiXactId - oldestMultiXactId;
- return true;
-}
-
-/*
- * Multixact members can be removed once the multixacts that refer to them
- * are older than every datminmxid. autovacuum_multixact_freeze_max_age and
- * vacuum_multixact_freeze_table_age work together to make sure we never have
- * too many multixacts; we hope that, at least under normal circumstances,
- * this will also be sufficient to keep us from using too many offsets.
- * However, if the average multixact has many members, we might exhaust the
- * members space while still using few enough members that these limits fail
- * to trigger full table scans for relminmxid advancement. At that point,
- * we'd have no choice but to start failing multixact-creating operations
- * with an error.
- *
- * To prevent that, if more than a threshold portion of the members space is
- * used, we effectively reduce autovacuum_multixact_freeze_max_age and
- * to a value just less than the number of multixacts in use. We hope that
- * this will quickly trigger autovacuuming on the table or tables with the
- * oldest relminmxid, thus allowing datminmxid values to advance and removing
- * some members.
- *
- * As the fraction of the member space currently in use grows, we become
- * more aggressive in clamping this value. That not only causes autovacuum
- * to ramp up, but also makes any manual vacuums the user issues more
- * aggressive. This happens because vacuum_set_xid_limits() clamps the
- * freeze table and the minimum freeze age based on the effective
- * autovacuum_multixact_freeze_max_age this function returns. In the worst
- * case, we'll claim the freeze_max_age to zero, and every vacuum of any
- * table will try to freeze every multixact.
- *
- * It's possible that these thresholds should be user-tunable, but for now
- * we keep it simple.
- */
-int
-MultiXactMemberFreezeThreshold(void)
-{
- MultiXactOffset members;
- uint32 multixacts;
- uint32 victim_multixacts;
- double fraction;
-
- /* If we can't determine member space utilization, assume the worst. */
- if (!ReadMultiXactCounts(&multixacts, &members))
- return 0;
-
- /* If member space utilization is low, no special action is required. */
- if (members <= MULTIXACT_MEMBER_SAFE_THRESHOLD)
- return autovacuum_multixact_freeze_max_age;
-
- /*
- * Compute a target for relminmxid advancement. The number of multixacts
- * we try to eliminate from the system is based on how far we are past
- * MULTIXACT_MEMBER_SAFE_THRESHOLD.
- */
- fraction = (double) (members - MULTIXACT_MEMBER_SAFE_THRESHOLD) /
- (MULTIXACT_MEMBER_DANGER_THRESHOLD - MULTIXACT_MEMBER_SAFE_THRESHOLD);
- victim_multixacts = multixacts * fraction;
-
- /* fraction could be > 1.0, but lowest possible freeze age is zero */
- if (victim_multixacts > multixacts)
- return 0;
- return multixacts - victim_multixacts;
-}
-
typedef struct mxtruncinfo
{
int earliestExistingPage;
@@ -2878,7 +2353,7 @@ typedef struct mxtruncinfo
* This callback determines the earliest existing page number.
*/
static bool
-SlruScanDirCbFindEarliest(SlruCtl ctl, char *filename, int segpage, void *data)
+SlruScanDirCbFindEarliest(SlruCtl ctl, char *filename, int64 segpage, void *data)
{
mxtruncinfo *trunc = (mxtruncinfo *) data;
@@ -2894,35 +2369,12 @@ SlruScanDirCbFindEarliest(SlruCtl ctl, char *filename, int segpage, void *data)
/*
* Delete members segments [oldest, newOldest)
- *
- * The members SLRU can, in contrast to the offsets one, be filled to almost
- * the full range at once. This means SimpleLruTruncate() can't trivially be
- * used - instead the to-be-deleted range is computed using the offsets
- * SLRU. C.f. TruncateMultiXact().
*/
static void
PerformMembersTruncation(MultiXactOffset oldestOffset, MultiXactOffset newOldestOffset)
{
- const int maxsegment = MXOffsetToMemberSegment(MaxMultiXactOffset);
- int startsegment = MXOffsetToMemberSegment(oldestOffset);
- int endsegment = MXOffsetToMemberSegment(newOldestOffset);
- int segment = startsegment;
-
- /*
- * Delete all the segments but the last one. The last segment can still
- * contain, possibly partially, valid data.
- */
- while (segment != endsegment)
- {
- elog(DEBUG2, "truncating multixact members segment %x", segment);
- SlruDeleteSegment(MultiXactMemberCtl, segment);
-
- /* move to next segment, handling wraparound correctly */
- if (segment == maxsegment)
- segment = 0;
- else
- segment += 1;
- }
+ SimpleLruTruncate(MultiXactMemberCtl,
+ MXOffsetToMemberPage(newOldestOffset));
}
/*
@@ -3040,7 +2492,7 @@ TruncateMultiXact(MultiXactId newOldestMulti, Oid newOldestMultiDB)
else if (!find_multixact_start(oldestMulti, &oldestOffset))
{
ereport(LOG,
- (errmsg("oldest MultiXact %u not found, earliest MultiXact %u, skipping truncation",
+ (errmsg("oldest MultiXact %" PRIu64 " not found, earliest MultiXact %" PRIu64 ", skipping truncation",
oldestMulti, earliest)));
LWLockRelease(MultiXactTruncationLock);
return;
@@ -3058,21 +2510,27 @@ TruncateMultiXact(MultiXactId newOldestMulti, Oid newOldestMultiDB)
else if (!find_multixact_start(newOldestMulti, &newOldestOffset))
{
ereport(LOG,
- (errmsg("cannot truncate up to MultiXact %u because it does not exist on disk, skipping truncation",
+ (errmsg("cannot truncate up to MultiXact %" PRIu64 " because it does not exist on disk, skipping truncation",
newOldestMulti)));
LWLockRelease(MultiXactTruncationLock);
return;
}
elog(DEBUG1, "performing multixact truncation: "
- "offsets [%u, %u), offsets segments [%x, %x), "
- "members [%u, %u), members segments [%x, %x)",
+ "offsets [" XID_FMT ", " XID_FMT "), "
+ "offsets segments [%04x%08x, %04x%08x), "
+ "members [" INT64_FORMAT ", " INT64_FORMAT "), "
+ "members segments [%04x%08x, %04x%08x)",
oldestMulti, newOldestMulti,
- MultiXactIdToOffsetSegment(oldestMulti),
- MultiXactIdToOffsetSegment(newOldestMulti),
+ (uint32) (MultiXactIdToOffsetSegment(oldestMulti) >> 32),
+ (uint32) MultiXactIdToOffsetSegment(oldestMulti),
+ (uint32) (MultiXactIdToOffsetSegment(newOldestMulti) >> 32),
+ (uint32) MultiXactIdToOffsetSegment(newOldestMulti),
oldestOffset, newOldestOffset,
- MXOffsetToMemberSegment(oldestOffset),
- MXOffsetToMemberSegment(newOldestOffset));
+ (uint32) (MXOffsetToMemberSegment(oldestOffset) >> 32),
+ (uint32) MXOffsetToMemberSegment(oldestOffset),
+ (uint32) (MXOffsetToMemberSegment(newOldestOffset) >> 32),
+ (uint32) MXOffsetToMemberSegment(newOldestOffset));
/*
* Do truncation, and the WAL logging of the truncation, in a critical
@@ -3125,81 +2583,40 @@ TruncateMultiXact(MultiXactId newOldestMulti, Oid newOldestMultiDB)
* Decide whether a MultiXactOffset page number is "older" for truncation
* purposes. Analogous to CLOGPagePrecedes().
*
- * Offsetting the values is optional, because MultiXactIdPrecedes() has
- * translational symmetry.
+ * With 64xid this function is just "<", but we left it as a function in order
+ * for its calls remain "vanilla" like.
*/
static bool
-MultiXactOffsetPagePrecedes(int page1, int page2)
+MultiXactOffsetPagePrecedes(int64 page1, int64 page2)
{
- MultiXactId multi1;
- MultiXactId multi2;
-
- multi1 = ((MultiXactId) page1) * MULTIXACT_OFFSETS_PER_PAGE;
- multi1 += FirstMultiXactId + 1;
- multi2 = ((MultiXactId) page2) * MULTIXACT_OFFSETS_PER_PAGE;
- multi2 += FirstMultiXactId + 1;
-
- return (MultiXactIdPrecedes(multi1, multi2) &&
- MultiXactIdPrecedes(multi1,
- multi2 + MULTIXACT_OFFSETS_PER_PAGE - 1));
+ return page1 < page2;
}
/*
- * Decide whether a MultiXactMember page number is "older" for truncation
- * purposes. There is no "invalid offset number" so use the numbers verbatim.
- */
-static bool
-MultiXactMemberPagePrecedes(int page1, int page2)
-{
- MultiXactOffset offset1;
- MultiXactOffset offset2;
-
- offset1 = ((MultiXactOffset) page1) * MULTIXACT_MEMBERS_PER_PAGE;
- offset2 = ((MultiXactOffset) page2) * MULTIXACT_MEMBERS_PER_PAGE;
-
- return (MultiXactOffsetPrecedes(offset1, offset2) &&
- MultiXactOffsetPrecedes(offset1,
- offset2 + MULTIXACT_MEMBERS_PER_PAGE - 1));
-}
-
-/*
- * Decide which of two MultiXactIds is earlier.
+ * Decide which of two offsets is earlier.
*
- * XXX do we need to do something special for InvalidMultiXactId?
- * (Doesn't look like it.)
+ * With 64xid this function is just "<", but we left it as a function in order
+ * for its calls remain "vanilla" like.
*/
-bool
-MultiXactIdPrecedes(MultiXactId multi1, MultiXactId multi2)
+static bool
+MultiXactOffsetPrecedes(MultiXactOffset offset1, MultiXactOffset offset2)
{
- int32 diff = (int32) (multi1 - multi2);
+ int64 diff = (int64) (offset1 - offset2);
return (diff < 0);
}
/*
- * MultiXactIdPrecedesOrEquals -- is multi1 logically <= multi2?
+ * Decide whether a MultiXactMember page number is "older" for truncation
+ * purposes. There is no "invalid offset number" so use the numbers verbatim.
*
- * XXX do we need to do something special for InvalidMultiXactId?
- * (Doesn't look like it.)
- */
-bool
-MultiXactIdPrecedesOrEquals(MultiXactId multi1, MultiXactId multi2)
-{
- int32 diff = (int32) (multi1 - multi2);
-
- return (diff <= 0);
-}
-
-
-/*
- * Decide which of two offsets is earlier.
+ * With 64xid this function is just "<", but we left it as a function in order
+ * for its calls remain "vanilla" like.
*/
static bool
-MultiXactOffsetPrecedes(MultiXactOffset offset1, MultiXactOffset offset2)
+MultiXactMemberPagePrecedes(int64 page1, int64 page2)
{
- int32 diff = (int32) (offset1 - offset2);
-
- return (diff < 0);
+ return page1 < page2;
}
/*
@@ -3207,10 +2624,10 @@ MultiXactOffsetPrecedes(MultiXactOffset offset1, MultiXactOffset offset2)
* OFFSETs page (info shows which)
*/
static void
-WriteMZeroPageXlogRec(int pageno, uint8 info)
+WriteMZeroPageXlogRec(int64 pageno, uint8 info)
{
XLogBeginInsert();
- XLogRegisterData((char *) (&pageno), sizeof(int));
+ XLogRegisterData((char *) (&pageno), sizeof(int64));
(void) XLogInsert(RM_MULTIXACT_ID, info);
}
@@ -3255,10 +2672,10 @@ multixact_redo(XLogReaderState *record)
if (info == XLOG_MULTIXACT_ZERO_OFF_PAGE)
{
- int pageno;
+ int64 pageno;
int slotno;
- memcpy(&pageno, XLogRecGetData(record), sizeof(int));
+ memcpy(&pageno, XLogRecGetData(record), sizeof(int64));
LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
@@ -3270,10 +2687,10 @@ multixact_redo(XLogReaderState *record)
}
else if (info == XLOG_MULTIXACT_ZERO_MEM_PAGE)
{
- int pageno;
+ int64 pageno;
int slotno;
- memcpy(&pageno, XLogRecGetData(record), sizeof(int));
+ memcpy(&pageno, XLogRecGetData(record), sizeof(int64));
LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);
@@ -3315,20 +2732,26 @@ multixact_redo(XLogReaderState *record)
else if (info == XLOG_MULTIXACT_TRUNCATE_ID)
{
xl_multixact_truncate xlrec;
- int pageno;
+ int64 pageno;
memcpy(&xlrec, XLogRecGetData(record),
SizeOfMultiXactTruncate);
elog(DEBUG1, "replaying multixact truncation: "
- "offsets [%u, %u), offsets segments [%x, %x), "
- "members [%u, %u), members segments [%x, %x)",
+ "offsets [" XID_FMT ", " XID_FMT "), "
+ "offsets segments [%04x%08x, %04x%08x), "
+ "members [" INT64_FORMAT ", " INT64_FORMAT "), "
+ "members segments [%04x%08x, %04x%08x)",
xlrec.startTruncOff, xlrec.endTruncOff,
- MultiXactIdToOffsetSegment(xlrec.startTruncOff),
- MultiXactIdToOffsetSegment(xlrec.endTruncOff),
+ (uint32) (MultiXactIdToOffsetSegment(xlrec.startTruncOff) >> 32),
+ (uint32) MultiXactIdToOffsetSegment(xlrec.startTruncOff),
+ (uint32) (MultiXactIdToOffsetSegment(xlrec.endTruncOff) >> 32),
+ (uint32) MultiXactIdToOffsetSegment(xlrec.endTruncOff),
xlrec.startTruncMemb, xlrec.endTruncMemb,
- MXOffsetToMemberSegment(xlrec.startTruncMemb),
- MXOffsetToMemberSegment(xlrec.endTruncMemb));
+ (uint32) (MXOffsetToMemberSegment(xlrec.startTruncMemb) >> 32),
+ (uint32) MXOffsetToMemberSegment(xlrec.startTruncMemb),
+ (uint32) (MXOffsetToMemberSegment(xlrec.endTruncMemb) >> 32),
+ (uint32) MXOffsetToMemberSegment(xlrec.endTruncMemb));
/* should not be required, but more than cheap enough */
LWLockAcquire(MultiXactTruncationLock, LW_EXCLUSIVE);
@@ -3372,7 +2795,7 @@ pg_get_multixact_members(PG_FUNCTION_ARGS)
if (mxid < FirstMultiXactId)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid MultiXactId: %u", mxid)));
+ errmsg("invalid MultiXactId: %" PRIu64, mxid)));
if (SRF_IS_FIRSTCALL())
{
@@ -3408,7 +2831,7 @@ pg_get_multixact_members(PG_FUNCTION_ARGS)
HeapTuple tuple;
char *values[2];
- values[0] = psprintf("%u", multi->members[multi->iter].xid);
+ values[0] = psprintf(XID_FMT, multi->members[multi->iter].xid);
values[1] = mxstatus_to_string(multi->members[multi->iter].status);
tuple = BuildTupleFromCStrings(funccxt->attinmeta, values);
diff --git a/src/backend/access/transam/slru.c b/src/backend/access/transam/slru.c
index 30a476ed5d..b442a4dc22 100644
--- a/src/backend/access/transam/slru.c
+++ b/src/backend/access/transam/slru.c
@@ -59,9 +59,11 @@
#include "pgstat.h"
#include "storage/fd.h"
#include "storage/shmem.h"
+#include "utils/builtins.h"
#define SlruFileName(ctl, path, seg) \
- snprintf(path, MAXPGPATH, "%s/%04X", (ctl)->Dir, seg)
+ snprintf(path, MAXPGPATH, "%s/%04X%08X", (ctl)->Dir, \
+ (uint32) ((seg) >> 32), (uint32) ((seg) & (int64)0xFFFFFFFF))
/*
* During SimpleLruWriteAll(), we will usually not need to write more than one
@@ -75,7 +77,7 @@ typedef struct SlruWriteAllData
{
int num_files; /* # files actually open */
int fd[MAX_WRITEALL_BUFFERS]; /* their FD's */
- int segno[MAX_WRITEALL_BUFFERS]; /* their log seg#s */
+ int64 segno[MAX_WRITEALL_BUFFERS]; /* their log seg#s */
} SlruWriteAllData;
typedef struct SlruWriteAllData *SlruWriteAll;
@@ -138,15 +140,16 @@ static int slru_errno;
static void SimpleLruZeroLSNs(SlruCtl ctl, int slotno);
static void SimpleLruWaitIO(SlruCtl ctl, int slotno);
static void SlruInternalWritePage(SlruCtl ctl, int slotno, SlruWriteAll fdata);
-static bool SlruPhysicalReadPage(SlruCtl ctl, int pageno, int slotno);
-static bool SlruPhysicalWritePage(SlruCtl ctl, int pageno, int slotno,
+static bool SlruPhysicalReadPage(SlruCtl ctl, int64 pageno, int slotno);
+static bool SlruPhysicalWritePage(SlruCtl ctl, int64 pageno, int slotno,
SlruWriteAll fdata);
-static void SlruReportIOError(SlruCtl ctl, int pageno, TransactionId xid);
-static int SlruSelectLRUPage(SlruCtl ctl, int pageno);
+static void SlruReportIOError(SlruCtl ctl, int64 pageno, TransactionId xid);
+static int SlruSelectLRUPage(SlruCtl ctl, int64 pageno);
static bool SlruScanDirCbDeleteCutoff(SlruCtl ctl, char *filename,
- int segpage, void *data);
-static void SlruInternalDeleteSegment(SlruCtl ctl, int segno);
+ int64 segpage, void *data);
+static void SlruInternalDeleteSegment(SlruCtl ctl, int64 segno);
+
/*
* Initialization of shared memory
@@ -162,7 +165,7 @@ SimpleLruShmemSize(int nslots, int nlsns)
sz += MAXALIGN(nslots * sizeof(char *)); /* page_buffer[] */
sz += MAXALIGN(nslots * sizeof(SlruPageStatus)); /* page_status[] */
sz += MAXALIGN(nslots * sizeof(bool)); /* page_dirty[] */
- sz += MAXALIGN(nslots * sizeof(int)); /* page_number[] */
+ sz += MAXALIGN(nslots * sizeof(int64)); /* page_number[] */
sz += MAXALIGN(nslots * sizeof(int)); /* page_lru_count[] */
sz += MAXALIGN(nslots * sizeof(LWLockPadded)); /* buffer_locks[] */
@@ -225,8 +228,8 @@ SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
offset += MAXALIGN(nslots * sizeof(SlruPageStatus));
shared->page_dirty = (bool *) (ptr + offset);
offset += MAXALIGN(nslots * sizeof(bool));
- shared->page_number = (int *) (ptr + offset);
- offset += MAXALIGN(nslots * sizeof(int));
+ shared->page_number = (int64 *) (ptr + offset);
+ offset += MAXALIGN(nslots * sizeof(int64));
shared->page_lru_count = (int *) (ptr + offset);
offset += MAXALIGN(nslots * sizeof(int));
@@ -277,7 +280,7 @@ SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
* Control lock must be held at entry, and will be held at exit.
*/
int
-SimpleLruZeroPage(SlruCtl ctl, int pageno)
+SimpleLruZeroPage(SlruCtl ctl, int64 pageno)
{
SlruShared shared = ctl->shared;
int slotno;
@@ -392,7 +395,7 @@ SimpleLruWaitIO(SlruCtl ctl, int slotno)
* Control lock must be held at entry, and will be held at exit.
*/
int
-SimpleLruReadPage(SlruCtl ctl, int pageno, bool write_ok,
+SimpleLruReadPage(SlruCtl ctl, int64 pageno, bool write_ok,
TransactionId xid)
{
SlruShared shared = ctl->shared;
@@ -492,7 +495,7 @@ SimpleLruReadPage(SlruCtl ctl, int pageno, bool write_ok,
* It is unspecified whether the lock will be shared or exclusive.
*/
int
-SimpleLruReadPage_ReadOnly(SlruCtl ctl, int pageno, TransactionId xid)
+SimpleLruReadPage_ReadOnly(SlruCtl ctl, int64 pageno, TransactionId xid)
{
SlruShared shared = ctl->shared;
int slotno;
@@ -539,7 +542,7 @@ static void
SlruInternalWritePage(SlruCtl ctl, int slotno, SlruWriteAll fdata)
{
SlruShared shared = ctl->shared;
- int pageno = shared->page_number[slotno];
+ int64 pageno = shared->page_number[slotno];
bool ok;
/* If a write is in progress, wait for it to finish */
@@ -623,11 +626,11 @@ SimpleLruWritePage(SlruCtl ctl, int slotno)
* large enough to contain the given page.
*/
bool
-SimpleLruDoesPhysicalPageExist(SlruCtl ctl, int pageno)
+SimpleLruDoesPhysicalPageExist(SlruCtl ctl, int64 pageno)
{
- int segno = pageno / SLRU_PAGES_PER_SEGMENT;
- int rpageno = pageno % SLRU_PAGES_PER_SEGMENT;
- int offset = rpageno * BLCKSZ;
+ int64 segno = pageno / SLRU_PAGES_PER_SEGMENT;
+ int64 rpageno = pageno % SLRU_PAGES_PER_SEGMENT;
+ int64 offset = rpageno * BLCKSZ;
char path[MAXPGPATH];
int fd;
bool result;
@@ -681,11 +684,11 @@ SimpleLruDoesPhysicalPageExist(SlruCtl ctl, int pageno)
* read/write operations. We could cache one virtual file pointer ...
*/
static bool
-SlruPhysicalReadPage(SlruCtl ctl, int pageno, int slotno)
+SlruPhysicalReadPage(SlruCtl ctl, int64 pageno, int slotno)
{
SlruShared shared = ctl->shared;
- int segno = pageno / SLRU_PAGES_PER_SEGMENT;
- int rpageno = pageno % SLRU_PAGES_PER_SEGMENT;
+ int64 segno = pageno / SLRU_PAGES_PER_SEGMENT;
+ int64 rpageno = pageno % SLRU_PAGES_PER_SEGMENT;
off_t offset = rpageno * BLCKSZ;
char path[MAXPGPATH];
int fd;
@@ -753,11 +756,11 @@ SlruPhysicalReadPage(SlruCtl ctl, int pageno, int slotno)
* SimpleLruWriteAll.
*/
static bool
-SlruPhysicalWritePage(SlruCtl ctl, int pageno, int slotno, SlruWriteAll fdata)
+SlruPhysicalWritePage(SlruCtl ctl, int64 pageno, int slotno, SlruWriteAll fdata)
{
SlruShared shared = ctl->shared;
- int segno = pageno / SLRU_PAGES_PER_SEGMENT;
- int rpageno = pageno % SLRU_PAGES_PER_SEGMENT;
+ int64 segno = pageno / SLRU_PAGES_PER_SEGMENT;
+ int64 rpageno = pageno % SLRU_PAGES_PER_SEGMENT;
off_t offset = rpageno * BLCKSZ;
char path[MAXPGPATH];
int fd = -1;
@@ -928,11 +931,11 @@ SlruPhysicalWritePage(SlruCtl ctl, int pageno, int slotno, SlruWriteAll fdata)
* SlruPhysicalWritePage. Call this after cleaning up shared-memory state.
*/
static void
-SlruReportIOError(SlruCtl ctl, int pageno, TransactionId xid)
+SlruReportIOError(SlruCtl ctl, int64 pageno, TransactionId xid)
{
- int segno = pageno / SLRU_PAGES_PER_SEGMENT;
- int rpageno = pageno % SLRU_PAGES_PER_SEGMENT;
- int offset = rpageno * BLCKSZ;
+ int64 segno = pageno / SLRU_PAGES_PER_SEGMENT;
+ int64 rpageno = pageno % SLRU_PAGES_PER_SEGMENT;
+ int64 offset = rpageno * BLCKSZ;
char path[MAXPGPATH];
SlruFileName(ctl, path, segno);
@@ -942,52 +945,52 @@ SlruReportIOError(SlruCtl ctl, int pageno, TransactionId xid)
case SLRU_OPEN_FAILED:
ereport(ERROR,
(errcode_for_file_access(),
- errmsg("could not access status of transaction %u", xid),
+ errmsg("could not access status of transaction %" PRIu64, xid),
errdetail("Could not open file \"%s\": %m.", path)));
break;
case SLRU_SEEK_FAILED:
ereport(ERROR,
(errcode_for_file_access(),
- errmsg("could not access status of transaction %u", xid),
- errdetail("Could not seek in file \"%s\" to offset %u: %m.",
+ errmsg("could not access status of transaction %" PRIu64, xid),
+ errdetail("Could not seek in file \"%s\" to offset %" PRIu64 ": %m.",
path, offset)));
break;
case SLRU_READ_FAILED:
if (errno)
ereport(ERROR,
(errcode_for_file_access(),
- errmsg("could not access status of transaction %u", xid),
- errdetail("Could not read from file \"%s\" at offset %u: %m.",
+ errmsg("could not access status of transaction %" PRIu64, xid),
+ errdetail("Could not read from file \"%s\" at offset %" PRIu64 ": %m.",
path, offset)));
else
ereport(ERROR,
- (errmsg("could not access status of transaction %u", xid),
- errdetail("Could not read from file \"%s\" at offset %u: read too few bytes.", path, offset)));
+ (errmsg("could not access status of transaction %" PRIu64, xid),
+ errdetail("Could not read from file \"%s\" at offset %" PRIu64 ": read too few bytes.", path, offset)));
break;
case SLRU_WRITE_FAILED:
if (errno)
ereport(ERROR,
(errcode_for_file_access(),
- errmsg("could not access status of transaction %u", xid),
- errdetail("Could not write to file \"%s\" at offset %u: %m.",
+ errmsg("could not access status of transaction %" PRIu64, xid),
+ errdetail("Could not write to file \"%s\" at offset %" PRIu64 ": %m.",
path, offset)));
else
ereport(ERROR,
- (errmsg("could not access status of transaction %u", xid),
- errdetail("Could not write to file \"%s\" at offset %u: wrote too few bytes.",
+ (errmsg("could not access status of transaction %" PRIu64, xid),
+ errdetail("Could not write to file \"%s\" at offset %" PRIu64 ": wrote too few bytes.",
path, offset)));
break;
case SLRU_FSYNC_FAILED:
ereport(data_sync_elevel(ERROR),
(errcode_for_file_access(),
- errmsg("could not access status of transaction %u", xid),
+ errmsg("could not access status of transaction %" PRIu64, xid),
errdetail("Could not fsync file \"%s\": %m.",
path)));
break;
case SLRU_CLOSE_FAILED:
ereport(ERROR,
(errcode_for_file_access(),
- errmsg("could not access status of transaction %u", xid),
+ errmsg("could not access status of transaction %" PRIu64, xid),
errdetail("Could not close file \"%s\": %m.",
path)));
break;
@@ -1013,7 +1016,7 @@ SlruReportIOError(SlruCtl ctl, int pageno, TransactionId xid)
* Control lock must be held at entry, and will be held at exit.
*/
static int
-SlruSelectLRUPage(SlruCtl ctl, int pageno)
+SlruSelectLRUPage(SlruCtl ctl, int64 pageno)
{
SlruShared shared = ctl->shared;
@@ -1024,10 +1027,10 @@ SlruSelectLRUPage(SlruCtl ctl, int pageno)
int cur_count;
int bestvalidslot = 0; /* keep compiler quiet */
int best_valid_delta = -1;
- int best_valid_page_number = 0; /* keep compiler quiet */
+ int64 best_valid_page_number = 0; /* keep compiler quiet */
int bestinvalidslot = 0; /* keep compiler quiet */
int best_invalid_delta = -1;
- int best_invalid_page_number = 0; /* keep compiler quiet */
+ int64 best_invalid_page_number = 0; /* keep compiler quiet */
/* See if page already has a buffer assigned */
for (slotno = 0; slotno < shared->num_slots; slotno++)
@@ -1068,7 +1071,7 @@ SlruSelectLRUPage(SlruCtl ctl, int pageno)
for (slotno = 0; slotno < shared->num_slots; slotno++)
{
int this_delta;
- int this_page_number;
+ int64 this_page_number;
if (shared->page_status[slotno] == SLRU_PAGE_EMPTY)
return slotno;
@@ -1158,7 +1161,7 @@ SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied)
SlruShared shared = ctl->shared;
SlruWriteAllData fdata;
int slotno;
- int pageno = 0;
+ int64 pageno = 0;
int i;
bool ok;
@@ -1223,7 +1226,7 @@ SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied)
* after it has accrued freshly-written data.
*/
void
-SimpleLruTruncate(SlruCtl ctl, int cutoffPage)
+SimpleLruTruncate(SlruCtl ctl, int64 cutoffPage)
{
SlruShared shared = ctl->shared;
int slotno;
@@ -1301,7 +1304,7 @@ restart:;
* they either can't yet contain anything, or have already been cleaned out.
*/
static void
-SlruInternalDeleteSegment(SlruCtl ctl, int segno)
+SlruInternalDeleteSegment(SlruCtl ctl, int64 segno)
{
char path[MAXPGPATH];
@@ -1324,7 +1327,7 @@ SlruInternalDeleteSegment(SlruCtl ctl, int segno)
* Delete an individual SLRU segment, identified by the segment number.
*/
void
-SlruDeleteSegment(SlruCtl ctl, int segno)
+SlruDeleteSegment(SlruCtl ctl, int64 segno)
{
SlruShared shared = ctl->shared;
int slotno;
@@ -1399,6 +1402,7 @@ SlruMayDeleteSegment(SlruCtl ctl, int segpage, int cutoffPage)
}
#ifdef USE_ASSERT_CHECKING
+#if 0 /* XXX remove unit tests */
static void
SlruPagePrecedesTestOffset(SlruCtl ctl, int per_page, uint32 offset)
{
@@ -1471,6 +1475,7 @@ SlruPagePrecedesTestOffset(SlruCtl ctl, int per_page, uint32 offset)
newestPage % SLRU_PAGES_PER_SEGMENT),
oldestPage));
}
+#endif
/*
* Unit-test a PagePrecedes function.
@@ -1484,10 +1489,12 @@ SlruPagePrecedesTestOffset(SlruCtl ctl, int per_page, uint32 offset)
void
SlruPagePrecedesUnitTests(SlruCtl ctl, int per_page)
{
+#if 0 /* XXX remove unit tests */
/* Test first, middle and last entries of a page. */
SlruPagePrecedesTestOffset(ctl, per_page, 0);
SlruPagePrecedesTestOffset(ctl, per_page, per_page / 2);
SlruPagePrecedesTestOffset(ctl, per_page, per_page - 1);
+#endif
}
#endif
@@ -1497,9 +1504,9 @@ SlruPagePrecedesUnitTests(SlruCtl ctl, int per_page)
* one containing the page passed as "data".
*/
bool
-SlruScanDirCbReportPresence(SlruCtl ctl, char *filename, int segpage, void *data)
+SlruScanDirCbReportPresence(SlruCtl ctl, char *filename, int64 segpage, void *data)
{
- int cutoffPage = *(int *) data;
+ int64 cutoffPage = *(int64 *) data;
if (SlruMayDeleteSegment(ctl, segpage, cutoffPage))
return true; /* found one; don't iterate any more */
@@ -1512,9 +1519,9 @@ SlruScanDirCbReportPresence(SlruCtl ctl, char *filename, int segpage, void *data
* This callback deletes segments prior to the one passed in as "data".
*/
static bool
-SlruScanDirCbDeleteCutoff(SlruCtl ctl, char *filename, int segpage, void *data)
+SlruScanDirCbDeleteCutoff(SlruCtl ctl, char *filename, int64 segpage, void *data)
{
- int cutoffPage = *(int *) data;
+ int64 cutoffPage = *(int64 *) data;
if (SlruMayDeleteSegment(ctl, segpage, cutoffPage))
SlruInternalDeleteSegment(ctl, segpage / SLRU_PAGES_PER_SEGMENT);
@@ -1527,7 +1534,7 @@ SlruScanDirCbDeleteCutoff(SlruCtl ctl, char *filename, int segpage, void *data)
* This callback deletes all segments.
*/
bool
-SlruScanDirCbDeleteAll(SlruCtl ctl, char *filename, int segpage, void *data)
+SlruScanDirCbDeleteAll(SlruCtl ctl, char *filename, int64 segpage, void *data)
{
SlruInternalDeleteSegment(ctl, segpage / SLRU_PAGES_PER_SEGMENT);
@@ -1555,8 +1562,8 @@ SlruScanDirectory(SlruCtl ctl, SlruScanCallback callback, void *data)
bool retval = false;
DIR *cldir;
struct dirent *clde;
- int segno;
- int segpage;
+ int64 segno;
+ int64 segpage;
cldir = AllocateDir(ctl->Dir);
while ((clde = ReadDir(cldir, ctl->Dir)) != NULL)
@@ -1565,10 +1572,10 @@ SlruScanDirectory(SlruCtl ctl, SlruScanCallback callback, void *data)
len = strlen(clde->d_name);
- if ((len == 4 || len == 5 || len == 6) &&
+ if ((len == 12 || len == 13 || len == 14) &&
strspn(clde->d_name, "0123456789ABCDEF") == len)
{
- segno = (int) strtol(clde->d_name, NULL, 16);
+ segno = pg_strtouint64(clde->d_name, NULL, 16);
segpage = segno * SLRU_PAGES_PER_SEGMENT;
elog(DEBUG2, "SlruScanDirectory invoking callback on %s/%s",
diff --git a/src/backend/access/transam/subtrans.c b/src/backend/access/transam/subtrans.c
index 66d3548155..2f60fcb79c 100644
--- a/src/backend/access/transam/subtrans.c
+++ b/src/backend/access/transam/subtrans.c
@@ -63,8 +63,8 @@ static SlruCtlData SubTransCtlData;
#define SubTransCtl (&SubTransCtlData)
-static int ZeroSUBTRANSPage(int pageno);
-static bool SubTransPagePrecedes(int page1, int page2);
+static bool SubTransPagePrecedes(int64 page1, int64 page2);
+static int ZeroSUBTRANSPage(int64 pageno);
/*
@@ -73,7 +73,7 @@ static bool SubTransPagePrecedes(int page1, int page2);
void
SubTransSetParent(TransactionId xid, TransactionId parent)
{
- int pageno = TransactionIdToPage(xid);
+ int64 pageno = TransactionIdToPage(xid);
int entryno = TransactionIdToEntry(xid);
int slotno;
TransactionId *ptr;
@@ -108,7 +108,7 @@ SubTransSetParent(TransactionId xid, TransactionId parent)
TransactionId
SubTransGetParent(TransactionId xid)
{
- int pageno = TransactionIdToPage(xid);
+ int64 pageno = TransactionIdToPage(xid);
int entryno = TransactionIdToEntry(xid);
int slotno;
TransactionId *ptr;
@@ -168,7 +168,7 @@ SubTransGetTopmostTransaction(TransactionId xid)
* structure that could lead to an infinite loop, so exit.
*/
if (!TransactionIdPrecedes(parentXid, previousXid))
- elog(ERROR, "pg_subtrans contains invalid entry: xid %u points to parent xid %u",
+ elog(ERROR, "pg_subtrans contains invalid entry: xid " XID_FMT " points to parent xid " XID_FMT,
previousXid, parentXid);
}
@@ -211,11 +211,14 @@ void
BootStrapSUBTRANS(void)
{
int slotno;
+ int64 pageno;
+
+ pageno = TransactionIdToPage(XidFromFullTransactionId(ShmemVariableCache->nextXid));
LWLockAcquire(SubtransSLRULock, LW_EXCLUSIVE);
/* Create and zero the first page of the subtrans log */
- slotno = ZeroSUBTRANSPage(0);
+ slotno = ZeroSUBTRANSPage(pageno);
/* Make sure it's written out */
SimpleLruWritePage(SubTransCtl, slotno);
@@ -233,7 +236,7 @@ BootStrapSUBTRANS(void)
* Control lock must be held at entry, and will be held at exit.
*/
static int
-ZeroSUBTRANSPage(int pageno)
+ZeroSUBTRANSPage(int64 pageno)
{
return SimpleLruZeroPage(SubTransCtl, pageno);
}
@@ -249,8 +252,8 @@ void
StartupSUBTRANS(TransactionId oldestActiveXID)
{
FullTransactionId nextXid;
- int startPage;
- int endPage;
+ int64 startPage;
+ int64 endPage;
/*
* Since we don't expect pg_subtrans to be valid across crashes, we
@@ -268,9 +271,6 @@ StartupSUBTRANS(TransactionId oldestActiveXID)
{
(void) ZeroSUBTRANSPage(startPage);
startPage++;
- /* must account for wraparound */
- if (startPage > TransactionIdToPage(MaxTransactionId))
- startPage = 0;
}
(void) ZeroSUBTRANSPage(startPage);
@@ -307,7 +307,7 @@ CheckPointSUBTRANS(void)
void
ExtendSUBTRANS(TransactionId newestXact)
{
- int pageno;
+ int64 pageno;
/*
* No work except at first XID of a page. But beware: just after
@@ -337,7 +337,7 @@ ExtendSUBTRANS(TransactionId newestXact)
void
TruncateSUBTRANS(TransactionId oldestXact)
{
- int cutoffPage;
+ int64 cutoffPage;
/*
* The cutoff point is the start of the segment containing oldestXact. We
@@ -347,6 +347,13 @@ TruncateSUBTRANS(TransactionId oldestXact)
* a page and oldestXact == next XID. In that case, if we didn't subtract
* one, we'd trigger SimpleLruTruncate's wraparound detection.
*/
+
+ if (oldestXact <= FirstNormalTransactionId)
+ {
+ SimpleLruTruncate(SubTransCtl, 0);
+ return;
+ }
+
TransactionIdRetreat(oldestXact);
cutoffPage = TransactionIdToPage(oldestXact);
@@ -357,18 +364,12 @@ TruncateSUBTRANS(TransactionId oldestXact)
/*
* Decide whether a SUBTRANS page number is "older" for truncation purposes.
* Analogous to CLOGPagePrecedes().
+ *
+ * With 64xid this function is just "<", but we left it as a function in order
+ * for its calls remain "vanilla" like.
*/
static bool
-SubTransPagePrecedes(int page1, int page2)
+SubTransPagePrecedes(int64 page1, int64 page2)
{
- TransactionId xid1;
- TransactionId xid2;
-
- xid1 = ((TransactionId) page1) * SUBTRANS_XACTS_PER_PAGE;
- xid1 += FirstNormalTransactionId + 1;
- xid2 = ((TransactionId) page2) * SUBTRANS_XACTS_PER_PAGE;
- xid2 += FirstNormalTransactionId + 1;
-
- return (TransactionIdPrecedes(xid1, xid2) &&
- TransactionIdPrecedes(xid1, xid2 + SUBTRANS_XACTS_PER_PAGE - 1));
+ return page1 < page2;
}
diff --git a/src/backend/access/transam/transam.c b/src/backend/access/transam/transam.c
index dbc5f884e8..a588b615b0 100644
--- a/src/backend/access/transam/transam.c
+++ b/src/backend/access/transam/transam.c
@@ -157,7 +157,7 @@ TransactionIdDidCommit(TransactionId transactionId)
parentXid = SubTransGetParent(transactionId);
if (!TransactionIdIsValid(parentXid))
{
- elog(WARNING, "no pg_subtrans entry for subcommitted XID %u",
+ elog(WARNING, "no pg_subtrans entry for subcommitted XID " XID_FMT,
transactionId);
return false;
}
@@ -206,7 +206,7 @@ TransactionIdDidAbort(TransactionId transactionId)
if (!TransactionIdIsValid(parentXid))
{
/* see notes in TransactionIdDidCommit */
- elog(WARNING, "no pg_subtrans entry for subcommitted XID %u",
+ elog(WARNING, "no pg_subtrans entry for subcommitted XID " XID_FMT,
transactionId);
return true;
}
@@ -293,70 +293,6 @@ TransactionIdAbortTree(TransactionId xid, int nxids, TransactionId *xids)
TRANSACTION_STATUS_ABORTED, InvalidXLogRecPtr);
}
-/*
- * TransactionIdPrecedes --- is id1 logically < id2?
- */
-bool
-TransactionIdPrecedes(TransactionId id1, TransactionId id2)
-{
- /*
- * If either ID is a permanent XID then we can just do unsigned
- * comparison. If both are normal, do a modulo-2^32 comparison.
- */
- int32 diff;
-
- if (!TransactionIdIsNormal(id1) || !TransactionIdIsNormal(id2))
- return (id1 < id2);
-
- diff = (int32) (id1 - id2);
- return (diff < 0);
-}
-
-/*
- * TransactionIdPrecedesOrEquals --- is id1 logically <= id2?
- */
-bool
-TransactionIdPrecedesOrEquals(TransactionId id1, TransactionId id2)
-{
- int32 diff;
-
- if (!TransactionIdIsNormal(id1) || !TransactionIdIsNormal(id2))
- return (id1 <= id2);
-
- diff = (int32) (id1 - id2);
- return (diff <= 0);
-}
-
-/*
- * TransactionIdFollows --- is id1 logically > id2?
- */
-bool
-TransactionIdFollows(TransactionId id1, TransactionId id2)
-{
- int32 diff;
-
- if (!TransactionIdIsNormal(id1) || !TransactionIdIsNormal(id2))
- return (id1 > id2);
-
- diff = (int32) (id1 - id2);
- return (diff > 0);
-}
-
-/*
- * TransactionIdFollowsOrEquals --- is id1 logically >= id2?
- */
-bool
-TransactionIdFollowsOrEquals(TransactionId id1, TransactionId id2)
-{
- int32 diff;
-
- if (!TransactionIdIsNormal(id1) || !TransactionIdIsNormal(id2))
- return (id1 >= id2);
-
- diff = (int32) (id1 - id2);
- return (diff >= 0);
-}
-
/*
* TransactionIdLatest --- get latest XID among a main xact and its children
diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c
index 874c8ed125..039914658b 100644
--- a/src/backend/access/transam/twophase.c
+++ b/src/backend/access/transam/twophase.c
@@ -849,7 +849,7 @@ TwoPhaseGetGXact(TransactionId xid, bool lock_held)
LWLockRelease(TwoPhaseStateLock);
if (result == NULL) /* should not happen */
- elog(ERROR, "failed to find GlobalTransaction for xid %u", xid);
+ elog(ERROR, "failed to find GlobalTransaction for xid " XID_FMT, xid);
cached_xid = xid;
cached_gxact = result;
@@ -941,7 +941,7 @@ TwoPhaseGetDummyProc(TransactionId xid, bool lock_held)
/************************************************************************/
#define TwoPhaseFilePath(path, xid) \
- snprintf(path, MAXPGPATH, TWOPHASE_DIR "/%08X", xid)
+ snprintf(path, MAXPGPATH, TWOPHASE_DIR "/%08X%08X", (uint32)(xid >> 32), (uint32)xid)
/*
* 2PC state file format:
@@ -1846,13 +1846,13 @@ restoreTwoPhaseData(void)
cldir = AllocateDir(TWOPHASE_DIR);
while ((clde = ReadDir(cldir, TWOPHASE_DIR)) != NULL)
{
- if (strlen(clde->d_name) == 8 &&
- strspn(clde->d_name, "0123456789ABCDEF") == 8)
+ if (strlen(clde->d_name) == 16 &&
+ strspn(clde->d_name, "0123456789ABCDEF") == 16)
{
TransactionId xid;
char *buf;
- xid = (TransactionId) strtoul(clde->d_name, NULL, 16);
+ xid = (TransactionId) pg_strtouint64(clde->d_name, NULL, 16);
buf = ProcessTwoPhaseBuffer(xid, InvalidXLogRecPtr,
true, false, false);
@@ -2054,7 +2054,7 @@ RecoverPreparedTransactions(void)
continue;
ereport(LOG,
- (errmsg("recovering prepared transaction %u from shared memory", xid)));
+ (errmsg("recovering prepared transaction %" PRIu64 " from shared memory", xid)));
hdr = (TwoPhaseFileHeader *) buf;
Assert(TransactionIdEquals(hdr->xid, xid));
@@ -2145,14 +2145,14 @@ ProcessTwoPhaseBuffer(TransactionId xid,
if (fromdisk)
{
ereport(WARNING,
- (errmsg("removing stale two-phase state file for transaction %u",
+ (errmsg("removing stale two-phase state file for transaction \"%" PRIu64 "\"",
xid)));
RemoveTwoPhaseFile(xid, true);
}
else
{
ereport(WARNING,
- (errmsg("removing stale two-phase state from memory for transaction %u",
+ (errmsg("removing stale two-phase state from memory for transaction \"%" PRIu64 "\"",
xid)));
PrepareRedoRemove(xid, true);
}
@@ -2165,14 +2165,14 @@ ProcessTwoPhaseBuffer(TransactionId xid,
if (fromdisk)
{
ereport(WARNING,
- (errmsg("removing future two-phase state file for transaction %u",
+ (errmsg("removing future two-phase state file for transaction \"%" PRIu64 "\"",
xid)));
RemoveTwoPhaseFile(xid, true);
}
else
{
ereport(WARNING,
- (errmsg("removing future two-phase state from memory for transaction %u",
+ (errmsg("removing future two-phase state from memory for transaction \"%" PRIu64 "\"",
xid)));
PrepareRedoRemove(xid, true);
}
@@ -2181,7 +2181,6 @@ ProcessTwoPhaseBuffer(TransactionId xid,
if (fromdisk)
{
- /* Read and validate file */
buf = ReadTwoPhaseFile(xid, false);
}
else
@@ -2197,12 +2196,12 @@ ProcessTwoPhaseBuffer(TransactionId xid,
if (fromdisk)
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
- errmsg("corrupted two-phase state file for transaction %u",
+ errmsg("corrupted two-phase state file for transaction %" PRIu64,
xid)));
else
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
- errmsg("corrupted two-phase state in memory for transaction %u",
+ errmsg("corrupted two-phase state in memory for transaction %" PRIu64,
xid)));
}
@@ -2358,7 +2357,7 @@ RecordTransactionAbortPrepared(TransactionId xid,
* RecordTransactionCommitPrepared ...
*/
if (TransactionIdDidCommit(xid))
- elog(PANIC, "cannot abort transaction %u, it was already committed",
+ elog(PANIC, "cannot abort transaction " XID_FMT ", it was already committed",
xid);
START_CRIT_SECTION();
@@ -2465,7 +2464,7 @@ PrepareRedoAdd(char *buf, XLogRecPtr start_lsn,
false /* backward */ , false /* WAL */ );
}
- elog(DEBUG2, "added 2PC data in shared memory for transaction %u", gxact->xid);
+ elog(DEBUG2, "added 2PC data in shared memory for transaction " XID_FMT, gxact->xid);
}
/*
@@ -2508,7 +2507,7 @@ PrepareRedoRemove(TransactionId xid, bool giveWarning)
/*
* And now we can clean up any files we may have left.
*/
- elog(DEBUG2, "removing 2PC data for transaction %u", xid);
+ elog(DEBUG2, "removing 2PC data for transaction " XID_FMT, xid);
if (gxact->ondisk)
RemoveTwoPhaseFile(xid, giveWarning);
RemoveGXact(gxact);
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index 748120a012..0a89721b00 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -68,7 +68,7 @@ GetNewTransactionId(bool isSubXact)
Assert(!isSubXact);
MyProc->xid = BootstrapTransactionId;
ProcGlobal->xids[MyProc->pgxactoff] = BootstrapTransactionId;
- return FullTransactionIdFromEpochAndXid(0, BootstrapTransactionId);
+ return FullTransactionIdFromXid(BootstrapTransactionId);
}
/* safety check, we should never get this far in a HS standby */
@@ -102,11 +102,6 @@ GetNewTransactionId(bool isSubXact)
* possibility of deadlock while doing get_database_name(). First,
* copy all the shared values we'll need in this path.
*/
- TransactionId xidWarnLimit = ShmemVariableCache->xidWarnLimit;
- TransactionId xidStopLimit = ShmemVariableCache->xidStopLimit;
- TransactionId xidWrapLimit = ShmemVariableCache->xidWrapLimit;
- Oid oldest_datoid = ShmemVariableCache->oldestXidDB;
-
LWLockRelease(XidGenLock);
/*
@@ -117,48 +112,6 @@ GetNewTransactionId(bool isSubXact)
if (IsUnderPostmaster && (xid % 65536) == 0)
SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
- if (IsUnderPostmaster &&
- TransactionIdFollowsOrEquals(xid, xidStopLimit))
- {
- char *oldest_datname = get_database_name(oldest_datoid);
-
- /* complain even if that DB has disappeared */
- if (oldest_datname)
- ereport(ERROR,
- (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg("database is not accepting commands to avoid wraparound data loss in database \"%s\"",
- oldest_datname),
- errhint("Stop the postmaster and vacuum that database in single-user mode.\n"
- "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
- else
- ereport(ERROR,
- (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg("database is not accepting commands to avoid wraparound data loss in database with OID %u",
- oldest_datoid),
- errhint("Stop the postmaster and vacuum that database in single-user mode.\n"
- "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
- }
- else if (TransactionIdFollowsOrEquals(xid, xidWarnLimit))
- {
- char *oldest_datname = get_database_name(oldest_datoid);
-
- /* complain even if that DB has disappeared */
- if (oldest_datname)
- ereport(WARNING,
- (errmsg("database \"%s\" must be vacuumed within %u transactions",
- oldest_datname,
- xidWrapLimit - xid),
- errhint("To avoid a database shutdown, execute a database-wide VACUUM in that database.\n"
- "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
- else
- ereport(WARNING,
- (errmsg("database with OID %u must be vacuumed within %u transactions",
- oldest_datoid,
- xidWrapLimit - xid),
- errhint("To avoid a database shutdown, execute a database-wide VACUUM in that database.\n"
- "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
- }
-
/* Re-acquire lock and start over */
LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
full_xid = ShmemVariableCache->nextXid;
@@ -270,7 +223,7 @@ ReadNextFullTransactionId(void)
}
/*
- * Advance nextXid to the value after a given xid. The epoch is inferred.
+ * Advance nextXid to the value after a given xid.
* This must only be called during recovery or from two-phase start-up code.
*/
void
@@ -278,7 +231,6 @@ AdvanceNextFullTransactionIdPastXid(TransactionId xid)
{
FullTransactionId newNextFullXid;
TransactionId next_xid;
- uint32 epoch;
/*
* It is safe to read nextXid without a lock, because this is only called
@@ -292,19 +244,9 @@ AdvanceNextFullTransactionIdPastXid(TransactionId xid)
if (!TransactionIdFollowsOrEquals(xid, next_xid))
return;
- /*
- * Compute the FullTransactionId that comes after the given xid. To do
- * this, we preserve the existing epoch, but detect when we've wrapped
- * into a new epoch. This is necessary because WAL records and 2PC state
- * currently contain 32 bit xids. The wrap logic is safe in those cases
- * because the span of active xids cannot exceed one epoch at any given
- * point in the WAL stream.
- */
+ /* Compute the FullTransactionId that comes after the given xid. */
TransactionIdAdvance(xid);
- epoch = EpochFromFullTransactionId(ShmemVariableCache->nextXid);
- if (unlikely(xid < next_xid))
- ++epoch;
- newNextFullXid = FullTransactionIdFromEpochAndXid(epoch, xid);
+ newNextFullXid = FullTransactionIdFromXid(xid);
/*
* We still need to take a lock to modify the value when there are
@@ -345,54 +287,10 @@ void
SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
{
TransactionId xidVacLimit;
- TransactionId xidWarnLimit;
- TransactionId xidStopLimit;
- TransactionId xidWrapLimit;
TransactionId curXid;
Assert(TransactionIdIsNormal(oldest_datfrozenxid));
- /*
- * The place where we actually get into deep trouble is halfway around
- * from the oldest potentially-existing XID. (This calculation is
- * probably off by one or two counts, because the special XIDs reduce the
- * size of the loop a little bit. But we throw in plenty of slop below,
- * so it doesn't matter.)
- */
- xidWrapLimit = oldest_datfrozenxid + (MaxTransactionId >> 1);
- if (xidWrapLimit < FirstNormalTransactionId)
- xidWrapLimit += FirstNormalTransactionId;
-
- /*
- * We'll refuse to continue assigning XIDs in interactive mode once we get
- * within 3M transactions of data loss. This leaves lots of room for the
- * DBA to fool around fixing things in a standalone backend, while not
- * being significant compared to total XID space. (VACUUM requires an XID
- * if it truncates at wal_level!=minimal. "VACUUM (ANALYZE)", which a DBA
- * might do by reflex, assigns an XID. Hence, we had better be sure
- * there's lots of XIDs left...) Also, at default BLCKSZ, this leaves two
- * completely-idle segments. In the event of edge-case bugs involving
- * page or segment arithmetic, idle segments render the bugs unreachable
- * outside of single-user mode.
- */
- xidStopLimit = xidWrapLimit - 3000000;
- if (xidStopLimit < FirstNormalTransactionId)
- xidStopLimit -= FirstNormalTransactionId;
-
- /*
- * We'll start complaining loudly when we get within 40M transactions of
- * data loss. This is kind of arbitrary, but if you let your gas gauge
- * get down to 2% of full, would you be looking for the next gas station?
- * We need to be fairly liberal about this number because there are lots
- * of scenarios where most transactions are done by automatic clients that
- * won't pay attention to warnings. (No, we're not gonna make this
- * configurable. If you know enough to configure it, you know enough to
- * not get in this kind of trouble in the first place.)
- */
- xidWarnLimit = xidWrapLimit - 40000000;
- if (xidWarnLimit < FirstNormalTransactionId)
- xidWarnLimit -= FirstNormalTransactionId;
-
/*
* We'll start trying to force autovacuums when oldest_datfrozenxid gets
* to be more than autovacuum_freeze_max_age transactions old.
@@ -416,18 +314,10 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
ShmemVariableCache->oldestXid = oldest_datfrozenxid;
ShmemVariableCache->xidVacLimit = xidVacLimit;
- ShmemVariableCache->xidWarnLimit = xidWarnLimit;
- ShmemVariableCache->xidStopLimit = xidStopLimit;
- ShmemVariableCache->xidWrapLimit = xidWrapLimit;
ShmemVariableCache->oldestXidDB = oldest_datoid;
curXid = XidFromFullTransactionId(ShmemVariableCache->nextXid);
LWLockRelease(XidGenLock);
- /* Log the info */
- ereport(DEBUG1,
- (errmsg_internal("transaction ID wrap limit is %u, limited by database with OID %u",
- xidWrapLimit, oldest_datoid)));
-
/*
* If past the autovacuum force point, immediately signal an autovac
* request. The reason for this is that autovac only processes one
@@ -438,41 +328,6 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
if (TransactionIdFollowsOrEquals(curXid, xidVacLimit) &&
IsUnderPostmaster && !InRecovery)
SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
-
- /* Give an immediate warning if past the wrap warn point */
- if (TransactionIdFollowsOrEquals(curXid, xidWarnLimit) && !InRecovery)
- {
- char *oldest_datname;
-
- /*
- * We can be called when not inside a transaction, for example during
- * StartupXLOG(). In such a case we cannot do database access, so we
- * must just report the oldest DB's OID.
- *
- * Note: it's also possible that get_database_name fails and returns
- * NULL, for example because the database just got dropped. We'll
- * still warn, even though the warning might now be unnecessary.
- */
- if (IsTransactionState())
- oldest_datname = get_database_name(oldest_datoid);
- else
- oldest_datname = NULL;
-
- if (oldest_datname)
- ereport(WARNING,
- (errmsg("database \"%s\" must be vacuumed within %u transactions",
- oldest_datname,
- xidWrapLimit - curXid),
- errhint("To avoid a database shutdown, execute a database-wide VACUUM in that database.\n"
- "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
- else
- ereport(WARNING,
- (errmsg("database with OID %u must be vacuumed within %u transactions",
- oldest_datoid,
- xidWrapLimit - curXid),
- errhint("To avoid a database shutdown, execute a database-wide VACUUM in that database.\n"
- "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
- }
}
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index adf763a8ea..fd9fe16288 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -1727,7 +1727,7 @@ RecordTransactionAbort(bool isSubXact)
* Check that we haven't aborted halfway through RecordTransactionCommit.
*/
if (TransactionIdDidCommit(xid))
- elog(PANIC, "cannot abort transaction %u, it was already committed",
+ elog(PANIC, "cannot abort transaction " XID_FMT ", it was already committed",
xid);
/* Fetch the data we need for the abort record */
@@ -5433,22 +5433,22 @@ ShowTransactionStateRec(const char *str, TransactionState s)
{
int i;
- appendStringInfo(&buf, ", children: %u", s->childXids[0]);
+ appendStringInfo(&buf, ", children: " XID_FMT, s->childXids[0]);
for (i = 1; i < s->nChildXids; i++)
- appendStringInfo(&buf, " %u", s->childXids[i]);
+ appendStringInfo(&buf, " " XID_FMT, s->childXids[i]);
}
if (s->parent)
ShowTransactionStateRec(str, s->parent);
ereport(DEBUG5,
- (errmsg_internal("%s(%d) name: %s; blockState: %s; state: %s, xid/subid/cid: %u/%u/%u%s%s",
+ (errmsg_internal("%s(%d) name: %s; blockState: %s; state: %s, xid/subid/cid: " XID_FMT "/" XID_FMT "/%u%s%s",
str, s->nestingLevel,
PointerIsValid(s->name) ? s->name : "unnamed",
BlockStateAsString(s->blockState),
TransStateAsString(s->state),
- (unsigned int) XidFromFullTransactionId(s->fullTransactionId),
- (unsigned int) s->subTransactionId,
+ XidFromFullTransactionId(s->fullTransactionId),
+ s->subTransactionId,
(unsigned int) currentCommandId,
currentCommandIdUsed ? " (used)" : "",
buf.data)));
@@ -5631,6 +5631,17 @@ XactLogCommitRecord(TimestampTz commit_time,
xl_subxacts.nsubxacts = nsubxacts;
}
+ if (TransactionIdIsValid(twophase_xid))
+ {
+ xl_xinfo.xinfo |= XACT_XINFO_HAS_TWOPHASE;
+ xl_twophase.xid_lo = (uint32)(twophase_xid & 0xFFFFFFFF);
+ xl_twophase.xid_hi = (uint32)(twophase_xid >> 32);
+ Assert(twophase_gid != NULL);
+
+ if (XLogLogicalInfoActive())
+ xl_xinfo.xinfo |= XACT_XINFO_HAS_GID;
+ }
+
if (nrels > 0)
{
xl_xinfo.xinfo |= XACT_XINFO_HAS_RELFILENODES;
@@ -5644,16 +5655,6 @@ XactLogCommitRecord(TimestampTz commit_time,
xl_invals.nmsgs = nmsgs;
}
- if (TransactionIdIsValid(twophase_xid))
- {
- xl_xinfo.xinfo |= XACT_XINFO_HAS_TWOPHASE;
- xl_twophase.xid = twophase_xid;
- Assert(twophase_gid != NULL);
-
- if (XLogLogicalInfoActive())
- xl_xinfo.xinfo |= XACT_XINFO_HAS_GID;
- }
-
/* dump transaction origin information */
if (replorigin_session_origin != InvalidRepOriginId)
{
@@ -5764,23 +5765,24 @@ XactLogAbortRecord(TimestampTz abort_time,
xl_subxacts.nsubxacts = nsubxacts;
}
- if (nrels > 0)
- {
- xl_xinfo.xinfo |= XACT_XINFO_HAS_RELFILENODES;
- xl_relfilenodes.nrels = nrels;
- info |= XLR_SPECIAL_REL_UPDATE;
- }
-
if (TransactionIdIsValid(twophase_xid))
{
xl_xinfo.xinfo |= XACT_XINFO_HAS_TWOPHASE;
- xl_twophase.xid = twophase_xid;
+ xl_twophase.xid_lo = (uint32)(twophase_xid & 0xFFFFFFFF);
+ xl_twophase.xid_hi = (uint32)(twophase_xid >> 32);
Assert(twophase_gid != NULL);
if (XLogLogicalInfoActive())
xl_xinfo.xinfo |= XACT_XINFO_HAS_GID;
}
+ if (nrels > 0)
+ {
+ xl_xinfo.xinfo |= XACT_XINFO_HAS_RELFILENODES;
+ xl_relfilenodes.nrels = nrels;
+ info |= XLR_SPECIAL_REL_UPDATE;
+ }
+
if (TransactionIdIsValid(twophase_xid) && XLogLogicalInfoActive())
{
xl_xinfo.xinfo |= XACT_XINFO_HAS_DBINFO;
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 0d2bd7a357..a1698e5593 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -134,6 +134,10 @@ int wal_retrieve_retry_interval = 5000;
int max_slot_wal_keep_size_mb = -1;
bool track_wal_io_timing = false;
+TransactionId start_xid = 0;
+MultiXactId start_mx_id = 0;
+MultiXactOffset start_mx_offset = 0;
+
#ifdef WAL_DEBUG
bool XLOG_DEBUG = false;
#endif
@@ -4545,13 +4549,13 @@ BootStrapXLOG(void)
checkPoint.PrevTimeLineID = BootstrapTimeLineID;
checkPoint.fullPageWrites = fullPageWrites;
checkPoint.nextXid =
- FullTransactionIdFromEpochAndXid(0, FirstNormalTransactionId);
+ FullTransactionIdFromXid(Max(FirstNormalTransactionId + 1, start_xid));
checkPoint.nextOid = FirstGenbkiObjectId;
- checkPoint.nextMulti = FirstMultiXactId;
- checkPoint.nextMultiOffset = 0;
- checkPoint.oldestXid = FirstNormalTransactionId;
+ checkPoint.nextMulti = Max(FirstMultiXactId + 1, start_mx_id);
+ checkPoint.nextMultiOffset = start_mx_offset;
+ checkPoint.oldestXid = XidFromFullTransactionId(checkPoint.nextXid) - 1;
checkPoint.oldestXidDB = TemplateDbOid;
- checkPoint.oldestMulti = FirstMultiXactId;
+ checkPoint.oldestMulti = checkPoint.nextMulti - 1;
checkPoint.oldestMultiDB = TemplateDbOid;
checkPoint.oldestCommitTsXid = InvalidTransactionId;
checkPoint.newestCommitTsXid = InvalidTransactionId;
@@ -6590,7 +6594,7 @@ CreateCheckPoint(int flags)
UpdateControlFile();
LWLockRelease(ControlFileLock);
- /* Update shared-memory copy of checkpoint XID/epoch */
+ /* Update shared-memory copy of checkpoint XID/base */
SpinLockAcquire(&XLogCtl->info_lck);
XLogCtl->ckptFullXid = checkPoint.nextXid;
SpinLockRelease(&XLogCtl->info_lck);
@@ -7604,7 +7608,7 @@ xlog_redo(XLogReaderState *record)
ControlFile->checkPointCopy.nextXid = checkPoint.nextXid;
LWLockRelease(ControlFileLock);
- /* Update shared-memory copy of checkpoint XID/epoch */
+ /* Update shared-memory copy of checkpoint XID/base */
SpinLockAcquire(&XLogCtl->info_lck);
XLogCtl->ckptFullXid = checkPoint.nextXid;
SpinLockRelease(&XLogCtl->info_lck);
@@ -7665,7 +7669,7 @@ xlog_redo(XLogReaderState *record)
ControlFile->checkPointCopy.nextXid = checkPoint.nextXid;
LWLockRelease(ControlFileLock);
- /* Update shared-memory copy of checkpoint XID/epoch */
+ /* Update shared-memory copy of checkpoint XID/base */
SpinLockAcquire(&XLogCtl->info_lck);
XLogCtl->ckptFullXid = checkPoint.nextXid;
SpinLockRelease(&XLogCtl->info_lck);
diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c
index c260310c4c..be4c80ee6f 100644
--- a/src/backend/access/transam/xloginsert.c
+++ b/src/backend/access/transam/xloginsert.c
@@ -246,6 +246,11 @@ XLogRegisterBuffer(uint8 block_id, Buffer buffer, uint8 flags)
BufferGetTag(buffer, ®buf->rnode, ®buf->forkno, ®buf->block);
regbuf->page = BufferGetPage(buffer);
regbuf->flags = flags;
+ if (IsBufferConverted(buffer))
+ {
+ regbuf->flags |= REGBUF_CONVERTED;
+ MarkBufferConverted(buffer, false);
+ }
regbuf->rdata_tail = (XLogRecData *) ®buf->rdata_head;
regbuf->rdata_len = 0;
@@ -561,6 +566,8 @@ XLogRecordAssemble(RmgrId rmid, uint8 info,
needs_backup = true;
else if (regbuf->flags & REGBUF_NO_IMAGE)
needs_backup = false;
+ else if (regbuf->flags & REGBUF_CONVERTED)
+ needs_backup = true;
else if (!doPageWrites)
needs_backup = false;
else
diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c
index 35029cf97d..abf7613002 100644
--- a/src/backend/access/transam/xlogreader.c
+++ b/src/backend/access/transam/xlogreader.c
@@ -1656,37 +1656,3 @@ RestoreBlockImage(XLogReaderState *record, uint8 block_id, char *page)
return true;
}
-
-#ifndef FRONTEND
-
-/*
- * Extract the FullTransactionId from a WAL record.
- */
-FullTransactionId
-XLogRecGetFullXid(XLogReaderState *record)
-{
- TransactionId xid,
- next_xid;
- uint32 epoch;
-
- /*
- * This function is only safe during replay, because it depends on the
- * replay state. See AdvanceNextFullTransactionIdPastXid() for more.
- */
- Assert(AmStartupProcess() || !IsUnderPostmaster);
-
- xid = XLogRecGetXid(record);
- next_xid = XidFromFullTransactionId(ShmemVariableCache->nextXid);
- epoch = EpochFromFullTransactionId(ShmemVariableCache->nextXid);
-
- /*
- * If xid is numerically greater than next_xid, it has to be from the last
- * epoch.
- */
- if (unlikely(xid > next_xid))
- --epoch;
-
- return FullTransactionIdFromEpochAndXid(epoch, xid);
-}
-
-#endif
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index f9f212680b..f58dcf65bc 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -518,7 +518,7 @@ InitWalRecovery(ControlFileData *ControlFile, bool *wasShutdown_ptr,
(errmsg("entering standby mode")));
else if (recoveryTarget == RECOVERY_TARGET_XID)
ereport(LOG,
- (errmsg("starting point-in-time recovery to XID %u",
+ (errmsg("starting point-in-time recovery to XID " XID_FMT,
recoveryTargetXid)));
else if (recoveryTarget == RECOVERY_TARGET_TIME)
ereport(LOG,
@@ -798,16 +798,16 @@ InitWalRecovery(ControlFileData *ControlFile, bool *wasShutdown_ptr,
U64FromFullTransactionId(checkPoint.nextXid),
checkPoint.nextOid)));
ereport(DEBUG1,
- (errmsg_internal("next MultiXactId: %u; next MultiXactOffset: %u",
+ (errmsg_internal("next MultiXactId: " XID_FMT "; next MultiXactOffset: " XID_FMT,
checkPoint.nextMulti, checkPoint.nextMultiOffset)));
ereport(DEBUG1,
- (errmsg_internal("oldest unfrozen transaction ID: %u, in database %u",
+ (errmsg_internal("oldest unfrozen transaction ID: " XID_FMT ", in database %u",
checkPoint.oldestXid, checkPoint.oldestXidDB)));
ereport(DEBUG1,
- (errmsg_internal("oldest MultiXactId: %u, in database %u",
+ (errmsg_internal("oldest MultiXactId: " XID_FMT ", in database %u",
checkPoint.oldestMulti, checkPoint.oldestMultiDB)));
ereport(DEBUG1,
- (errmsg_internal("commit timestamp Xid oldest/newest: %u/%u",
+ (errmsg_internal("commit timestamp Xid oldest/newest: " XID_FMT "/" XID_FMT,
checkPoint.oldestCommitTsXid,
checkPoint.newestCommitTsXid)));
if (!TransactionIdIsNormal(XidFromFullTransactionId(checkPoint.nextXid)))
@@ -2490,14 +2490,14 @@ recoveryStopsBefore(XLogReaderState *record)
if (isCommit)
{
ereport(LOG,
- (errmsg("recovery stopping before commit of transaction %u, time %s",
+ (errmsg("recovery stopping before commit of transaction " XID_FMT ", time %s",
recoveryStopXid,
timestamptz_to_str(recoveryStopTime))));
}
else
{
ereport(LOG,
- (errmsg("recovery stopping before abort of transaction %u, time %s",
+ (errmsg("recovery stopping before abort of transaction " XID_FMT ", time %s",
recoveryStopXid,
timestamptz_to_str(recoveryStopTime))));
}
@@ -2635,7 +2635,7 @@ recoveryStopsAfter(XLogReaderState *record)
xact_info == XLOG_XACT_COMMIT_PREPARED)
{
ereport(LOG,
- (errmsg("recovery stopping after commit of transaction %u, time %s",
+ (errmsg("recovery stopping after commit of transaction " XID_FMT ", time %s",
recoveryStopXid,
timestamptz_to_str(recoveryStopTime))));
}
@@ -2643,7 +2643,7 @@ recoveryStopsAfter(XLogReaderState *record)
xact_info == XLOG_XACT_ABORT_PREPARED)
{
ereport(LOG,
- (errmsg("recovery stopping after abort of transaction %u, time %s",
+ (errmsg("recovery stopping after abort of transaction " XID_FMT ", time %s",
recoveryStopXid,
timestamptz_to_str(recoveryStopTime))));
}
@@ -2679,7 +2679,7 @@ getRecoveryStopReason(void)
if (recoveryTarget == RECOVERY_TARGET_XID)
snprintf(reason, sizeof(reason),
- "%s transaction %u",
+ "%s transaction " XID_FMT,
recoveryStopAfter ? "after" : "before",
recoveryStopXid);
else if (recoveryTarget == RECOVERY_TARGET_TIME)
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 9fa8fdd4cf..2e3efc1fc0 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -120,7 +120,7 @@ static const struct typinfo TypInfo[] = {
F_OIDIN, F_OIDOUT},
{"tid", TIDOID, 0, 6, false, TYPALIGN_SHORT, TYPSTORAGE_PLAIN, InvalidOid,
F_TIDIN, F_TIDOUT},
- {"xid", XIDOID, 0, 4, true, TYPALIGN_INT, TYPSTORAGE_PLAIN, InvalidOid,
+ {"xid", XIDOID, 0, 8, FLOAT8PASSBYVAL, TYPALIGN_DOUBLE, TYPSTORAGE_PLAIN, InvalidOid,
F_XIDIN, F_XIDOUT},
{"cid", CIDOID, 0, 4, true, TYPALIGN_INT, TYPSTORAGE_PLAIN, InvalidOid,
F_CIDIN, F_CIDOUT},
@@ -221,7 +221,11 @@ BootstrapModeMain(int argc, char *argv[], bool check_only)
argv++;
argc--;
- while ((flag = getopt(argc, argv, "B:c:d:D:Fkr:X:-:")) != -1)
+ start_xid = 0;
+ start_mx_id = 0;
+ start_mx_offset = 0;
+
+ while ((flag = getopt(argc, argv, "B:c:d:D:Fkm:o:r:X:Z:-:")) != -1)
{
switch (flag)
{
@@ -250,9 +254,30 @@ BootstrapModeMain(int argc, char *argv[], bool check_only)
case 'k':
bootstrap_data_checksum_version = PG_DATA_CHECKSUM_VERSION;
break;
+ case 'm':
+ if (sscanf(optarg, XID_FMT, &start_mx_id) != 1
+ || !StartMultiXactIdIsValid(start_mx_id))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid start multixact id value")));
+ break;
+ case 'o':
+ if (sscanf(optarg, XID_FMT, &start_mx_offset) != 1
+ || !StartMultiXactOffsetIsValid(start_mx_offset))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid start multixact offset value")));
+ break;
case 'r':
strlcpy(OutputFileName, optarg, MAXPGPATH);
break;
+ case 'Z':
+ if (sscanf(optarg, XID_FMT, &start_xid) != 1
+ || !StartTransactionIdIsValid(start_xid))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid start xid value")));
+ break;
case 'X':
{
int WalSegSz = strtoul(optarg, NULL, 0);
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 7e99de88b3..10fdc5af6c 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -173,8 +173,8 @@ static const FormData_pg_attribute a2 = {
.attnum = MinTransactionIdAttributeNumber,
.attcacheoff = -1,
.atttypmod = -1,
- .attbyval = true,
- .attalign = TYPALIGN_INT,
+ .attbyval = FLOAT8PASSBYVAL,
+ .attalign = TYPALIGN_DOUBLE,
.attstorage = TYPSTORAGE_PLAIN,
.attnotnull = true,
.attislocal = true,
@@ -201,8 +201,8 @@ static const FormData_pg_attribute a4 = {
.attnum = MaxTransactionIdAttributeNumber,
.attcacheoff = -1,
.atttypmod = -1,
- .attbyval = true,
- .attalign = TYPALIGN_INT,
+ .attbyval = FLOAT8PASSBYVAL,
+ .attalign = TYPALIGN_DOUBLE,
.attstorage = TYPSTORAGE_PLAIN,
.attnotnull = true,
.attislocal = true,
diff --git a/src/backend/catalog/pg_inherits.c b/src/backend/catalog/pg_inherits.c
index 92afbc2f25..3a1eda413f 100644
--- a/src/backend/catalog/pg_inherits.c
+++ b/src/backend/catalog/pg_inherits.c
@@ -146,7 +146,7 @@ find_inheritance_children_extended(Oid parentrelId, bool omit_detached,
TransactionId xmin;
Snapshot snap;
- xmin = HeapTupleHeaderGetXmin(inheritsTuple->t_data);
+ xmin = HeapTupleGetXmin(inheritsTuple);
snap = GetActiveSnapshot();
if (!XidInMVCCSnapshot(xmin, snap))
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index 455d895a44..4b22140459 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -186,8 +186,8 @@ typedef struct AsyncQueueEntry
char data[NAMEDATALEN + NOTIFY_PAYLOAD_MAX_LENGTH];
} AsyncQueueEntry;
-/* Currently, no field of AsyncQueueEntry requires more than int alignment */
-#define QUEUEALIGN(len) INTALIGN(len)
+/* AsyncQueueEntry.xid requires 8-byte alignment */
+#define QUEUEALIGN(len) MAXALIGN(len)
#define AsyncQueueEntryEmptySize (offsetof(AsyncQueueEntry, data) + 2)
@@ -444,7 +444,7 @@ bool Trace_notify = false;
/* local function prototypes */
static int asyncQueuePageDiff(int p, int q);
-static bool asyncQueuePagePrecedes(int p, int q);
+static bool asyncQueuePagePrecedes(int64 p, int64 q);
static void queue_listen(ListenActionKind action, const char *channel);
static void Async_UnlistenOnExit(int code, Datum arg);
static void Exec_ListenPreCommit(void);
@@ -497,16 +497,10 @@ asyncQueuePageDiff(int p, int q)
return diff;
}
-/*
- * Is p < q, accounting for wraparound?
- *
- * Since asyncQueueIsFull() blocks creation of a page that could precede any
- * extant page, we need not assess entries within a page.
- */
static bool
-asyncQueuePagePrecedes(int p, int q)
+asyncQueuePagePrecedes(int64 p, int64 q)
{
- return asyncQueuePageDiff(p, q) < 0;
+ return p < q;
}
/*
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index ab592ce2f1..f750153e64 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -55,16 +55,6 @@
*/
#define SEQ_LOG_VALS 32
-/*
- * The "special area" of a sequence's buffer page looks like this.
- */
-#define SEQ_MAGIC 0x1717
-
-typedef struct sequence_magic
-{
- uint32 magic;
-} sequence_magic;
-
/*
* We store a SeqTable item for every sequence we have touched in the current
* session. This is needed to hold onto nextval/currval state. (We can't
@@ -342,10 +332,9 @@ ResetSequence(Oid seq_relid)
static void
fill_seq_with_data(Relation rel, HeapTuple tuple)
{
- Buffer buf;
- Page page;
- sequence_magic *sm;
- OffsetNumber offnum;
+ Buffer buf;
+ Page page;
+ OffsetNumber offnum;
/* Initialize first page of relation with special magic number */
@@ -354,9 +343,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
page = BufferGetPage(buf);
- PageInit(page, BufferGetPageSize(buf), sizeof(sequence_magic));
- sm = (sequence_magic *) PageGetSpecialPointer(page);
- sm->magic = SEQ_MAGIC;
+ PageInit(page, BufferGetPageSize(buf), sizeof(HeapPageSpecialData));
+ HeapPageGetSpecial(page)->pd_xid_base = RecentXmin - FirstNormalTransactionId;
/* Now insert sequence tuple */
@@ -369,10 +357,12 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
* because if the current transaction aborts, no other xact will ever
* examine the sequence tuple anyway.
*/
- HeapTupleHeaderSetXmin(tuple->t_data, FrozenTransactionId);
+ HeapTupleSetXmin(tuple, FrozenTransactionId);
+ HeapTupleHeaderSetXmin(page, tuple);
HeapTupleHeaderSetXminFrozen(tuple->t_data);
HeapTupleHeaderSetCmin(tuple->t_data, FirstCommandId);
- HeapTupleHeaderSetXmax(tuple->t_data, InvalidTransactionId);
+ HeapTupleSetXmax(tuple, InvalidTransactionId);
+ HeapTupleHeaderSetXmax(page, tuple);
tuple->t_data->t_infomask |= HEAP_XMAX_INVALID;
ItemPointerSet(&tuple->t_data->t_ctid, 0, FirstOffsetNumber);
@@ -1202,25 +1192,19 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
{
Page page;
ItemId lp;
- sequence_magic *sm;
Form_pg_sequence_data seq;
*buf = ReadBuffer(rel, 0);
LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
page = BufferGetPage(*buf);
- sm = (sequence_magic *) PageGetSpecialPointer(page);
-
- if (sm->magic != SEQ_MAGIC)
- elog(ERROR, "bad magic number in sequence \"%s\": %08X",
- RelationGetRelationName(rel), sm->magic);
-
lp = PageGetItemId(page, FirstOffsetNumber);
Assert(ItemIdIsNormal(lp));
/* Note we currently only bother to set these two fields of *seqdatatuple */
seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
seqdatatuple->t_len = ItemIdGetLength(lp);
+ HeapTupleCopyBaseFromPage(seqdatatuple, page);
/*
* Previous releases of Postgres neglected to prevent SELECT FOR UPDATE on
@@ -1231,9 +1215,10 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
* this again if the update gets lost.
*/
Assert(!(seqdatatuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI));
- if (HeapTupleHeaderGetRawXmax(seqdatatuple->t_data) != InvalidTransactionId)
+ if (HeapTupleGetRawXmax(seqdatatuple) != InvalidTransactionId)
{
- HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId);
+ HeapTupleSetXmax(seqdatatuple, InvalidTransactionId);
+ HeapTupleHeaderSetXmax(page, seqdatatuple);
seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED;
seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID;
MarkBufferDirtyHint(*buf, true);
@@ -1895,7 +1880,6 @@ seq_redo(XLogReaderState *record)
char *item;
Size itemsz;
xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
- sequence_magic *sm;
if (info != XLOG_SEQ_LOG)
elog(PANIC, "seq_redo: unknown op code %u", info);
@@ -1914,9 +1898,8 @@ seq_redo(XLogReaderState *record)
*/
localpage = (Page) palloc(BufferGetPageSize(buffer));
- PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
- sm = (sequence_magic *) PageGetSpecialPointer(localpage);
- sm->magic = SEQ_MAGIC;
+ PageInit(localpage, BufferGetPageSize(buffer), sizeof(HeapPageSpecialData));
+ HeapPageGetSpecial(localpage)->pd_xid_base = RecentXmin - FirstNormalTransactionId;
item = (char *) xlrec + sizeof(xl_seq_rec);
itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 50a4a612e5..ad9fc57205 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -63,13 +63,12 @@
/*
* GUC parameters
*/
-int vacuum_freeze_min_age;
-int vacuum_freeze_table_age;
-int vacuum_multixact_freeze_min_age;
-int vacuum_multixact_freeze_table_age;
-int vacuum_failsafe_age;
-int vacuum_multixact_failsafe_age;
-
+int64 vacuum_freeze_min_age;
+int64 vacuum_freeze_table_age;
+int64 vacuum_multixact_freeze_min_age;
+int64 vacuum_multixact_freeze_table_age;
+int64 vacuum_failsafe_age;
+int64 vacuum_multixact_failsafe_age;
/* A few variables that don't seem worth passing around as parameters */
static MemoryContext vac_context = NULL;
@@ -956,23 +955,25 @@ get_all_vacuum_rels(int options)
*/
bool
vacuum_set_xid_limits(Relation rel,
- int freeze_min_age,
- int freeze_table_age,
- int multixact_freeze_min_age,
- int multixact_freeze_table_age,
+ int64 freeze_min_age,
+ int64 freeze_table_age,
+ int64 multixact_freeze_min_age,
+ int64 multixact_freeze_table_age,
TransactionId *oldestXmin,
TransactionId *freezeLimit,
MultiXactId *multiXactCutoff)
{
- int freezemin;
- int mxid_freezemin;
- int effective_multixact_freeze_max_age;
+ int64 freezemin;
+ int64 mxid_freezemin;
+ int64 effective_multixact_freeze_max_age;
TransactionId limit;
TransactionId safeLimit;
MultiXactId oldestMxact;
MultiXactId mxactLimit;
MultiXactId safeMxactLimit;
- int freezetable;
+ int64 freezetable;
+ TransactionId nextXid;
+ TransactionId nextMxactId;
/*
* We can always ignore processes running lazy vacuum. This is because we
@@ -1021,8 +1022,10 @@ vacuum_set_xid_limits(Relation rel,
/*
* Compute the cutoff XID, being careful not to generate a "permanent" XID
*/
- limit = *oldestXmin - freezemin;
- if (!TransactionIdIsNormal(limit))
+ limit = *oldestXmin;
+ if (limit > FirstNormalTransactionId + freezemin)
+ limit -= freezemin;
+ else
limit = FirstNormalTransactionId;
/*
@@ -1030,8 +1033,10 @@ vacuum_set_xid_limits(Relation rel,
* autovacuum_freeze_max_age / 2 XIDs old), complain and force a minimum
* freeze age of zero.
*/
- safeLimit = ReadNextTransactionId() - autovacuum_freeze_max_age;
- if (!TransactionIdIsNormal(safeLimit))
+ nextXid = ReadNextTransactionId();
+ if (nextXid > FirstNormalTransactionId + autovacuum_freeze_max_age)
+ safeLimit = nextXid - autovacuum_freeze_max_age;
+ else
safeLimit = FirstNormalTransactionId;
if (TransactionIdPrecedes(limit, safeLimit))
@@ -1050,7 +1055,7 @@ vacuum_set_xid_limits(Relation rel,
* normally autovacuum_multixact_freeze_max_age, but may be less if we are
* short of multixact member space.
*/
- effective_multixact_freeze_max_age = MultiXactMemberFreezeThreshold();
+ effective_multixact_freeze_max_age = autovacuum_multixact_freeze_max_age;
/*
* Determine the minimum multixact freeze age to use: as specified by
@@ -1071,16 +1076,23 @@ vacuum_set_xid_limits(Relation rel,
if (mxactLimit < FirstMultiXactId)
mxactLimit = FirstMultiXactId;
- safeMxactLimit =
- ReadNextMultiXactId() - effective_multixact_freeze_max_age;
- if (safeMxactLimit < FirstMultiXactId)
+ nextMxactId = ReadNextMultiXactId();
+ if (nextMxactId > FirstMultiXactId + effective_multixact_freeze_max_age)
+ safeMxactLimit = nextMxactId - effective_multixact_freeze_max_age;
+ else
safeMxactLimit = FirstMultiXactId;
if (MultiXactIdPrecedes(mxactLimit, safeMxactLimit))
{
ereport(WARNING,
- (errmsg("oldest multixact is far in the past"),
- errhint("Close open transactions with multixacts soon to avoid wraparound problems.")));
+ (errmsg("oldest multixact is far in the past: "
+ INT64_FORMAT " " INT64_FORMAT " "
+ INT64_FORMAT " " INT64_FORMAT " " INT64_FORMAT " "
+ INT64_FORMAT " " INT64_FORMAT " ",
+ multixact_freeze_min_age, vacuum_multixact_freeze_min_age,
+ mxactLimit, mxid_freezemin, oldestMxact,
+ safeMxactLimit, effective_multixact_freeze_max_age),
+ errhint("Close open transactions with multixacts soon to enable SLRU truncation.")));
/* Use the safe limit, unless an older mxact is still running */
if (MultiXactIdPrecedes(oldestMxact, safeMxactLimit))
mxactLimit = oldestMxact;
@@ -1110,8 +1122,10 @@ vacuum_set_xid_limits(Relation rel,
* Compute XID limit causing an aggressive vacuum, being careful not to
* generate a "permanent" XID
*/
- limit = ReadNextTransactionId() - freezetable;
- if (!TransactionIdIsNormal(limit))
+ limit = ReadNextTransactionId();
+ if (limit > FirstNormalTransactionId + freezetable)
+ limit -= freezetable;
+ else
limit = FirstNormalTransactionId;
if (TransactionIdPrecedesOrEquals(rel->rd_rel->relfrozenxid,
limit))
@@ -1136,8 +1150,10 @@ vacuum_set_xid_limits(Relation rel,
* Compute MultiXact limit causing an aggressive vacuum, being careful to
* generate a valid MultiXact value
*/
- mxactLimit = ReadNextMultiXactId() - freezetable;
- if (mxactLimit < FirstMultiXactId)
+ mxactLimit = ReadNextMultiXactId();
+ if (mxactLimit > FirstMultiXactId + freezetable)
+ mxactLimit -= freezetable;
+ else
mxactLimit = FirstMultiXactId;
if (MultiXactIdPrecedesOrEquals(rel->rd_rel->relminmxid,
mxactLimit))
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index d6f7d7c2d7..1276035aef 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -3154,6 +3154,7 @@ ExecEvalFieldStoreDeForm(ExprState *state, ExprEvalStep *op, ExprContext *econte
tmptup.t_len = HeapTupleHeaderGetDatumLength(tuphdr);
ItemPointerSetInvalid(&(tmptup.t_self));
tmptup.t_tableOid = InvalidOid;
+ HeapTupleSetZeroBase(&tmptup);
tmptup.t_data = tuphdr;
heap_deform_tuple(&tmptup, tupDesc,
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 9df1f81ea8..d8c92f4846 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -1070,6 +1070,7 @@ GetAttributeByName(HeapTupleHeader tuple, const char *attname, bool *isNull)
tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
ItemPointerSetInvalid(&(tmptup.t_self));
tmptup.t_tableOid = InvalidOid;
+ HeapTupleSetZeroBase(&tmptup);
tmptup.t_data = tuple;
result = heap_getattr(&tmptup,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 5ec699a9bd..5c54737308 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -2538,6 +2538,7 @@ ExecModifyTable(PlanState *pstate)
HeapTupleHeaderGetDatumLength(oldtupdata.t_data);
ItemPointerSetInvalid(&(oldtupdata.t_self));
/* Historically, view triggers see invalid t_tableOid. */
+ HeapTupleCopyHeaderXids(&oldtupdata);
oldtupdata.t_tableOid =
(relkind == RELKIND_VIEW) ? InvalidOid :
RelationGetRelid(resultRelInfo->ri_RelationDesc);
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 5b353cb93a..399a5b98f6 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -1154,6 +1154,7 @@ SPI_modifytuple(Relation rel, HeapTuple tuple, int natts, int *attnum,
mtuple->t_data->t_ctid = tuple->t_data->t_ctid;
mtuple->t_self = tuple->t_self;
mtuple->t_tableOid = tuple->t_tableOid;
+ HeapTupleCopyBase(mtuple, tuple);
}
else
{
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6bdad462c7..089f8fe0d5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2772,8 +2772,8 @@ _outIndexStmt(StringInfo str, const IndexStmt *node)
WRITE_STRING_FIELD(idxcomment);
WRITE_OID_FIELD(indexOid);
WRITE_OID_FIELD(oldNode);
- WRITE_UINT_FIELD(oldCreateSubid);
- WRITE_UINT_FIELD(oldFirstRelfilenodeSubid);
+ WRITE_UINT64_FIELD(oldCreateSubid);
+ WRITE_UINT64_FIELD(oldFirstRelfilenodeSubid);
WRITE_BOOL_FIELD(unique);
WRITE_BOOL_FIELD(nulls_not_distinct);
WRITE_BOOL_FIELD(primary);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index a5002ad895..5461a38b41 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -228,7 +228,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
* src/backend/access/heap/README.HOT for discussion.
*/
if (index->indcheckxmin &&
- !TransactionIdPrecedes(HeapTupleHeaderGetXmin(indexRelation->rd_indextuple->t_data),
+ !TransactionIdPrecedes(HeapTupleGetXmin(indexRelation->rd_indextuple),
TransactionXmin))
{
root->glob->transientPlan = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 681ef91b81..98e5e48e19 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -121,8 +121,8 @@ int autovacuum_vac_ins_thresh;
double autovacuum_vac_ins_scale;
int autovacuum_anl_thresh;
double autovacuum_anl_scale;
-int autovacuum_freeze_max_age;
-int autovacuum_multixact_freeze_max_age;
+int64 autovacuum_freeze_max_age;
+int64 autovacuum_multixact_freeze_max_age;
double autovacuum_vac_cost_delay;
int autovacuum_vac_cost_limit;
@@ -148,10 +148,10 @@ static TransactionId recentXid;
static MultiXactId recentMulti;
/* Default freeze ages to use for autovacuum (varies by database) */
-static int default_freeze_min_age;
-static int default_freeze_table_age;
-static int default_multixact_freeze_min_age;
-static int default_multixact_freeze_table_age;
+static int64 default_freeze_min_age;
+static int64 default_freeze_table_age;
+static int64 default_multixact_freeze_min_age;
+static int64 default_multixact_freeze_table_age;
/* Memory context for long-lived data */
static MemoryContext AutovacMemCxt;
@@ -327,15 +327,15 @@ static void FreeWorkerInfo(int code, Datum arg);
static autovac_table *table_recheck_autovac(Oid relid, HTAB *table_toast_map,
TupleDesc pg_class_desc,
- int effective_multixact_freeze_max_age);
+ int64 effective_multixact_freeze_max_age);
static void recheck_relation_needs_vacanalyze(Oid relid, AutoVacOpts *avopts,
Form_pg_class classForm,
- int effective_multixact_freeze_max_age,
+ int64 effective_multixact_freeze_max_age,
bool *dovacuum, bool *doanalyze, bool *wraparound);
static void relation_needs_vacanalyze(Oid relid, AutoVacOpts *relopts,
Form_pg_class classForm,
PgStat_StatTabEntry *tabentry,
- int effective_multixact_freeze_max_age,
+ int64 effective_multixact_freeze_max_age,
bool *dovacuum, bool *doanalyze, bool *wraparound);
static void autovacuum_do_vac_analyze(autovac_table *tab,
@@ -1157,6 +1157,7 @@ do_start_worker(void)
ListCell *cell;
TransactionId xidForceLimit;
MultiXactId multiForceLimit;
+ int64 multiMembersThreshold;
bool for_xid_wrap;
bool for_multi_wrap;
avw_dbase *avdb;
@@ -1196,17 +1197,18 @@ do_start_worker(void)
* particular tables, but not loosened.)
*/
recentXid = ReadNextTransactionId();
- xidForceLimit = recentXid - autovacuum_freeze_max_age;
- /* ensure it's a "normal" XID, else TransactionIdPrecedes misbehaves */
- /* this can cause the limit to go backwards by 3, but that's OK */
- if (xidForceLimit < FirstNormalTransactionId)
- xidForceLimit -= FirstNormalTransactionId;
+ if (recentXid > FirstNormalTransactionId + autovacuum_freeze_max_age)
+ xidForceLimit = recentXid - autovacuum_freeze_max_age;
+ else
+ xidForceLimit = FirstNormalTransactionId;
/* Also determine the oldest datminmxid we will consider. */
recentMulti = ReadNextMultiXactId();
- multiForceLimit = recentMulti - MultiXactMemberFreezeThreshold();
- if (multiForceLimit < FirstMultiXactId)
- multiForceLimit -= FirstMultiXactId;
+ multiMembersThreshold = autovacuum_multixact_freeze_max_age;
+ if (recentMulti > FirstMultiXactId + multiMembersThreshold)
+ multiForceLimit = recentMulti - multiMembersThreshold;
+ else
+ multiForceLimit = FirstMultiXactId;
/*
* Choose a database to connect to. We pick the database that was least
@@ -1971,7 +1973,7 @@ do_autovacuum(void)
BufferAccessStrategy bstrategy;
ScanKeyData key;
TupleDesc pg_class_desc;
- int effective_multixact_freeze_max_age;
+ int64 effective_multixact_freeze_max_age;
bool did_vacuum = false;
bool found_concurrent_worker = false;
int i;
@@ -2007,7 +2009,7 @@ do_autovacuum(void)
* normally autovacuum_multixact_freeze_max_age, but may be less if we are
* short of multixact member space.
*/
- effective_multixact_freeze_max_age = MultiXactMemberFreezeThreshold();
+ effective_multixact_freeze_max_age = autovacuum_multixact_freeze_max_age;
/*
* Find the pg_database entry and select the default freeze ages. We use
@@ -2803,7 +2805,7 @@ get_pgstat_tabentry_relid(Oid relid, bool isshared, PgStat_StatDBEntry *shared,
static autovac_table *
table_recheck_autovac(Oid relid, HTAB *table_toast_map,
TupleDesc pg_class_desc,
- int effective_multixact_freeze_max_age)
+ int64 effective_multixact_freeze_max_age)
{
Form_pg_class classForm;
HeapTuple classTup;
@@ -2872,10 +2874,10 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
/* OK, it needs something done */
if (doanalyze || dovacuum)
{
- int freeze_min_age;
- int freeze_table_age;
- int multixact_freeze_min_age;
- int multixact_freeze_table_age;
+ int64 freeze_min_age;
+ int64 freeze_table_age;
+ int64 multixact_freeze_min_age;
+ int64 multixact_freeze_table_age;
int vac_cost_limit;
double vac_cost_delay;
int log_min_duration;
@@ -2995,7 +2997,7 @@ static void
recheck_relation_needs_vacanalyze(Oid relid,
AutoVacOpts *avopts,
Form_pg_class classForm,
- int effective_multixact_freeze_max_age,
+ int64 effective_multixact_freeze_max_age,
bool *dovacuum,
bool *doanalyze,
bool *wraparound)
@@ -3064,7 +3066,7 @@ relation_needs_vacanalyze(Oid relid,
AutoVacOpts *relopts,
Form_pg_class classForm,
PgStat_StatTabEntry *tabentry,
- int effective_multixact_freeze_max_age,
+ int64 effective_multixact_freeze_max_age,
/* output params below */
bool *dovacuum,
bool *doanalyze,
@@ -3093,8 +3095,8 @@ relation_needs_vacanalyze(Oid relid,
anltuples;
/* freeze parameters */
- int freeze_max_age;
- int multixact_freeze_max_age;
+ int64 freeze_max_age;
+ int64 multixact_freeze_max_age;
TransactionId xidForceLimit;
MultiXactId multiForceLimit;
@@ -3144,17 +3146,19 @@ relation_needs_vacanalyze(Oid relid,
av_enabled = (relopts ? relopts->enabled : true);
/* Force vacuum if table is at risk of wraparound */
- xidForceLimit = recentXid - freeze_max_age;
- if (xidForceLimit < FirstNormalTransactionId)
- xidForceLimit -= FirstNormalTransactionId;
+ if (recentXid > FirstNormalTransactionId + freeze_max_age)
+ xidForceLimit = recentXid - freeze_max_age;
+ else
+ xidForceLimit = FirstNormalTransactionId;
force_vacuum = (TransactionIdIsNormal(classForm->relfrozenxid) &&
TransactionIdPrecedes(classForm->relfrozenxid,
xidForceLimit));
if (!force_vacuum)
{
- multiForceLimit = recentMulti - multixact_freeze_max_age;
- if (multiForceLimit < FirstMultiXactId)
- multiForceLimit -= FirstMultiXactId;
+ if (recentMulti > FirstMultiXactId + multixact_freeze_max_age)
+ multiForceLimit = recentMulti - multixact_freeze_max_age;
+ else
+ multiForceLimit = FirstMultiXactId;
force_vacuum = MultiXactIdIsValid(classForm->relminmxid) &&
MultiXactIdPrecedes(classForm->relminmxid, multiForceLimit);
}
diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c
index 18cf931822..6d6751607b 100644
--- a/src/backend/replication/logical/decode.c
+++ b/src/backend/replication/logical/decode.c
@@ -848,8 +848,12 @@ DecodeInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
xl_heap_insert *xlrec;
ReorderBufferChange *change;
RelFileNode target_node;
+ bool isinit = (XLogRecGetInfo(r) & XLOG_HEAP_INIT_PAGE) != 0;
+ Pointer rec_data = (Pointer) XLogRecGetData(r);
- xlrec = (xl_heap_insert *) XLogRecGetData(r);
+ if (isinit)
+ rec_data += sizeof(TransactionId);
+ xlrec = (xl_heap_insert *) rec_data;
/*
* Ignore insert records without new tuples (this does happen when
@@ -905,8 +909,12 @@ DecodeUpdate(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
ReorderBufferChange *change;
char *data;
RelFileNode target_node;
+ bool isinit = (XLogRecGetInfo(r) & XLOG_HEAP_INIT_PAGE) != 0;
+ Pointer rec_data = (Pointer) XLogRecGetData(r);
- xlrec = (xl_heap_update *) XLogRecGetData(r);
+ if (isinit)
+ rec_data += sizeof(TransactionId);
+ xlrec = (xl_heap_update *) rec_data;
/* only interested in our database */
XLogRecGetBlockTag(r, 0, &target_node, NULL, NULL);
@@ -1066,8 +1074,12 @@ DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
char *tupledata;
Size tuplelen;
RelFileNode rnode;
+ bool isinit = (XLogRecGetInfo(r) & XLOG_HEAP_INIT_PAGE) != 0;
+ Pointer rec_data = (Pointer) XLogRecGetData(r);
- xlrec = (xl_heap_multi_insert *) XLogRecGetData(r);
+ if (isinit)
+ rec_data += sizeof(TransactionId);
+ xlrec = (xl_heap_multi_insert *) rec_data;
/*
* Ignore insert records without new tuples. This happens when a
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 934aa13f2d..d3323bf315 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -1702,7 +1702,7 @@ LogicalIncreaseXminForSlot(XLogRecPtr current_lsn, TransactionId xmin)
SpinLockRelease(&slot->mutex);
if (got_new_xmin)
- elog(DEBUG1, "got new catalog xmin %u at %X/%X", xmin,
+ elog(DEBUG1, "got new catalog xmin " XID_FMT " at %X/%X", xmin,
LSN_FORMAT_ARGS(current_lsn));
/* candidate already valid with the current flush position, apply */
diff --git a/src/backend/replication/logical/proto.c b/src/backend/replication/logical/proto.c
index c9b0eeefd7..adf1d46b23 100644
--- a/src/backend/replication/logical/proto.c
+++ b/src/backend/replication/logical/proto.c
@@ -50,7 +50,7 @@ logicalrep_write_begin(StringInfo out, ReorderBufferTXN *txn)
/* fixed fields */
pq_sendint64(out, txn->final_lsn);
pq_sendint64(out, txn->xact_time.commit_time);
- pq_sendint32(out, txn->xid);
+ pq_sendint64(out, txn->xid);
}
/*
@@ -64,7 +64,7 @@ logicalrep_read_begin(StringInfo in, LogicalRepBeginData *begin_data)
if (begin_data->final_lsn == InvalidXLogRecPtr)
elog(ERROR, "final_lsn not set in begin message");
begin_data->committime = pq_getmsgint64(in);
- begin_data->xid = pq_getmsgint(in, 4);
+ begin_data->xid = pq_getmsgint64(in);
}
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index c2d9be81fa..68b6cc232c 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -2774,7 +2774,7 @@ ReorderBufferProcessTXN(ReorderBuffer *rb, ReorderBufferTXN *txn,
/* this is just a sanity check against bad output plugin behaviour */
if (GetCurrentTransactionIdIfAny() != InvalidTransactionId)
- elog(ERROR, "output plugin used XID %u",
+ elog(ERROR, "output plugin used XID " XID_FMT,
GetCurrentTransactionId());
/*
@@ -3213,7 +3213,7 @@ ReorderBufferAbortOld(ReorderBuffer *rb, TransactionId oldestRunningXid)
if (TransactionIdPrecedes(txn->xid, oldestRunningXid))
{
- elog(DEBUG2, "aborting old transaction %u", txn->xid);
+ elog(DEBUG2, "aborting old transaction " XID_FMT, txn->xid);
/* remove potential on-disk data, and deallocate this tx */
ReorderBufferCleanupTXN(rb, txn);
@@ -3855,7 +3855,7 @@ ReorderBufferSerializeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
Size spilled = 0;
Size size = txn->size;
- elog(DEBUG2, "spill %u changes in XID %u to disk",
+ elog(DEBUG2, "spill %u changes in XID " XID_FMT " to disk",
(uint32) txn->nentries_mem, txn->xid);
/* do the same to all child TXs */
@@ -4165,7 +4165,7 @@ ReorderBufferSerializeChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
errno = save_errno ? save_errno : ENOSPC;
ereport(ERROR,
(errcode_for_file_access(),
- errmsg("could not write to data file for XID %u: %m",
+ errmsg("could not write to data file for XID %" PRIu64 ": %m",
txn->xid)));
}
pgstat_report_wait_end();
@@ -4848,7 +4848,7 @@ ReorderBufferSerializedPath(char *path, ReplicationSlot *slot, TransactionId xid
XLogSegNoOffsetToRecPtr(segno, 0, wal_segment_size, recptr);
- snprintf(path, MAXPGPATH, "pg_replslot/%s/xid-%u-lsn-%X-%X.spill",
+ snprintf(path, MAXPGPATH, "pg_replslot/%s/xid-" XID_FMT "-lsn-%X-%X.spill",
NameStr(MyReplicationSlot->data.name),
xid, LSN_FORMAT_ARGS(recptr));
}
@@ -5429,8 +5429,12 @@ UpdateLogicalMappings(HTAB *tuplecid_data, Oid relid, Snapshot snapshot)
TransactionId f_mapped_xid;
TransactionId f_create_xid;
XLogRecPtr f_lsn;
- uint32 f_hi,
- f_lo;
+ uint32 f_lsn_hi,
+ f_lsn_lo,
+ f_mapped_xid_hi,
+ f_mapped_xid_lo,
+ f_create_xid_hi,
+ f_create_xid_lo;
RewriteMappingFile *f;
if (strcmp(mapping_de->d_name, ".") == 0 ||
@@ -5442,11 +5446,14 @@ UpdateLogicalMappings(HTAB *tuplecid_data, Oid relid, Snapshot snapshot)
continue;
if (sscanf(mapping_de->d_name, LOGICAL_REWRITE_FORMAT,
- &f_dboid, &f_relid, &f_hi, &f_lo,
- &f_mapped_xid, &f_create_xid) != 6)
+ &f_dboid, &f_relid, &f_lsn_hi, &f_lsn_lo,
+ &f_mapped_xid_hi, &f_mapped_xid_lo,
+ &f_create_xid_hi, &f_create_xid_lo) != 8)
elog(ERROR, "could not parse filename \"%s\"", mapping_de->d_name);
- f_lsn = ((uint64) f_hi) << 32 | f_lo;
+ f_lsn = ((uint64) f_lsn_hi) << 32 | f_lsn_lo;
+ f_mapped_xid = ((uint64) f_mapped_xid_hi) << 32 | f_mapped_xid_lo;
+ f_create_xid = ((uint64) f_create_xid_hi) << 32 | f_create_xid_lo;
/* mapping for another database */
if (f_dboid != dboid)
@@ -5479,7 +5486,7 @@ UpdateLogicalMappings(HTAB *tuplecid_data, Oid relid, Snapshot snapshot)
{
RewriteMappingFile *f = (RewriteMappingFile *) lfirst(file);
- elog(DEBUG1, "applying mapping: \"%s\" in %u", f->fname,
+ elog(DEBUG1, "applying mapping: \"%s\" in " XID_FMT, f->fname,
snapshot->subxip[0]);
ApplyLogicalMappingFile(tuplecid_data, relid, f->fname);
pfree(f);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index 83fca8a77d..8d26600892 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -847,7 +847,7 @@ SnapBuildDistributeNewCatalogSnapshot(SnapBuild *builder, XLogRecPtr lsn)
if (rbtxn_prepared(txn) || rbtxn_skip_prepared(txn))
continue;
- elog(DEBUG2, "adding a new snapshot to %u at %X/%X",
+ elog(DEBUG2, "adding a new snapshot to " XID_FMT " at %X/%X",
txn->xid, LSN_FORMAT_ARGS(lsn));
/*
@@ -922,7 +922,7 @@ SnapBuildPurgeCommittedTxn(SnapBuild *builder)
memcpy(builder->committed.xip, workspace,
surviving_xids * sizeof(TransactionId));
- elog(DEBUG3, "purged committed transactions from %u to %u, xmin: %u, xmax: %u",
+ elog(DEBUG3, "purged committed transactions from %u to %u, xmin: " XID_FMT ", xmax: " XID_FMT,
(uint32) builder->committed.xcnt, (uint32) surviving_xids,
builder->xmin, builder->xmax);
builder->committed.xcnt = surviving_xids;
@@ -988,7 +988,7 @@ SnapBuildCommitTxn(SnapBuild *builder, XLogRecPtr lsn, TransactionId xid,
sub_needs_timetravel = true;
needs_snapshot = true;
- elog(DEBUG1, "found subtransaction %u:%u with catalog changes",
+ elog(DEBUG1, "found subtransaction " XID_FMT ":" XID_FMT " with catalog changes",
xid, subxid);
SnapBuildAddCommittedTxn(builder, subxid);
@@ -1014,7 +1014,7 @@ SnapBuildCommitTxn(SnapBuild *builder, XLogRecPtr lsn, TransactionId xid,
/* if top-level modified catalog, it'll need a snapshot */
if (ReorderBufferXidHasCatalogChanges(builder->reorder, xid))
{
- elog(DEBUG2, "found top level transaction %u, with catalog changes",
+ elog(DEBUG2, "found top level transaction " XID_FMT ", with catalog changes",
xid);
needs_snapshot = true;
needs_timetravel = true;
@@ -1027,7 +1027,7 @@ SnapBuildCommitTxn(SnapBuild *builder, XLogRecPtr lsn, TransactionId xid,
}
else if (needs_timetravel)
{
- elog(DEBUG2, "forced transaction %u to do timetravel", xid);
+ elog(DEBUG2, "forced transaction " XID_FMT " to do timetravel", xid);
SnapBuildAddCommittedTxn(builder, xid);
}
@@ -1149,7 +1149,7 @@ SnapBuildProcessRunningXacts(SnapBuild *builder, XLogRecPtr lsn, xl_running_xact
xmin = ReorderBufferGetOldestXmin(builder->reorder);
if (xmin == InvalidTransactionId)
xmin = running->oldestRunningXid;
- elog(DEBUG3, "xmin: %u, xmax: %u, oldest running: %u, oldest xmin: %u",
+ elog(DEBUG3, "xmin: " XID_FMT ", xmax: " XID_FMT ", oldest running: " XID_FMT ", oldest xmin: " XID_FMT,
builder->xmin, builder->xmax, running->oldestRunningXid, xmin);
LogicalIncreaseXminForSlot(lsn, xmin);
@@ -1239,7 +1239,7 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
ereport(DEBUG1,
(errmsg_internal("skipping snapshot at %X/%X while building logical decoding snapshot, xmin horizon too low",
LSN_FORMAT_ARGS(lsn)),
- errdetail_internal("initial xmin horizon of %u vs the snapshot's %u",
+ errdetail_internal("initial xmin horizon of " XID_FMT " vs the snapshot's " XID_FMT,
builder->initial_xmin_horizon, running->oldestRunningXid)));
@@ -1322,7 +1322,7 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
ereport(LOG,
(errmsg("logical decoding found initial starting point at %X/%X",
LSN_FORMAT_ARGS(lsn)),
- errdetail("Waiting for transactions (approximately %d) older than %u to end.",
+ errdetail("Waiting for transactions (approximately %d) older than %" PRIu64 " to end.",
running->xcnt, running->nextXid)));
SnapBuildWaitSnapshot(running, running->nextXid);
@@ -1346,7 +1346,7 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
ereport(LOG,
(errmsg("logical decoding found initial consistent point at %X/%X",
LSN_FORMAT_ARGS(lsn)),
- errdetail("Waiting for transactions (approximately %d) older than %u to end.",
+ errdetail("Waiting for transactions (approximately %d) older than %" PRIu64 " to end.",
running->xcnt, running->nextXid)));
SnapBuildWaitSnapshot(running, running->nextXid);
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 7e267f7960..0c4b37c57c 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -1046,7 +1046,7 @@ apply_handle_stream_prepare(StringInfo s)
logicalrep_read_stream_prepare(s, &prepare_data);
set_apply_error_context_xact(prepare_data.xid, prepare_data.prepare_time);
- elog(DEBUG1, "received prepare for streamed transaction %u", prepare_data.xid);
+ elog(DEBUG1, "received prepare for streamed transaction " XID_FMT, prepare_data.xid);
/* Replay all the spooled operations. */
apply_spooled_messages(prepare_data.xid, prepare_data.prepare_lsn);
@@ -1428,7 +1428,7 @@ apply_handle_stream_commit(StringInfo s)
xid = logicalrep_read_stream_commit(s, &commit_data);
set_apply_error_context_xact(xid, commit_data.committime);
- elog(DEBUG1, "received commit for streamed transaction %u", xid);
+ elog(DEBUG1, "received commit for streamed transaction " XID_FMT, xid);
apply_spooled_messages(xid, commit_data.commit_lsn);
@@ -3200,14 +3200,14 @@ subxact_info_add(TransactionId xid)
static inline void
subxact_filename(char *path, Oid subid, TransactionId xid)
{
- snprintf(path, MAXPGPATH, "%u-%u.subxacts", subid, xid);
+ snprintf(path, MAXPGPATH, "%u-" XID_FMT ".subxacts", subid, xid);
}
/* format filename for file containing serialized changes */
static inline void
changes_filename(char *path, Oid subid, TransactionId xid)
{
- snprintf(path, MAXPGPATH, "%u-%u.changes", subid, xid);
+ snprintf(path, MAXPGPATH, "%u-" XID_FMT ".changes", subid, xid);
}
/*
@@ -3369,7 +3369,7 @@ TwoPhaseTransactionGid(Oid subid, TransactionId xid, char *gid, int szgid)
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg_internal("invalid two-phase transaction ID")));
- snprintf(gid, szgid, "pg_gid_%u_%u", subid, xid);
+ snprintf(gid, szgid, "pg_gid_%u_" XID_FMT, subid, xid);
}
/* Logical Replication Apply worker entry point */
@@ -3672,7 +3672,7 @@ apply_error_callback(void *arg)
/* append transaction information */
if (TransactionIdIsNormal(errarg->remote_xid))
{
- appendStringInfo(&buf, _(" in transaction %u"), errarg->remote_xid);
+ appendStringInfo(&buf, _(" in transaction " XID_FMT), errarg->remote_xid);
if (errarg->ts != 0)
appendStringInfo(&buf, _(" at %s"),
timestamptz_to_str(errarg->ts));
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index ceaff097b9..1b0fc7db4d 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -1143,10 +1143,6 @@ static void
XLogWalRcvSendHSFeedback(bool immed)
{
TimestampTz now;
- FullTransactionId nextFullXid;
- TransactionId nextXid;
- uint32 xmin_epoch,
- catalog_xmin_epoch;
TransactionId xmin,
catalog_xmin;
static TimestampTz sendTime = 0;
@@ -1203,30 +1199,15 @@ XLogWalRcvSendHSFeedback(bool immed)
catalog_xmin = InvalidTransactionId;
}
- /*
- * Get epoch and adjust if nextXid and oldestXmin are different sides of
- * the epoch boundary.
- */
- nextFullXid = ReadNextFullTransactionId();
- nextXid = XidFromFullTransactionId(nextFullXid);
- xmin_epoch = EpochFromFullTransactionId(nextFullXid);
- catalog_xmin_epoch = xmin_epoch;
- if (nextXid < xmin)
- xmin_epoch--;
- if (nextXid < catalog_xmin)
- catalog_xmin_epoch--;
-
- elog(DEBUG2, "sending hot standby feedback xmin %u epoch %u catalog_xmin %u catalog_xmin_epoch %u",
- xmin, xmin_epoch, catalog_xmin, catalog_xmin_epoch);
+ elog(DEBUG2, "sending hot standby feedback xmin " XID_FMT " catalog_xmin " XID_FMT,
+ xmin, catalog_xmin);
/* Construct the message and send it. */
resetStringInfo(&reply_message);
pq_sendbyte(&reply_message, 'h');
pq_sendint64(&reply_message, GetCurrentTimestamp());
- pq_sendint32(&reply_message, xmin);
- pq_sendint32(&reply_message, xmin_epoch);
- pq_sendint32(&reply_message, catalog_xmin);
- pq_sendint32(&reply_message, catalog_xmin_epoch);
+ pq_sendint64(&reply_message, xmin);
+ pq_sendint64(&reply_message, catalog_xmin);
walrcv_send(wrconn, reply_message.data, reply_message.len);
if (TransactionIdIsValid(xmin) || TransactionIdIsValid(catalog_xmin))
primary_has_standby_xmin = true;
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 5a718b1fe9..b6d30a9516 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -253,7 +253,6 @@ static void WalSndUpdateProgress(LogicalDecodingContext *ctx, XLogRecPtr lsn, Tr
static XLogRecPtr WalSndWaitForWal(XLogRecPtr loc);
static void LagTrackerWrite(XLogRecPtr lsn, TimestampTz local_flush_time);
static TimeOffset LagTrackerRead(int head, XLogRecPtr lsn, TimestampTz now);
-static bool TransactionIdInRecentPast(TransactionId xid, uint32 epoch);
static void WalSndSegmentOpen(XLogReaderState *state, XLogSegNo nextSegNo,
TimeLineID *tli_p);
@@ -2146,44 +2145,6 @@ PhysicalReplicationSlotNewXmin(TransactionId feedbackXmin, TransactionId feedbac
}
}
-/*
- * Check that the provided xmin/epoch are sane, that is, not in the future
- * and not so far back as to be already wrapped around.
- *
- * Epoch of nextXid should be same as standby, or if the counter has
- * wrapped, then one greater than standby.
- *
- * This check doesn't care about whether clog exists for these xids
- * at all.
- */
-static bool
-TransactionIdInRecentPast(TransactionId xid, uint32 epoch)
-{
- FullTransactionId nextFullXid;
- TransactionId nextXid;
- uint32 nextEpoch;
-
- nextFullXid = ReadNextFullTransactionId();
- nextXid = XidFromFullTransactionId(nextFullXid);
- nextEpoch = EpochFromFullTransactionId(nextFullXid);
-
- if (xid <= nextXid)
- {
- if (epoch != nextEpoch)
- return false;
- }
- else
- {
- if (epoch + 1 != nextEpoch)
- return false;
- }
-
- if (!TransactionIdPrecedesOrEquals(xid, nextXid))
- return false; /* epoch OK, but it's wrapped around */
-
- return true;
-}
-
/*
* Hot Standby feedback
*/
@@ -2191,9 +2152,7 @@ static void
ProcessStandbyHSFeedbackMessage(void)
{
TransactionId feedbackXmin;
- uint32 feedbackEpoch;
TransactionId feedbackCatalogXmin;
- uint32 feedbackCatalogEpoch;
TimestampTz replyTime;
/*
@@ -2202,10 +2161,8 @@ ProcessStandbyHSFeedbackMessage(void)
* of this message.
*/
replyTime = pq_getmsgint64(&reply_message);
- feedbackXmin = pq_getmsgint(&reply_message, 4);
- feedbackEpoch = pq_getmsgint(&reply_message, 4);
- feedbackCatalogXmin = pq_getmsgint(&reply_message, 4);
- feedbackCatalogEpoch = pq_getmsgint(&reply_message, 4);
+ feedbackXmin = pq_getmsgint64(&reply_message);
+ feedbackCatalogXmin = pq_getmsgint64(&reply_message);
if (message_level_is_interesting(DEBUG2))
{
@@ -2214,11 +2171,9 @@ ProcessStandbyHSFeedbackMessage(void)
/* Copy because timestamptz_to_str returns a static buffer */
replyTimeStr = pstrdup(timestamptz_to_str(replyTime));
- elog(DEBUG2, "hot standby feedback xmin %u epoch %u, catalog_xmin %u epoch %u reply_time %s",
+ elog(DEBUG2, "hot standby feedback xmin " XID_FMT ", catalog_xmin " XID_FMT " reply_time %s",
feedbackXmin,
- feedbackEpoch,
feedbackCatalogXmin,
- feedbackCatalogEpoch,
replyTimeStr);
pfree(replyTimeStr);
@@ -2249,18 +2204,6 @@ ProcessStandbyHSFeedbackMessage(void)
return;
}
- /*
- * Check that the provided xmin/epoch are sane, that is, not in the future
- * and not so far back as to be already wrapped around. Ignore if not.
- */
- if (TransactionIdIsNormal(feedbackXmin) &&
- !TransactionIdInRecentPast(feedbackXmin, feedbackEpoch))
- return;
-
- if (TransactionIdIsNormal(feedbackCatalogXmin) &&
- !TransactionIdInRecentPast(feedbackCatalogXmin, feedbackCatalogEpoch))
- return;
-
/*
* Set the WalSender's xmin equal to the standby's requested xmin, so that
* the xmin will be taken into account by GetSnapshotData() /
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index ca48395d5c..804d95a285 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -2431,6 +2431,7 @@ statext_expressions_load(Oid stxoid, bool inh, int idx)
ItemPointerSetInvalid(&(tmptup.t_self));
tmptup.t_tableOid = InvalidOid;
tmptup.t_data = td;
+ HeapTupleCopyHeaderXids(&tmptup);
tup = heap_copytuple(&tmptup);
diff --git a/src/backend/storage/buffer/Makefile b/src/backend/storage/buffer/Makefile
index fd7c40dcb0..ffcc0fc290 100644
--- a/src/backend/storage/buffer/Makefile
+++ b/src/backend/storage/buffer/Makefile
@@ -17,6 +17,7 @@ OBJS = \
buf_table.o \
bufmgr.o \
freelist.o \
- localbuf.o
+ localbuf.o \
+ heap_convert.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index f5459c68f8..bfe000d8f5 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -458,7 +458,7 @@ ForgetPrivateRefCountEntry(PrivateRefCountEntry *ref)
)
-static Buffer ReadBuffer_common(SMgrRelation reln, char relpersistence,
+static Buffer ReadBuffer_common(Relation rel, SMgrRelation reln, char relpersistence,
ForkNumber forkNum, BlockNumber blockNum,
ReadBufferMode mode, BufferAccessStrategy strategy,
bool *hit);
@@ -760,7 +760,8 @@ ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum,
* miss.
*/
pgstat_count_buffer_read(reln);
- buf = ReadBuffer_common(RelationGetSmgr(reln), reln->rd_rel->relpersistence,
+ buf = ReadBuffer_common(reln, RelationGetSmgr(reln),
+ reln->rd_rel->relpersistence,
forkNum, blockNum, mode, strategy, &hit);
if (hit)
pgstat_count_buffer_hit(reln);
@@ -788,7 +789,7 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
Assert(InRecovery);
- return ReadBuffer_common(smgr, RELPERSISTENCE_PERMANENT, forkNum, blockNum,
+ return ReadBuffer_common(NULL, smgr, RELPERSISTENCE_PERMANENT, forkNum, blockNum,
mode, strategy, &hit);
}
@@ -799,7 +800,7 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
* *hit is set to true if the request was satisfied from shared buffer cache.
*/
static Buffer
-ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
+ReadBuffer_common(Relation rel, SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
BlockNumber blockNum, ReadBufferMode mode,
BufferAccessStrategy strategy, bool *hit)
{
@@ -1031,6 +1032,27 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
blockNum,
relpath(smgr->smgr_rnode, forkNum))));
}
+
+ if (PageGetPageLayoutVersion(bufBlock) != PG_PAGE_LAYOUT_VERSION &&
+ !PageIsNew((Page) bufBlock))
+ {
+ Buffer buf = BufferDescriptorGetBuffer(bufHdr);
+
+ /*
+ * Normally we should not be here with rel == NULL, since XLog
+ * replayer should read new page versions from Full Page Write
+ * records.
+ *
+ * But there is TryReadRepairBlock in repairworker.c that
+ * could. (PgProEE feature).
+ */
+ if (rel != NULL)
+ {
+ LWLockAcquire(BufferDescriptorGetContentLock(bufHdr), LW_EXCLUSIVE);
+ convert_page(rel, bufBlock, buf, blockNum);
+ LWLockRelease(BufferDescriptorGetContentLock(bufHdr));
+ }
+ }
}
}
@@ -3956,6 +3978,64 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std)
}
}
+/*
+ * Mark buffer as converted - ie its format is changed without logical changes.
+ *
+ * It will override `full_page_write` GUC setting in XLogRecordAssemble.
+ */
+void
+MarkBufferConverted(Buffer buffer, bool converted)
+{
+ BufferDesc *bufHdr;
+ uint32 buf_state;
+ bool has_mark;
+
+ if (!BufferIsValid(buffer))
+ elog(ERROR, "bad buffer ID: %d", buffer);
+
+ Assert(!BufferIsLocal(buffer));
+
+ bufHdr = GetBufferDescriptor(buffer - 1);
+
+ Assert(GetPrivateRefCount(buffer) > 0);
+ if (converted)
+ {
+ /* here, either share or exclusive lock is OK */
+ Assert(LWLockHeldByMe(BufferDescriptorGetContentLock(bufHdr)));
+ }
+
+ buf_state = pg_atomic_read_u32(&bufHdr->state);
+ has_mark = (buf_state & BM_CONVERTED) != 0;
+ if (converted == has_mark)
+ return;
+
+ buf_state = LockBufHdr(bufHdr);
+ buf_state &= ~BM_CONVERTED;
+ if (converted)
+ buf_state |= BM_CONVERTED;
+ UnlockBufHdr(bufHdr, buf_state);
+}
+
+bool
+IsBufferConverted(Buffer buffer)
+{
+
+ BufferDesc *bufHdr;
+ uint32 buf_state;
+
+ if (!BufferIsValid(buffer))
+ elog(ERROR, "bad buffer ID: %d", buffer);
+
+ Assert(!BufferIsLocal(buffer));
+
+ bufHdr = GetBufferDescriptor(buffer - 1);
+
+ Assert(GetPrivateRefCount(buffer) > 0);
+
+ buf_state = pg_atomic_read_u32(&bufHdr->state);
+ return (buf_state & BM_CONVERTED) != 0;
+}
+
/*
* Release buffer content locks for shared buffers.
*
diff --git a/src/backend/storage/buffer/heap_convert.c b/src/backend/storage/buffer/heap_convert.c
new file mode 100644
index 0000000000..b9284ba992
--- /dev/null
+++ b/src/backend/storage/buffer/heap_convert.c
@@ -0,0 +1,510 @@
+/*
+ * heap_convert.c
+ * Heap page converter from 32bit to 64bit xid format
+ *
+ * Copyright (c) 2017, Postgres Professional
+ *
+ * src/backend/storage/buffer/heap_convert.c
+ */
+
+#include "postgres.h"
+
+#include "access/brin_page.h"
+#include "access/ginblock.h"
+#include "access/generic_xlog.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/nbtree.h"
+#include "access/transam.h"
+#include "access/xact.h"
+#include "catalog/pg_am.h"
+#include "catalog/pg_control.h"
+#include "common/controldata_utils.h"
+#include "miscadmin.h"
+#include "storage/bufmgr.h"
+#include "storage/bufpage.h"
+#include "storage/checksum.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+
+/*
+ * Sorting support for repack_tuples()
+ */
+typedef struct itemData
+{
+ uint16 index; /* linpointer array index */
+ int16 offset; /* page offset of item data */
+ uint16 alignedlen; /* MAXALIGN(item data len) */
+} itemData;
+
+/* Initialize special heap page area */
+static void
+InitHeapPageSpecial(PageHeader new, TransactionId xid_base,
+ MultiXactId multi_base, TransactionId prune_xid)
+{
+ HeapPageSpecial special;
+
+ new->pd_special = BLCKSZ - MAXALIGN(sizeof(HeapPageSpecialData));
+
+ special = (HeapPageSpecial) ((char *) new + new->pd_special);
+ special->pd_xid_base = xid_base;
+ special->pd_multi_base = multi_base;
+ HeapPageSetPruneXid(new, prune_xid);
+}
+
+/* Used by get_previous_edition() */
+ControlFileData *ControlFile = NULL;
+
+static bool was_32bit_xid(PageHeader page);
+static void convert_heap(Relation rel, Page page, Buffer buf, BlockNumber blkno);
+static void repack_heap_tuples(Relation rel, Page page, Buffer buf,
+ BlockNumber blkno, bool double_xmax);
+static void tuple_set_double_xmax(HeapTupleHeader tuple);
+
+
+/*
+ * itemoffcompare
+ * Sorting support for repack_tuples()
+ */
+int
+itemoffcompare(const void *item1, const void *item2)
+{
+ /* Sort in decreasing itemoff order */
+ return ((itemIdCompactData *) item2)->itemoff -
+ ((itemIdCompactData *) item1)->itemoff;
+}
+
+static bool
+was_32bit_xid(PageHeader page)
+{
+ return PageGetPageLayoutVersion(page) < 5;
+}
+
+void
+convert_page(Relation rel, Page page, Buffer buf, BlockNumber blkno)
+{
+ static unsigned logcnt = 0;
+ bool logit;
+ bool recovery;
+ PageHeader hdr = (PageHeader) page;
+ GenericXLogState *state = NULL;
+ Page tmp_page = page;
+ uint16 checksum;
+
+ /* Not during XLog replaying */
+ Assert(rel != NULL);
+
+ recovery = RecoveryInProgress();
+
+ /* Verify checksum */
+ if (hdr->pd_checksum)
+ {
+ checksum = pg_checksum_page((char *) page, blkno);
+ if (checksum != hdr->pd_checksum)
+ ereport(ERROR,
+ (errcode(ERRCODE_INDEX_CORRUPTED),
+ errmsg("page verification failed, calculated checksum %u but expected %u",
+ checksum, hdr->pd_checksum)));
+ }
+
+ /*
+ * We occasionally force logging of page conversion, so never-changed
+ * pages are converted in the end. FORCE_LOG_EVERY is chosen arbitrarily
+ * to log neither too much nor too little.
+ */
+#define FORCE_LOG_EVERY 128
+ logit = !recovery && XLogIsNeeded() && RelationNeedsWAL(rel);
+ logit = logit && (++logcnt % FORCE_LOG_EVERY) == 0;
+ if (logit)
+ {
+ state = GenericXLogStart(rel);
+ tmp_page = GenericXLogRegisterBuffer(state, buf,
+ GENERIC_XLOG_FULL_IMAGE);
+ }
+
+ PageSetPageSizeAndVersion((hdr), PageGetPageSize(hdr),
+ PG_PAGE_LAYOUT_VERSION);
+
+ if (was_32bit_xid(hdr))
+ {
+ switch (rel->rd_rel->relkind)
+ {
+ case 'r':
+ case 'p':
+ case 't':
+ case 'm':
+ convert_heap(rel, tmp_page, buf, blkno);
+ break;
+ case 'i':
+ /* no need to convert index */
+ case 'S':
+ /* no real need to convert sequences */
+ break;
+ default:
+ elog(ERROR,
+ "Conversion for relkind '%c' is not implemented",
+ rel->rd_rel->relkind);
+ }
+ }
+
+ hdr = (PageHeader) page;
+ hdr->pd_checksum = pg_checksum_page((char *) page, blkno);
+
+ if (logit)
+ {
+ /*
+ * Finish logging buffer conversion and mark buffer as dirty.
+ */
+ Assert(state != NULL);
+ MarkBufferDirty(buf);
+ GenericXLogFinish(state);
+ }
+ else
+ {
+ /*
+ * Otherwise, it will be logged with full-page-write record on first
+ * actual change.
+ */
+ MarkBufferConverted(buf, true);
+ }
+}
+
+static void
+convert_heap(Relation rel, Page page, Buffer buf, BlockNumber blkno)
+{
+ PageHeader page_hdr = (PageHeader) page;
+ bool heap_special_fits;
+
+ /* Is there enough space to fit new page format? */
+ heap_special_fits = page_hdr->pd_upper - page_hdr->pd_lower >= SizeOfPageSpecial;
+ repack_heap_tuples(rel, page, buf, blkno, !heap_special_fits);
+}
+
+/*
+ * Convert possibly wrapped around heap tuple's transaction and
+ * multixact IDs after pg_upgrade.
+ */
+static void
+convert_heap_tuple_xids(HeapTupleHeader tuple, TransactionId xid_base,
+ MultiXactId mxid_base, bool double_xmax)
+{
+ TransactionId xid;
+
+ /* Convert xmin xid */
+ if (double_xmax)
+ tuple_set_double_xmax(tuple);
+ else
+ {
+ /* Subtract xid_base from normal xmin */
+ xid = tuple->t_choice.t_heap.t_xmin;
+
+ if (TransactionIdIsNormal(xid))
+ {
+ Assert(xid >= xid_base + FirstNormalTransactionId);
+ tuple->t_choice.t_heap.t_xmin = xid - xid_base;
+ }
+ }
+
+ /* If tuple has multixact flag, handle mxid wraparound */
+ if ((tuple->t_infomask & HEAP_XMAX_IS_MULTI) &&
+ !(tuple->t_infomask & HEAP_XMAX_INVALID))
+ {
+ MultiXactId mxid = tuple->t_choice.t_heap.t_xmax;
+
+ /* Handle mxid wraparound */
+ if (mxid < mxid_base)
+ {
+ mxid += ((MultiXactId) 1 << 32) - FirstMultiXactId;
+ Assert(mxid >= mxid_base);
+ }
+
+ if (double_xmax)
+ {
+ /* Save converted mxid into xmin/max */
+ HeapTupleHeaderSetDoubleXmax(tuple, mxid);
+ }
+ else
+ {
+ /*
+ * Save converted mxid offset relative to (minmxid - 1),
+ * which will be page's mxid base.
+ */
+ Assert(mxid - mxid_base + FirstMultiXactId <= PG_UINT32_MAX);
+ tuple->t_choice.t_heap.t_xmax =
+ (uint32) (mxid - mxid_base + FirstMultiXactId);
+ }
+ }
+ /* Convert xmax xid */
+ else if (!(tuple->t_infomask & HEAP_XMAX_INVALID))
+ {
+ xid = tuple->t_choice.t_heap.t_xmax;
+
+ if (double_xmax)
+ {
+ /* Save converted mxid into xmin/max */
+ HeapTupleHeaderSetDoubleXmax(tuple, xid);
+ }
+ else if (TransactionIdIsNormal(xid))
+ {
+ /* Subtract xid_base from normal xmax */
+ Assert(xid >= xid_base + FirstNormalTransactionId);
+ tuple->t_choice.t_heap.t_xmax = xid - xid_base;
+ }
+ }
+}
+
+/*
+ * Compute page's [m]xid min/max values for based/"double xmax"
+ * format conversions
+ */
+static void
+compute_xid_min_max(HeapTuple tuple, MultiXactId mxid_base,
+ TransactionId *xid_min, TransactionId *xid_max,
+ MultiXactId *mxid_min, MultiXactId *mxid_max)
+{
+ if (!HeapTupleHeaderXminInvalid(tuple->t_data) &&
+ !HeapTupleHeaderXminFrozen(tuple->t_data))
+ {
+ TransactionId xid = HeapTupleGetRawXmin(tuple);
+
+ if (TransactionIdIsNormal(xid))
+ {
+ if (*xid_max < xid)
+ *xid_max = xid;
+ if (*xid_min > xid)
+ *xid_min = xid;
+ }
+ }
+
+ if (!(tuple->t_data->t_infomask & HEAP_XMAX_INVALID))
+ {
+ TransactionId xid;
+
+ if (tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)
+ {
+ MultiXactId mxid = HeapTupleGetRawXmax(tuple);
+
+ Assert(MultiXactIdIsValid(mxid));
+
+ /* Handle mxid wraparound */
+ if (mxid < mxid_base)
+ {
+ mxid += ((MultiXactId) 1 << 32) - FirstMultiXactId;
+ Assert(mxid >= mxid_base);
+ }
+
+ if (*mxid_max < mxid)
+ *mxid_max = mxid;
+ if (*mxid_min > mxid)
+ *mxid_min = mxid;
+
+ /*
+ * Also take into account hidden update xid, which can be
+ * extracted by the vacuum.
+ */
+ if (tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY)
+ xid = InvalidTransactionId;
+ else
+ xid = HeapTupleGetUpdateXid(tuple);
+ }
+ else
+ {
+ xid = HeapTupleGetRawXmax(tuple);
+ }
+
+ if (TransactionIdIsNormal(xid))
+ {
+ if (*xid_max < xid)
+ *xid_max = xid;
+ if (*xid_min > xid)
+ *xid_min = xid;
+ }
+ }
+}
+
+/* Returns true, if "double xmax" format */
+static bool
+init_heap_page_header(Relation rel, BlockNumber blkno, PageHeader new_hdr,
+ TransactionId prune_xid, bool header_fits,
+ TransactionId xid_min, TransactionId xid_max,
+ MultiXactId mxid_min, MultiXactId mxid_max,
+ TransactionId *xid_base, MultiXactId *mxid_base)
+{
+ if (header_fits &&
+ (xid_max == InvalidTransactionId ||
+ xid_max - xid_min <= MaxShortTransactionId - FirstNormalTransactionId) &&
+ (mxid_max == InvalidMultiXactId ||
+ mxid_max - mxid_min <= MaxShortTransactionId - FirstMultiXactId))
+ {
+ Assert(xid_max == InvalidTransactionId || xid_max >= xid_min);
+ Assert(mxid_max == InvalidMultiXactId || mxid_max >= mxid_min);
+ *xid_base = xid_max == InvalidTransactionId ? InvalidTransactionId : xid_min - FirstNormalTransactionId;
+ *mxid_base = mxid_max == InvalidMultiXactId ? InvalidMultiXactId : mxid_min - FirstMultiXactId;
+
+ InitHeapPageSpecial(new_hdr, *xid_base, *mxid_base, prune_xid);
+ return false;
+ }
+ else
+ {
+ /* No space for special area, switch to "double xmax" format */
+ new_hdr->pd_special = BLCKSZ;
+
+ *xid_base = InvalidTransactionId;
+ *mxid_base = InvalidMultiXactId;
+
+ elog(DEBUG2, "convert heap page %u of relation %u to double xmax format",
+ blkno, RelationGetRelid(rel));
+ return true;
+ }
+}
+
+/*
+ * repack_heap_tuples
+ * Convert heap page format reusing space of dead tuples
+ */
+static void
+repack_heap_tuples(Relation rel, Page page, Buffer buf, BlockNumber blkno,
+ bool perhaps_double_xmax)
+{
+ itemIdCompactData items[MaxHeapTuplesPerPage];
+ itemIdCompact itemPtr = items;
+ ItemId lp;
+ int nitems = 0;
+ int maxoff = PageGetMaxOffsetNumber(page);
+ Offset upper;
+ int idx;
+ bool double_xmax;
+
+ PageHeader hdr;
+ PageHeader new_hdr;
+ char new_page[BLCKSZ];
+ MultiXactId mxid_base = rel->rd_rel->relminmxid;
+ MultiXactId mxid_min = MaxMultiXactId;
+ MultiXactId mxid_max = InvalidMultiXactId;
+ TransactionId xid_base = rel->rd_rel->relfrozenxid;
+ TransactionId xid_min = MaxTransactionId;
+ TransactionId xid_max = InvalidTransactionId;
+
+ int occupied_space = 0;
+
+ hdr = (PageHeader) page;
+
+ if (TransactionIdIsNormal(hdr->pd_prune_xid))
+ xid_min = xid_max = hdr->pd_prune_xid;
+
+ for (idx = 0; idx < maxoff; idx++)
+ {
+ HeapTupleData tuple;
+
+ lp = PageGetItemId(page, idx + 1);
+
+ /* Skip redirects and items without storage */
+ if (!ItemIdHasStorage(lp))
+ continue;
+
+ /* Build in-memory tuple representation */
+ tuple.t_tableOid = 1; /* doesn't matter in this case */
+ tuple.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+ HeapTupleCopyHeaderXids(&tuple);
+ tuple.t_len = ItemIdGetLength(lp);
+ ItemPointerSet(&(tuple.t_self), blkno, ItemIdGetOffset(lp));
+
+ /*
+ * This is only needed to determine whether tuple is HEAPTUPLE_DEAD or
+ * HEAPTUPLE_RECENTLY_DEAD. And since this is the first time we read
+ * page after pg_upgrade, it cannot be HEAPTUPLE_RECENTLY_DEAD. See
+ * HeapTupleSatisfiesVacuum() for details
+ */
+ if (perhaps_double_xmax &&
+ HeapTupleSatisfiesVacuum(&tuple, FirstUpgradedTransactionId, buf) == HEAPTUPLE_DEAD)
+ {
+ ItemIdSetDead(lp);
+ }
+
+ if (ItemIdIsNormal(lp) && ItemIdHasStorage(lp))
+ {
+ itemPtr->offsetindex = idx;
+ itemPtr->itemoff = ItemIdGetOffset(lp);
+ if (unlikely(itemPtr->itemoff < hdr->pd_upper ||
+ itemPtr->itemoff >= hdr->pd_special))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_CORRUPTED),
+ errmsg("corrupted item pointer: %u",
+ itemPtr->itemoff)));
+ itemPtr->alignedlen = MAXALIGN(ItemIdGetLength(lp));
+ occupied_space += itemPtr->alignedlen;
+ nitems++;
+ itemPtr++;
+ if (perhaps_double_xmax)
+ {
+ HeapTupleSetXmin(&tuple, FrozenTransactionId);
+ HeapTupleHeaderSetXminFrozen(tuple.t_data);
+ }
+
+ compute_xid_min_max(&tuple, mxid_base,
+ &xid_min, &xid_max,
+ &mxid_min, &mxid_max);
+ }
+ }
+
+ /* Write new header */
+ new_hdr = (PageHeader) new_page;
+ *new_hdr = *hdr;
+ new_hdr->pd_lower = SizeOfPageHeaderData + maxoff * sizeof(ItemIdData);
+
+ double_xmax = init_heap_page_header(rel, blkno, new_hdr,
+ hdr->pd_prune_xid,
+ BLCKSZ - new_hdr->pd_lower - occupied_space >= sizeof(HeapPageSpecialData),
+ xid_min, xid_max,
+ mxid_min, mxid_max,
+ &xid_base, &mxid_base);
+ if (!perhaps_double_xmax && double_xmax)
+ return repack_heap_tuples(rel, page, buf, blkno, true);
+
+ /* Copy ItemIds with an offset */
+ memcpy(new_page + SizeOfPageHeaderData,
+ page + SizeOfPageHeaderData,
+ hdr->pd_lower - SizeOfPageHeaderData);
+
+ /* Move live tuples */
+ upper = new_hdr->pd_special;
+ for (idx = 0; idx < nitems; idx++)
+ {
+ HeapTupleHeader tuple;
+
+ itemPtr = &items[idx];
+ lp = PageGetItemId(new_page, itemPtr->offsetindex + 1);
+ upper -= itemPtr->alignedlen;
+
+ memcpy((char *) new_page + upper,
+ (char *) page + itemPtr->itemoff,
+ itemPtr->alignedlen);
+
+ tuple = (HeapTupleHeader) (((char *) new_page) + upper);
+
+ convert_heap_tuple_xids(tuple, xid_base, mxid_base, double_xmax);
+
+ lp->lp_off = upper;
+
+ occupied_space -= itemPtr->alignedlen;
+ }
+ Assert(occupied_space == 0);
+
+ new_hdr->pd_upper = upper;
+ if (new_hdr->pd_lower > new_hdr->pd_upper)
+ elog(ERROR, "cannot convert block %u of relation '%s'",
+ blkno, RelationGetRelationName(rel));
+
+ memcpy(page, new_page, BLCKSZ);
+}
+
+/*
+ * Convert tuple for "double xmax" page format.
+ */
+static void
+tuple_set_double_xmax(HeapTupleHeader tuple)
+{
+ tuple->t_infomask |= HEAP_XMIN_FROZEN;
+ tuple->t_choice.t_heap.t_xmin = 0;
+}
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 13d192ec2b..b9676dd3c7 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -66,7 +66,7 @@
#include "utils/rel.h"
#include "utils/snapmgr.h"
-#define UINT32_ACCESS_ONCE(var) ((uint32)(*((volatile uint32 *)&(var))))
+#define UINT64_ACCESS_ONCE(var) ((uint64)(*((volatile uint64 *)&(var))))
/* Our shared memory area */
typedef struct ProcArrayStruct
@@ -351,9 +351,6 @@ static inline void ProcArrayEndTransactionInternal(PGPROC *proc, TransactionId l
static void ProcArrayGroupClearXid(PGPROC *proc, TransactionId latestXid);
static void MaintainLatestCompletedXid(TransactionId latestXid);
static void MaintainLatestCompletedXidRecovery(TransactionId latestXid);
-
-static inline FullTransactionId FullXidRelativeTo(FullTransactionId rel,
- TransactionId xid);
static void GlobalVisUpdateApply(ComputeXidHorizonsResult *horizons);
static inline int GetProcArrayMaxProcs(void);
@@ -962,10 +959,7 @@ MaintainLatestCompletedXid(TransactionId latestXid)
Assert(LWLockHeldByMe(ProcArrayLock));
if (TransactionIdPrecedes(XidFromFullTransactionId(cur_latest), latestXid))
- {
- ShmemVariableCache->latestCompletedXid =
- FullXidRelativeTo(cur_latest, latestXid);
- }
+ ShmemVariableCache->latestCompletedXid = FullTransactionIdFromXid(latestXid);
Assert(IsBootstrapProcessingMode() ||
FullTransactionIdIsNormal(ShmemVariableCache->latestCompletedXid));
@@ -978,7 +972,6 @@ static void
MaintainLatestCompletedXidRecovery(TransactionId latestXid)
{
FullTransactionId cur_latest = ShmemVariableCache->latestCompletedXid;
- FullTransactionId rel;
Assert(AmStartupProcess() || !IsUnderPostmaster);
Assert(LWLockHeldByMe(ProcArrayLock));
@@ -988,14 +981,12 @@ MaintainLatestCompletedXidRecovery(TransactionId latestXid)
* latestCompletedXid to be initialized in recovery. But in recovery it's
* safe to access nextXid without a lock for the startup process.
*/
- rel = ShmemVariableCache->nextXid;
Assert(FullTransactionIdIsValid(ShmemVariableCache->nextXid));
if (!FullTransactionIdIsValid(cur_latest) ||
TransactionIdPrecedes(XidFromFullTransactionId(cur_latest), latestXid))
{
- ShmemVariableCache->latestCompletedXid =
- FullXidRelativeTo(rel, latestXid);
+ ShmemVariableCache->latestCompletedXid = FullTransactionIdFromXid(latestXid);
}
Assert(FullTransactionIdIsNormal(ShmemVariableCache->latestCompletedXid));
@@ -1105,7 +1096,7 @@ ProcArrayApplyRecoveryInfo(RunningTransactions running)
else
elog(trace_recovery(DEBUG1),
"recovery snapshot waiting for non-overflowed snapshot or "
- "until oldest active xid on standby is at least %u (now %u)",
+ "until oldest active xid on standby is at least " XID_FMT " (now " XID_FMT ")",
standbySnapshotPendingXmin,
running->oldestRunningXid);
return;
@@ -1191,7 +1182,7 @@ ProcArrayApplyRecoveryInfo(RunningTransactions running)
if (i > 0 && TransactionIdEquals(xids[i - 1], xids[i]))
{
elog(DEBUG1,
- "found duplicated transaction %u for KnownAssignedXids insertion",
+ "found duplicated transaction " XID_FMT " for KnownAssignedXids insertion",
xids[i]);
continue;
}
@@ -1276,7 +1267,7 @@ ProcArrayApplyRecoveryInfo(RunningTransactions running)
else
elog(trace_recovery(DEBUG1),
"recovery snapshot waiting for non-overflowed snapshot or "
- "until oldest active xid on standby is at least %u (now %u)",
+ "until oldest active xid on standby is at least " XID_FMT " (now " XID_FMT ")",
standbySnapshotPendingXmin,
running->oldestRunningXid);
}
@@ -1469,7 +1460,7 @@ TransactionIdIsInProgress(TransactionId xid)
continue;
/* Fetch xid just once - see GetNewTransactionId */
- pxid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
+ pxid = UINT64_ACCESS_ONCE(other_xids[pgxactoff]);
if (!TransactionIdIsValid(pxid))
continue;
@@ -1501,7 +1492,7 @@ TransactionIdIsInProgress(TransactionId xid)
for (j = pxids - 1; j >= 0; j--)
{
/* Fetch xid just once - see GetNewTransactionId */
- TransactionId cxid = UINT32_ACCESS_ONCE(proc->subxids.xids[j]);
+ TransactionId cxid = UINT64_ACCESS_ONCE(proc->subxids.xids[j]);
if (TransactionIdEquals(cxid, xid))
{
@@ -1625,7 +1616,7 @@ TransactionIdIsActive(TransactionId xid)
TransactionId pxid;
/* Fetch xid just once - see GetNewTransactionId */
- pxid = UINT32_ACCESS_ONCE(other_xids[i]);
+ pxid = UINT64_ACCESS_ONCE(other_xids[i]);
if (!TransactionIdIsValid(pxid))
continue;
@@ -1776,8 +1767,8 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
TransactionId xmin;
/* Fetch xid just once - see GetNewTransactionId */
- xid = UINT32_ACCESS_ONCE(other_xids[index]);
- xmin = UINT32_ACCESS_ONCE(proc->xmin);
+ xid = UINT64_ACCESS_ONCE(other_xids[index]);
+ xmin = UINT64_ACCESS_ONCE(proc->xmin);
/*
* Consider both the transaction's Xmin, and its Xid.
@@ -2318,7 +2309,7 @@ GetSnapshotData(Snapshot snapshot)
for (int pgxactoff = 0; pgxactoff < numProcs; pgxactoff++)
{
/* Fetch xid just once - see GetNewTransactionId */
- TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
+ TransactionId xid = UINT64_ACCESS_ONCE(other_xids[pgxactoff]);
uint8 statusFlags;
Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
@@ -2468,12 +2459,7 @@ GetSnapshotData(Snapshot snapshot)
FullTransactionId def_vis_fxid_data;
FullTransactionId oldestfxid;
- /*
- * Converting oldestXid is only safe when xid horizon cannot advance,
- * i.e. holding locks. While we don't hold the lock anymore, all the
- * necessary data has been gathered with lock held.
- */
- oldestfxid = FullXidRelativeTo(latest_completed, oldestxid);
+ oldestfxid = FullTransactionIdFromXid(oldestxid);
/* apply vacuum_defer_cleanup_age */
def_vis_xid_data =
@@ -2496,8 +2482,8 @@ GetSnapshotData(Snapshot snapshot)
def_vis_xid =
TransactionIdOlder(replication_slot_catalog_xmin, def_vis_xid);
- def_vis_fxid = FullXidRelativeTo(latest_completed, def_vis_xid);
- def_vis_fxid_data = FullXidRelativeTo(latest_completed, def_vis_xid_data);
+ def_vis_fxid = FullTransactionIdFromXid(def_vis_xid);
+ def_vis_fxid_data = FullTransactionIdFromXid(def_vis_xid_data);
/*
* Check if we can increase upper bound. As a previous
@@ -2516,7 +2502,7 @@ GetSnapshotData(Snapshot snapshot)
/* See temp_oldest_nonremovable computation in ComputeXidHorizons() */
if (TransactionIdIsNormal(myxid))
GlobalVisTempRels.definitely_needed =
- FullXidRelativeTo(latest_completed, myxid);
+ FullTransactionIdFromXid(myxid);
else
{
GlobalVisTempRels.definitely_needed = latest_completed;
@@ -2623,7 +2609,7 @@ ProcArrayInstallImportedXmin(TransactionId xmin,
/*
* Likewise, let's just make real sure its xmin does cover us.
*/
- xid = UINT32_ACCESS_ONCE(proc->xmin);
+ xid = UINT64_ACCESS_ONCE(proc->xmin);
if (!TransactionIdIsNormal(xid) ||
!TransactionIdPrecedesOrEquals(xid, xmin))
continue;
@@ -2678,7 +2664,7 @@ ProcArrayInstallRestoredXmin(TransactionId xmin, PGPROC *proc)
* can't go backwards. Also, make sure it's running in the same database,
* so that the per-database xmin cannot go backwards.
*/
- xid = UINT32_ACCESS_ONCE(proc->xmin);
+ xid = UINT64_ACCESS_ONCE(proc->xmin);
if (proc->databaseId == MyDatabaseId &&
TransactionIdIsNormal(xid) &&
TransactionIdPrecedesOrEquals(xid, xmin))
@@ -2796,7 +2782,7 @@ GetRunningTransactionData(void)
TransactionId xid;
/* Fetch xid just once - see GetNewTransactionId */
- xid = UINT32_ACCESS_ONCE(other_xids[index]);
+ xid = UINT64_ACCESS_ONCE(other_xids[index]);
/*
* We don't need to store transactions that don't have a TransactionId
@@ -2935,7 +2921,7 @@ GetOldestActiveTransactionId(void)
TransactionId xid;
/* Fetch xid just once - see GetNewTransactionId */
- xid = UINT32_ACCESS_ONCE(other_xids[index]);
+ xid = UINT64_ACCESS_ONCE(other_xids[index]);
if (!TransactionIdIsNormal(xid))
continue;
@@ -3033,7 +3019,7 @@ GetOldestSafeDecodingTransactionId(bool catalogOnly)
TransactionId xid;
/* Fetch xid just once - see GetNewTransactionId */
- xid = UINT32_ACCESS_ONCE(other_xids[index]);
+ xid = UINT64_ACCESS_ONCE(other_xids[index]);
if (!TransactionIdIsNormal(xid))
continue;
@@ -3315,7 +3301,7 @@ GetCurrentVirtualXIDs(TransactionId limitXmin, bool excludeXmin0,
if (allDbs || proc->databaseId == MyDatabaseId)
{
/* Fetch xmin just once - might change on us */
- TransactionId pxmin = UINT32_ACCESS_ONCE(proc->xmin);
+ TransactionId pxmin = UINT64_ACCESS_ONCE(proc->xmin);
if (excludeXmin0 && !TransactionIdIsValid(pxmin))
continue;
@@ -3410,7 +3396,7 @@ GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid)
proc->databaseId == dbOid)
{
/* Fetch xmin just once - can't change on us, but good coding */
- TransactionId pxmin = UINT32_ACCESS_ONCE(proc->xmin);
+ TransactionId pxmin = UINT64_ACCESS_ONCE(proc->xmin);
/*
* We ignore an invalid pxmin because this means that backend has
@@ -3992,7 +3978,7 @@ XidCacheRemoveRunningXids(TransactionId xid,
* debug warning.
*/
if (j < 0 && !MyProc->subxidStatus.overflowed)
- elog(WARNING, "did not find subXID %u in MyProc", anxid);
+ elog(WARNING, "did not find subXID " XID_FMT " in MyProc", anxid);
}
for (j = MyProc->subxidStatus.count - 1; j >= 0; j--)
@@ -4008,7 +3994,7 @@ XidCacheRemoveRunningXids(TransactionId xid,
}
/* Ordinarily we should have found it, unless the cache has overflowed */
if (j < 0 && !MyProc->subxidStatus.overflowed)
- elog(WARNING, "did not find subXID %u in MyProc", xid);
+ elog(WARNING, "did not find subXID " XID_FMT " in MyProc", xid);
/* Also advance global latestCompletedXid while holding the lock */
MaintainLatestCompletedXid(latestXid);
@@ -4115,17 +4101,13 @@ static void
GlobalVisUpdateApply(ComputeXidHorizonsResult *horizons)
{
GlobalVisSharedRels.maybe_needed =
- FullXidRelativeTo(horizons->latest_completed,
- horizons->shared_oldest_nonremovable);
+ FullTransactionIdFromXid(horizons->shared_oldest_nonremovable);
GlobalVisCatalogRels.maybe_needed =
- FullXidRelativeTo(horizons->latest_completed,
- horizons->catalog_oldest_nonremovable);
+ FullTransactionIdFromXid(horizons->catalog_oldest_nonremovable);
GlobalVisDataRels.maybe_needed =
- FullXidRelativeTo(horizons->latest_completed,
- horizons->data_oldest_nonremovable);
+ FullTransactionIdFromXid(horizons->data_oldest_nonremovable);
GlobalVisTempRels.maybe_needed =
- FullXidRelativeTo(horizons->latest_completed,
- horizons->temp_oldest_nonremovable);
+ FullTransactionIdFromXid(horizons->temp_oldest_nonremovable);
/*
* In longer running transactions it's possible that transactions we
@@ -4214,15 +4196,7 @@ GlobalVisTestIsRemovableXid(GlobalVisState *state, TransactionId xid)
{
FullTransactionId fxid;
- /*
- * Convert 32 bit argument to FullTransactionId. We can do so safely
- * because we know the xid has to, at the very least, be between
- * [oldestXid, nextFullXid), i.e. within 2 billion of xid. To avoid taking
- * a lock to determine either, we can just compare with
- * state->definitely_needed, which was based on those value at the time
- * the current snapshot was built.
- */
- fxid = FullXidRelativeTo(state->definitely_needed, xid);
+ fxid = FullTransactionIdFromXid(xid);
return GlobalVisTestIsRemovableFullXid(state, fxid);
}
@@ -4285,32 +4259,6 @@ GlobalVisCheckRemovableXid(Relation rel, TransactionId xid)
return GlobalVisTestIsRemovableXid(state, xid);
}
-/*
- * Convert a 32 bit transaction id into 64 bit transaction id, by assuming it
- * is within MaxTransactionId / 2 of XidFromFullTransactionId(rel).
- *
- * Be very careful about when to use this function. It can only safely be used
- * when there is a guarantee that xid is within MaxTransactionId / 2 xids of
- * rel. That e.g. can be guaranteed if the caller assures a snapshot is
- * held by the backend and xid is from a table (where vacuum/freezing ensures
- * the xid has to be within that range), or if xid is from the procarray and
- * prevents xid wraparound that way.
- */
-static inline FullTransactionId
-FullXidRelativeTo(FullTransactionId rel, TransactionId xid)
-{
- TransactionId rel_xid = XidFromFullTransactionId(rel);
-
- Assert(TransactionIdIsValid(xid));
- Assert(TransactionIdIsValid(rel_xid));
-
- /* not guaranteed to find issues, but likely to catch mistakes */
- AssertTransactionIdInAllowableRange(xid);
-
- return FullTransactionIdFromU64(U64FromFullTransactionId(rel)
- + (int32) (xid - rel_xid));
-}
-
/* ----------------------------------------------
* KnownAssignedTransactionIds sub-module
@@ -4385,7 +4333,7 @@ RecordKnownAssignedTransactionIds(TransactionId xid)
Assert(TransactionIdIsValid(xid));
Assert(TransactionIdIsValid(latestObservedXid));
- elog(trace_recovery(DEBUG4), "record known xact %u latestObservedXid %u",
+ elog(trace_recovery(DEBUG4), "record known xact " XID_FMT " latestObservedXid " XID_FMT,
xid, latestObservedXid);
/*
@@ -4896,7 +4844,7 @@ KnownAssignedXidsRemove(TransactionId xid)
{
Assert(TransactionIdIsValid(xid));
- elog(trace_recovery(DEBUG4), "remove KnownAssignedXid %u", xid);
+ elog(trace_recovery(DEBUG4), "remove KnownAssignedXid " XID_FMT, xid);
/*
* Note: we cannot consider it an error to remove an XID that's not
@@ -4956,7 +4904,7 @@ KnownAssignedXidsRemovePreceding(TransactionId removeXid)
return;
}
- elog(trace_recovery(DEBUG4), "prune KnownAssignedXids to %u", removeXid);
+ elog(trace_recovery(DEBUG4), "prune KnownAssignedXids to " XID_FMT, removeXid);
/*
* Mark entries invalid starting at the tail. Since array is sorted, we
@@ -5144,7 +5092,7 @@ KnownAssignedXidsDisplay(int trace_level)
if (KnownAssignedXidsValid[i])
{
nxids++;
- appendStringInfo(&buf, "[%d]=%u ", i, KnownAssignedXids[i]);
+ appendStringInfo(&buf, "[%d]=" XID_FMT " ", i, KnownAssignedXids[i]);
}
}
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index 27361ac861..82f031fbd2 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -37,7 +37,7 @@
#include "utils/timestamp.h"
/* User-settable GUC parameters */
-int vacuum_defer_cleanup_age;
+int64 vacuum_defer_cleanup_age;
int max_standby_archive_delay = 30 * 1000;
int max_standby_streaming_delay = 30 * 1000;
bool log_recovery_conflict_waits = false;
@@ -995,13 +995,13 @@ StandbyReleaseLockList(List *locks)
LOCKTAG locktag;
elog(trace_recovery(DEBUG4),
- "releasing recovery lock: xid %u db %u rel %u",
+ "releasing recovery lock: xid " XID_FMT " db %u rel %u",
lock->xid, lock->dbOid, lock->relOid);
SET_LOCKTAG_RELATION(locktag, lock->dbOid, lock->relOid);
if (!LockRelease(&locktag, AccessExclusiveLock, true))
{
elog(LOG,
- "RecoveryLockLists contains entry for lock no longer recorded by lock manager: xid %u database %u relation %u",
+ "RecoveryLockLists contains entry for lock no longer recorded by lock manager: xid " XID_FMT " database %u relation %u",
lock->xid, lock->dbOid, lock->relOid);
Assert(false);
}
@@ -1306,7 +1306,7 @@ LogCurrentRunningXacts(RunningTransactions CurrRunningXacts)
if (CurrRunningXacts->subxid_overflow)
elog(trace_recovery(DEBUG2),
- "snapshot of %u running transactions overflowed (lsn %X/%X oldest xid %u latest complete %u next xid %u)",
+ "snapshot of %u running transactions overflowed (lsn %X/%X oldest xid " XID_FMT " latest complete " XID_FMT " next xid " XID_FMT ")",
CurrRunningXacts->xcnt,
LSN_FORMAT_ARGS(recptr),
CurrRunningXacts->oldestRunningXid,
@@ -1314,7 +1314,7 @@ LogCurrentRunningXacts(RunningTransactions CurrRunningXacts)
CurrRunningXacts->nextXid);
else
elog(trace_recovery(DEBUG2),
- "snapshot of %u+%u running transaction ids (lsn %X/%X oldest xid %u latest complete %u next xid %u)",
+ "snapshot of %u+%u running transaction ids (lsn %X/%X oldest xid " XID_FMT " latest complete " XID_FMT " next xid " XID_FMT ")",
CurrRunningXacts->xcnt, CurrRunningXacts->subxcnt,
LSN_FORMAT_ARGS(recptr),
CurrRunningXacts->oldestRunningXid,
diff --git a/src/backend/storage/lmgr/lmgr.c b/src/backend/storage/lmgr/lmgr.c
index 5ae52dd14d..5246dd3660 100644
--- a/src/backend/storage/lmgr/lmgr.c
+++ b/src/backend/storage/lmgr/lmgr.c
@@ -1135,10 +1135,17 @@ DescribeLockTag(StringInfo buf, const LOCKTAG *tag)
tag->locktag_field1);
break;
case LOCKTAG_TRANSACTION:
- appendStringInfo(buf,
- _("transaction %u"),
- tag->locktag_field1);
+ {
+ char xid_str[32];
+
+ /* make translatable string */
+ snprintf(xid_str, sizeof(xid_str), XID_FMT,
+ (TransactionId) tag->locktag_field1 |
+ ((TransactionId) tag->locktag_field2 << 32));
+
+ appendStringInfo(buf, _("transaction %s"), xid_str);
break;
+ }
case LOCKTAG_VIRTUALTRANSACTION:
appendStringInfo(buf,
_("virtual transaction %d/%u"),
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index e337aad5b2..ae6ff86ef5 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -438,7 +438,7 @@ static void SetPossibleUnsafeConflict(SERIALIZABLEXACT *roXact, SERIALIZABLEXACT
static void ReleaseRWConflict(RWConflict conflict);
static void FlagSxactUnsafe(SERIALIZABLEXACT *sxact);
-static bool SerialPagePrecedesLogically(int page1, int page2);
+static bool SerialPagePrecedesLogically(int64 page1, int64 page2);
static void SerialInit(void);
static void SerialAdd(TransactionId xid, SerCommitSeqNo minConflictCommitSeqNo);
static SerCommitSeqNo SerialGetMinConflictCommitSeqNo(TransactionId xid);
@@ -788,27 +788,21 @@ FlagSxactUnsafe(SERIALIZABLEXACT *sxact)
* Analogous to CLOGPagePrecedes().
*/
static bool
-SerialPagePrecedesLogically(int page1, int page2)
+SerialPagePrecedesLogically(int64 page1, int64 page2)
{
- TransactionId xid1;
- TransactionId xid2;
-
- xid1 = ((TransactionId) page1) * SERIAL_ENTRIESPERPAGE;
- xid1 += FirstNormalTransactionId + 1;
- xid2 = ((TransactionId) page2) * SERIAL_ENTRIESPERPAGE;
- xid2 += FirstNormalTransactionId + 1;
-
- return (TransactionIdPrecedes(xid1, xid2) &&
- TransactionIdPrecedes(xid1, xid2 + SERIAL_ENTRIESPERPAGE - 1));
+ return page1 < page2;
}
#ifdef USE_ASSERT_CHECKING
+#define SerialPagePrecedesLogically(p1, p2) ((p1) < (p2))
+
static void
SerialPagePrecedesLogicallyUnitTests(void)
{
+#if 0 /* XXX remove unit tests */
int per_page = SERIAL_ENTRIESPERPAGE,
offset = per_page / 2;
- int newestPage,
+ int64 newestPage,
oldestPage,
headPage,
targetPage;
@@ -856,9 +850,11 @@ SerialPagePrecedesLogicallyUnitTests(void)
#if 0
Assert(SerialPagePrecedesLogically(headPage, targetPage));
#endif
+#endif
}
#endif
+
/*
* Initialize for the tracking of old serializable committed xids.
*/
@@ -4168,7 +4164,7 @@ CheckForSerializableConflictOut(Relation relation, TransactionId xid, Snapshot s
ereport(ERROR,
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
errmsg("could not serialize access due to read/write dependencies among transactions"),
- errdetail_internal("Reason code: Canceled on conflict out to old pivot %u.", xid),
+ errdetail_internal("Reason code: Canceled on conflict out to old pivot " XID_FMT ".", xid),
errhint("The transaction might succeed if retried.")));
if (SxactHasSummaryConflictIn(MySerializableXact)
@@ -4176,7 +4172,7 @@ CheckForSerializableConflictOut(Relation relation, TransactionId xid, Snapshot s
ereport(ERROR,
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
errmsg("could not serialize access due to read/write dependencies among transactions"),
- errdetail_internal("Reason code: Canceled on identification as a pivot, with conflict out to old committed transaction %u.", xid),
+ errdetail_internal("Reason code: Canceled on identification as a pivot, with conflict out to old committed transaction " XID_FMT ".", xid),
errhint("The transaction might succeed if retried.")));
MySerializableXact->flags |= SXACT_FLAG_SUMMARY_CONFLICT_OUT;
@@ -4806,7 +4802,7 @@ OnConflict_CheckForSerializationFailure(const SERIALIZABLEXACT *reader,
ereport(ERROR,
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
errmsg("could not serialize access due to read/write dependencies among transactions"),
- errdetail_internal("Reason code: Canceled on conflict out to pivot %u, during read.", writer->topXid),
+ errdetail_internal("Reason code: Canceled on conflict out to pivot " XID_FMT ", during read.", writer->topXid),
errhint("The transaction might succeed if retried.")));
}
writer->flags |= SXACT_FLAG_DOOMED;
diff --git a/src/backend/storage/page/bufpage.c b/src/backend/storage/page/bufpage.c
index 147ba4d923..0edfbceb42 100644
--- a/src/backend/storage/page/bufpage.c
+++ b/src/backend/storage/page/bufpage.c
@@ -21,11 +21,26 @@
#include "storage/checksum.h"
#include "utils/memdebug.h"
#include "utils/memutils.h"
+#include "utils/snapmgr.h"
/* GUC variable */
bool ignore_checksum_failure = false;
+/*
+ * HeapPageSpecialData used when pd_special == BLCKSZ. This is special format
+ * used when page with 32-bit xids doesn't fit HeapPageSpecialData. Then
+ * all xmin's are frozen (can do this for all live tuples after pg_upgrade),
+ * while 64-bit xmax is stored in both t_heap.t_xmin and t_heap.t_xmax.
+ * This is so-called "double xmax" format.
+ */
+static HeapPageSpecialData doubleXmaxSpecialData =
+{
+ .pd_xid_base = MaxTransactionId,
+ .pd_multi_base = MaxTransactionId
+};
+HeapPageSpecial doubleXmaxSpecial = &doubleXmaxSpecialData;
+
/* ----------------------------------------------------------------
* Page support functions
@@ -432,15 +447,131 @@ PageRestoreTempPage(Page tempPage, Page oldPage)
}
/*
- * Tuple defrag support for PageRepairFragmentation and PageIndexMultiDelete
+ * Get minimum and maximum values of xid and multixact on "double xmax" page.
*/
-typedef struct itemIdCompactData
+static void
+heap_page_double_xmax_get_min_max(Page page,
+ TransactionId *xid_min,
+ TransactionId *xid_max,
+ MultiXactId *multi_min,
+ MultiXactId *multi_max)
{
- uint16 offsetindex; /* linp array index */
- int16 itemoff; /* page offset of item data */
- uint16 alignedlen; /* MAXALIGN(item data len) */
-} itemIdCompactData;
-typedef itemIdCompactData *itemIdCompact;
+ bool xid_found = false,
+ multi_found = false;
+ OffsetNumber offnum,
+ maxoff;
+
+ maxoff = PageGetMaxOffsetNumber(page);
+
+ for (offnum = FirstOffsetNumber;
+ offnum <= maxoff;
+ offnum = OffsetNumberNext(offnum))
+ {
+ ItemId itemid;
+ HeapTupleHeader htup;
+ TransactionId xmax;
+
+ itemid = PageGetItemId(page, offnum);
+
+ if (!ItemIdIsNormal(itemid))
+ continue;
+
+ htup = (HeapTupleHeader) PageGetItem(page, itemid);
+
+ xmax = HeapTupleHeaderGetDoubleXmax(htup);
+
+ if (!TransactionIdIsNormal(xmax))
+ continue;
+
+ if (!(htup->t_infomask & HEAP_XMAX_IS_MULTI))
+ {
+ if (!xid_found)
+ {
+ *xid_min = *xid_max = xmax;
+ xid_found = true;
+ }
+ else
+ {
+ *xid_min = Min(*xid_min, xmax);
+ *xid_max = Max(*xid_max, xmax);
+ }
+ }
+ else
+ {
+ if (!multi_found)
+ {
+ *multi_min = *multi_max = xmax;
+ multi_found = true;
+ }
+ else
+ {
+ *multi_min = Min(*multi_min, xmax);
+ *multi_max = Max(*multi_max, xmax);
+ }
+ }
+ }
+}
+
+/*
+ * Add special area to heap page, so convert from "double xmax" to normal
+ * format.
+ */
+static void
+heap_page_add_special_area(itemIdCompact itemidbase, int nitems, Page page,
+ TransactionId xid_base, MultiXactId multi_base)
+{
+ char newPage[BLCKSZ];
+ PageHeader phdr = (PageHeader) page;
+ PageHeader new_phdr = (PageHeader) newPage;
+ HeapPageSpecial special;
+ Offset upper;
+ int i;
+
+ memcpy(newPage, page, phdr->pd_lower);
+
+ /* Add special area */
+ new_phdr->pd_special = PageGetPageSize(newPage) - sizeof(HeapPageSpecialData);
+ special = (HeapPageSpecial) ((Pointer) (newPage) + new_phdr->pd_special);
+ special->pd_xid_base = xid_base;
+ special->pd_multi_base = multi_base;
+
+ /* sort itemIdSortData array into decreasing itemoff order */
+ qsort((char *) itemidbase, nitems, sizeof(itemIdCompactData),
+ itemoffcompare);
+
+ upper = new_phdr->pd_special;
+ for (i = 0; i < nitems; i++)
+ {
+ itemIdCompact itemidptr = &itemidbase[i];
+ ItemId lp;
+ HeapTupleHeader old_htup;
+ HeapTupleHeader new_htup;
+ TransactionId xmax;
+
+ lp = PageGetItemId(page, itemidptr->offsetindex + 1);
+ old_htup = (HeapTupleHeader) PageGetItem(page, lp);
+ upper -= itemidptr->alignedlen;
+ memcpy((Pointer) newPage + upper,
+ (Pointer) page + itemidptr->itemoff,
+ itemidptr->alignedlen);
+ lp = PageGetItemId(newPage, itemidptr->offsetindex + 1);
+ lp->lp_off = upper;
+ new_htup = (HeapTupleHeader) PageGetItem(newPage, lp);
+
+ /* Convert xmax value */
+ new_htup->t_choice.t_heap.t_xmin = FrozenTransactionId;
+ xmax = HeapTupleHeaderGetDoubleXmax(old_htup);
+ if (!(new_htup->t_infomask & HEAP_XMAX_IS_MULTI))
+ new_htup->t_choice.t_heap.t_xmax = NormalTransactionIdToShort(xid_base, xmax);
+ else
+ new_htup->t_choice.t_heap.t_xmax = NormalTransactionIdToShort(multi_base, xmax);
+ }
+
+ new_phdr->pd_upper = upper;
+
+ memcpy(page, newPage, PageGetPageSize(newPage));
+ elog(DEBUG2, "convert heap page from double xmax to normal format");
+}
/*
* After removing or marking some line pointers unused, move the tuples to
@@ -471,7 +602,8 @@ typedef itemIdCompactData *itemIdCompact;
* Callers must ensure that nitems is > 0
*/
static void
-compactify_tuples(itemIdCompact itemidbase, int nitems, Page page, bool presorted)
+compactify_tuples(itemIdCompact itemidbase, int nitems, Page page,
+ bool presorted, bool addspecial)
{
PageHeader phdr = (PageHeader) page;
Offset upper;
@@ -483,9 +615,33 @@ compactify_tuples(itemIdCompact itemidbase, int nitems, Page page, bool presorte
/* Code within will not work correctly if nitems == 0 */
Assert(nitems > 0);
- if (presorted)
+ /* Add special area to the heap page if possible */
+ if (addspecial)
{
+ TransactionId xid_min = FirstNormalTransactionId,
+ xid_max = FirstNormalTransactionId;
+ MultiXactId multi_min = FirstNormalTransactionId,
+ multi_max = FirstNormalTransactionId;
+
+ Assert(phdr->pd_special == PageGetPageSize(page));
+ heap_page_double_xmax_get_min_max(page, &xid_min, &xid_max,
+ &multi_min, &multi_max);
+
+ if (xid_max - xid_min < (TransactionId) (MaxShortTransactionId - FirstNormalTransactionId) &&
+ multi_max - multi_min < (TransactionId) (MaxShortTransactionId - FirstNormalTransactionId))
+ {
+ Assert(xid_min >= FirstNormalTransactionId);
+ Assert(multi_min >= FirstNormalTransactionId);
+ heap_page_add_special_area(itemidbase, nitems, page,
+ xid_min - FirstNormalTransactionId,
+ multi_min - FirstNormalTransactionId);
+ return;
+ }
+ }
+
+ if (presorted)
+ {
#ifdef USE_ASSERT_CHECKING
{
/*
@@ -783,11 +939,21 @@ PageRepairFragmentation(Page page)
nstorage = itemidptr - itemidbase;
if (nstorage == 0)
{
+ if (pd_special == PageGetPageSize(page))
+ {
+ pd_special = PageGetPageSize(page) - sizeof(HeapPageSpecialData);
+ ((PageHeader) page)->pd_special = pd_special;
+ HeapPageGetSpecial(page)->pd_xid_base = 0;
+ HeapPageGetSpecial(page)->pd_multi_base = 0;
+ }
+
/* Page is completely empty, so just reset it quickly */
((PageHeader) page)->pd_upper = pd_special;
}
else
{
+ bool addspecial = false;
+
/* Need to compact the page the hard way */
if (totallen > (Size) (pd_special - pd_lower))
ereport(ERROR,
@@ -795,7 +961,15 @@ PageRepairFragmentation(Page page)
errmsg("corrupted item lengths: total %u, available space %u",
(unsigned int) totallen, pd_special - pd_lower)));
- compactify_tuples(itemidbase, nstorage, page, presorted);
+ /*
+ * Try to add special area to the heap page if it has enough of free
+ * space.
+ */
+ if (pd_special == PageGetPageSize(page) &&
+ (Size) (pd_special - pd_lower) - totallen >= sizeof(HeapPageSpecialData))
+ addspecial = true;
+
+ compactify_tuples(itemidbase, nstorage, page, presorted, addspecial);
}
/* Set hint bit for PageAddItemExtended */
@@ -985,6 +1159,9 @@ PageGetHeapFreeSpace(Page page)
{
Size space;
+ if (HeapPageIsDoubleXmax(page))
+ return 0;
+
space = PageGetFreeSpace(page);
if (space > 0)
{
@@ -1268,7 +1445,7 @@ PageIndexMultiDelete(Page page, OffsetNumber *itemnos, int nitems)
/* and compactify the tuple data */
if (nused > 0)
- compactify_tuples(itemidbase, nused, page, presorted);
+ compactify_tuples(itemidbase, nused, page, presorted, false);
else
phdr->pd_upper = pd_special;
}
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 34c13a1113..fdedad7b2b 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -4007,7 +4007,6 @@ PostgresSingleUserMain(int argc, char *argv[],
PostgresMain(dbname, username);
}
-
/* ----------------------------------------------------------------
* PostgresMain
* postgres main loop -- all backends, interactive or otherwise loop here
diff --git a/src/backend/utils/adt/enum.c b/src/backend/utils/adt/enum.c
index 0cc7a6d8ad..04ca6ff16e 100644
--- a/src/backend/utils/adt/enum.c
+++ b/src/backend/utils/adt/enum.c
@@ -76,7 +76,7 @@ check_safe_enum_use(HeapTuple enumval_tup)
* Usually, a row would get hinted as committed when it's read or loaded
* into syscache; but just in case not, let's check the xmin directly.
*/
- xmin = HeapTupleHeaderGetXmin(enumval_tup->t_data);
+ xmin = HeapTupleGetXmin(enumval_tup);
if (!TransactionIdIsInProgress(xmin) &&
TransactionIdDidCommit(xmin))
return;
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 2457061f97..8c77b800ca 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -3782,6 +3782,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
tuple.t_len = HeapTupleHeaderGetDatumLength(tuphead);
ItemPointerSetInvalid(&(tuple.t_self));
tuple.t_tableOid = InvalidOid;
+ HeapTupleSetZeroBase(&tuple);
tuple.t_data = tuphead;
tuplestore_puttuple(state->tuple_store, &tuple);
diff --git a/src/backend/utils/adt/lockfuncs.c b/src/backend/utils/adt/lockfuncs.c
index 944cd6df03..bb7047358e 100644
--- a/src/backend/utils/adt/lockfuncs.c
+++ b/src/backend/utils/adt/lockfuncs.c
@@ -77,9 +77,9 @@ VXIDGetDatum(BackendId bid, LocalTransactionId lxid)
* The representation is "<bid>/<lxid>", decimal and unsigned decimal
* respectively. Note that elog.c also knows how to format a vxid.
*/
- char vxidstr[32];
+ char vxidstr[64];
- snprintf(vxidstr, sizeof(vxidstr), "%d/%u", bid, lxid);
+ snprintf(vxidstr, sizeof(vxidstr), "%d/" XID_FMT, bid, lxid);
return CStringGetTextDatum(vxidstr);
}
@@ -292,7 +292,9 @@ pg_lock_status(PG_FUNCTION_ARGS)
break;
case LOCKTAG_TRANSACTION:
values[6] =
- TransactionIdGetDatum(instance->locktag.locktag_field1);
+ TransactionIdGetDatum(
+ (TransactionId)instance->locktag.locktag_field1 |
+ ((TransactionId)instance->locktag.locktag_field2 << 32));
nulls[1] = true;
nulls[2] = true;
nulls[3] = true;
@@ -304,7 +306,8 @@ pg_lock_status(PG_FUNCTION_ARGS)
break;
case LOCKTAG_VIRTUALTRANSACTION:
values[5] = VXIDGetDatum(instance->locktag.locktag_field1,
- instance->locktag.locktag_field2);
+ (TransactionId)instance->locktag.locktag_field2 |
+ ((TransactionId)instance->locktag.locktag_field3 << 32));
nulls[1] = true;
nulls[2] = true;
nulls[3] = true;
diff --git a/src/backend/utils/adt/numutils.c b/src/backend/utils/adt/numutils.c
index cc3f95d399..0da1b4e214 100644
--- a/src/backend/utils/adt/numutils.c
+++ b/src/backend/utils/adt/numutils.c
@@ -602,3 +602,25 @@ pg_ultostr(char *str, uint32 value)
return str + len;
}
+
+/*
+ * pg_strtouint64
+ * Converts 'str' into an unsigned 64-bit integer.
+ *
+ * This has the identical API to strtoul(3), except that it will handle
+ * 64-bit ints even where "long" is narrower than that.
+ *
+ * For the moment it seems sufficient to assume that the platform has
+ * such a function somewhere; let's not roll our own.
+ */
+uint64
+pg_strtouint64(const char *str, char **endptr, int base)
+{
+#ifdef _MSC_VER /* MSVC only */
+ return _strtoui64(str, endptr, base);
+#elif defined(HAVE_STRTOULL) && SIZEOF_LONG < 8
+ return strtoull(str, endptr, base);
+#else
+ return strtoul(str, endptr, base);
+#endif
+}
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index fd993d0d5f..9498bf6e20 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -15,6 +15,7 @@
#include "postgres.h"
#include "access/htup_details.h"
+#include "access/xact.h"
#include "access/xlog.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_type.h"
diff --git a/src/backend/utils/adt/rowtypes.c b/src/backend/utils/adt/rowtypes.c
index db843a0fbf..5e08d02d64 100644
--- a/src/backend/utils/adt/rowtypes.c
+++ b/src/backend/utils/adt/rowtypes.c
@@ -327,6 +327,7 @@ record_out(PG_FUNCTION_ARGS)
tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
ItemPointerSetInvalid(&(tuple.t_self));
tuple.t_tableOid = InvalidOid;
+ HeapTupleSetZeroBase(&tuple);
tuple.t_data = rec;
/*
@@ -694,6 +695,7 @@ record_send(PG_FUNCTION_ARGS)
tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
ItemPointerSetInvalid(&(tuple.t_self));
tuple.t_tableOid = InvalidOid;
+ HeapTupleSetZeroBase(&tuple);
tuple.t_data = rec;
/*
@@ -844,10 +846,12 @@ record_cmp(FunctionCallInfo fcinfo)
tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
ItemPointerSetInvalid(&(tuple1.t_self));
tuple1.t_tableOid = InvalidOid;
+ HeapTupleSetZeroBase(&tuple1);
tuple1.t_data = record1;
tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
ItemPointerSetInvalid(&(tuple2.t_self));
tuple2.t_tableOid = InvalidOid;
+ HeapTupleSetZeroBase(&tuple2);
tuple2.t_data = record2;
/*
@@ -1089,10 +1093,12 @@ record_eq(PG_FUNCTION_ARGS)
ItemPointerSetInvalid(&(tuple1.t_self));
tuple1.t_tableOid = InvalidOid;
tuple1.t_data = record1;
+ HeapTupleSetZeroBase(&tuple1);
tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
ItemPointerSetInvalid(&(tuple2.t_self));
tuple2.t_tableOid = InvalidOid;
tuple2.t_data = record2;
+ HeapTupleSetZeroBase(&tuple2);
/*
* We arrange to look up the needed comparison info just once per series
@@ -1351,10 +1357,12 @@ record_image_cmp(FunctionCallInfo fcinfo)
ItemPointerSetInvalid(&(tuple1.t_self));
tuple1.t_tableOid = InvalidOid;
tuple1.t_data = record1;
+ HeapTupleSetZeroBase(&tuple1);
tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
ItemPointerSetInvalid(&(tuple2.t_self));
tuple2.t_tableOid = InvalidOid;
tuple2.t_data = record2;
+ HeapTupleSetZeroBase(&tuple2);
/*
* We arrange to look up the needed comparison info just once per series
@@ -1597,10 +1605,12 @@ record_image_eq(PG_FUNCTION_ARGS)
ItemPointerSetInvalid(&(tuple1.t_self));
tuple1.t_tableOid = InvalidOid;
tuple1.t_data = record1;
+ HeapTupleSetZeroBase(&tuple1);
tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
ItemPointerSetInvalid(&(tuple2.t_self));
tuple2.t_tableOid = InvalidOid;
tuple2.t_data = record2;
+ HeapTupleSetZeroBase(&tuple2);
/*
* We arrange to look up the needed comparison info just once per series
@@ -1800,6 +1810,7 @@ hash_record(PG_FUNCTION_ARGS)
ItemPointerSetInvalid(&(tuple.t_self));
tuple.t_tableOid = InvalidOid;
tuple.t_data = record;
+ HeapTupleSetZeroBase(&tuple);
/*
* We arrange to look up the needed hashing info just once per series of
@@ -1921,6 +1932,7 @@ hash_record_extended(PG_FUNCTION_ARGS)
ItemPointerSetInvalid(&(tuple.t_self));
tuple.t_tableOid = InvalidOid;
tuple.t_data = record;
+ HeapTupleSetZeroBase(&tuple);
/*
* We arrange to look up the needed hashing info just once per series of
diff --git a/src/backend/utils/adt/xid.c b/src/backend/utils/adt/xid.c
index e4b4952a28..be5c31cc10 100644
--- a/src/backend/utils/adt/xid.c
+++ b/src/backend/utils/adt/xid.c
@@ -32,16 +32,16 @@ xidin(PG_FUNCTION_ARGS)
{
char *str = PG_GETARG_CSTRING(0);
- PG_RETURN_TRANSACTIONID((TransactionId) strtoul(str, NULL, 0));
+ PG_RETURN_TRANSACTIONID((TransactionId) pg_strtouint64(str, NULL, 0));
}
Datum
xidout(PG_FUNCTION_ARGS)
{
TransactionId transactionId = PG_GETARG_TRANSACTIONID(0);
- char *result = (char *) palloc(16);
+ char *result = (char *) palloc(32);
- snprintf(result, 16, "%lu", (unsigned long) transactionId);
+ snprintf(result, 32, XID_FMT, transactionId);
PG_RETURN_CSTRING(result);
}
@@ -52,8 +52,12 @@ Datum
xidrecv(PG_FUNCTION_ARGS)
{
StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
+ uint32 lo, hi;
+
+ lo = (uint32) pq_getmsgint(buf, sizeof(TransactionId));
+ hi = (uint32) pq_getmsgint(buf, sizeof(TransactionId));
- PG_RETURN_TRANSACTIONID((TransactionId) pq_getmsgint(buf, sizeof(TransactionId)));
+ PG_RETURN_TRANSACTIONID((uint64) lo + ((uint64) hi << 32));
}
/*
@@ -64,9 +68,14 @@ xidsend(PG_FUNCTION_ARGS)
{
TransactionId arg1 = PG_GETARG_TRANSACTIONID(0);
StringInfoData buf;
+ uint32 lo, hi;
+
+ lo = (uint32) (arg1 & 0xFFFFFFFF);
+ hi = (uint32) (arg1 >> 32);
pq_begintypsend(&buf);
- pq_sendint32(&buf, arg1);
+ pq_sendint(&buf, lo, sizeof(lo));
+ pq_sendint(&buf, hi, sizeof(hi));
PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
}
@@ -105,9 +114,9 @@ xid_age(PG_FUNCTION_ARGS)
/* Permanent XIDs are always infinitely old */
if (!TransactionIdIsNormal(xid))
- PG_RETURN_INT32(INT_MAX);
+ PG_RETURN_INT64(PG_INT8_MAX);
- PG_RETURN_INT32((int32) (now - xid));
+ PG_RETURN_INT64((int64) (now - xid));
}
/*
@@ -120,9 +129,9 @@ mxid_age(PG_FUNCTION_ARGS)
MultiXactId now = ReadNextMultiXactId();
if (!MultiXactIdIsValid(xid))
- PG_RETURN_INT32(INT_MAX);
+ PG_RETURN_INT64(PG_INT8_MAX);
- PG_RETURN_INT32((int32) (now - xid));
+ PG_RETURN_INT64((int64) (now - xid));
}
/*
diff --git a/src/backend/utils/adt/xid8funcs.c b/src/backend/utils/adt/xid8funcs.c
index 60e57876de..a508d74d2f 100644
--- a/src/backend/utils/adt/xid8funcs.c
+++ b/src/backend/utils/adt/xid8funcs.c
@@ -79,8 +79,7 @@ typedef struct
* It is an ERROR if the xid is in the future. Otherwise, returns true if
* the transaction is still new enough that we can determine whether it
* committed and false otherwise. If *extracted_xid is not NULL, it is set
- * to the low 32 bits of the transaction ID (i.e. the actual XID, without the
- * epoch).
+ * to the actual transaction ID.
*
* The caller must hold XactTruncationLock since it's dealing with arbitrary
* XIDs, and must continue to hold it until it's done with any clog lookups
@@ -89,15 +88,10 @@ typedef struct
static bool
TransactionIdInRecentPast(FullTransactionId fxid, TransactionId *extracted_xid)
{
- uint32 xid_epoch = EpochFromFullTransactionId(fxid);
TransactionId xid = XidFromFullTransactionId(fxid);
- uint32 now_epoch;
- TransactionId now_epoch_next_xid;
FullTransactionId now_fullxid;
now_fullxid = ReadNextFullTransactionId();
- now_epoch_next_xid = XidFromFullTransactionId(now_fullxid);
- now_epoch = EpochFromFullTransactionId(now_fullxid);
if (extracted_xid != NULL)
*extracted_xid = xid;
@@ -127,48 +121,15 @@ TransactionIdInRecentPast(FullTransactionId fxid, TransactionId *extracted_xid)
Assert(LWLockHeldByMe(XactTruncationLock));
/*
- * If the transaction ID has wrapped around, it's definitely too old to
- * determine the commit status. Otherwise, we can compare it to
- * ShmemVariableCache->oldestClogXid to determine whether the relevant
- * CLOG entry is guaranteed to still exist.
+ * We compare xid to ShmemVariableCache->oldestClogXid to determine whether
+ * the relevant CLOG entry is guaranteed to still exist.
*/
- if (xid_epoch + 1 < now_epoch
- || (xid_epoch + 1 == now_epoch && xid < now_epoch_next_xid)
- || TransactionIdPrecedes(xid, ShmemVariableCache->oldestClogXid))
+ if (TransactionIdPrecedes(xid, ShmemVariableCache->oldestClogXid))
return false;
return true;
}
-/*
- * Convert a TransactionId obtained from a snapshot held by the caller to a
- * FullTransactionId. Use next_fxid as a reference FullTransactionId, so that
- * we can compute the high order bits. It must have been obtained by the
- * caller with ReadNextFullTransactionId() after the snapshot was created.
- */
-static FullTransactionId
-widen_snapshot_xid(TransactionId xid, FullTransactionId next_fxid)
-{
- TransactionId next_xid = XidFromFullTransactionId(next_fxid);
- uint32 epoch = EpochFromFullTransactionId(next_fxid);
-
- /* Special transaction ID. */
- if (!TransactionIdIsNormal(xid))
- return FullTransactionIdFromEpochAndXid(0, xid);
-
- /*
- * The 64 bit result must be <= next_fxid, since next_fxid hadn't been
- * issued yet when the snapshot was created. Every TransactionId in the
- * snapshot must therefore be from the same epoch as next_fxid, or the
- * epoch before. We know this because next_fxid is never allow to get
- * more than one epoch ahead of the TransactionIds in any snapshot.
- */
- if (xid > next_xid)
- epoch--;
-
- return FullTransactionIdFromEpochAndXid(epoch, xid);
-}
-
/*
* txid comparator for qsort/bsearch
*/
@@ -397,7 +358,6 @@ pg_current_snapshot(PG_FUNCTION_ARGS)
uint32 nxip,
i;
Snapshot cur;
- FullTransactionId next_fxid = ReadNextFullTransactionId();
cur = GetActiveSnapshot();
if (cur == NULL)
@@ -415,11 +375,11 @@ pg_current_snapshot(PG_FUNCTION_ARGS)
snap = palloc(PG_SNAPSHOT_SIZE(nxip));
/* fill */
- snap->xmin = widen_snapshot_xid(cur->xmin, next_fxid);
- snap->xmax = widen_snapshot_xid(cur->xmax, next_fxid);
+ snap->xmin = FullTransactionIdFromXid(cur->xmin);
+ snap->xmax = FullTransactionIdFromXid(cur->xmax);
snap->nxip = nxip;
for (i = 0; i < nxip; i++)
- snap->xip[i] = widen_snapshot_xid(cur->xip[i], next_fxid);
+ snap->xip[i] = FullTransactionIdFromXid(cur->xip[i]);
/*
* We want them guaranteed to be in ascending order. This also removes
@@ -655,8 +615,7 @@ pg_snapshot_xip(PG_FUNCTION_ARGS)
* Report the status of a recent transaction ID, or null for wrapped,
* truncated away or otherwise too old XIDs.
*
- * The passed epoch-qualified xid is treated as a normal xid, not a
- * multixact id.
+ * The passed xid is treated as a normal xid, not a multixact id.
*
* If it points to a committed subxact the result is the subxact status even
* though the parent xact may still be in progress or may have aborted.
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index ec073e1ed0..eeec2b4a41 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1839,6 +1839,7 @@ CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, Datum *arguments,
memcpy((char *) ct->tuple.t_data,
(const char *) dtp->t_data,
dtp->t_len);
+ HeapTupleCopyBase(&ct->tuple, dtp);
MemoryContextSwitchTo(oldcxt);
if (dtp != ntp)
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index fccffce572..acd04b3c44 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -2286,8 +2286,7 @@ RelationReloadIndexInfo(Relation relation)
relation->rd_index->indislive = index->indislive;
/* Copy xmin too, as that is needed to make sense of indcheckxmin */
- HeapTupleHeaderSetXmin(relation->rd_indextuple->t_data,
- HeapTupleHeaderGetXmin(tuple->t_data));
+ HeapTupleSetXmin(relation->rd_indextuple, HeapTupleGetXmin(tuple));
ReleaseSysCache(tuple);
}
diff --git a/src/backend/utils/error/csvlog.c b/src/backend/utils/error/csvlog.c
index 89f78b447d..8bf5213d7e 100644
--- a/src/backend/utils/error/csvlog.c
+++ b/src/backend/utils/error/csvlog.c
@@ -153,11 +153,11 @@ write_csvlog(ErrorData *edata)
/* Virtual transaction id */
/* keep VXID format in sync with lockfuncs.c */
if (MyProc != NULL && MyProc->backendId != InvalidBackendId)
- appendStringInfo(&buf, "%d/%u", MyProc->backendId, MyProc->lxid);
+ appendStringInfo(&buf, "%d/" XID_FMT, MyProc->backendId, MyProc->lxid);
appendStringInfoChar(&buf, ',');
/* Transaction id */
- appendStringInfo(&buf, "%u", GetTopTransactionIdIfAny());
+ appendStringInfo(&buf, XID_FMT, GetTopTransactionIdIfAny());
appendStringInfoChar(&buf, ',');
/* Error severity */
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 7402696986..ab6a0f8c09 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -2754,12 +2754,12 @@ log_line_prefix(StringInfo buf, ErrorData *edata)
{
char strfbuf[128];
- snprintf(strfbuf, sizeof(strfbuf) - 1, "%d/%u",
+ snprintf(strfbuf, sizeof(strfbuf) - 1, "%d/" XID_FMT,
MyProc->backendId, MyProc->lxid);
appendStringInfo(buf, "%*s", padding, strfbuf);
}
else
- appendStringInfo(buf, "%d/%u", MyProc->backendId, MyProc->lxid);
+ appendStringInfo(buf, "%d/" XID_FMT, MyProc->backendId, MyProc->lxid);
}
else if (padding != 0)
appendStringInfoSpaces(buf,
@@ -2767,9 +2767,9 @@ log_line_prefix(StringInfo buf, ErrorData *edata)
break;
case 'x':
if (padding != 0)
- appendStringInfo(buf, "%*u", padding, GetTopTransactionIdIfAny());
+ appendStringInfo(buf, "%*" INT64_MODIFIER "u", padding, GetTopTransactionIdIfAny());
else
- appendStringInfo(buf, "%u", GetTopTransactionIdIfAny());
+ appendStringInfo(buf, XID_FMT, GetTopTransactionIdIfAny());
break;
case 'e':
if (padding != 0)
diff --git a/src/backend/utils/error/jsonlog.c b/src/backend/utils/error/jsonlog.c
index 843641c865..7ef41813d0 100644
--- a/src/backend/utils/error/jsonlog.c
+++ b/src/backend/utils/error/jsonlog.c
@@ -198,11 +198,11 @@ write_jsonlog(ErrorData *edata)
/* Virtual transaction id */
/* keep VXID format in sync with lockfuncs.c */
if (MyProc != NULL && MyProc->backendId != InvalidBackendId)
- appendJSONKeyValueFmt(&buf, "vxid", true, "%d/%u", MyProc->backendId,
+ appendJSONKeyValueFmt(&buf, "vxid", true, "%d/" XID_FMT, MyProc->backendId,
MyProc->lxid);
/* Transaction id */
- appendJSONKeyValueFmt(&buf, "txid", false, "%u",
+ appendJSONKeyValueFmt(&buf, "txid", false, XID_FMT,
GetTopTransactionIdIfAny());
/* Error severity */
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index b2e72b3243..478447e4d4 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -540,7 +540,7 @@ lookup_C_func(HeapTuple procedureTuple)
NULL);
if (entry == NULL)
return NULL; /* no such entry */
- if (entry->fn_xmin == HeapTupleHeaderGetRawXmin(procedureTuple->t_data) &&
+ if (entry->fn_xmin == HeapTupleGetRawXmin(procedureTuple) &&
ItemPointerEquals(&entry->fn_tid, &procedureTuple->t_self))
return entry; /* OK */
return NULL; /* entry is out of date */
@@ -576,7 +576,7 @@ record_C_func(HeapTuple procedureTuple,
HASH_ENTER,
&found);
/* OID is already filled in */
- entry->fn_xmin = HeapTupleHeaderGetRawXmin(procedureTuple->t_data);
+ entry->fn_xmin = HeapTupleGetRawXmin(procedureTuple);
entry->fn_tid = procedureTuple->t_self;
entry->user_fn = user_fn;
entry->inforec = inforec;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 3fe2b28c6b..08be8b0ad0 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -2653,74 +2653,6 @@ static struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
- {
- {"vacuum_freeze_min_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
- gettext_noop("Minimum age at which VACUUM should freeze a table row."),
- NULL
- },
- &vacuum_freeze_min_age,
- 50000000, 0, 1000000000,
- NULL, NULL, NULL
- },
-
- {
- {"vacuum_freeze_table_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
- gettext_noop("Age at which VACUUM should scan whole table to freeze tuples."),
- NULL
- },
- &vacuum_freeze_table_age,
- 150000000, 0, 2000000000,
- NULL, NULL, NULL
- },
-
- {
- {"vacuum_multixact_freeze_min_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
- gettext_noop("Minimum age at which VACUUM should freeze a MultiXactId in a table row."),
- NULL
- },
- &vacuum_multixact_freeze_min_age,
- 5000000, 0, 1000000000,
- NULL, NULL, NULL
- },
-
- {
- {"vacuum_multixact_freeze_table_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
- gettext_noop("Multixact age at which VACUUM should scan whole table to freeze tuples."),
- NULL
- },
- &vacuum_multixact_freeze_table_age,
- 150000000, 0, 2000000000,
- NULL, NULL, NULL
- },
-
- {
- {"vacuum_defer_cleanup_age", PGC_SIGHUP, REPLICATION_PRIMARY,
- gettext_noop("Number of transactions by which VACUUM and HOT cleanup should be deferred, if any."),
- NULL
- },
- &vacuum_defer_cleanup_age,
- 0, 0, 1000000, /* see ComputeXidHorizons */
- NULL, NULL, NULL
- },
- {
- {"vacuum_failsafe_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
- gettext_noop("Age at which VACUUM should trigger failsafe to avoid a wraparound outage."),
- NULL
- },
- &vacuum_failsafe_age,
- 1600000000, 0, 2100000000,
- NULL, NULL, NULL
- },
- {
- {"vacuum_multixact_failsafe_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
- gettext_noop("Multixact age at which VACUUM should trigger failsafe to avoid a wraparound outage."),
- NULL
- },
- &vacuum_multixact_failsafe_age,
- 1600000000, 0, 2100000000,
- NULL, NULL, NULL
- },
-
/*
* See also CheckRequiredParameterValues() if this parameter changes
*/
@@ -3319,28 +3251,6 @@ static struct config_int ConfigureNamesInt[] =
50, 0, INT_MAX,
NULL, NULL, NULL
},
- {
- /* see varsup.c for why this is PGC_POSTMASTER not PGC_SIGHUP */
- {"autovacuum_freeze_max_age", PGC_POSTMASTER, AUTOVACUUM,
- gettext_noop("Age at which to autovacuum a table to prevent transaction ID wraparound."),
- NULL
- },
- &autovacuum_freeze_max_age,
-
- /* see vacuum_failsafe_age if you change the upper-limit value. */
- 200000000, 100000, 2000000000,
- NULL, NULL, NULL
- },
- {
- /* see multixact.c for why this is PGC_POSTMASTER not PGC_SIGHUP */
- {"autovacuum_multixact_freeze_max_age", PGC_POSTMASTER, AUTOVACUUM,
- gettext_noop("Multixact age at which to autovacuum a table to prevent multixact wraparound."),
- NULL
- },
- &autovacuum_multixact_freeze_max_age,
- 400000000, 10000, 2000000000,
- NULL, NULL, NULL
- },
{
/* see max_connections */
{"autovacuum_max_workers", PGC_POSTMASTER, AUTOVACUUM,
@@ -3887,6 +3797,100 @@ static struct config_real ConfigureNamesReal[] =
static struct config_int64 ConfigureNamesInt64[] =
{
+ {
+ {"vacuum_freeze_min_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+ gettext_noop("Minimum age at which VACUUM should freeze a table row."),
+ NULL
+ },
+ &vacuum_freeze_min_age,
+ INT64CONST(50000000), INT64CONST(0), INT64CONST(0x7FFFFFFFFFFFFFFF),
+ NULL, NULL, NULL
+ },
+
+ {
+ {"vacuum_freeze_table_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+ gettext_noop("Age at which VACUUM should scan whole table to freeze tuples."),
+ NULL
+ },
+ &vacuum_freeze_table_age,
+ INT64CONST(150000000), INT64CONST(0), INT64CONST(0x7FFFFFFFFFFFFFFF),
+ NULL, NULL, NULL
+ },
+
+ {
+ {"vacuum_multixact_freeze_min_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+ gettext_noop("Minimum age at which VACUUM should freeze a MultiXactId in a table row."),
+ NULL
+ },
+ &vacuum_multixact_freeze_min_age,
+ INT64CONST(5000000), INT64CONST(0), INT64CONST(0x7FFFFFFFFFFFFFFF),
+ NULL, NULL, NULL
+ },
+
+ {
+ {"vacuum_multixact_freeze_table_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+ gettext_noop("Multixact age at which VACUUM should scan whole table to freeze tuples."),
+ NULL
+ },
+ &vacuum_multixact_freeze_table_age,
+ INT64CONST(150000000), INT64CONST(0), INT64CONST(0x7FFFFFFFFFFFFFFF),
+ NULL, NULL, NULL
+ },
+
+ {
+ {"vacuum_defer_cleanup_age", PGC_SIGHUP, REPLICATION_PRIMARY,
+ gettext_noop("Number of transactions by which VACUUM and HOT cleanup should be deferred, if any."),
+ NULL
+ },
+ &vacuum_defer_cleanup_age,
+ INT64CONST(0), INT64CONST(0), INT64CONST(1000000),
+ NULL, NULL, NULL
+ },
+
+ {
+ {"vacuum_failsafe_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+ gettext_noop("Age at which VACUUM should trigger failsafe to avoid a wraparound outage."),
+ NULL
+ },
+ &vacuum_failsafe_age,
+ INT64CONST(1600000000), INT64CONST(0), INT64CONST(2100000000),
+ NULL, NULL, NULL
+ },
+
+ {
+ {"vacuum_multixact_failsafe_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+ gettext_noop("Multixact age at which VACUUM should trigger failsafe to avoid a wraparound outage."),
+ NULL
+ },
+ &vacuum_multixact_failsafe_age,
+ INT64CONST(1600000000), INT64CONST(0), INT64CONST(2100000000),
+ NULL, NULL, NULL
+ },
+
+ {
+ /* see varsup.c for why this is PGC_POSTMASTER not PGC_SIGHUP */
+ {"autovacuum_freeze_max_age", PGC_POSTMASTER, AUTOVACUUM,
+ gettext_noop("Age at which to autovacuum a table to prevent transaction ID wraparound."),
+ NULL
+ },
+ &autovacuum_freeze_max_age,
+
+ /* see vacuum_failsafe_age if you change the upper-limit value. */
+ INT64CONST(10000000000), INT64CONST(100000), INT64CONST(0x7FFFFFFFFFFFFFFF),
+ NULL, NULL, NULL
+ },
+
+ {
+ /* see multixact.c for why this is PGC_POSTMASTER not PGC_SIGHUP */
+ {"autovacuum_multixact_freeze_max_age", PGC_POSTMASTER, AUTOVACUUM,
+ gettext_noop("Multixact age at which to autovacuum a table to prevent multixact wraparound."),
+ NULL
+ },
+ &autovacuum_multixact_freeze_max_age,
+ INT64CONST(20000000000), INT64CONST(10000), INT64CONST(0x7FFFFFFFFFFFFFFF),
+ NULL, NULL, NULL
+ },
+
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, 0.0, 0.0, 0.0, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/help_config.c b/src/backend/utils/misc/help_config.c
index 61c83f3590..8c7c50a3ef 100644
--- a/src/backend/utils/misc/help_config.c
+++ b/src/backend/utils/misc/help_config.c
@@ -33,6 +33,7 @@ typedef union
struct config_bool _bool;
struct config_real real;
struct config_int integer;
+ struct config_int64 integer8;
struct config_string string;
struct config_enum _enum;
} mixedStruct;
@@ -107,7 +108,12 @@ printMixedStruct(mixedStruct *structToPrint)
structToPrint->integer.min,
structToPrint->integer.max);
break;
-
+ case PGC_INT64:
+ printf("INT64\t" INT64_FORMAT "\t" INT64_FORMAT "\t" INT64_FORMAT "\t",
+ structToPrint->integer8.reset_val,
+ structToPrint->integer8.min,
+ structToPrint->integer8.max);
+ break;
case PGC_REAL:
printf("REAL\t%g\t%g\t%g\t",
structToPrint->real.reset_val,
diff --git a/src/backend/utils/misc/pg_controldata.c b/src/backend/utils/misc/pg_controldata.c
index 781f8b8758..54b719ef3d 100644
--- a/src/backend/utils/misc/pg_controldata.c
+++ b/src/backend/utils/misc/pg_controldata.c
@@ -164,8 +164,7 @@ pg_control_checkpoint(PG_FUNCTION_ARGS)
values[5] = BoolGetDatum(ControlFile->checkPointCopy.fullPageWrites);
nulls[5] = false;
- values[6] = CStringGetTextDatum(psprintf("%u:%u",
- EpochFromFullTransactionId(ControlFile->checkPointCopy.nextXid),
+ values[6] = CStringGetTextDatum(psprintf(XID_FMT,
XidFromFullTransactionId(ControlFile->checkPointCopy.nextXid)));
nulls[6] = false;
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 4a094bb38b..a21b2d5ee4 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -643,9 +643,9 @@
#autovacuum_vacuum_insert_scale_factor = 0.2 # fraction of inserts over table
# size before insert vacuum
#autovacuum_analyze_scale_factor = 0.1 # fraction of table size before analyze
-#autovacuum_freeze_max_age = 200000000 # maximum XID age before forced vacuum
+#autovacuum_freeze_max_age = 10000000000 # maximum XID age before forced vacuum
# (change requires restart)
-#autovacuum_multixact_freeze_max_age = 400000000 # maximum multixact age
+#autovacuum_multixact_freeze_max_age = 20000000000 # maximum multixact age
# before forced vacuum
# (change requires restart)
#autovacuum_vacuum_cost_delay = 2ms # default vacuum cost delay for
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index 086e948fca..dba589f50e 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -4081,11 +4081,13 @@ static void
writetup_cluster(Tuplesortstate *state, LogicalTape *tape, SortTuple *stup)
{
HeapTuple tuple = (HeapTuple) stup->tuple;
- unsigned int tuplen = tuple->t_len + sizeof(ItemPointerData) + sizeof(int);
+ unsigned int tuplen = tuple->t_len + sizeof(ItemPointerData) + 2 * sizeof(TransactionId) + sizeof(int);
/* We need to store t_self, but not other fields of HeapTupleData */
LogicalTapeWrite(tape, &tuplen, sizeof(tuplen));
LogicalTapeWrite(tape, &tuple->t_self, sizeof(ItemPointerData));
+ LogicalTapeWrite(tape, &tuple->t_xmin, sizeof(TransactionId));
+ LogicalTapeWrite(tape, &tuple->t_xmax, sizeof(TransactionId));
LogicalTapeWrite(tape, tuple->t_data, tuple->t_len);
if (state->randomAccess) /* need trailing length word? */
LogicalTapeWrite(tape, &tuplen, sizeof(tuplen));
@@ -4101,7 +4103,7 @@ static void
readtup_cluster(Tuplesortstate *state, SortTuple *stup,
LogicalTape *tape, unsigned int tuplen)
{
- unsigned int t_len = tuplen - sizeof(ItemPointerData) - sizeof(int);
+ unsigned int t_len = tuplen - sizeof(ItemPointerData) - 2 * sizeof(TransactionId) - sizeof(int);
HeapTuple tuple = (HeapTuple) readtup_alloc(state,
t_len + HEAPTUPLESIZE);
@@ -4109,6 +4111,8 @@ readtup_cluster(Tuplesortstate *state, SortTuple *stup,
tuple->t_data = (HeapTupleHeader) ((char *) tuple + HEAPTUPLESIZE);
tuple->t_len = t_len;
LogicalTapeReadExact(tape, &tuple->t_self, sizeof(ItemPointerData));
+ LogicalTapeReadExact(tape, &tuple->t_xmin, sizeof(TransactionId));
+ LogicalTapeReadExact(tape, &tuple->t_xmax, sizeof(TransactionId));
/* We don't currently bother to reconstruct t_tableOid */
tuple->t_tableOid = InvalidOid;
/* Read in the tuple body */
diff --git a/src/backend/utils/time/combocid.c b/src/backend/utils/time/combocid.c
index 6613dc0534..ca3ac89db7 100644
--- a/src/backend/utils/time/combocid.c
+++ b/src/backend/utils/time/combocid.c
@@ -101,12 +101,13 @@ static CommandId GetRealCmax(CommandId combocid);
*/
CommandId
-HeapTupleHeaderGetCmin(HeapTupleHeader tup)
+HeapTupleGetCmin(HeapTuple tuple)
{
+ HeapTupleHeader tup = tuple->t_data;
CommandId cid = HeapTupleHeaderGetRawCommandId(tup);
Assert(!(tup->t_infomask & HEAP_MOVED));
- Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tup)));
+ Assert(TransactionIdIsCurrentTransactionId(HeapTupleGetXmin(tuple)));
if (tup->t_infomask & HEAP_COMBOCID)
return GetRealCmin(cid);
@@ -115,8 +116,9 @@ HeapTupleHeaderGetCmin(HeapTupleHeader tup)
}
CommandId
-HeapTupleHeaderGetCmax(HeapTupleHeader tup)
+HeapTupleGetCmax(HeapTuple tuple)
{
+ HeapTupleHeader tup = tuple->t_data;
CommandId cid = HeapTupleHeaderGetRawCommandId(tup);
Assert(!(tup->t_infomask & HEAP_MOVED));
@@ -128,7 +130,7 @@ HeapTupleHeaderGetCmax(HeapTupleHeader tup)
* things too much.
*/
Assert(CritSectionCount > 0 ||
- TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tup)));
+ TransactionIdIsCurrentTransactionId(HeapTupleGetUpdateXidAny(tuple)));
if (tup->t_infomask & HEAP_COMBOCID)
return GetRealCmax(cid);
@@ -150,7 +152,7 @@ HeapTupleHeaderGetCmax(HeapTupleHeader tup)
* changes the tuple in shared buffers.
*/
void
-HeapTupleHeaderAdjustCmax(HeapTupleHeader tup,
+HeapTupleHeaderAdjustCmax(HeapTuple tup,
CommandId *cmax,
bool *iscombo)
{
@@ -160,10 +162,10 @@ HeapTupleHeaderAdjustCmax(HeapTupleHeader tup,
* Test for HeapTupleHeaderXminCommitted() first, because it's cheaper
* than a TransactionIdIsCurrentTransactionId call.
*/
- if (!HeapTupleHeaderXminCommitted(tup) &&
- TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tup)))
+ if (!HeapTupleHeaderXminCommitted(tup->t_data) &&
+ TransactionIdIsCurrentTransactionId(HeapTupleGetRawXmin(tup)))
{
- CommandId cmin = HeapTupleHeaderGetCmin(tup);
+ CommandId cmin = HeapTupleGetCmin(tup);
*cmax = GetComboCommandId(cmin, *cmax);
*iscombo = true;
diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c
index a0b81bf154..e1a96bb928 100644
--- a/src/backend/utils/time/snapmgr.c
+++ b/src/backend/utils/time/snapmgr.c
@@ -1172,8 +1172,9 @@ ExportSnapshot(Snapshot snapshot)
* Generate file path for the snapshot. We start numbering of snapshots
* inside the transaction from 1.
*/
- snprintf(path, sizeof(path), SNAPSHOT_EXPORT_DIR "/%08X-%08X-%d",
- MyProc->backendId, MyProc->lxid, list_length(exportedSnapshots) + 1);
+ snprintf(path, sizeof(path), SNAPSHOT_EXPORT_DIR "/%08X-%08X%08X-%d",
+ MyProc->backendId, (uint32) (MyProc->lxid >> 32),
+ (uint32) MyProc->lxid, list_length(exportedSnapshots) + 1);
/*
* Copy the snapshot into TopTransactionContext, add it to the
@@ -1200,14 +1201,14 @@ ExportSnapshot(Snapshot snapshot)
*/
initStringInfo(&buf);
- appendStringInfo(&buf, "vxid:%d/%u\n", MyProc->backendId, MyProc->lxid);
+ appendStringInfo(&buf, "vxid:%d/" XID_FMT "\n", MyProc->backendId, MyProc->lxid);
appendStringInfo(&buf, "pid:%d\n", MyProcPid);
appendStringInfo(&buf, "dbid:%u\n", MyDatabaseId);
appendStringInfo(&buf, "iso:%d\n", XactIsoLevel);
appendStringInfo(&buf, "ro:%d\n", XactReadOnly);
- appendStringInfo(&buf, "xmin:%u\n", snapshot->xmin);
- appendStringInfo(&buf, "xmax:%u\n", snapshot->xmax);
+ appendStringInfo(&buf, "xmin:" XID_FMT "\n", snapshot->xmin);
+ appendStringInfo(&buf, "xmax:" XID_FMT "\n", snapshot->xmax);
/*
* We must include our own top transaction ID in the top-xid data, since
@@ -1224,9 +1225,9 @@ ExportSnapshot(Snapshot snapshot)
TransactionIdPrecedes(topXid, snapshot->xmax)) ? 1 : 0;
appendStringInfo(&buf, "xcnt:%d\n", snapshot->xcnt + addTopXid);
for (i = 0; i < snapshot->xcnt; i++)
- appendStringInfo(&buf, "xip:%u\n", snapshot->xip[i]);
+ appendStringInfo(&buf, "xip:" XID_FMT "\n", snapshot->xip[i]);
if (addTopXid)
- appendStringInfo(&buf, "xip:%u\n", topXid);
+ appendStringInfo(&buf, "xip:" XID_FMT "\n", topXid);
/*
* Similarly, we add our subcommitted child XIDs to the subxid data. Here,
@@ -1240,9 +1241,9 @@ ExportSnapshot(Snapshot snapshot)
appendStringInfoString(&buf, "sof:0\n");
appendStringInfo(&buf, "sxcnt:%d\n", snapshot->subxcnt + nchildren);
for (i = 0; i < snapshot->subxcnt; i++)
- appendStringInfo(&buf, "sxp:%u\n", snapshot->subxip[i]);
+ appendStringInfo(&buf, "sxp:" XID_FMT "\n", snapshot->subxip[i]);
for (i = 0; i < nchildren; i++)
- appendStringInfo(&buf, "sxp:%u\n", children[i]);
+ appendStringInfo(&buf, "sxp:" XID_FMT "\n", children[i]);
}
appendStringInfo(&buf, "rec:%u\n", snapshot->takenDuringRecovery);
@@ -1345,7 +1346,7 @@ parseXidFromText(const char *prefix, char **s, const char *filename)
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid snapshot data in file \"%s\"", filename)));
ptr += prefixlen;
- if (sscanf(ptr, "%u", &val) != 1)
+ if (sscanf(ptr, XID_FMT, &val) != 1)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid snapshot data in file \"%s\"", filename)));
@@ -1370,7 +1371,7 @@ parseVxidFromText(const char *prefix, char **s, const char *filename,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid snapshot data in file \"%s\"", filename)));
ptr += prefixlen;
- if (sscanf(ptr, "%d/%u", &vxid->backendId, &vxid->localTransactionId) != 2)
+ if (sscanf(ptr, "%d/" XID_FMT, &vxid->backendId, &vxid->localTransactionId) != 2)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid snapshot data in file \"%s\"", filename)));
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 97f15971e2..07b0da08d4 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -149,6 +149,9 @@ static bool data_checksums = false;
static char *xlog_dir = NULL;
static char *str_wal_segment_size_mb = NULL;
static int wal_segment_size_mb;
+static TransactionId start_xid = 0;
+static MultiXactId start_mx_id = 0;
+static MultiXactOffset start_mx_offset = 0;
/* internal vars */
@@ -1409,10 +1412,13 @@ bootstrap_template1(void)
unsetenv("PGCLIENTENCODING");
snprintf(cmd, sizeof(cmd),
- "\"%s\" --boot -X %d %s %s %s %s",
+ "\"%s\" --boot -X %d %s %s " XID_FMT " %s " XID_FMT " %s " XID_FMT " %s %s %s",
backend_exec,
wal_segment_size_mb * (1024 * 1024),
data_checksums ? "-k" : "",
+ "-Z", start_xid,
+ "-m", start_mx_id,
+ "-o", start_mx_offset,
boot_options, extra_options,
debug ? "-d 5" : "");
@@ -2216,13 +2222,21 @@ usage(const char *progname)
printf(_(" -W, --pwprompt prompt for a password for the new superuser\n"));
printf(_(" -X, --waldir=WALDIR location for the write-ahead log directory\n"));
printf(_(" --wal-segsize=SIZE size of WAL segments, in megabytes\n"));
+ printf(_(" -x, --xid=START_XID specify start xid value in decimal format for new db instance to test 64-bit xids,\n"
+ " default value is 0, max value is 2^62-1\n"));
printf(_("\nLess commonly used options:\n"));
printf(_(" -d, --debug generate lots of debugging output\n"));
printf(_(" --discard-caches set debug_discard_caches=1\n"));
printf(_(" -L DIRECTORY where to find the input files\n"));
+ printf(_(" -m, --multixact-id=START_MX_ID\n"
+ " specify start multixact id value in decimal format for new db instance\n"
+ " to test 64-bit xids, default value is 0, max value is 2^62-1\n"));
printf(_(" -n, --no-clean do not clean up after errors\n"));
printf(_(" -N, --no-sync do not wait for changes to be written safely to disk\n"));
printf(_(" --no-instructions do not print instructions for next steps\n"));
+ printf(_(" -o, --multixact-offset=START_MX_OFFSET\n"
+ " specify start multixact offset value in decimal format for new db instance\n"
+ " to test 64-bit xids, default value is 0, max value is 2^62-1\n"));
printf(_(" -s, --show show internal settings\n"));
printf(_(" -S, --sync-only only sync database files to disk, then exit\n"));
printf(_("\nOther options:\n"));
@@ -2899,6 +2913,10 @@ main(int argc, char *argv[])
{"data-checksums", no_argument, NULL, 'k'},
{"allow-group-access", no_argument, NULL, 'g'},
{"discard-caches", no_argument, NULL, 14},
+ {"no-data-checksums", no_argument, NULL, 15},
+ {"xid", required_argument, NULL, 'x'},
+ {"multixact-id", required_argument, NULL, 'm'},
+ {"multixact-offset", required_argument, NULL, 'o'},
{NULL, 0, NULL, 0}
};
@@ -2940,7 +2958,7 @@ main(int argc, char *argv[])
/* process command-line options */
- while ((c = getopt_long(argc, argv, "A:dD:E:gkL:nNsST:U:WX:", long_options, &option_index)) != -1)
+ while ((c = getopt_long(argc, argv, "A:dD:E:gkL:m:nNo:sST:U:Wx:X:", long_options, &option_index)) != -1)
{
switch (c)
{
@@ -2979,6 +2997,20 @@ main(int argc, char *argv[])
debug = true;
printf(_("Running in debug mode.\n"));
break;
+ case 'm':
+ if (sscanf(optarg, XID_FMT, &start_mx_id) != 1)
+ {
+ fprintf(stderr, "%s: invalid decimal START_MX_ID value\n",
+ progname);
+ exit(1);
+ }
+ if (!StartMultiXactIdIsValid(start_mx_id))
+ {
+ fprintf(stderr, "%s: out-of-range START_MX_ID value (the value must be less than 2^62)\n",
+ progname);
+ exit(1);
+ }
+ break;
case 'n':
noclean = true;
printf(_("Running in no-clean mode. Mistakes will not be cleaned up.\n"));
@@ -2986,6 +3018,20 @@ main(int argc, char *argv[])
case 'N':
do_sync = false;
break;
+ case 'o':
+ if (sscanf(optarg, XID_FMT, &start_mx_offset) != 1)
+ {
+ fprintf(stderr, "%s: invalid decimal START_MX_OFFSET value\n",
+ progname);
+ exit(1);
+ }
+ if (!StartMultiXactOffsetIsValid(start_mx_offset))
+ {
+ fprintf(stderr, "%s: out-of-range START_MX_OFFSET value (the value must be less than 2^62)\n",
+ progname);
+ exit(1);
+ }
+ break;
case 'S':
sync_only = true;
break;
@@ -3045,6 +3091,23 @@ main(int argc, char *argv[])
extra_options,
"-c debug_discard_caches=1");
break;
+ case 15:
+ data_checksums = false;
+ break;
+ case 'x':
+ if (sscanf(optarg, XID_FMT, &start_xid) != 1)
+ {
+ fprintf(stderr, "%s: invalid decimal START_XID value\n",
+ progname);
+ exit(1);
+ }
+ if (!StartTransactionIdIsValid(start_xid))
+ {
+ fprintf(stderr, "%s: out-of-range START_XID value (the value must be less than 2^62)\n",
+ progname);
+ exit(1);
+ }
+ break;
default:
/* getopt_long already emitted a complaint */
fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
diff --git a/src/bin/pg_amcheck/t/004_verify_heapam.pl b/src/bin/pg_amcheck/t/004_verify_heapam.pl
index 94d691a614..e12c3444bd 100644
--- a/src/bin/pg_amcheck/t/004_verify_heapam.pl
+++ b/src/bin/pg_amcheck/t/004_verify_heapam.pl
@@ -10,6 +10,8 @@ use PostgreSQL::Test::Utils;
use Fcntl qw(:seek);
use Test::More;
+use Data::Dumper;
+
# This regression test demonstrates that the pg_amcheck binary correctly
# identifies specific kinds of corruption within pages. To test this, we need
# a mechanism to create corrupt pages with predictable, repeatable corruption.
@@ -86,6 +88,60 @@ use Test::More;
use constant HEAPTUPLE_PACK_CODE => 'LLLSSSSSCCLLCCCCCCCCCCllLL';
use constant HEAPTUPLE_PACK_LENGTH => 58; # Total size
+use constant HEAPPAGE_SPECIAL_PACK_CODE => 'QQ';
+use constant HEAPPAGE_SPECIAL_PACK_LENGTH => 16;
+use constant HEAPPAGE_SIZE => 8192;
+
+# Some #define constants from access/htup_details.h for use while corrupting.
+use constant HEAP_HASNULL => 0x0001;
+use constant HEAP_XMAX_LOCK_ONLY => 0x0080;
+use constant HEAP_XMIN_COMMITTED => 0x0100;
+use constant HEAP_XMIN_INVALID => 0x0200;
+use constant HEAP_XMAX_COMMITTED => 0x0400;
+use constant HEAP_XMAX_INVALID => 0x0800;
+use constant HEAP_NATTS_MASK => 0x07FF;
+use constant HEAP_XMAX_IS_MULTI => 0x1000;
+use constant HEAP_KEYS_UPDATED => 0x2000;
+
+use constant FIRST_NORMAL_TRANSACTION_ID => 3;
+
+# Read page special data
+sub read_special_data
+{
+ my ($fh, $offset) = @_;
+ my ($buffer, %special);
+ $offset -= $offset % HEAPPAGE_SIZE;
+ $offset += HEAPPAGE_SIZE - HEAPPAGE_SPECIAL_PACK_LENGTH;
+ seek($fh, $offset, SEEK_SET)
+ or BAIL_OUT("seek failed: $!");
+ defined(sysread($fh, $buffer, HEAPPAGE_SPECIAL_PACK_LENGTH))
+ or BAIL_OUT("sysread failed: $!");
+
+ @_ = unpack(HEAPPAGE_SPECIAL_PACK_CODE, $buffer);
+ %special = (
+ pd_xid_base => shift,
+ pd_multi_base => shift);
+ return \%special;
+}
+
+# Write page special data
+sub write_special_data
+{
+ my ($fh, $offset, $special) = @_;
+
+ $offset -= $offset % HEAPPAGE_SIZE;
+ $offset += HEAPPAGE_SIZE - HEAPPAGE_SPECIAL_PACK_LENGTH;
+
+ my $buffer = pack(
+ HEAPPAGE_SPECIAL_PACK_CODE,
+ $special->{pd_xid_base}, $special->{pd_multi_base});
+
+ seek($fh, $offset, SEEK_SET)
+ or BAIL_OUT("seek failed: $!");
+ defined(syswrite($fh, $buffer, HEAPPAGE_SPECIAL_PACK_LENGTH))
+ or BAIL_OUT("syswrite failed: $!");
+ return;
+}
# Read a tuple of our table from a heap page.
#
@@ -97,7 +153,7 @@ use constant HEAPTUPLE_PACK_LENGTH => 58; # Total size
#
sub read_tuple
{
- my ($fh, $offset) = @_;
+ my ($fh, $offset, $raw) = @_;
my ($buffer, %tup);
seek($fh, $offset, SEEK_SET)
or BAIL_OUT("seek failed: $!");
@@ -134,6 +190,18 @@ sub read_tuple
c_va_toastrelid => shift);
# Stitch together the text for column 'b'
$tup{b} = join('', map { chr($tup{"b_body$_"}) } (1 .. 7));
+
+ if (!$raw)
+ {
+ my $special = read_special_data($fh, $offset);
+
+ $tup{t_xmin} += $special->{pd_xid_base};
+ my $is_multi = $tup{t_infomask} & HEAP_XMAX_IS_MULTI;
+ $tup{t_xmax} += !$is_multi ?
+ $special->{pd_xid_base} :
+ $special->{pd_multi_base};
+ }
+
return \%tup;
}
@@ -149,7 +217,32 @@ sub read_tuple
#
sub write_tuple
{
- my ($fh, $offset, $tup) = @_;
+ my ($fh, $offset, $tup, $raw) = @_;
+ if (!$raw)
+ {
+ my $special = read_special_data($fh, $offset);
+
+ my $xmin = $tup->{t_xmin} - $special->{pd_xid_base};
+ die "tuple x_min $tup->{t_xmin} is too smal for pd_xid_base $special->{pd_xid_base}"
+ if $xmin < 3;
+ $tup->{t_xmin} = $xmin;
+
+ if (($tup->{t_infomask} & HEAP_XMAX_IS_MULTI) == 0)
+ {
+ my $xmax = $tup->{t_xmax} - $special->{pd_xid_base};
+ die "tuple x_max $tup->{t_xmax} is too smal for pd_xid_base $special->{pd_xid_base}"
+ if $xmax < 3;
+ $tup->{t_xmax} = $xmax;
+ }
+ else
+ {
+ my $xmax = $tup->{t_xmax} - $special->{pd_multi_base};
+ die "tuple multi x_max $tup->{t_xmax} is too smal for pd_multi_base $special->{pd_multi_base}"
+ if $xmax < 3;
+ $tup->{t_xmax} = $xmax;
+ }
+ }
+
my $buffer = pack(
HEAPTUPLE_PACK_CODE,
$tup->{t_xmin}, $tup->{t_xmax},
@@ -172,6 +265,41 @@ sub write_tuple
return;
}
+# move pd_xid_base and pd_multi_base to more suitable position for tests.
+sub fixup_page
+{
+ my ($fh, $page, $xid_base, $multi_base, $lp_off) = @_;
+ my $offset = $page * HEAPPAGE_SIZE;
+ my $special = read_special_data($fh, $offset);
+
+ die "xid_base $xid_base should be lesser than existed $special->{pd_xid_base}"
+ if ($xid_base > $special->{pd_xid_base});
+ die "multi_base $multi_base should be lesser than existed $special->{pd_multi_base}"
+ if ($multi_base > $special->{pd_multi_base} && $special->{pd_multi_base} != 0);
+ return if ($xid_base == $special->{pd_xid_base} &&
+ $multi_base == $special->{pd_multi_base});
+
+ my $xid_delta = $special->{pd_xid_base} - $xid_base;
+ my $multi_delta = $special->{pd_multi_base} - $multi_base;
+
+ for my $off (@$lp_off)
+ {
+ # change only tuples on this page.
+ next if ($off < $offset && $off > $offset + HEAPPAGE_SIZE);
+
+ my $tup = read_tuple($fh, $off, 1);
+ $tup->{t_xmin} += $xid_delta;
+ my $is_multi = $tup->{t_infomask} & HEAP_XMAX_IS_MULTI;
+ $tup->{t_xmax} += !$is_multi ? $xid_delta : $multi_delta;
+ write_tuple($fh, $off, $tup, 1);
+ }
+
+ $special->{pd_xid_base} = $xid_base;
+ $special->{pd_multi_base} = $multi_base;
+
+ write_special_data($fh, $offset, $special);
+}
+
# Set umask so test directories and files are created with default permissions
umask(0077);
@@ -234,6 +362,10 @@ my $relfrozenxid = $node->safe_psql('postgres',
q(select relfrozenxid from pg_class where relname = 'test'));
my $datfrozenxid = $node->safe_psql('postgres',
q(select datfrozenxid from pg_database where datname = 'postgres'));
+my $datminmxid = $node->safe_psql('postgres',
+ q(select datminmxid from pg_database where datname = 'postgres'));
+my $txid_current = $node->safe_psql('postgres',
+ q(select txid_current()));
# Sanity check that our 'test' table has a relfrozenxid newer than the
# datfrozenxid for the database, and that the datfrozenxid is greater than the
@@ -292,6 +424,11 @@ for (my $tupidx = 0; $tupidx < ROWCOUNT; $tupidx++)
# Determine endianness of current platform from the 1-byte varlena header
$ENDIANNESS = $tup->{b_header} == 0x11 ? "little" : "big";
}
+
+# Set 64bit xid bases a bit in the past therefore we can set xmin/xmax a bit
+# in the past
+fixup_page($file, 0, $datfrozenxid - 100, $datminmxid - 100, \@lp_off);
+
close($file)
or BAIL_OUT("close failed: $!");
$node->start;
@@ -309,17 +446,6 @@ $node->command_ok([ 'pg_amcheck', '-p', $port, 'postgres' ],
$node->stop;
-# Some #define constants from access/htup_details.h for use while corrupting.
-use constant HEAP_HASNULL => 0x0001;
-use constant HEAP_XMAX_LOCK_ONLY => 0x0080;
-use constant HEAP_XMIN_COMMITTED => 0x0100;
-use constant HEAP_XMIN_INVALID => 0x0200;
-use constant HEAP_XMAX_COMMITTED => 0x0400;
-use constant HEAP_XMAX_INVALID => 0x0800;
-use constant HEAP_NATTS_MASK => 0x07FF;
-use constant HEAP_XMAX_IS_MULTI => 0x1000;
-use constant HEAP_KEYS_UPDATED => 0x2000;
-
# Helper function to generate a regular expression matching the header we
# expect verify_heapam() to return given which fields we expect to be non-null.
sub header
@@ -363,39 +489,39 @@ for (my $tupidx = 0; $tupidx < ROWCOUNT; $tupidx++)
# Expected corruption report
push @expected,
- qr/${header}xmin $xmin precedes relation freeze threshold 0:\d+/;
+ qr/${header}xmin $xmin precedes relation freeze threshold \d+/;
}
if ($offnum == 2)
{
# Corruptly set xmin < datfrozenxid
- my $xmin = 3;
+ my $xmin = $datfrozenxid - 12;
$tup->{t_xmin} = $xmin;
$tup->{t_infomask} &= ~HEAP_XMIN_COMMITTED;
$tup->{t_infomask} &= ~HEAP_XMIN_INVALID;
push @expected,
- qr/${$header}xmin $xmin precedes oldest valid transaction ID 0:\d+/;
+ qr/${$header}xmin $xmin precedes oldest valid transaction ID \d+/;
}
elsif ($offnum == 3)
{
- # Corruptly set xmin < datfrozenxid, further back, noting circularity
- # of xid comparison. For a new cluster with epoch = 0, the corrupt
- # xmin will be interpreted as in the future
- $tup->{t_xmin} = 4026531839;
+ # Corruptly set xmin > next transaction id.
+ my $xmin = $relfrozenxid + 1000000;
+ $tup->{t_xmin} = $xmin;
$tup->{t_infomask} &= ~HEAP_XMIN_COMMITTED;
$tup->{t_infomask} &= ~HEAP_XMIN_INVALID;
push @expected,
- qr/${$header}xmin 4026531839 equals or exceeds next valid transaction ID 0:\d+/;
+ qr/${$header}xmin $xmin equals or exceeds next valid transaction ID \d+/;
}
elsif ($offnum == 4)
{
- # Corruptly set xmax < relminmxid;
- $tup->{t_xmax} = 4026531839;
+ # Corruptly set xmax > next transaction id.
+ my $xmax = $relfrozenxid + 1000000;
+ $tup->{t_xmax} = $xmax;
$tup->{t_infomask} &= ~HEAP_XMAX_INVALID;
push @expected,
- qr/${$header}xmax 4026531839 equals or exceeds next valid transaction ID 0:\d+/;
+ qr/${$header}xmax $xmax equals or exceeds next valid transaction ID \d+/;
}
elsif ($offnum == 5)
{
@@ -498,20 +624,22 @@ for (my $tupidx = 0; $tupidx < ROWCOUNT; $tupidx++)
# Set both HEAP_XMAX_COMMITTED and HEAP_XMAX_IS_MULTI
$tup->{t_infomask} |= HEAP_XMAX_COMMITTED;
$tup->{t_infomask} |= HEAP_XMAX_IS_MULTI;
- $tup->{t_xmax} = 4;
+ my $xmax = $datminmxid + 1000000;
+ $tup->{t_xmax} = $xmax;
push @expected,
- qr/${header}multitransaction ID 4 equals or exceeds next valid multitransaction ID 1/;
+ qr/${header}multitransaction ID $xmax equals or exceeds next valid multitransaction ID \d+/;
}
elsif ($offnum == 15) # Last offnum must equal ROWCOUNT
{
# Set both HEAP_XMAX_COMMITTED and HEAP_XMAX_IS_MULTI
$tup->{t_infomask} |= HEAP_XMAX_COMMITTED;
$tup->{t_infomask} |= HEAP_XMAX_IS_MULTI;
- $tup->{t_xmax} = 4000000000;
+ my $xmax = $datminmxid - 10;
+ $tup->{t_xmax} = $xmax;
push @expected,
- qr/${header}multitransaction ID 4000000000 precedes relation minimum multitransaction ID threshold 1/;
+ qr/${header}multitransaction ID $xmax precedes relation minimum multitransaction ID threshold \d+/;
}
write_tuple($file, $offset, $tup);
}
diff --git a/src/bin/pg_controldata/pg_controldata.c b/src/bin/pg_controldata/pg_controldata.c
index f911f98d94..997df822e8 100644
--- a/src/bin/pg_controldata/pg_controldata.c
+++ b/src/bin/pg_controldata/pg_controldata.c
@@ -247,28 +247,27 @@ main(int argc, char *argv[])
ControlFile->checkPointCopy.PrevTimeLineID);
printf(_("Latest checkpoint's full_page_writes: %s\n"),
ControlFile->checkPointCopy.fullPageWrites ? _("on") : _("off"));
- printf(_("Latest checkpoint's NextXID: %u:%u\n"),
- EpochFromFullTransactionId(ControlFile->checkPointCopy.nextXid),
+ printf(_("Latest checkpoint's NextXID: " XID_FMT "\n"),
XidFromFullTransactionId(ControlFile->checkPointCopy.nextXid));
printf(_("Latest checkpoint's NextOID: %u\n"),
ControlFile->checkPointCopy.nextOid);
- printf(_("Latest checkpoint's NextMultiXactId: %u\n"),
+ printf(_("Latest checkpoint's NextMultiXactId: " XID_FMT "\n"),
ControlFile->checkPointCopy.nextMulti);
- printf(_("Latest checkpoint's NextMultiOffset: %u\n"),
+ printf(_("Latest checkpoint's NextMultiOffset: " XID_FMT "\n"),
ControlFile->checkPointCopy.nextMultiOffset);
- printf(_("Latest checkpoint's oldestXID: %u\n"),
+ printf(_("Latest checkpoint's oldestXID: " XID_FMT "\n"),
ControlFile->checkPointCopy.oldestXid);
printf(_("Latest checkpoint's oldestXID's DB: %u\n"),
ControlFile->checkPointCopy.oldestXidDB);
- printf(_("Latest checkpoint's oldestActiveXID: %u\n"),
+ printf(_("Latest checkpoint's oldestActiveXID: " XID_FMT "\n"),
ControlFile->checkPointCopy.oldestActiveXid);
- printf(_("Latest checkpoint's oldestMultiXid: %u\n"),
+ printf(_("Latest checkpoint's oldestMultiXid: " XID_FMT "\n"),
ControlFile->checkPointCopy.oldestMulti);
printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
ControlFile->checkPointCopy.oldestMultiDB);
- printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
+ printf(_("Latest checkpoint's oldestCommitTsXid:" XID_FMT "\n"),
ControlFile->checkPointCopy.oldestCommitTsXid);
- printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
+ printf(_("Latest checkpoint's newestCommitTsXid:" XID_FMT "\n"),
ControlFile->checkPointCopy.newestCommitTsXid);
printf(_("Time of latest checkpoint: %s\n"),
ckpttime_str);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e69dcf8a48..9fbdb65d41 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -314,6 +314,7 @@ static void appendReloptionsArrayAH(PQExpBuffer buffer, const char *reloptions,
static char *get_synchronized_snapshot(Archive *fout);
static void setupDumpWorker(Archive *AHX);
static TableInfo *getRootTableInfo(const TableInfo *tbinfo);
+static uint64 pg_strtouint64(const char *str, char **endptr, int base);
int
@@ -2774,7 +2775,7 @@ dumpDatabase(Archive *fout)
*datistemplate,
*datconnlimit,
*tablespace;
- uint32 frozenxid,
+ uint64 frozenxid,
minmxid;
char *qdatname;
@@ -2828,8 +2829,8 @@ dumpDatabase(Archive *fout)
encoding = PQgetvalue(res, 0, i_encoding);
collate = PQgetvalue(res, 0, i_collate);
ctype = PQgetvalue(res, 0, i_ctype);
- frozenxid = atooid(PQgetvalue(res, 0, i_frozenxid));
- minmxid = atooid(PQgetvalue(res, 0, i_minmxid));
+ frozenxid = pg_strtouint64(PQgetvalue(res, 0, i_frozenxid), NULL, 0);
+ minmxid = pg_strtouint64(PQgetvalue(res, 0, i_minmxid), NULL, 0);
dbdacl.acl = PQgetvalue(res, 0, i_datacl);
dbdacl.acldefault = PQgetvalue(res, 0, i_acldefault);
datistemplate = PQgetvalue(res, 0, i_datistemplate);
@@ -3037,7 +3038,7 @@ dumpDatabase(Archive *fout)
{
appendPQExpBufferStr(creaQry, "\n-- For binary upgrade, set datfrozenxid and datminmxid.\n");
appendPQExpBuffer(creaQry, "UPDATE pg_catalog.pg_database\n"
- "SET datfrozenxid = '%u', datminmxid = '%u'\n"
+ "SET datfrozenxid = '" XID_FMT "', datminmxid = '" XID_FMT "'\n"
"WHERE datname = ",
frozenxid, minmxid);
appendStringLiteralAH(creaQry, datname, fout);
@@ -3087,10 +3088,10 @@ dumpDatabase(Archive *fout)
appendPQExpBufferStr(loOutQry, "\n-- For binary upgrade, set pg_largeobject relfrozenxid and relminmxid\n");
appendPQExpBuffer(loOutQry, "UPDATE pg_catalog.pg_class\n"
- "SET relfrozenxid = '%u', relminmxid = '%u'\n"
+ "SET relfrozenxid = '%s', relminmxid = '%s'\n"
"WHERE oid = %u;\n",
- atooid(PQgetvalue(lo_res, 0, i_relfrozenxid)),
- atooid(PQgetvalue(lo_res, 0, i_relminmxid)),
+ (PQgetvalue(lo_res, 0, i_relfrozenxid)),
+ (PQgetvalue(lo_res, 0, i_relminmxid)),
LargeObjectRelationId);
ArchiveEntry(fout, nilCatalogId, createDumpId(),
ARCHIVE_OPTS(.tag = "pg_largeobject",
@@ -6294,11 +6295,11 @@ getTables(Archive *fout, int *numTables)
tblinfo[i].relreplident = *(PQgetvalue(res, i, i_relreplident));
tblinfo[i].rowsec = (strcmp(PQgetvalue(res, i, i_relrowsec), "t") == 0);
tblinfo[i].forcerowsec = (strcmp(PQgetvalue(res, i, i_relforcerowsec), "t") == 0);
- tblinfo[i].frozenxid = atooid(PQgetvalue(res, i, i_relfrozenxid));
- tblinfo[i].toast_frozenxid = atooid(PQgetvalue(res, i, i_toastfrozenxid));
+ tblinfo[i].frozenxid = pg_strtouint64(PQgetvalue(res, i, i_relfrozenxid), NULL, 0);
+ tblinfo[i].toast_frozenxid = pg_strtouint64(PQgetvalue(res, i, i_toastfrozenxid), NULL, 0);
tblinfo[i].toast_oid = atooid(PQgetvalue(res, i, i_toastoid));
- tblinfo[i].minmxid = atooid(PQgetvalue(res, i, i_relminmxid));
- tblinfo[i].toast_minmxid = atooid(PQgetvalue(res, i, i_toastminmxid));
+ tblinfo[i].minmxid = pg_strtouint64(PQgetvalue(res, i, i_relminmxid), NULL, 0);
+ tblinfo[i].toast_minmxid = pg_strtouint64(PQgetvalue(res, i, i_toastminmxid), NULL, 0);
tblinfo[i].reloptions = pg_strdup(PQgetvalue(res, i, i_reloptions));
if (PQgetisnull(res, i, i_checkoption))
tblinfo[i].checkoption = NULL;
@@ -15431,7 +15432,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
{
appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
- "SET relfrozenxid = '%u', relminmxid = '%u'\n"
+ "SET relfrozenxid = '" XID_FMT "', relminmxid = '" XID_FMT "'\n"
"WHERE oid = ",
tbinfo->frozenxid, tbinfo->minmxid);
appendStringLiteralAH(q, qualrelname, fout);
@@ -15445,7 +15446,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
*/
appendPQExpBufferStr(q, "\n-- For binary upgrade, set toast's relfrozenxid and relminmxid\n");
appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
- "SET relfrozenxid = '%u', relminmxid = '%u'\n"
+ "SET relfrozenxid = '" XID_FMT "', relminmxid = '" XID_FMT "'\n"
"WHERE oid = '%u';\n",
tbinfo->toast_frozenxid,
tbinfo->toast_minmxid, tbinfo->toast_oid);
@@ -18009,3 +18010,27 @@ appendReloptionsArrayAH(PQExpBuffer buffer, const char *reloptions,
if (!res)
pg_log_warning("could not parse %s array", "reloptions");
}
+
+/*
+ * Copied from src/backend/utils/adt/numutils.c
+ *
+ * pg_strtouint64
+ * Converts 'str' into an unsigned 64-bit integer.
+ *
+ * This has the identical API to strtoul(3), except that it will handle
+ * 64-bit ints even where "long" is narrower than that.
+ *
+ * For the moment it seems sufficient to assume that the platform has
+ * such a function somewhere; let's not roll our own.
+ */
+static uint64
+pg_strtouint64(const char *str, char **endptr, int base)
+{
+#ifdef _MSC_VER /* MSVC only */
+ return _strtoui64(str, endptr, base);
+#elif defined(HAVE_STRTOULL) && SIZEOF_LONG < 8
+ return strtoull(str, endptr, base);
+#else
+ return strtoul(str, endptr, base);
+#endif
+}
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 997a3b6071..356af29da8 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -298,11 +298,11 @@ typedef struct _tableInfo
bool rowsec; /* is row security enabled? */
bool forcerowsec; /* is row security forced? */
bool hasoids; /* does it have OIDs? */
- uint32 frozenxid; /* table's relfrozenxid */
- uint32 minmxid; /* table's relminmxid */
+ uint64 frozenxid; /* table's relfrozenxid */
+ uint64 minmxid; /* table's relminmxid */
Oid toast_oid; /* toast table's OID, or 0 if none */
- uint32 toast_frozenxid; /* toast table's relfrozenxid, if any */
- uint32 toast_minmxid; /* toast table's relminmxid */
+ uint64 toast_frozenxid; /* toast table's relfrozenxid, if any */
+ uint64 toast_minmxid; /* toast table's relminmxid */
int ncheck; /* # of CHECK expressions */
Oid reltype; /* OID of table's composite type, if any */
Oid reloftype; /* underlying type for typed table */
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 1eb4509fca..0bfe1a1461 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -63,7 +63,6 @@ static ControlFileData ControlFile; /* pg_control values */
static XLogSegNo newXlogSegNo; /* new XLOG segment # */
static bool guessed = false; /* T if we had to guess at any values */
static const char *progname;
-static uint32 set_xid_epoch = (uint32) -1;
static TransactionId set_oldest_xid = 0;
static TransactionId set_xid = 0;
static TransactionId set_oldest_commit_ts_xid = 0;
@@ -87,6 +86,7 @@ static void KillExistingXLOG(void);
static void KillExistingArchiveStatus(void);
static void WriteEmptyXLOG(void);
static void usage(void);
+static uint64 str2uint64(const char *str, char **endptr, int base);
int
@@ -95,7 +95,6 @@ main(int argc, char *argv[])
static struct option long_options[] = {
{"commit-timestamp-ids", required_argument, NULL, 'c'},
{"pgdata", required_argument, NULL, 'D'},
- {"epoch", required_argument, NULL, 'e'},
{"force", no_argument, NULL, 'f'},
{"next-wal-file", required_argument, NULL, 'l'},
{"multixact-ids", required_argument, NULL, 'm'},
@@ -137,7 +136,7 @@ main(int argc, char *argv[])
}
- while ((c = getopt_long(argc, argv, "c:D:e:fl:m:no:O:u:x:", long_options, NULL)) != -1)
+ while ((c = getopt_long(argc, argv, "c:D:fl:m:no:O:u:x:", long_options, NULL)) != -1)
{
switch (c)
{
@@ -153,27 +152,9 @@ main(int argc, char *argv[])
noupdate = true;
break;
- case 'e':
- errno = 0;
- set_xid_epoch = strtoul(optarg, &endptr, 0);
- if (endptr == optarg || *endptr != '\0' || errno != 0)
- {
- /*------
- translator: the second %s is a command line argument (-e, etc) */
- pg_log_error("invalid argument for option %s", "-e");
- fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
- exit(1);
- }
- if (set_xid_epoch == -1)
- {
- pg_log_error("transaction ID epoch (-e) must not be -1");
- exit(1);
- }
- break;
-
case 'u':
errno = 0;
- set_oldest_xid = strtoul(optarg, &endptr, 0);
+ set_oldest_xid = str2uint64(optarg, &endptr, 0);
if (endptr == optarg || *endptr != '\0' || errno != 0)
{
pg_log_error("invalid argument for option %s", "-u");
@@ -182,14 +163,14 @@ main(int argc, char *argv[])
}
if (!TransactionIdIsNormal(set_oldest_xid))
{
- pg_log_error("oldest transaction ID (-u) must be greater than or equal to %u", FirstNormalTransactionId);
+ pg_log_error("oldest transaction ID (-u) must be greater than or equal to " XID_FMT, FirstNormalTransactionId);
exit(1);
}
break;
case 'x':
errno = 0;
- set_xid = strtoul(optarg, &endptr, 0);
+ set_xid = str2uint64(optarg, &endptr, 0);
if (endptr == optarg || *endptr != '\0' || errno != 0)
{
pg_log_error("invalid argument for option %s", "-x");
@@ -198,21 +179,21 @@ main(int argc, char *argv[])
}
if (!TransactionIdIsNormal(set_xid))
{
- pg_log_error("transaction ID (-x) must be greater than or equal to %u", FirstNormalTransactionId);
+ pg_log_error("transaction ID (-x) must be greater than or equal to " XID_FMT, FirstNormalTransactionId);
exit(1);
}
break;
case 'c':
errno = 0;
- set_oldest_commit_ts_xid = strtoul(optarg, &endptr, 0);
+ set_oldest_commit_ts_xid = str2uint64(optarg, &endptr, 0);
if (endptr == optarg || *endptr != ',' || errno != 0)
{
pg_log_error("invalid argument for option %s", "-c");
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
exit(1);
}
- set_newest_commit_ts_xid = strtoul(endptr + 1, &endptr2, 0);
+ set_newest_commit_ts_xid = str2uint64(endptr + 1, &endptr2, 0);
if (endptr2 == endptr + 1 || *endptr2 != '\0' || errno != 0)
{
pg_log_error("invalid argument for option %s", "-c");
@@ -253,7 +234,7 @@ main(int argc, char *argv[])
case 'm':
errno = 0;
- set_mxid = strtoul(optarg, &endptr, 0);
+ set_mxid = str2uint64(optarg, &endptr, 0);
if (endptr == optarg || *endptr != ',' || errno != 0)
{
pg_log_error("invalid argument for option %s", "-m");
@@ -261,7 +242,7 @@ main(int argc, char *argv[])
exit(1);
}
- set_oldestmxid = strtoul(endptr + 1, &endptr2, 0);
+ set_oldestmxid = str2uint64(endptr + 1, &endptr2, 0);
if (endptr2 == endptr + 1 || *endptr2 != '\0' || errno != 0)
{
pg_log_error("invalid argument for option %s", "-m");
@@ -287,7 +268,7 @@ main(int argc, char *argv[])
case 'O':
errno = 0;
- set_mxoff = strtoul(optarg, &endptr, 0);
+ set_mxoff = str2uint64(optarg, &endptr, 0);
if (endptr == optarg || *endptr != '\0' || errno != 0)
{
pg_log_error("invalid argument for option %s", "-O");
@@ -448,11 +429,6 @@ main(int argc, char *argv[])
* Adjust fields if required by switches. (Do this now so that printout,
* if any, includes these values.)
*/
- if (set_xid_epoch != -1)
- ControlFile.checkPointCopy.nextXid =
- FullTransactionIdFromEpochAndXid(set_xid_epoch,
- XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
-
if (set_oldest_xid != 0)
{
ControlFile.checkPointCopy.oldestXid = set_oldest_xid;
@@ -460,9 +436,7 @@ main(int argc, char *argv[])
}
if (set_xid != 0)
- ControlFile.checkPointCopy.nextXid =
- FullTransactionIdFromEpochAndXid(EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
- set_xid);
+ ControlFile.checkPointCopy.nextXid = FullTransactionIdFromXid(set_xid);
if (set_oldest_commit_ts_xid != 0)
ControlFile.checkPointCopy.oldestCommitTsXid = set_oldest_commit_ts_xid;
@@ -702,7 +676,7 @@ GuessControlValues(void)
ControlFile.checkPointCopy.PrevTimeLineID = 1;
ControlFile.checkPointCopy.fullPageWrites = false;
ControlFile.checkPointCopy.nextXid =
- FullTransactionIdFromEpochAndXid(0, FirstNormalTransactionId);
+ FullTransactionIdFromXid(FirstNormalTransactionId);
ControlFile.checkPointCopy.nextOid = FirstGenbkiObjectId;
ControlFile.checkPointCopy.nextMulti = FirstMultiXactId;
ControlFile.checkPointCopy.nextMultiOffset = 0;
@@ -753,10 +727,17 @@ GuessControlValues(void)
*
* NB: this display should be just those fields that will not be
* reset by RewriteControlFile().
+ *
+ * Special macros help to make translatable strings.
*/
static void
PrintControlValues(bool guessed)
{
+ char xid_str[32];
+
+#define FORMAT_XID(val) \
+ (snprintf(xid_str, sizeof(xid_str), XID_FMT, (val)), xid_str)
+
if (guessed)
printf(_("Guessed pg_control values:\n\n"));
else
@@ -772,29 +753,28 @@ PrintControlValues(bool guessed)
ControlFile.checkPointCopy.ThisTimeLineID);
printf(_("Latest checkpoint's full_page_writes: %s\n"),
ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
- printf(_("Latest checkpoint's NextXID: %u:%u\n"),
- EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid),
- XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
+ printf(_("Latest checkpoint's NextXID: %s\n"),
+ FORMAT_XID(XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid)));
printf(_("Latest checkpoint's NextOID: %u\n"),
ControlFile.checkPointCopy.nextOid);
- printf(_("Latest checkpoint's NextMultiXactId: %u\n"),
- ControlFile.checkPointCopy.nextMulti);
- printf(_("Latest checkpoint's NextMultiOffset: %u\n"),
+ printf(_("Latest checkpoint's NextMultiXactId: %s\n"),
+ FORMAT_XID(ControlFile.checkPointCopy.nextMulti));
+ printf(_("Latest checkpoint's NextMultiOffset: " INT64_FORMAT "\n"),
ControlFile.checkPointCopy.nextMultiOffset);
- printf(_("Latest checkpoint's oldestXID: %u\n"),
- ControlFile.checkPointCopy.oldestXid);
+ printf(_("Latest checkpoint's oldestXID: %s\n"),
+ FORMAT_XID(ControlFile.checkPointCopy.oldestXid));
printf(_("Latest checkpoint's oldestXID's DB: %u\n"),
ControlFile.checkPointCopy.oldestXidDB);
- printf(_("Latest checkpoint's oldestActiveXID: %u\n"),
- ControlFile.checkPointCopy.oldestActiveXid);
- printf(_("Latest checkpoint's oldestMultiXid: %u\n"),
- ControlFile.checkPointCopy.oldestMulti);
+ printf(_("Latest checkpoint's oldestActiveXID: %s\n"),
+ FORMAT_XID(ControlFile.checkPointCopy.oldestActiveXid));
+ printf(_("Latest checkpoint's oldestMultiXid: %s\n"),
+ FORMAT_XID(ControlFile.checkPointCopy.oldestMulti));
printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
ControlFile.checkPointCopy.oldestMultiDB);
- printf(_("Latest checkpoint's oldestCommitTsXid:%u\n"),
- ControlFile.checkPointCopy.oldestCommitTsXid);
- printf(_("Latest checkpoint's newestCommitTsXid:%u\n"),
- ControlFile.checkPointCopy.newestCommitTsXid);
+ printf(_("Latest checkpoint's oldestCommitTsXid:%s\n"),
+ FORMAT_XID(ControlFile.checkPointCopy.oldestCommitTsXid));
+ printf(_("Latest checkpoint's newestCommitTsXid:%s\n"),
+ FORMAT_XID(ControlFile.checkPointCopy.newestCommitTsXid));
printf(_("Maximum data alignment: %u\n"),
ControlFile.maxAlign);
/* we don't print floatFormat since can't say much useful about it */
@@ -830,6 +810,7 @@ PrintControlValues(bool guessed)
static void
PrintNewControlValues(void)
{
+ char xid_str[32];
char fname[MAXFNAMELEN];
/* This will be always printed in order to keep format same. */
@@ -841,18 +822,18 @@ PrintNewControlValues(void)
if (set_mxid != 0)
{
- printf(_("NextMultiXactId: %u\n"),
- ControlFile.checkPointCopy.nextMulti);
- printf(_("OldestMultiXid: %u\n"),
- ControlFile.checkPointCopy.oldestMulti);
+ printf(_("NextMultiXactId: %s\n"),
+ FORMAT_XID(ControlFile.checkPointCopy.nextMulti));
+ printf(_("OldestMultiXid: %s\n"),
+ FORMAT_XID(ControlFile.checkPointCopy.oldestMulti));
printf(_("OldestMulti's DB: %u\n"),
ControlFile.checkPointCopy.oldestMultiDB);
}
if (set_mxoff != -1)
{
- printf(_("NextMultiOffset: %u\n"),
- ControlFile.checkPointCopy.nextMultiOffset);
+ printf(_("NextMultiOffset: %s\n"),
+ FORMAT_XID(ControlFile.checkPointCopy.nextMultiOffset));
}
if (set_oid != 0)
@@ -863,29 +844,23 @@ PrintNewControlValues(void)
if (set_xid != 0)
{
- printf(_("NextXID: %u\n"),
- XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
- printf(_("OldestXID: %u\n"),
- ControlFile.checkPointCopy.oldestXid);
+ printf(_("NextXID: %s\n"),
+ FORMAT_XID(XidFromFullTransactionId(ControlFile.checkPointCopy.nextXid)));
+ printf(_("OldestXID: %s\n"),
+ FORMAT_XID(ControlFile.checkPointCopy.oldestXid));
printf(_("OldestXID's DB: %u\n"),
ControlFile.checkPointCopy.oldestXidDB);
}
- if (set_xid_epoch != -1)
- {
- printf(_("NextXID epoch: %u\n"),
- EpochFromFullTransactionId(ControlFile.checkPointCopy.nextXid));
- }
-
if (set_oldest_commit_ts_xid != 0)
{
- printf(_("oldestCommitTsXid: %u\n"),
- ControlFile.checkPointCopy.oldestCommitTsXid);
+ printf(_("oldestCommitTsXid: %s\n"),
+ FORMAT_XID(ControlFile.checkPointCopy.oldestCommitTsXid));
}
if (set_newest_commit_ts_xid != 0)
{
- printf(_("newestCommitTsXid: %u\n"),
- ControlFile.checkPointCopy.newestCommitTsXid);
+ printf(_("newestCommitTsXid: %s\n"),
+ FORMAT_XID(ControlFile.checkPointCopy.newestCommitTsXid));
}
if (set_wal_segsize != 0)
@@ -1228,7 +1203,6 @@ usage(void)
" set oldest and newest transactions bearing\n"
" commit timestamp (zero means no change)\n"));
printf(_(" [-D, --pgdata=]DATADIR data directory\n"));
- printf(_(" -e, --epoch=XIDEPOCH set next transaction ID epoch\n"));
printf(_(" -f, --force force update to be done\n"));
printf(_(" -l, --next-wal-file=WALFILE set minimum starting location for new WAL\n"));
printf(_(" -m, --multixact-ids=MXID,MXID set next and oldest multitransaction ID\n"));
@@ -1243,3 +1217,20 @@ usage(void)
printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
}
+
+/*
+ * str2uint64()
+ *
+ * convert string to 64-bit unsigned int
+ */
+static uint64
+str2uint64(const char *str, char **endptr, int base)
+{
+#ifdef _MSC_VER /* MSVC only */
+ return _strtoui64(str, endptr, base);
+#elif defined(HAVE_STRTOULL) && SIZEOF_LONG < 8
+ return strtoull(str, endptr, base);
+#else
+ return strtoul(str, endptr, base);
+#endif
+}
diff --git a/src/bin/pg_upgrade/Makefile b/src/bin/pg_upgrade/Makefile
index 49b94f0ac7..fdbfa314dc 100644
--- a/src/bin/pg_upgrade/Makefile
+++ b/src/bin/pg_upgrade/Makefile
@@ -20,6 +20,7 @@ OBJS = \
parallel.o \
pg_upgrade.o \
relfilenode.o \
+ segresize.o \
server.o \
tablespace.o \
util.o \
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 019bcb6c7b..5015dbca23 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -31,6 +31,7 @@ static void check_for_pg_role_prefix(ClusterInfo *cluster);
static void check_for_new_tablespace_dir(ClusterInfo *new_cluster);
static void check_for_user_defined_encoding_conversions(ClusterInfo *cluster);
static char *get_canonical_locale_name(int category, const char *locale);
+static void check_for_32bit_xid_usage(ClusterInfo *cluster);
/*
@@ -159,6 +160,17 @@ check_and_dump_old_cluster(bool live_check)
if (GET_MAJOR_VERSION(old_cluster.major_version) <= 903)
old_9_3_check_for_line_data_type_usage(&old_cluster);
+ /* Prepare for 64bit xid */
+ if (!ALREADY_64bit_XID(old_cluster))
+ {
+ /* Check if 32-bit xid type is used in tables */
+ check_for_32bit_xid_usage(&old_cluster);
+ /* Check indexes to be upgraded */
+ invalidate_spgist_indexes(&old_cluster, true);
+ invalidate_gin_indexes(&old_cluster, true);
+ invalidate_external_indexes(&old_cluster, true);
+ }
+
/*
* While not a check option, we do this now because this is the only time
* the old server is running.
@@ -233,6 +245,17 @@ issue_warnings_and_set_wal_level(void)
if (GET_MAJOR_VERSION(old_cluster.major_version) <= 906)
old_9_6_invalidate_hash_indexes(&new_cluster, false);
+ /* Raindex for 64bit xid */
+ if (!ALREADY_64bit_XID(old_cluster))
+ {
+ /* Check if 32-bit xid type is used in tables */
+ check_for_32bit_xid_usage(&old_cluster);
+ /* Check indexes to be upgraded */
+ invalidate_spgist_indexes(&old_cluster, true);
+ invalidate_gin_indexes(&old_cluster, true);
+ invalidate_external_indexes(&old_cluster, true);
+ }
+
report_extension_updates(&new_cluster);
stop_postmaster(false);
@@ -1327,3 +1350,94 @@ get_canonical_locale_name(int category, const char *locale)
return res;
}
+
+/*
+ * check_for_32bit_xid_usage()
+ *
+ * Postgres Pro Enterprise changes xid storage format to 64-bit. Check if
+ * xid type is used in tables.
+ */
+static void
+check_for_32bit_xid_usage(ClusterInfo *cluster)
+{
+ int dbnum;
+ FILE *script = NULL;
+ bool found = false;
+ char output_path[MAXPGPATH];
+
+ prep_status("Checking for incompatible \"xid\" data type");
+
+ snprintf(output_path, sizeof(output_path), "tables_using_xid.txt");
+
+ for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
+ {
+ PGresult *res;
+ bool db_used = false;
+ int ntups;
+ int rowno;
+ int i_nspname,
+ i_relname,
+ i_attname;
+ DbInfo *active_db = &cluster->dbarr.dbs[dbnum];
+ PGconn *conn = connectToServer(cluster, active_db->db_name);
+
+ /*
+ * While several relkinds don't store any data, e.g. views, they can
+ * be used to define data types of other columns, so we check all
+ * relkinds.
+ */
+ res = executeQueryOrDie(conn,
+ "SELECT n.nspname, c.relname, a.attname "
+ "FROM pg_catalog.pg_class c, "
+ " pg_catalog.pg_namespace n, "
+ " pg_catalog.pg_attribute a "
+ "WHERE c.oid = a.attrelid AND "
+ " a.attnum >= 1 AND "
+ " a.atttypid = 'pg_catalog.xid'::pg_catalog.regtype AND "
+ " c.relnamespace = n.oid AND "
+ /* exclude possible orphaned temp tables */
+ " n.nspname !~ '^pg_temp_' AND "
+ " n.nspname NOT IN ('pg_catalog', 'information_schema')");
+
+ ntups = PQntuples(res);
+ i_nspname = PQfnumber(res, "nspname");
+ i_relname = PQfnumber(res, "relname");
+ i_attname = PQfnumber(res, "attname");
+ for (rowno = 0; rowno < ntups; rowno++)
+ {
+ found = true;
+ if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
+ pg_fatal("could not open file \"%s\": %s\n",
+ output_path, strerror(errno));
+ if (!db_used)
+ {
+ fprintf(script, "Database: %s\n", active_db->db_name);
+ db_used = true;
+ }
+ fprintf(script, " %s.%s.%s\n",
+ PQgetvalue(res, rowno, i_nspname),
+ PQgetvalue(res, rowno, i_relname),
+ PQgetvalue(res, rowno, i_attname));
+ }
+
+ PQclear(res);
+
+ PQfinish(conn);
+ }
+
+ if (script)
+ fclose(script);
+
+ if (found)
+ {
+ pg_log(PG_REPORT, "fatal\n");
+ pg_fatal("Your installation contains the \"xid\" data type in user tables.\n"
+ "The internal format of \"xid\" changed in Postgres Pro Enterprise so this cluster\n"
+ "cannot currently be upgraded. Note that even dropped attributes cause a problem.\n"
+ "You can remove the problem tables and restart the upgrade.\n"
+ "A list of the problem columns is in the file:\n"
+ " %s\n\n", output_path);
+ }
+ else
+ check_ok();
+}
\ No newline at end of file
diff --git a/src/bin/pg_upgrade/controldata.c b/src/bin/pg_upgrade/controldata.c
index 41b8f69b8c..09e6efddd3 100644
--- a/src/bin/pg_upgrade/controldata.c
+++ b/src/bin/pg_upgrade/controldata.c
@@ -269,9 +269,11 @@ get_control_data(ClusterInfo *cluster, bool live_check)
pg_fatal("%d: controldata retrieval problem\n", __LINE__);
p++; /* remove ':' char */
- cluster->controldata.chkpnt_nxtepoch = str2uint(p);
+ cluster->controldata.chkpnt_nxtxid = str2uint64(p);
/*
+ * Try to read 32-bit XID format 'epoch:xid'.
+ *
* Delimiter changed from '/' to ':' in 9.6. We don't test for
* the catalog version of the change because the catalog version
* is pulled from pg_controldata too, and it isn't worth adding an
@@ -284,11 +286,19 @@ get_control_data(ClusterInfo *cluster, bool live_check)
else
p = NULL;
- if (p == NULL || strlen(p) <= 1)
- pg_fatal("%d: controldata retrieval problem\n", __LINE__);
+ if (p)
+ {
+ /* Read lowest 32 bits of xid, epoch was read previously */
+ if (strlen(p) <= 1)
+ pg_fatal("%d: controldata retrieval problem\n", __LINE__);
+
+ p++; /* remove '/' or ':' char */
+
+ Assert((cluster->controldata.chkpnt_nxtxid >> 32) == 0);
+ cluster->controldata.chkpnt_nxtxid <<= 32;
+ cluster->controldata.chkpnt_nxtxid |= str2uint(p);
+ }
- p++; /* remove '/' or ':' char */
- cluster->controldata.chkpnt_nxtxid = str2uint(p);
got_xid = true;
}
else if ((p = strstr(bufin, "Latest checkpoint's NextOID:")) != NULL)
@@ -310,7 +320,7 @@ get_control_data(ClusterInfo *cluster, bool live_check)
pg_fatal("%d: controldata retrieval problem\n", __LINE__);
p++; /* remove ':' char */
- cluster->controldata.chkpnt_nxtmulti = str2uint(p);
+ cluster->controldata.chkpnt_nxtmulti = str2uint64(p);
got_multi = true;
}
else if ((p = strstr(bufin, "Latest checkpoint's oldestXID:")) != NULL)
@@ -321,7 +331,7 @@ get_control_data(ClusterInfo *cluster, bool live_check)
pg_fatal("%d: controldata retrieval problem\n", __LINE__);
p++; /* remove ':' char */
- cluster->controldata.chkpnt_oldstxid = str2uint(p);
+ cluster->controldata.chkpnt_oldstxid = str2uint64(p);
got_oldestxid = true;
}
else if ((p = strstr(bufin, "Latest checkpoint's oldestMultiXid:")) != NULL)
@@ -332,7 +342,7 @@ get_control_data(ClusterInfo *cluster, bool live_check)
pg_fatal("%d: controldata retrieval problem\n", __LINE__);
p++; /* remove ':' char */
- cluster->controldata.chkpnt_oldstMulti = str2uint(p);
+ cluster->controldata.chkpnt_oldstMulti = str2uint64(p);
got_oldestmulti = true;
}
else if ((p = strstr(bufin, "Latest checkpoint's NextMultiOffset:")) != NULL)
@@ -343,7 +353,7 @@ get_control_data(ClusterInfo *cluster, bool live_check)
pg_fatal("%d: controldata retrieval problem\n", __LINE__);
p++; /* remove ':' char */
- cluster->controldata.chkpnt_nxtmxoff = str2uint(p);
+ cluster->controldata.chkpnt_nxtmxoff = str2uint64(p);
got_mxoff = true;
}
else if ((p = strstr(bufin, "First log segment after reset:")) != NULL)
diff --git a/src/bin/pg_upgrade/file.c b/src/bin/pg_upgrade/file.c
index b84868c751..4ae6a40ae9 100644
--- a/src/bin/pg_upgrade/file.c
+++ b/src/bin/pg_upgrade/file.c
@@ -25,6 +25,7 @@
#include "storage/bufpage.h"
#include "storage/checksum.h"
#include "storage/checksum_impl.h"
+#include "storage/fsm_internals.h"
/*
@@ -152,6 +153,112 @@ linkFile(const char *src, const char *dst,
schemaName, relName, src, dst, strerror(errno));
}
+/* Context for file rewriting */
+typedef struct FileRewriteContext
+{
+ const char *fromfile;
+ const char *tofile;
+ const char *schemaName;
+ const char *relName;
+ int src_fd;
+ int dst_fd;
+ ssize_t src_filesize;
+ ssize_t totalBytesRead;
+ BlockNumber last_blkno;
+ bool old_lastblk;
+} FileRewriteContext;
+
+/* Initialize context for file rewriting */
+static void
+rewriteFileInit(FileRewriteContext *cxt,
+ const char *fromfile, const char *tofile,
+ const char *schemaName, const char *relName)
+{
+ struct stat statbuf;
+
+ cxt->fromfile = fromfile;
+ cxt->tofile = tofile;
+ cxt->schemaName = schemaName;
+ cxt->relName = relName;
+ cxt->totalBytesRead = 0;
+ cxt->last_blkno = InvalidBlockNumber;
+ cxt->old_lastblk = false;
+
+ /* Open old and new files */
+ if ((cxt->src_fd = open(fromfile, O_RDONLY | PG_BINARY, 0)) < 0)
+ pg_fatal("error while copying relation \"%s.%s\": could not open file \"%s\": %s\n",
+ schemaName, relName, fromfile, strerror(errno));
+
+ if (fstat(cxt->src_fd, &statbuf) != 0)
+ pg_fatal("error while copying relation \"%s.%s\": could not stat file \"%s\": %s\n",
+ schemaName, relName, fromfile, strerror(errno));
+
+ if ((cxt->dst_fd = open(tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
+ pg_file_create_mode)) < 0)
+ pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
+ schemaName, relName, tofile, strerror(errno));
+
+ /* Save old file size */
+ cxt->src_filesize = statbuf.st_size;
+}
+
+/* Clean up file rewriting context */
+static void
+rewriteFileCleanup(FileRewriteContext *cxt)
+{
+ close(cxt->dst_fd);
+ close(cxt->src_fd);
+}
+
+/* Read old page of the rewritten file */
+static ssize_t
+rewriteFileReadPage(FileRewriteContext *cxt, Page page)
+{
+ ssize_t bytesRead;
+
+ if (cxt->totalBytesRead >= cxt->src_filesize)
+ return 0;
+
+ if ((bytesRead = read(cxt->src_fd, page, BLCKSZ)) != BLCKSZ)
+ {
+ if (bytesRead < 0)
+ pg_fatal("error while copying relation \"%s.%s\": could not read file \"%s\": %s\n",
+ cxt->schemaName, cxt->relName, cxt->fromfile, strerror(errno));
+ else
+ pg_fatal("error while copying relation \"%s.%s\": partial page found in file \"%s\"\n",
+ cxt->schemaName, cxt->relName, cxt->fromfile);
+ }
+
+ cxt->totalBytesRead += BLCKSZ;
+ cxt->old_lastblk = (cxt->totalBytesRead == cxt->src_filesize);
+
+ return bytesRead;
+}
+
+/* Write new page of the rewritten file */
+static void
+rewriteFileWritePage(FileRewriteContext *cxt, Page page, BlockNumber blkno)
+{
+ /* Set new checksum for page, if enabled */
+ if (new_cluster.controldata.data_checksum_version != 0)
+ ((PageHeader) page)->pd_checksum = pg_checksum_page(page, blkno);
+
+ /* Write page */
+ errno = 0;
+
+ if ((blkno != (cxt->last_blkno == InvalidBlockNumber ? 0 : cxt->last_blkno + 1) &&
+ lseek(cxt->dst_fd, (off_t) BLCKSZ * blkno, SEEK_SET) != (off_t) BLCKSZ * blkno) ||
+ write(cxt->dst_fd, page, BLCKSZ) != BLCKSZ)
+ {
+ /* if write didn't set errno, assume problem is no disk space */
+ if (errno == 0)
+ errno = ENOSPC;
+ pg_fatal("error while copying relation \"%s.%s\": could not write file \"%s\": %s\n",
+ cxt->schemaName, cxt->relName, cxt->tofile, strerror(errno));
+ }
+
+ cxt->last_blkno = blkno;
+}
/*
* rewriteVisibilityMap()
@@ -171,36 +278,19 @@ linkFile(const char *src, const char *dst,
*/
void
rewriteVisibilityMap(const char *fromfile, const char *tofile,
- const char *schemaName, const char *relName)
+ const char *schemaName, const char *relName)
{
- int src_fd;
- int dst_fd;
+ FileRewriteContext cxt;
PGAlignedBlock buffer;
PGAlignedBlock new_vmbuf;
- ssize_t totalBytesRead = 0;
- ssize_t src_filesize;
int rewriteVmBytesPerPage;
BlockNumber new_blkno = 0;
- struct stat statbuf;
+ ssize_t bytesRead;
/* Compute number of old-format bytes per new page */
rewriteVmBytesPerPage = (BLCKSZ - SizeOfPageHeaderData) / 2;
- if ((src_fd = open(fromfile, O_RDONLY | PG_BINARY, 0)) < 0)
- pg_fatal("error while copying relation \"%s.%s\": could not open file \"%s\": %s\n",
- schemaName, relName, fromfile, strerror(errno));
-
- if (fstat(src_fd, &statbuf) != 0)
- pg_fatal("error while copying relation \"%s.%s\": could not stat file \"%s\": %s\n",
- schemaName, relName, fromfile, strerror(errno));
-
- if ((dst_fd = open(tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
- pg_file_create_mode)) < 0)
- pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s\n",
- schemaName, relName, tofile, strerror(errno));
-
- /* Save old file size */
- src_filesize = statbuf.st_size;
+ rewriteFileInit(&cxt, fromfile, tofile, schemaName, relName);
/*
* Turn each visibility map page into 2 pages one by one. Each new page
@@ -208,27 +298,12 @@ rewriteVisibilityMap(const char *fromfile, const char *tofile,
* last page is empty, we skip it, mostly to avoid turning one-page
* visibility maps for small relations into two pages needlessly.
*/
- while (totalBytesRead < src_filesize)
+ while ((bytesRead = rewriteFileReadPage(&cxt, buffer.data)) > 0)
{
- ssize_t bytesRead;
char *old_cur;
char *old_break;
char *old_blkend;
PageHeaderData pageheader;
- bool old_lastblk;
-
- if ((bytesRead = read(src_fd, buffer.data, BLCKSZ)) != BLCKSZ)
- {
- if (bytesRead < 0)
- pg_fatal("error while copying relation \"%s.%s\": could not read file \"%s\": %s\n",
- schemaName, relName, fromfile, strerror(errno));
- else
- pg_fatal("error while copying relation \"%s.%s\": partial page found in file \"%s\"\n",
- schemaName, relName, fromfile);
- }
-
- totalBytesRead += BLCKSZ;
- old_lastblk = (totalBytesRead == src_filesize);
/* Save the page header data */
memcpy(&pageheader, buffer.data, SizeOfPageHeaderData);
@@ -253,7 +328,7 @@ rewriteVisibilityMap(const char *fromfile, const char *tofile,
memcpy(new_vmbuf.data, &pageheader, SizeOfPageHeaderData);
/* Rewriting the last part of the last old page? */
- old_lastpart = old_lastblk && (old_break == old_blkend);
+ old_lastpart = cxt.old_lastblk && (old_break == old_blkend);
new_cur = new_vmbuf.data + SizeOfPageHeaderData;
@@ -287,20 +362,7 @@ rewriteVisibilityMap(const char *fromfile, const char *tofile,
if (old_lastpart && empty)
break;
- /* Set new checksum for visibility map page, if enabled */
- if (new_cluster.controldata.data_checksum_version != 0)
- ((PageHeader) new_vmbuf.data)->pd_checksum =
- pg_checksum_page(new_vmbuf.data, new_blkno);
-
- errno = 0;
- if (write(dst_fd, new_vmbuf.data, BLCKSZ) != BLCKSZ)
- {
- /* if write didn't set errno, assume problem is no disk space */
- if (errno == 0)
- errno = ENOSPC;
- pg_fatal("error while copying relation \"%s.%s\": could not write file \"%s\": %s\n",
- schemaName, relName, tofile, strerror(errno));
- }
+ rewriteFileWritePage(&cxt, new_vmbuf.data, new_blkno);
/* Advance for next new page */
old_break += rewriteVmBytesPerPage;
@@ -308,9 +370,7 @@ rewriteVisibilityMap(const char *fromfile, const char *tofile,
}
}
- /* Clean up */
- close(dst_fd);
- close(src_fd);
+ rewriteFileCleanup(&cxt);
}
void
@@ -371,4 +431,4 @@ check_hard_link(void)
strerror(errno));
unlink(new_link_file);
-}
+}
\ No newline at end of file
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index ecb3e1f647..a226d593ae 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -44,6 +44,9 @@
#include <langinfo.h>
#endif
+#include "access/multixact.h"
+#include "access/transam.h"
+#include "access/xlog_internal.h"
#include "catalog/pg_class_d.h"
#include "common/file_perm.h"
#include "common/logging.h"
@@ -323,7 +326,6 @@ setup(char *argv0, bool *live_check)
}
}
-
static void
prepare_new_cluster(void)
{
@@ -469,11 +471,10 @@ create_new_objects(void)
check_ok();
/*
- * We don't have minmxids for databases or relations in pre-9.3 clusters,
- * so set those after we have restored the schema.
+ * Refix datfrozenxid and datminmxid
*/
- if (GET_MAJOR_VERSION(old_cluster.major_version) <= 902)
- set_frozenxids(true);
+ if (ALREADY_64bit_XID(old_cluster) != ALREADY_64bit_XID(new_cluster))
+ set_frozenxids(false);
/* update new_cluster info now that we have objects in the databases */
get_db_and_rel_infos(&new_cluster);
@@ -527,18 +528,36 @@ copy_subdir_files(const char *old_subdir, const char *new_subdir)
static void
copy_xact_xlog_xid(void)
{
- /*
- * Copy old commit logs to new data dir. pg_clog has been renamed to
- * pg_xact in post-10 clusters.
- */
- copy_subdir_files(GET_MAJOR_VERSION(old_cluster.major_version) <= 906 ?
- "pg_clog" : "pg_xact",
- GET_MAJOR_VERSION(new_cluster.major_version) <= 906 ?
- "pg_clog" : "pg_xact");
+ TransactionId next_xid;
+
+#define GetClogDirName(cluster) \
+ GET_MAJOR_VERSION(cluster.major_version) <= 906 ? "pg_clog" : "pg_xact"
+
+ /* Set next xid to 2^32 if we're upgrading from 32 bit postgres */
+ next_xid = ALREADY_64bit_XID(old_cluster) == ALREADY_64bit_XID(new_cluster) ?
+ old_cluster.controldata.chkpnt_nxtxid :
+ FirstUpgradedTransactionId;
+
+ if (ALREADY_64bit_XID(old_cluster) == ALREADY_64bit_XID(new_cluster))
+ {
+ /*
+ * Copy old commit logs to new data dir. pg_clog has been renamed to
+ * pg_xact in post-10 clusters.
+ */
+ copy_subdir_files(GetClogDirName(old_cluster), GetClogDirName(new_cluster));
+ }
+ else
+ {
+ /* Convert commit logs and copy to the new data dir */
+ prep_status("Transforming commit log segments");
+ convert_clog(psprintf("%s/%s", old_cluster.pgdata, GetClogDirName(old_cluster)),
+ psprintf("%s/%s", new_cluster.pgdata, GetClogDirName(new_cluster)));
+ check_ok();
+ }
prep_status("Setting oldest XID for new cluster");
exec_prog(UTILITY_LOG_FILE, NULL, true, true,
- "\"%s/pg_resetwal\" -f -u %u \"%s\"",
+ "\"%s/pg_resetwal\" -f -u " XID_FMT " \"%s\"",
new_cluster.bindir, old_cluster.controldata.chkpnt_oldstxid,
new_cluster.pgdata);
check_ok();
@@ -546,19 +565,21 @@ copy_xact_xlog_xid(void)
/* set the next transaction id and epoch of the new cluster */
prep_status("Setting next transaction ID and epoch for new cluster");
exec_prog(UTILITY_LOG_FILE, NULL, true, true,
- "\"%s/pg_resetwal\" -f -x %u \"%s\"",
- new_cluster.bindir, old_cluster.controldata.chkpnt_nxtxid,
+ "\"%s/pg_resetwal\" -f -x " XID_FMT " \"%s\"",
+ new_cluster.bindir, next_xid,
new_cluster.pgdata);
+#ifdef NOT_USED
exec_prog(UTILITY_LOG_FILE, NULL, true, true,
"\"%s/pg_resetwal\" -f -e %u \"%s\"",
new_cluster.bindir, old_cluster.controldata.chkpnt_nxtepoch,
new_cluster.pgdata);
+#endif
/* must reset commit timestamp limits also */
exec_prog(UTILITY_LOG_FILE, NULL, true, true,
- "\"%s/pg_resetwal\" -f -c %u,%u \"%s\"",
+ "\"%s/pg_resetwal\" -f -c " XID_FMT "," XID_FMT " \"%s\"",
new_cluster.bindir,
- old_cluster.controldata.chkpnt_nxtxid,
- old_cluster.controldata.chkpnt_nxtxid,
+ next_xid,
+ next_xid,
new_cluster.pgdata);
check_ok();
@@ -571,8 +592,45 @@ copy_xact_xlog_xid(void)
if (old_cluster.controldata.cat_ver >= MULTIXACT_FORMATCHANGE_CAT_VER &&
new_cluster.controldata.cat_ver >= MULTIXACT_FORMATCHANGE_CAT_VER)
{
- copy_subdir_files("pg_multixact/offsets", "pg_multixact/offsets");
- copy_subdir_files("pg_multixact/members", "pg_multixact/members");
+ uint64 oldest_mxid = old_cluster.controldata.chkpnt_oldstMulti;
+ uint64 next_mxid = old_cluster.controldata.chkpnt_nxtmulti;
+ uint64 next_mxoff = old_cluster.controldata.chkpnt_nxtmxoff;
+
+ if (ALREADY_64bit_XID(old_cluster))
+ {
+ copy_subdir_files("pg_multixact/offsets", "pg_multixact/offsets");
+ copy_subdir_files("pg_multixact/members", "pg_multixact/members");
+ }
+ else
+ {
+ MultiXactOffset oldest_mxoff;
+
+ remove_new_subdir("pg_multixact/offsets", false);
+ oldest_mxoff = convert_multixact_offsets("pg_multixact/offsets", "pg_multixact/offsets");
+
+ remove_new_subdir("pg_multixact/members", false);
+ convert_multixact_members("pg_multixact/members", "pg_multixact/members", oldest_mxoff);
+
+ /*
+ * Handle wraparound if we're upgrading from 32 bit postgres.
+ * Invalid 0 mxids/offsets are skipped, so 1 becomes 2^32.
+ */
+ if (oldest_mxoff)
+ {
+ if (next_mxid < oldest_mxid)
+ next_mxid += ((uint64) 1 << 32) - FirstMultiXactId;
+
+ if (next_mxoff < oldest_mxoff)
+ next_mxoff += ((uint64) 1 << 32) - 1;
+
+ /* Offsets and members were rewritten, oldest_mxoff = 1 */
+ next_mxoff -= oldest_mxoff - 1;
+ oldest_mxoff = 1;
+
+ /* Save converted next_mxid for possible usage in set_frozenxids() */
+ old_cluster.controldata.chkpnt_nxtmulti = next_mxid;
+ }
+ }
prep_status("Setting next multixact ID and offset for new cluster");
@@ -581,11 +639,9 @@ copy_xact_xlog_xid(void)
* counters here and the oldest multi present on system.
*/
exec_prog(UTILITY_LOG_FILE, NULL, true, true,
- "\"%s/pg_resetwal\" -O %u -m %u,%u \"%s\"",
+ "\"%s/pg_resetwal\" -O " XID_FMT " -m " XID_FMT "," XID_FMT " \"%s\"",
new_cluster.bindir,
- old_cluster.controldata.chkpnt_nxtmxoff,
- old_cluster.controldata.chkpnt_nxtmulti,
- old_cluster.controldata.chkpnt_oldstMulti,
+ next_mxoff, next_mxid, oldest_mxid,
new_cluster.pgdata);
check_ok();
}
@@ -609,7 +665,7 @@ copy_xact_xlog_xid(void)
* next=MaxMultiXactId, but multixact.c can cope with that just fine.
*/
exec_prog(UTILITY_LOG_FILE, NULL, true, true,
- "\"%s/pg_resetwal\" -m %u,%u \"%s\"",
+ "\"%s/pg_resetwal\" -m " XID_FMT "," XID_FMT " \"%s\"",
new_cluster.bindir,
old_cluster.controldata.chkpnt_nxtmulti + 1,
old_cluster.controldata.chkpnt_nxtmulti,
@@ -659,6 +715,8 @@ set_frozenxids(bool minmxid_only)
int ntups;
int i_datname;
int i_datallowconn;
+ TransactionId frozen_xid;
+ MultiXactId minmxid;
if (!minmxid_only)
prep_status("Setting frozenxid and minmxid counters in new cluster");
@@ -667,18 +725,24 @@ set_frozenxids(bool minmxid_only)
conn_template1 = connectToServer(&new_cluster, "template1");
+ frozen_xid = ALREADY_64bit_XID(old_cluster) == ALREADY_64bit_XID(new_cluster) ?
+ old_cluster.controldata.chkpnt_nxtxid :
+ FirstNormalTransactionId;
+
+ minmxid = old_cluster.controldata.chkpnt_nxtmulti;
+
if (!minmxid_only)
/* set pg_database.datfrozenxid */
PQclear(executeQueryOrDie(conn_template1,
"UPDATE pg_catalog.pg_database "
- "SET datfrozenxid = '%u'",
- old_cluster.controldata.chkpnt_nxtxid));
+ "SET datfrozenxid = '" XID_FMT "'",
+ frozen_xid));
/* set pg_database.datminmxid */
PQclear(executeQueryOrDie(conn_template1,
"UPDATE pg_catalog.pg_database "
- "SET datminmxid = '%u'",
- old_cluster.controldata.chkpnt_nxtmulti));
+ "SET datminmxid = '" XID_FMT "'",
+ minmxid));
/* get database names */
dbres = executeQueryOrDie(conn_template1,
@@ -712,24 +776,24 @@ set_frozenxids(bool minmxid_only)
/* set pg_class.relfrozenxid */
PQclear(executeQueryOrDie(conn,
"UPDATE pg_catalog.pg_class "
- "SET relfrozenxid = '%u' "
+ "SET relfrozenxid = '" XID_FMT "' "
/* only heap, materialized view, and TOAST are vacuumed */
"WHERE relkind IN ("
CppAsString2(RELKIND_RELATION) ", "
CppAsString2(RELKIND_MATVIEW) ", "
CppAsString2(RELKIND_TOASTVALUE) ")",
- old_cluster.controldata.chkpnt_nxtxid));
+ frozen_xid));
/* set pg_class.relminmxid */
PQclear(executeQueryOrDie(conn,
"UPDATE pg_catalog.pg_class "
- "SET relminmxid = '%u' "
+ "SET relminmxid = '" XID_FMT "' "
/* only heap, materialized view, and TOAST are vacuumed */
"WHERE relkind IN ("
CppAsString2(RELKIND_RELATION) ", "
CppAsString2(RELKIND_MATVIEW) ", "
CppAsString2(RELKIND_TOASTVALUE) ")",
- old_cluster.controldata.chkpnt_nxtmulti));
+ minmxid));
PQfinish(conn);
/* Reset datallowconn flag */
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index ca86c11292..1b8368a7e7 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -21,6 +21,7 @@
#define MESSAGE_WIDTH 60
#define GET_MAJOR_VERSION(v) ((v) / 100)
+#define ALREADY_64bit_XID(cluster) (GET_MAJOR_VERSION((cluster).major_version) >= 1500)
/* contains both global db information and CREATE DATABASE commands */
#define GLOBALS_DUMP_FILE "pg_upgrade_dump_globals.sql"
@@ -191,13 +192,13 @@ typedef struct
uint32 ctrl_ver;
uint32 cat_ver;
char nextxlogfile[25];
- uint32 chkpnt_nxtxid;
- uint32 chkpnt_nxtepoch;
+ uint64 chkpnt_nxtxid;
+ uint32 chkpnt_nxtepoch; /* for 32bit xids only */
uint32 chkpnt_nxtoid;
- uint32 chkpnt_nxtmulti;
- uint32 chkpnt_nxtmxoff;
- uint32 chkpnt_oldstMulti;
- uint32 chkpnt_oldstxid;
+ uint64 chkpnt_nxtmulti;
+ uint64 chkpnt_nxtmxoff;
+ uint64 chkpnt_oldstMulti;
+ uint64 chkpnt_oldstxid;
uint32 align;
uint32 blocksz;
uint32 largesz;
@@ -431,6 +432,7 @@ void prep_status(const char *fmt,...) pg_attribute_printf(1, 2);
void prep_status_progress(const char *fmt,...) pg_attribute_printf(1, 2);
void check_ok(void);
unsigned int str2uint(const char *str);
+uint64 str2uint64(const char *str);
/* version.c */
@@ -449,6 +451,10 @@ void old_9_6_invalidate_hash_indexes(ClusterInfo *cluster,
void old_11_check_for_sql_identifier_data_type_usage(ClusterInfo *cluster);
void report_extension_updates(ClusterInfo *cluster);
+void invalidate_spgist_indexes(ClusterInfo *cluster, bool check_mode);
+void invalidate_gin_indexes(ClusterInfo *cluster, bool check_mode);
+void invalidate_external_indexes(ClusterInfo *cluster, bool check_mode);
+
/* parallel.c */
void parallel_exec_prog(const char *log_file, const char *opt_log_file,
const char *fmt,...) pg_attribute_printf(3, 4);
@@ -456,3 +462,9 @@ void parallel_transfer_all_new_dbs(DbInfoArr *old_db_arr, DbInfoArr *new_db_arr
char *old_pgdata, char *new_pgdata,
char *old_tablespace);
bool reap_child(bool wait_for_child);
+
+/* segresize.c */
+void convert_clog(const char *olddir, const char *newdir);
+MultiXactOffset convert_multixact_offsets(const char *olddir, const char *newdir);
+void convert_multixact_members(const char *olddir, const char *newdir,
+ MultiXactOffset oldest_mxoff);
\ No newline at end of file
diff --git a/src/bin/pg_upgrade/segresize.c b/src/bin/pg_upgrade/segresize.c
new file mode 100644
index 0000000000..dc3eb1f591
--- /dev/null
+++ b/src/bin/pg_upgrade/segresize.c
@@ -0,0 +1,571 @@
+/*
+ * segresize.c
+ * Segment resize utility
+ *
+ * Copyright (c) 2015-2016, Postgres Professional
+ * src/bin/pg_upgrade/segresize.c
+ */
+
+
+#include "postgres_fe.h"
+
+#include "pg_upgrade.h"
+#include "access/multixact.h"
+#include "access/transam.h"
+
+
+#define OldFileName(path, seg) \
+ psprintf("%s/%04X", path, seg)
+
+#define NewFileName(path, seg) \
+ psprintf("%s/%04X%08X", path, \
+ (uint32) ((seg) >> 32), (uint32) ((seg) & (int64)0xFFFFFFFF))
+
+#define SLRU_PAGES_PER_SEGMENT_OLD 32
+#define SLRU_PAGES_PER_SEGMENT_NEW 2048 /* XXX SLRU_PAGES_PER_SEGMENT */
+
+
+static FILE*
+create_target_file(const char *dir, int64 segno, char **pfn)
+{
+ char *fn;
+ FILE *filedesc;
+
+ fn = NewFileName(dir, segno);
+ filedesc = fopen(fn, "wb");
+
+ if (!filedesc)
+ pg_fatal("Cannot create file: %s", fn);
+
+ if (pfn)
+ {
+ if (*pfn)
+ pfree(*pfn);
+ *pfn = fn;
+ }
+ else
+ pfree(fn);
+
+ return filedesc;
+}
+
+typedef struct SLRUSegmentState
+{
+ const char *dir;
+ char *fn;
+ FILE *file;
+ uint64 segno;
+ uint64 pageno;
+ bool leading_gap;
+} SLRUSegmentState;
+
+static void
+close_segment(SLRUSegmentState *state)
+{
+ if (state->file != NULL)
+ {
+ fclose(state->file);
+ state->file = NULL;
+ }
+
+ if (state->fn)
+ {
+ pfree(state->fn);
+ state->fn = NULL;
+ }
+}
+
+static int
+read_old_segment_page(SLRUSegmentState *state, void *buf, bool *is_empty)
+{
+ size_t len;
+
+ /* Open next segment file, if needed */
+ if (!state->fn)
+ {
+ if (!state->segno)
+ state->leading_gap = true;
+ state->fn = OldFileName(state->dir, (uint32) state->segno);
+ state->file = fopen(state->fn, "rb");
+
+ /* Set position to the needed page */
+ if (state->file && state->pageno > 0)
+ {
+ if (fseek(state->file, state->pageno * BLCKSZ, SEEK_SET))
+ {
+ fclose(state->file);
+ state->file = NULL;
+ }
+ }
+ }
+
+ if (state->file)
+ {
+ /* Segment file do exists, read page from it */
+ state->leading_gap = false;
+
+ len = fread(buf, sizeof(char), BLCKSZ, state->file);
+
+ /* Are we done or was there an error? */
+ if (len <= 0)
+ {
+ if (ferror(state->file))
+ pg_fatal("Error reading file: %s", state->fn);
+
+ if (feof(state->file))
+ {
+ *is_empty = true;
+ len = -1;
+ fclose(state->file);
+ state->file = NULL;
+ }
+ }
+ else
+ *is_empty = false;
+ }
+ else if (!state->leading_gap)
+ {
+ /* We reached the last segment */
+ len = -1;
+ *is_empty = true;
+ }
+ else
+ {
+ /* Skip few first segments if they were frozen and removed */
+ len = BLCKSZ;
+ *is_empty = true;
+ }
+
+ state->pageno++;
+
+ if (state->pageno >= SLRU_PAGES_PER_SEGMENT_OLD)
+ {
+ /* Start new segment */
+ state->segno++;
+ state->pageno = 0;
+ close_segment(state);
+ }
+
+ return (int) len;
+}
+
+static void
+write_new_segment_page(SLRUSegmentState *state, void *buf, bool is_empty)
+{
+ /*
+ * Create a new segment file if we still didn't. Creation is
+ * postponed until the first non-empty page is found. This helps
+ * not to create completely empty segments.
+ */
+ if (!state->file && !is_empty)
+ {
+ state->file = create_target_file(state->dir, state->segno, &state->fn);
+
+ /* Write zeroes to the previously skipped prefix */
+ if (state->pageno > 0)
+ {
+ char zerobuf[BLCKSZ] = {0};
+
+ for (int64 i = 0; i < state->pageno; i++)
+ {
+ if (fwrite(zerobuf, sizeof(char), BLCKSZ, state->file) != BLCKSZ)
+ pg_fatal("Could not write file: %s", state->fn);
+ }
+ }
+ }
+
+ /* Write page to the new segment (if it was created) */
+ if (state->file)
+ {
+ if (is_empty)
+ memset(buf, 0, BLCKSZ);
+
+ if (fwrite(buf, sizeof(char), BLCKSZ, state->file) != BLCKSZ)
+ pg_fatal("Could not write file: %s", state->fn);
+ }
+
+ state->pageno++;
+
+ /*
+ * Did we reach the maximum page number? Then close segment file
+ * and create a new one on the next iteration
+ */
+ if (state->pageno >= SLRU_PAGES_PER_SEGMENT_NEW)
+ {
+ state->segno++;
+ state->pageno = 0;
+ close_segment(state);
+ }
+}
+
+#define CLOG_BITS_PER_XACT 2
+#define CLOG_XACTS_PER_BYTE 4
+#define CLOG_XACTS_PER_PAGE (BLCKSZ * CLOG_XACTS_PER_BYTE)
+
+#define MaxTransactionIdOld ((TransactionId) 0xFFFFFFFF)
+
+/*
+ * Convert pg_xact segments.
+ */
+void
+convert_clog(const char *old_subdir, const char *new_subdir)
+{
+ SLRUSegmentState oldseg = {0};
+ SLRUSegmentState newseg = {0};
+ TransactionId oldest_xid = old_cluster.controldata.chkpnt_oldstxid;
+ TransactionId next_xid = old_cluster.controldata.chkpnt_nxtxid;
+ TransactionId xid;
+ uint64 pageno;
+ char buf[BLCKSZ] = {0};
+
+ oldseg.dir = old_subdir;
+ newseg.dir = new_subdir;
+
+ pageno = oldest_xid / CLOG_XACTS_PER_PAGE;
+
+ oldseg.segno = pageno / SLRU_PAGES_PER_SEGMENT_OLD;
+ oldseg.pageno = pageno % SLRU_PAGES_PER_SEGMENT_OLD;
+
+ newseg.segno = pageno / SLRU_PAGES_PER_SEGMENT_NEW;
+ newseg.pageno = pageno % SLRU_PAGES_PER_SEGMENT_NEW;
+
+ if (next_xid < oldest_xid)
+ next_xid += FirstUpgradedTransactionId; /* wraparound */
+
+ /* Copy xid flags reading only needed segment pages */
+ for (xid = oldest_xid & ~(CLOG_XACTS_PER_PAGE - 1);
+ xid <= ((next_xid - 1) & ~(CLOG_XACTS_PER_PAGE - 1));
+ xid += CLOG_XACTS_PER_PAGE)
+ {
+ bool is_empty;
+ int len;
+
+ /* Handle possible segment wraparound */
+ if (oldseg.segno > MaxTransactionIdOld / CLOG_XACTS_PER_PAGE / SLRU_PAGES_PER_SEGMENT_OLD) {
+ pageno = (MaxTransactionIdOld + 1) / CLOG_XACTS_PER_PAGE;
+
+ Assert(oldseg.segno == pageno / SLRU_PAGES_PER_SEGMENT_OLD);
+ Assert(!oldseg.pageno);
+ Assert(!oldseg.file && !oldseg.fn);
+ oldseg.segno = 0;
+
+ Assert(newseg.segno == pageno / SLRU_PAGES_PER_SEGMENT_NEW);
+ Assert(!newseg.pageno);
+ Assert(!newseg.file);
+ newseg.segno = 0;
+ }
+
+ len = read_old_segment_page(&oldseg, buf, &is_empty);
+
+ /*
+ * Ignore read errors, copy all existing segment pages in the
+ * interesting xid range.
+ */
+ is_empty |= len <= 0;
+
+ if (!is_empty && len < BLCKSZ)
+ memset(&buf[len], 0, BLCKSZ - len);
+
+ write_new_segment_page(&newseg, buf, is_empty);
+ }
+
+ /* Release resources */
+ close_segment(&oldseg);
+ close_segment(&newseg);
+}
+
+typedef uint32 MultiXactIdOld;
+typedef uint64 MultiXactIdNew;
+
+typedef uint32 MultiXactOffsetOld;
+typedef uint64 MultiXactOffsetNew;
+
+#define MaxMultiXactIdOld ((MultiXactIdOld) 0xFFFFFFFF)
+#define MaxMultiXactOffsetOld ((MultiXactOffsetOld) 0xFFFFFFFF)
+
+#define MXACT_OFFSETS_PER_BLOCK_OLD (BLCKSZ / sizeof(MultiXactOffsetOld))
+#define MXACT_OFFSETS_PER_BLOCK_NEW (BLCKSZ / sizeof(MultiXactOffsetNew))
+
+/*
+ * Convert pg_multixact/offsets segments and return oldest mxid offset.
+ */
+MultiXactOffsetNew
+convert_multixact_offsets(const char *old_subdir, const char *new_subdir)
+{
+ SLRUSegmentState oldseg = {0};
+ SLRUSegmentState newseg = {0};
+ MultiXactOffsetOld oldbuf[MXACT_OFFSETS_PER_BLOCK_OLD] = {0};
+ MultiXactOffsetNew newbuf[MXACT_OFFSETS_PER_BLOCK_NEW] = {0};
+ MultiXactOffsetOld oldest_mxoff = 0;
+ MultiXactId oldest_mxid = old_cluster.controldata.chkpnt_oldstMulti;
+ MultiXactId next_mxid = old_cluster.controldata.chkpnt_nxtmulti;
+ MultiXactId mxid;
+ uint64 old_entry;
+ uint64 new_entry;
+ bool oldest_mxoff_known = false;
+
+ oldseg.dir = psprintf("%s/%s", old_cluster.pgdata, old_subdir);
+ newseg.dir = psprintf("%s/%s", new_cluster.pgdata, new_subdir);
+
+ old_entry = oldest_mxid % MXACT_OFFSETS_PER_BLOCK_OLD;
+ oldseg.pageno = oldest_mxid / MXACT_OFFSETS_PER_BLOCK_OLD;
+ oldseg.segno = oldseg.pageno / SLRU_PAGES_PER_SEGMENT_OLD;
+ oldseg.pageno %= SLRU_PAGES_PER_SEGMENT_OLD;
+
+ new_entry = oldest_mxid % MXACT_OFFSETS_PER_BLOCK_NEW;
+ newseg.pageno = oldest_mxid / MXACT_OFFSETS_PER_BLOCK_NEW;
+ newseg.segno = newseg.pageno / SLRU_PAGES_PER_SEGMENT_NEW;
+ newseg.pageno %= SLRU_PAGES_PER_SEGMENT_NEW;
+
+ if (next_mxid < oldest_mxid)
+ next_mxid += (uint64) 1 << 32; /* wraparound */
+
+ prep_status("Converting old %s to new format", old_subdir);
+
+ /* Copy mxid offsets reading only needed segment pages */
+ for (mxid = oldest_mxid; mxid < next_mxid; old_entry = 0)
+ {
+ int oldlen;
+ bool is_empty;
+
+ /* Handle possible segment wraparound */
+ if (oldseg.segno > MaxMultiXactIdOld / MXACT_OFFSETS_PER_BLOCK_OLD / SLRU_PAGES_PER_SEGMENT_OLD) /* 0xFFFF */
+ oldseg.segno = 0;
+
+ oldlen = read_old_segment_page(&oldseg, oldbuf, &is_empty);
+
+ if (oldlen <= 0 || is_empty)
+ {
+ char pageno_str[32];
+
+ snprintf(pageno_str, sizeof(pageno_str), UINT64_FORMAT, oldseg.pageno);
+ pg_fatal("Cannot read page %s from segment: %s\n",
+ pageno_str, oldseg.fn);
+ }
+
+ if (oldlen < BLCKSZ)
+ memset((char *) oldbuf + oldlen, 0, BLCKSZ - oldlen);
+
+ /* Save oldest mxid offset */
+ if (!oldest_mxoff_known)
+ {
+ oldest_mxoff = oldbuf[old_entry];
+ oldest_mxoff_known = true;
+ }
+
+ /* Skip wrapped-around invalid MultiXactIds */
+ if (mxid == (MultiXactId) 1 << 32)
+ {
+ Assert(oldseg.segno == 0);
+ Assert(oldseg.pageno == 1);
+ Assert(old_entry == 0);
+ mxid += FirstMultiXactId;
+ old_entry = FirstMultiXactId;
+ }
+
+ /* Copy entries to the new page */
+ for (; mxid < next_mxid && old_entry < MXACT_OFFSETS_PER_BLOCK_OLD;
+ mxid++, old_entry++)
+ {
+ MultiXactOffsetNew mxoff = oldbuf[old_entry];
+
+ /* Handle possible offset wraparound (1 becomes 2^32) */
+ if (mxoff < oldest_mxoff)
+ mxoff += ((uint64) 1 << 32) - 1;
+
+ /* Subtract oldest_mxoff, so new offsets will start from 1 */
+ newbuf[new_entry++] = mxoff - oldest_mxoff + 1;
+
+ if (new_entry >= MXACT_OFFSETS_PER_BLOCK_NEW)
+ {
+ /* Write new page */
+ write_new_segment_page(&newseg, newbuf, false);
+ new_entry = 0;
+ }
+ }
+ }
+
+ /* Write the last incomplete page */
+ if (new_entry > 0 || oldest_mxid == next_mxid)
+ {
+ memset(&newbuf[new_entry], 0,
+ sizeof(newbuf[0]) * (MXACT_OFFSETS_PER_BLOCK_NEW - new_entry));
+ write_new_segment_page(&newseg, newbuf, false);
+ }
+
+ /* Use next_mxoff as oldest_mxoff, if oldest_mxid == next_mxid */
+ if (!oldest_mxoff_known)
+ {
+ Assert(oldest_mxid == next_mxid);
+ oldest_mxoff = (MultiXactOffsetNew) old_cluster.controldata.chkpnt_nxtmxoff;
+ }
+
+ /* Release resources */
+ close_segment(&oldseg);
+ close_segment(&newseg);
+
+ pfree((char *) oldseg.dir);
+ pfree((char *) newseg.dir);
+
+ check_ok();
+
+ return oldest_mxoff;
+}
+
+typedef uint32 TransactionIdOld;
+typedef uint64 TransactionIdNew;
+
+#define MXACT_MEMBERS_FLAG_BYTES 1
+
+#define MXACT_MEMBERS_PER_GROUP_OLD 4
+#define MXACT_MEMBERS_GROUP_SIZE_OLD (MXACT_MEMBERS_PER_GROUP_OLD * (sizeof(TransactionIdOld) + MXACT_MEMBERS_FLAG_BYTES))
+#define MXACT_MEMBER_GROUPS_PER_PAGE_OLD (BLCKSZ / MXACT_MEMBERS_GROUP_SIZE_OLD)
+#define MXACT_MEMBERS_PER_PAGE_OLD (MXACT_MEMBERS_PER_GROUP_OLD * MXACT_MEMBER_GROUPS_PER_PAGE_OLD)
+#define MXACT_MEMBER_FLAG_BYTES_PER_GROUP_OLD MXACT_MEMBERS_FLAG_BYTES * MXACT_MEMBERS_PER_GROUP_OLD
+
+#define MXACT_MEMBERS_PER_GROUP_NEW 8
+#define MXACT_MEMBERS_GROUP_SIZE_NEW (MXACT_MEMBERS_PER_GROUP_NEW * (sizeof(TransactionIdNew) + MXACT_MEMBERS_FLAG_BYTES))
+#define MXACT_MEMBER_GROUPS_PER_PAGE_NEW (BLCKSZ / MXACT_MEMBERS_GROUP_SIZE_NEW)
+
+/*
+ * Convert pg_multixact/members segments, offsets will start from 1.
+ */
+void
+convert_multixact_members(const char *old_subdir, const char *new_subdir,
+ MultiXactOffset oldest_mxoff)
+{
+ MultiXactOffsetNew next_mxoff = (MultiXactOffsetNew) old_cluster.controldata.chkpnt_nxtmxoff;
+ MultiXactOffsetNew mxoff;
+ SLRUSegmentState oldseg = { 0 };
+ SLRUSegmentState newseg = { 0 };
+ char oldbuf[BLCKSZ] = { 0 };
+ char newbuf[BLCKSZ] = { 0 };
+ int newgroup;
+ int newmember;
+ char *newflag = newbuf;
+ TransactionIdNew *newxid = (TransactionIdNew *)(newflag + MXACT_MEMBERS_FLAG_BYTES * MXACT_MEMBERS_PER_GROUP_NEW);
+ int newidx;
+ int oldidx;
+
+ oldseg.dir = psprintf("%s/%s", old_cluster.pgdata, old_subdir);
+ newseg.dir = psprintf("%s/%s", new_cluster.pgdata, new_subdir);
+
+ prep_status("Converting old %s to new format", old_subdir);
+
+ if (next_mxoff < oldest_mxoff)
+ next_mxoff += (uint64) 1 << 32;
+
+ /* Initialize old starting position */
+ oldidx = oldest_mxoff % MXACT_MEMBERS_PER_PAGE_OLD;
+ oldseg.pageno = oldest_mxoff / MXACT_MEMBERS_PER_PAGE_OLD;
+ oldseg.segno = oldseg.pageno / SLRU_PAGES_PER_SEGMENT_OLD;
+ oldseg.pageno %= SLRU_PAGES_PER_SEGMENT_OLD;
+
+ /* Initialize new starting position (skip invalid zero offset) */
+ newgroup = 0;
+ newidx = 1;
+ newmember = 1;
+ newflag++;
+ newxid++;
+
+ /* Iterate through the original directory */
+ for (mxoff = oldest_mxoff; mxoff < next_mxoff; oldidx = 0)
+ {
+ bool old_is_empty;
+ int oldlen = read_old_segment_page(&oldseg, oldbuf, &old_is_empty);
+ int ngroups;
+ int oldgroup;
+ int oldmember;
+
+ if (old_is_empty || oldlen <= 0)
+ {
+ char pageno_str[32];
+
+ snprintf(pageno_str, sizeof(pageno_str), UINT64_FORMAT, oldseg.pageno);
+ pg_fatal("Cannot read page %s from segment: %s\n",
+ pageno_str, oldseg.fn);
+ }
+
+ if (oldlen < BLCKSZ)
+ {
+ memset(oldbuf + oldlen, 0, BLCKSZ - oldlen);
+ oldlen = BLCKSZ;
+ }
+
+ ngroups = oldlen / MXACT_MEMBERS_GROUP_SIZE_OLD;
+
+ /* Iterate through old member groups */
+ for (oldgroup = oldidx / MXACT_MEMBERS_PER_GROUP_OLD,
+ oldmember = oldidx % MXACT_MEMBERS_PER_GROUP_OLD;
+ oldgroup < ngroups && mxoff < next_mxoff;
+ oldgroup++, oldmember = 0)
+ {
+ char *oldflag = (char *) oldbuf + oldgroup * MXACT_MEMBERS_GROUP_SIZE_OLD;
+ TransactionIdOld *oldxid = (TransactionIdOld *)(oldflag + MXACT_MEMBER_FLAG_BYTES_PER_GROUP_OLD);
+
+ oldxid += oldmember;
+ oldflag += oldmember;
+
+ /* Iterate through old members */
+ for (int j = 0;
+ j < MXACT_MEMBERS_PER_GROUP_OLD && mxoff < next_mxoff;
+ j++)
+ {
+ /* Copy member's xid and flags to the new page */
+ *newflag++ = *oldflag++;
+ *newxid++ = (TransactionIdNew) *oldxid++;
+
+ newidx++;
+ oldidx++;
+ mxoff++;
+
+ if (++newmember >= MXACT_MEMBERS_PER_GROUP_NEW)
+ {
+ /* Start next member group */
+ newmember = 0;
+
+ if (++newgroup >= MXACT_MEMBER_GROUPS_PER_PAGE_NEW)
+ {
+ /* Write current page and start new */
+ newgroup = 0;
+ newidx = 0;
+ write_new_segment_page(&newseg, newbuf, false);
+ memset(newbuf, 0, BLCKSZ);
+ }
+
+ newflag = (char *) newbuf + newgroup * MXACT_MEMBERS_GROUP_SIZE_NEW;
+ newxid = (TransactionIdNew *)(newflag + MXACT_MEMBERS_FLAG_BYTES * MXACT_MEMBERS_PER_GROUP_NEW);
+ }
+
+ /* Handle offset wraparound */
+ if (mxoff > MaxMultiXactOffsetOld)
+ {
+ Assert(mxoff == (uint64) 1 << 32);
+ Assert(oldseg.segno == MaxMultiXactOffsetOld / MXACT_MEMBERS_PER_PAGE_OLD / SLRU_PAGES_PER_SEGMENT_OLD);
+ Assert(oldseg.pageno == MaxMultiXactOffsetOld / MXACT_MEMBERS_PER_PAGE_OLD % SLRU_PAGES_PER_SEGMENT_OLD);
+ Assert(oldmember == MaxMultiXactOffsetOld % MXACT_MEMBERS_PER_PAGE_OLD);
+
+ /* Switch to segment 0000 */
+ close_segment(&oldseg);
+ oldseg.segno = 0;
+ oldseg.pageno = 0;
+
+ oldidx = 1; /* skip invalid zero mxid offset */
+ }
+ }
+ }
+ }
+
+ /* Write last page, unless it is empty */
+ if (newflag > (char *) newbuf || oldest_mxoff == next_mxoff)
+ write_new_segment_page(&newseg, newbuf, false);
+
+ /* Release resources */
+ close_segment(&oldseg);
+ close_segment(&newseg);
+
+ pfree((char *) oldseg.dir);
+ pfree((char *) newseg.dir);
+
+ check_ok();
+}
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index ef328b3062..f46b424115 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -24,7 +24,7 @@ standard_initdb() {
# without increasing test runtime, run these tests with a custom setting.
# Also, specify "-A trust" explicitly to suppress initdb's warning.
# --allow-group-access and --wal-segsize have been added in v11.
- "$1" -N --wal-segsize 1 --allow-group-access -A trust
+ "$1" -N --wal-segsize 1 --allow-group-access -A trust -x 21000000000
if [ -n "$TEMP_CONFIG" -a -r "$TEMP_CONFIG" ]
then
cat "$TEMP_CONFIG" >> "$PGDATA/postgresql.conf"
@@ -195,6 +195,12 @@ if "$MAKE" -C "$oldsrc" installcheck-parallel; then
fi
fi
+ psql -X -d regression << EOF
+ CREATE TABLE t1 (id SERIAL NOT NULL PRIMARY KEY, plt text, pln NUMERIC(8, 4));
+ INSERT INTO t1 (plt, pln) SELECT md5(random()::text), random() * 9999 FROM generate_series(1, 1000);
+EOF
+ psql -X -d regression -c"SELECT relfrozenxid, relminmxid FROM pg_class WHERE relname = 't1';" > "$temp_root"/old_xids.txt
+
pg_dumpall $extra_dump_options --no-sync \
-f "$temp_root"/dump1.sql || pg_dumpall1_status=$?
@@ -255,6 +261,17 @@ esac
pg_ctl start -l "$logdir/postmaster2.log" -o "$POSTMASTER_OPTS" -w
+psql -X -d regression -c"SELECT relfrozenxid, relminmxid FROM pg_class WHERE relname = 't1';" > "$temp_root"/new_xids.txt
+
+if diff -u "$temp_root"/new_xids.txt "$temp_root"/old_xids.txt > "$temp_root"/xids.diff; then
+ rm "$temp_root"/xids.diff
+ echo "xids are identical, PASSED"
+else
+ echo "Files $temp_root/new_xids.txt and $temp_root/old_xids.txt differ"
+ echo "See $temp_root/xids.diff"
+ exit 1
+fi
+
pg_dumpall $extra_dump_options --no-sync \
-f "$temp_root"/dump2.sql || pg_dumpall2_status=$?
pg_ctl -m fast stop
diff --git a/src/bin/pg_upgrade/util.c b/src/bin/pg_upgrade/util.c
index 414de06349..125a069fcc 100644
--- a/src/bin/pg_upgrade/util.c
+++ b/src/bin/pg_upgrade/util.c
@@ -280,3 +280,20 @@ str2uint(const char *str)
{
return strtoul(str, NULL, 10);
}
+
+/*
+ * str2uint64()
+ *
+ * convert string to 64-bit unsigned int
+ */
+uint64
+str2uint64(const char *str)
+{
+#ifdef _MSC_VER /* MSVC only */
+ return _strtoui64(str, NULL, 10);
+#elif defined(HAVE_STRTOULL) && SIZEOF_LONG < 8
+ return strtoull(str, NULL, 10);
+#else
+ return strtoul(str, NULL, 10);
+#endif
+}
diff --git a/src/bin/pg_upgrade/version.c b/src/bin/pg_upgrade/version.c
index c694558c3d..d3a04373ad 100644
--- a/src/bin/pg_upgrade/version.c
+++ b/src/bin/pg_upgrade/version.c
@@ -9,6 +9,7 @@
#include "postgres_fe.h"
+#include "access/transam.h"
#include "catalog/pg_class_d.h"
#include "fe_utils/string_utils.h"
#include "pg_upgrade.h"
@@ -238,19 +239,21 @@ old_9_6_check_for_unknown_data_type_usage(ClusterInfo *cluster)
}
/*
- * old_9_6_invalidate_hash_indexes()
- * 9.6 -> 10
- * Hash index binary format has changed from 9.6->10.0
+ * invalidate_indexes()
+ * Invalidates all indexes satisfying given predicate.
*/
-void
-old_9_6_invalidate_hash_indexes(ClusterInfo *cluster, bool check_mode)
+static void
+invalidate_indexes(ClusterInfo *cluster, bool check_mode,
+ const char *name, const char *pred)
{
int dbnum;
FILE *script = NULL;
bool found = false;
- char *output_path = "reindex_hash.sql";
+ char output_path[MAXPGPATH];
+
+ snprintf(output_path, sizeof(output_path), "reindex_%s.sql", name);
- prep_status("Checking for hash indexes");
+ prep_status("Checking for %s indexes", name);
for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
{
@@ -263,18 +266,28 @@ old_9_6_invalidate_hash_indexes(ClusterInfo *cluster, bool check_mode)
DbInfo *active_db = &cluster->dbarr.dbs[dbnum];
PGconn *conn = connectToServer(cluster, active_db->db_name);
- /* find hash indexes */
- res = executeQueryOrDie(conn,
- "SELECT n.nspname, c.relname "
- "FROM pg_catalog.pg_class c, "
- " pg_catalog.pg_index i, "
- " pg_catalog.pg_am a, "
- " pg_catalog.pg_namespace n "
- "WHERE i.indexrelid = c.oid AND "
- " c.relam = a.oid AND "
- " c.relnamespace = n.oid AND "
- " a.amname = 'hash'"
- );
+
+ /*
+ * Find indexes satisfying predicate.
+ *
+ * System indexes (with oids < FirstNormalObjectId) are excluded from
+ * the search as they are recreated in the new cluster during initdb.
+ */
+ res = executeQueryOrDie(
+ conn,
+ "SELECT n.nspname, c.relname, i.indexrelid "
+ "FROM pg_catalog.pg_class c, "
+ " pg_catalog.pg_index i, "
+ " pg_catalog.pg_am a, "
+ " pg_catalog.pg_namespace n "
+ "WHERE i.indexrelid = c.oid AND "
+ " c.relam = a.oid AND "
+ " c.relnamespace = n.oid AND "
+ " i.indexrelid >= '%u'::pg_catalog.oid AND "
+ " %s "
+ "ORDER BY i.indexrelid ASC",
+ FirstNormalObjectId,
+ pred);
ntups = PQntuples(res);
i_nspname = PQfnumber(res, "nspname");
@@ -307,17 +320,26 @@ old_9_6_invalidate_hash_indexes(ClusterInfo *cluster, bool check_mode)
if (!check_mode && db_used)
{
- /* mark hash indexes as invalid */
- PQclear(executeQueryOrDie(conn,
- "UPDATE pg_catalog.pg_index i "
- "SET indisvalid = false "
- "FROM pg_catalog.pg_class c, "
- " pg_catalog.pg_am a, "
- " pg_catalog.pg_namespace n "
- "WHERE i.indexrelid = c.oid AND "
- " c.relam = a.oid AND "
- " c.relnamespace = n.oid AND "
- " a.amname = 'hash'"));
+ /*
+ * Mark indexes satisfying predicate as invalid.
+ *
+ * System indexes (with oids < FirstNormalObjectId) are excluded
+ * from the search (see above).
+ */
+ PQclear(executeQueryOrDie(
+ conn,
+ "UPDATE pg_catalog.pg_index i "
+ "SET indisvalid = false "
+ "FROM pg_catalog.pg_class c, "
+ " pg_catalog.pg_am a, "
+ " pg_catalog.pg_namespace n "
+ "WHERE i.indexrelid = c.oid AND "
+ " c.relam = a.oid AND "
+ " c.relnamespace = n.oid AND "
+ " i.indexrelid >= '%u'::pg_catalog.oid AND "
+ " %s",
+ FirstNormalObjectId,
+ pred));
}
PQfinish(conn);
@@ -331,24 +353,37 @@ old_9_6_invalidate_hash_indexes(ClusterInfo *cluster, bool check_mode)
report_status(PG_WARNING, "warning");
if (check_mode)
pg_log(PG_WARNING, "\n"
- "Your installation contains hash indexes. These indexes have different\n"
+ "Your installation contains %s indexes. These indexes have different\n"
"internal formats between your old and new clusters, so they must be\n"
"reindexed with the REINDEX command. After upgrading, you will be given\n"
- "REINDEX instructions.\n\n");
+ "REINDEX instructions.\n\n",
+ name);
else
pg_log(PG_WARNING, "\n"
- "Your installation contains hash indexes. These indexes have different\n"
+ "Your installation contains %s indexes. These indexes have different\n"
"internal formats between your old and new clusters, so they must be\n"
"reindexed with the REINDEX command. The file\n"
" %s\n"
"when executed by psql by the database superuser will recreate all invalid\n"
"indexes; until then, none of these indexes will be used.\n\n",
+ name,
output_path);
}
else
check_ok();
}
+/*
+ * old_9_6_invalidate_hash_indexes()
+ * 9.6 -> 10
+ * Hash index binary format has changed from 9.6->10.0
+ */
+void
+old_9_6_invalidate_hash_indexes(ClusterInfo *cluster, bool check_mode)
+{
+ invalidate_indexes(cluster, check_mode, "hash", "a.amname = 'hash'");
+}
+
/*
* old_11_check_for_sql_identifier_data_type_usage()
* 11 -> 12
@@ -458,3 +493,36 @@ report_extension_updates(ClusterInfo *cluster)
else
check_ok();
}
+
+/*
+ * invalidate_spgist_indexes()
+ * 32bit -> 64bit
+ * SP-GIST contains xids.
+ */
+void
+invalidate_spgist_indexes(ClusterInfo *cluster, bool check_mode)
+{
+ invalidate_indexes(cluster, check_mode, "spgist", "a.amname = 'spgist'");
+}
+
+/*
+ * invalidate_gin_indexes()
+ * 32bit -> 64bit
+ * Gin indexes contains xids in deleted pages.
+ */
+void
+invalidate_gin_indexes(ClusterInfo *cluster, bool check_mode)
+{
+ invalidate_indexes(cluster, check_mode, "gin", "a.amname = 'gin'");
+}
+
+/*
+ * invalidate_external_indexes()
+ * Generate script to REINDEX non standard external indexes (like RUM etc)
+ */
+void
+invalidate_external_indexes(ClusterInfo *cluster, bool check_mode)
+{
+ invalidate_indexes(cluster, check_mode, "external",
+ "NOT a.amname IN ('btree', 'hash', 'gist', 'gin', 'spgist', 'brin')");
+}
diff --git a/src/bin/pg_verifybackup/t/003_corruption.pl b/src/bin/pg_verifybackup/t/003_corruption.pl
index 406c0c9877..acfe6d4657 100644
--- a/src/bin/pg_verifybackup/t/003_corruption.pl
+++ b/src/bin/pg_verifybackup/t/003_corruption.pl
@@ -176,7 +176,7 @@ sub mutilate_extra_tablespace_file
sub mutilate_missing_file
{
my ($backup_path) = @_;
- my $pathname = "$backup_path/pg_xact/0000";
+ my $pathname = "$backup_path/pg_xact/0000000048C0";
unlink($pathname) || die "$pathname: $!";
return;
}
diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c
index 2340dc247b..6eed5b81f0 100644
--- a/src/bin/pg_waldump/pg_waldump.c
+++ b/src/bin/pg_waldump/pg_waldump.c
@@ -487,7 +487,7 @@ XLogDumpDisplayRecord(XLogDumpConfig *config, XLogReaderState *record)
XLogDumpRecordLen(record, &rec_len, &fpi_len);
- printf("rmgr: %-11s len (rec/tot): %6u/%6u, tx: %10u, lsn: %X/%08X, prev %X/%08X, ",
+ printf("rmgr: %-11s len (rec/tot): %6u/%6u, tx: " XID_FMT ", lsn: %X/%08X, prev %X/%08X, ",
desc->rm_name,
rec_len, XLogRecGetTotalLen(record),
XLogRecGetXid(record),
@@ -947,7 +947,7 @@ main(int argc, char **argv)
}
break;
case 'x':
- if (sscanf(optarg, "%u", &config.filter_by_xid) != 1)
+ if (sscanf(optarg, XID_FMT, &config.filter_by_xid) != 1)
{
pg_log_error("could not parse \"%s\" as a transaction ID",
optarg);
diff --git a/src/include/access/clog.h b/src/include/access/clog.h
index 543f2e2643..73bc172309 100644
--- a/src/include/access/clog.h
+++ b/src/include/access/clog.h
@@ -31,7 +31,7 @@ typedef int XidStatus;
typedef struct xl_clog_truncate
{
- int pageno;
+ int64 pageno;
TransactionId oldestXact;
Oid oldestXactDb;
} xl_clog_truncate;
diff --git a/src/include/access/commit_ts.h b/src/include/access/commit_ts.h
index 7662f8e1a9..d2cfb02e14 100644
--- a/src/include/access/commit_ts.h
+++ b/src/include/access/commit_ts.h
@@ -60,7 +60,7 @@ typedef struct xl_commit_ts_set
typedef struct xl_commit_ts_truncate
{
- int pageno;
+ int64 pageno;
TransactionId oldestXid;
} xl_commit_ts_truncate;
diff --git a/src/include/access/ginblock.h b/src/include/access/ginblock.h
index 9347f464f3..4db042c319 100644
--- a/src/include/access/ginblock.h
+++ b/src/include/access/ginblock.h
@@ -133,8 +133,15 @@ typedef struct GinMetaPageData
* We should reclaim deleted page only once every transaction started before
* its deletion is over.
*/
-#define GinPageGetDeleteXid(page) ( ((PageHeader) (page))->pd_prune_xid )
-#define GinPageSetDeleteXid(page, xid) ( ((PageHeader) (page))->pd_prune_xid = xid)
+#define GinPageGetDeleteXid(page) ( \
+ (((PageHeader) (page))->pd_upper == BLCKSZ - sizeof(GinPageOpaqueData) - sizeof(TransactionId)) ? \
+ *((TransactionId *) ((char *) (page) + BLCKSZ - sizeof(GinPageOpaqueData) - sizeof(TransactionId))) : \
+ InvalidTransactionId )
+#define GinPageSetDeleteXid(page, xid) \
+ do { \
+ ((PageHeader) (page))->pd_upper = BLCKSZ - sizeof(GinPageOpaqueData) - sizeof(TransactionId); \
+ *((TransactionId *) ((char *) (page) + BLCKSZ - sizeof(GinPageOpaqueData) - sizeof(TransactionId))) = xid; \
+ } while (false)
extern bool GinPageIsRecyclable(Page page);
/*
diff --git a/src/include/access/gist.h b/src/include/access/gist.h
index a3337627b8..41de3052fd 100644
--- a/src/include/access/gist.h
+++ b/src/include/access/gist.h
@@ -223,7 +223,7 @@ GistPageGetDeleteXid(Page page)
return ((GISTDeletedPageContents *) PageGetContents(page))->deleteXid;
}
else
- return FullTransactionIdFromEpochAndXid(0, FirstNormalTransactionId);
+ return FullTransactionIdFromXid(FirstNormalTransactionId);
}
/*
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index b46ab7d739..38d3b6b6ca 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -146,6 +146,10 @@ extern void ReleaseBulkInsertStatePin(BulkInsertState bistate);
extern void heap_insert(Relation relation, HeapTuple tup, CommandId cid,
int options, BulkInsertState bistate);
+extern bool heap_page_prepare_for_xid(Relation relation, Buffer buffer,
+ TransactionId xid, bool multi);
+extern bool rewrite_page_prepare_for_xid(Page page, TransactionId xid,
+ bool multi);
extern void heap_multi_insert(Relation relation, struct TupleTableSlot **slots,
int ntuples, CommandId cid, int options,
BulkInsertState bistate);
@@ -164,12 +168,12 @@ extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
Buffer *buffer, struct TM_FailureData *tmfd);
extern void heap_inplace_update(Relation relation, HeapTuple tuple);
-extern bool heap_freeze_tuple(HeapTupleHeader tuple,
+extern bool heap_freeze_tuple(HeapTuple tuple,
TransactionId relfrozenxid, TransactionId relminmxid,
TransactionId cutoff_xid, TransactionId cutoff_multi);
-extern bool heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
+extern bool heap_tuple_needs_freeze(HeapTuple htup, TransactionId cutoff_xid,
MultiXactId cutoff_multi);
-extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
+extern bool heap_tuple_needs_eventual_freeze(HeapTuple htup);
extern void simple_heap_insert(Relation relation, HeapTuple tup);
extern void simple_heap_delete(Relation relation, ItemPointer tid);
@@ -187,11 +191,13 @@ extern int heap_page_prune(Relation relation, Buffer buffer,
TransactionId old_snap_xmin,
TimestampTz old_snap_ts_ts,
int *nnewlpdead,
- OffsetNumber *off_loc);
+ OffsetNumber *off_loc,
+ bool repairFragmentation);
extern void heap_page_prune_execute(Buffer buffer,
OffsetNumber *redirected, int nredirected,
OffsetNumber *nowdead, int ndead,
- OffsetNumber *nowunused, int nunused);
+ OffsetNumber *nowunused, int nunused,
+ bool repairFragmentation);
extern void heap_get_root_tuples(Page page, OffsetNumber *root_offsets);
/* in heap/vacuumlazy.c */
@@ -210,7 +216,7 @@ extern HTSV_Result HeapTupleSatisfiesVacuumHorizon(HeapTuple stup, Buffer buffer
TransactionId *dead_after);
extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
uint16 infomask, TransactionId xid);
-extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple);
+extern bool HeapTupleIsOnlyLocked(HeapTuple htup);
extern bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
extern bool HeapTupleIsSurelyDead(HeapTuple htup,
struct GlobalVisState *vistest);
diff --git a/src/include/access/heapam_xlog.h b/src/include/access/heapam_xlog.h
index 5c47fdcec8..d014026929 100644
--- a/src/include/access/heapam_xlog.h
+++ b/src/include/access/heapam_xlog.h
@@ -59,6 +59,8 @@
#define XLOG_HEAP2_LOCK_UPDATED 0x60
#define XLOG_HEAP2_NEW_CID 0x70
+#define XLOG_HEAP3_BASE_SHIFT 0x00
+
/*
* xl_heap_insert/xl_heap_multi_insert flag values, 8 bits are available.
*/
@@ -389,7 +391,16 @@ typedef struct xl_heap_rewrite_mapping
XLogRecPtr start_lsn; /* Insert LSN at begin of rewrite */
} xl_heap_rewrite_mapping;
-extern void HeapTupleHeaderAdvanceLatestRemovedXid(HeapTupleHeader tuple,
+/* shift the base of xids on heap page */
+typedef struct xl_heap_base_shift
+{
+ int64 delta; /* delta value to shift the base */
+ bool multi; /* true to shift multixact base */
+} xl_heap_base_shift;
+
+#define SizeOfHeapBaseShift (offsetof(xl_heap_base_shift, multi) + sizeof(bool))
+
+extern void HeapTupleHeaderAdvanceLatestRemovedXid(HeapTuple tuple,
TransactionId *latestRemovedXid);
extern void heap_redo(XLogReaderState *record);
@@ -399,19 +410,24 @@ extern void heap_mask(char *pagedata, BlockNumber blkno);
extern void heap2_redo(XLogReaderState *record);
extern void heap2_desc(StringInfo buf, XLogReaderState *record);
extern const char *heap2_identify(uint8 info);
+extern void heap3_redo(XLogReaderState *record);
+extern void heap3_desc(StringInfo buf, XLogReaderState *record);
+extern const char *heap3_identify(uint8 info);
extern void heap_xlog_logical_rewrite(XLogReaderState *r);
extern XLogRecPtr log_heap_freeze(Relation reln, Buffer buffer,
TransactionId cutoff_xid, xl_heap_freeze_tuple *tuples,
int ntuples);
-extern bool heap_prepare_freeze_tuple(HeapTupleHeader tuple,
+extern bool heap_prepare_freeze_tuple(HeapTuple htup,
TransactionId relfrozenxid,
TransactionId relminmxid,
TransactionId cutoff_xid,
TransactionId cutoff_multi,
xl_heap_freeze_tuple *frz,
bool *totally_frozen);
-extern void heap_execute_freeze_tuple(HeapTupleHeader tuple,
+extern void heap_execute_freeze_tuple(HeapTuple tuple,
+ xl_heap_freeze_tuple *xlrec_tp);
+extern void heap_execute_freeze_tuple_page(Page page, HeapTupleHeader htup,
xl_heap_freeze_tuple *xlrec_tp);
extern XLogRecPtr log_heap_visible(RelFileNode rnode, Buffer heap_buffer,
Buffer vm_buffer, TransactionId cutoff_xid, uint8 flags);
diff --git a/src/include/access/heaptoast.h b/src/include/access/heaptoast.h
index a75699054a..8c599ad7d9 100644
--- a/src/include/access/heaptoast.h
+++ b/src/include/access/heaptoast.h
@@ -22,7 +22,7 @@
*/
#define MaximumBytesPerTuple(tuplesPerPage) \
MAXALIGN_DOWN((BLCKSZ - \
- MAXALIGN(SizeOfPageHeaderData + (tuplesPerPage) * sizeof(ItemIdData))) \
+ MAXALIGN(SizeOfPageHeaderData + (tuplesPerPage) * sizeof(ItemIdData)) - MAXALIGN(sizeof(HeapPageSpecialData))) \
/ (tuplesPerPage))
/*
diff --git a/src/include/access/htup.h b/src/include/access/htup.h
index a4bc7256ed..2a68b222b1 100644
--- a/src/include/access/htup.h
+++ b/src/include/access/htup.h
@@ -54,6 +54,12 @@ typedef MinimalTupleData *MinimalTuple;
* this can't be told apart from case #1 by inspection; code setting up
* or destroying this representation has to know what it's doing.
*
+ * t_xmin and t_xmax are TransactionId values stored in heap tuple header.
+ * Normally they are calculated from ShortTransactionId-sized on-disk tuple
+ * xmin/xmax representation:
+ * t_data->t_choice.t_heap.t_xmin/t_data->t_choice.t_heap.t_xmin
+ * and pd_xid_base and pd_multi_base commmon values for all tuples on a page.
+ *
* t_len should always be valid, except in the pointer-to-nothing case.
* t_self and t_tableOid should be valid if the HeapTupleData points to
* a disk buffer, or if it represents a copy of a tuple on disk. They
@@ -61,10 +67,12 @@ typedef MinimalTupleData *MinimalTuple;
*/
typedef struct HeapTupleData
{
- uint32 t_len; /* length of *t_data */
- ItemPointerData t_self; /* SelfItemPointer */
- Oid t_tableOid; /* table the tuple came from */
-#define FIELDNO_HEAPTUPLEDATA_DATA 3
+ TransactionId t_xmin; /* base value for normal transaction ids */
+ TransactionId t_xmax; /* base value for mutlixact */
+ uint32 t_len; /* length of *t_data */
+ ItemPointerData t_self; /* SelfItemPointer */
+ Oid t_tableOid; /* table the tuple came from */
+#define FIELDNO_HEAPTUPLEDATA_DATA 5
HeapTupleHeader t_data; /* -> tuple header and data */
} HeapTupleData;
@@ -78,12 +86,12 @@ typedef HeapTupleData *HeapTuple;
#define HeapTupleIsValid(tuple) PointerIsValid(tuple)
/* HeapTupleHeader functions implemented in utils/time/combocid.c */
-extern CommandId HeapTupleHeaderGetCmin(HeapTupleHeader tup);
-extern CommandId HeapTupleHeaderGetCmax(HeapTupleHeader tup);
-extern void HeapTupleHeaderAdjustCmax(HeapTupleHeader tup,
+extern CommandId HeapTupleGetCmin(HeapTuple tup);
+extern CommandId HeapTupleGetCmax(HeapTuple tup);
+extern void HeapTupleHeaderAdjustCmax(HeapTuple tup,
CommandId *cmax, bool *iscombo);
/* Prototype for HeapTupleHeader accessors in heapam.c */
-extern TransactionId HeapTupleGetUpdateXid(HeapTupleHeader tuple);
+extern TransactionId HeapTupleGetUpdateXid(HeapTuple tuple);
#endif /* HTUP_H */
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index b2d52ed16c..80f83efcea 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -120,13 +120,13 @@
typedef struct HeapTupleFields
{
- TransactionId t_xmin; /* inserting xact ID */
- TransactionId t_xmax; /* deleting or locking xact ID */
+ ShortTransactionId t_xmin; /* inserting xact ID */
+ ShortTransactionId t_xmax; /* deleting or locking xact ID */
union
{
CommandId t_cid; /* inserting or deleting command ID, or both */
- TransactionId t_xvac; /* old-style VACUUM FULL xact ID */
+ ShortTransactionId t_xvac; /* old-style VACUUM FULL xact ID */
} t_field3;
} HeapTupleFields;
@@ -222,7 +222,7 @@ struct HeapTupleHeaderData
* HEAP_XMAX_LOCK_ONLY bit is set; or, for pg_upgrade's sake, if the Xmax is
* not a multi and the EXCL_LOCK bit is set.
*
- * See also HeapTupleHeaderIsOnlyLocked, which also checks for a possible
+ * See also HeapTupleIsOnlyLocked, which also checks for a possible
* aborted updater transaction.
*
* Beware of multiple evaluations of the argument.
@@ -298,27 +298,79 @@ struct HeapTupleHeaderData
*/
/*
- * HeapTupleHeaderGetRawXmin returns the "raw" xmin field, which is the xid
+ * Copy base values for xid and multixacts from one heap tuple to heap tuple.
+ * Should be called on tuple copy or making desc tuple on the base on src tuple
+ * saving visibility information.
+ */
+#define HeapTupleCopyBase(dest, src) \
+{ \
+ (dest)->t_xmin = (src)->t_xmin; \
+ (dest)->t_xmax = (src)->t_xmax; \
+}
+
+/*
+ * Set base values for tuple xids/multixacts to zero. Used when visibility
+ * infromation is negligible or will be set later.
+ */
+#define HeapTupleSetZeroBase(tup) \
+{ \
+ (tup)->t_xmin = 0; \
+ (tup)->t_xmax = 0; \
+}
+
+/*
+ * Copy HeapTupleHeader xmin/xmax in raw way ???
+ */
+#define HeapTupleCopyHeaderXids(tup) \
+{ \
+ (tup)->t_xmin = (tup)->t_data->t_choice.t_heap.t_xmin; \
+ (tup)->t_xmax = (tup)->t_data->t_choice.t_heap.t_xmax; \
+}
+
+/*
+ * Macros for accessing "double xmax". On pg_upgraded instances, it might
+ * happend that we can't fit new special area to the page. But we still
+ * might neep to write xmax of tuples for updates and deletes. The trick is
+ * that we actually don't need xmin field. After pg_upgrade (wich implies
+ * restart) no insertions went to this page yet (otherwise special area could
+ * fit). So, if tuple is visible (othewise it would be deleted), then it's
+ * visible for everybody. Thus, t_xmin isn't needed. Therefore, we can use
+ * both t_xmin and t_xmax to store 64-bit xmax.
+ *
+ * See heap_convert.c for details.
+ */
+#define HeapTupleHeaderGetDoubleXmax(tup) \
+ ((TransactionId)(tup)->t_choice.t_heap.t_xmax + \
+ ((TransactionId)(tup)->t_choice.t_heap.t_xmin << 32))
+
+#define HeapTupleHeaderSetDoubleXmax(tup, xid) \
+do { \
+ (tup)->t_choice.t_heap.t_xmax = (TransactionId) (xid) & 0xFFFFFFFF; \
+ (tup)->t_choice.t_heap.t_xmin = ((TransactionId) (xid) >> 32) & 0xFFFFFFFF; \
+} while (0)
+
+/*
+ * HeapTupleGetRawXmin returns the "raw" xmin field, which is the xid
* originally used to insert the tuple. However, the tuple might actually
* be frozen (via HeapTupleHeaderSetXminFrozen) in which case the tuple's xmin
* is visible to every snapshot. Prior to PostgreSQL 9.4, we actually changed
* the xmin to FrozenTransactionId, and that value may still be encountered
* on disk.
*/
-#define HeapTupleHeaderGetRawXmin(tup) \
-( \
- (tup)->t_choice.t_heap.t_xmin \
-)
+#define HeapTupleGetRawXmin(tup) ((tup)->t_xmin)
-#define HeapTupleHeaderGetXmin(tup) \
+#define HeapTupleGetXmin(tup) \
( \
- HeapTupleHeaderXminFrozen(tup) ? \
- FrozenTransactionId : HeapTupleHeaderGetRawXmin(tup) \
+ HeapTupleHeaderXminFrozen((tup)->t_data) ? \
+ FrozenTransactionId : HeapTupleGetRawXmin(tup) \
)
-#define HeapTupleHeaderSetXmin(tup, xid) \
+#define HeapTupleSetXmin(tup, xid) ((tup)->t_xmin = (xid))
+
+#define HeapTupleHeaderSetXmin(page, tup) \
( \
- (tup)->t_choice.t_heap.t_xmin = (xid) \
+ AssertMacro(!HeapPageIsDoubleXmax(page)), \
+ (tup)->t_data->t_choice.t_heap.t_xmin = NormalTransactionIdToShort(HeapPageGetSpecial(page)->pd_xid_base, (tup)->t_xmin) \
)
#define HeapTupleHeaderXminCommitted(tup) \
@@ -362,30 +414,47 @@ struct HeapTupleHeaderData
* to resolve the MultiXactId if necessary. This might involve multixact I/O,
* so it should only be used if absolutely necessary.
*/
-#define HeapTupleHeaderGetUpdateXid(tup) \
+#define HeapTupleGetUpdateXidAny(tup) \
( \
- (!((tup)->t_infomask & HEAP_XMAX_INVALID) && \
- ((tup)->t_infomask & HEAP_XMAX_IS_MULTI) && \
- !((tup)->t_infomask & HEAP_XMAX_LOCK_ONLY)) ? \
+ (!((tup)->t_data->t_infomask & HEAP_XMAX_INVALID) && \
+ ((tup)->t_data->t_infomask & HEAP_XMAX_IS_MULTI) && \
+ !((tup)->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY)) ? \
HeapTupleGetUpdateXid(tup) \
: \
- HeapTupleHeaderGetRawXmax(tup) \
+ HeapTupleGetRawXmax(tup) \
)
-#define HeapTupleHeaderGetRawXmax(tup) \
-( \
- (tup)->t_choice.t_heap.t_xmax \
-)
+#define HeapTupleGetRawXmax(tup) ((tup)->t_xmax)
-#define HeapTupleHeaderSetXmax(tup, xid) \
+#define HeapTupleHeaderGetRawXmax(page, tup) \
( \
- (tup)->t_choice.t_heap.t_xmax = (xid) \
+ HeapPageIsDoubleXmax(page) ? \
+ HeapTupleHeaderGetDoubleXmax(tup) : \
+ ShortTransactionIdToNormal( \
+ ((tup)->t_infomask & HEAP_XMAX_IS_MULTI) ? HeapPageGetSpecial(page)->pd_multi_base : HeapPageGetSpecial(page)->pd_xid_base, \
+ (tup)->t_choice.t_heap.t_xmax) \
)
+#define HeapTupleSetXmax(tup, xid) \
+do { \
+ (tup)->t_xmax = (xid); \
+} while (0)
+
+#define HeapTupleHeaderSetXmax(page, tup) \
+do { \
+ if (HeapPageIsDoubleXmax(page)) \
+ HeapTupleHeaderSetDoubleXmax((tup)->t_data, (tup)->t_xmax); \
+ else \
+ (tup)->t_data->t_choice.t_heap.t_xmax = \
+ NormalTransactionIdToShort( \
+ ((tup)->t_data->t_infomask & HEAP_XMAX_IS_MULTI) ? HeapPageGetSpecial(page)->pd_multi_base : HeapPageGetSpecial(page)->pd_xid_base, \
+ ((tup)->t_xmax)); \
+} while (0)
+
/*
* HeapTupleHeaderGetRawCommandId will give you what's in the header whether
- * it is useful or not. Most code should use HeapTupleHeaderGetCmin or
- * HeapTupleHeaderGetCmax instead, but note that those Assert that you can
+ * it is useful or not. Most code should use HeapTupleGetCmin or
+ * HeapTupleGetCmax instead, but note that those Assert that you can
* get a legitimate result, ie you are in the originating transaction!
*/
#define HeapTupleHeaderGetRawCommandId(tup) \
@@ -556,7 +625,7 @@ do { \
* ItemIds and tuples have different alignment requirements, don't assume that
* you can, say, fit 2 tuples of size MaxHeapTupleSize/2 on the same page.
*/
-#define MaxHeapTupleSize (BLCKSZ - MAXALIGN(SizeOfPageHeaderData + sizeof(ItemIdData)))
+#define MaxHeapTupleSize (BLCKSZ - MAXALIGN(SizeOfPageHeaderData + sizeof(ItemIdData)) - MAXALIGN(sizeof(HeapPageSpecialData)))
#define MinHeapTupleSize MAXALIGN(SizeofHeapTupleHeader)
/*
@@ -771,6 +840,32 @@ extern Datum fastgetattr(HeapTuple tup, int attnum, TupleDesc tupleDesc,
heap_getsysattr((tup), (attnum), (tupleDesc), (isnull)) \
)
+/*
+ * Copy base values for xid and multixacts from page to heap tuple. Should be
+ * called each time tuple is read from page. Otherwise, it would be impossible
+ * to correctly read tuple xmin and xmax.
+ */
+static inline void
+HeapTupleCopyBaseFromPage(HeapTuple tup, void *page)
+{
+ HeapTupleHeader tup_hdr = tup->t_data;
+ if (HeapPageIsDoubleXmax(page))
+ {
+ tup->t_xmin = FrozenTransactionId;
+ tup->t_xmax = HeapTupleHeaderGetDoubleXmax(tup_hdr);
+ }
+ else
+ {
+ tup->t_xmin = HeapTupleHeaderXminFrozen(tup_hdr) ?
+ FrozenTransactionId :
+ ShortTransactionIdToNormal(
+ HeapPageGetSpecial(page)->pd_xid_base, tup_hdr->t_choice.t_heap.t_xmin);
+ tup->t_xmax = ShortTransactionIdToNormal(
+ (tup_hdr->t_infomask & HEAP_XMAX_IS_MULTI) ? HeapPageGetSpecial(page)->pd_multi_base : HeapPageGetSpecial(page)->pd_xid_base,
+ tup_hdr->t_choice.t_heap.t_xmax);
+ }
+}
+
/* prototypes for functions in common/heaptuple.c */
extern Size heap_compute_data_size(TupleDesc tupleDesc,
diff --git a/src/include/access/multixact.h b/src/include/access/multixact.h
index a5600a320a..c474f572e2 100644
--- a/src/include/access/multixact.h
+++ b/src/include/access/multixact.h
@@ -18,16 +18,16 @@
/*
* The first two MultiXactId values are reserved to store the truncation Xid
- * and epoch of the first segment, so we start assigning multixact values from
+ * and base of the first segment, so we start assigning multixact values from
* 2.
*/
-#define InvalidMultiXactId ((MultiXactId) 0)
-#define FirstMultiXactId ((MultiXactId) 1)
-#define MaxMultiXactId ((MultiXactId) 0xFFFFFFFF)
+#define InvalidMultiXactId UINT64CONST(0)
+#define FirstMultiXactId UINT64CONST(1)
+#define MaxMultiXactId UINT64CONST(0xFFFFFFFFFFFFFFFF)
#define MultiXactIdIsValid(multi) ((multi) != InvalidMultiXactId)
-#define MaxMultiXactOffset ((MultiXactOffset) 0xFFFFFFFF)
+#define MaxMultiXactOffset UINT64CONST(0xFFFFFFFFFFFFFFFF)
/* Number of SLRU buffers to use for multixact */
#define NUM_MULTIXACTOFFSET_BUFFERS 8
@@ -114,9 +114,6 @@ extern bool MultiXactIdIsRunning(MultiXactId multi, bool isLockOnly);
extern void MultiXactIdSetOldestMember(void);
extern int GetMultiXactIdMembers(MultiXactId multi, MultiXactMember **xids,
bool allow_old, bool isLockOnly);
-extern bool MultiXactIdPrecedes(MultiXactId multi1, MultiXactId multi2);
-extern bool MultiXactIdPrecedesOrEquals(MultiXactId multi1,
- MultiXactId multi2);
extern int multixactoffsetssyncfiletag(const FileTag *ftag, char *path);
extern int multixactmemberssyncfiletag(const FileTag *ftag, char *path);
@@ -146,7 +143,6 @@ extern void MultiXactSetNextMXact(MultiXactId nextMulti,
extern void MultiXactAdvanceNextMXact(MultiXactId minMulti,
MultiXactOffset minMultiOffset);
extern void MultiXactAdvanceOldest(MultiXactId oldestMulti, Oid oldestMultiDB);
-extern int MultiXactMemberFreezeThreshold(void);
extern void multixact_twophase_recover(TransactionId xid, uint16 info,
void *recdata, uint32 len);
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 9fec6fb1a8..2d6157f269 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -62,8 +62,10 @@ typedef uint16 BTCycleId;
typedef struct BTPageOpaqueData
{
BlockNumber btpo_prev; /* left sibling, or P_NONE if leftmost */
+ /* ... or next transaction ID (lower part) */
BlockNumber btpo_next; /* right sibling, or P_NONE if rightmost */
uint32 btpo_level; /* tree level --- zero for leaf pages */
+ /* ... or next transaction ID (lower part) */
uint16 btpo_flags; /* flag bits, see below */
BTCycleId btpo_cycleid; /* vacuum cycle ID of latest split */
} BTPageOpaqueData;
@@ -90,6 +92,14 @@ typedef BTPageOpaqueData *BTPageOpaque;
*/
#define MAX_BT_CYCLE_ID 0xFF7F
+/* Macros for access xact */
+#define BTP_GET_XACT(opaque) (((uint64) ((BTPageOpaque) opaque)->btpo_prev << 32) | \
+ (uint64) ((BTPageOpaque) opaque)->btpo_level)
+#define BTP_SET_XACT(opaque, xact) \
+do { \
+ ((BTPageOpaque) opaque)->btpo_prev = (uint32) (xact >> 32); \
+ ((BTPageOpaque) opaque)->btpo_level = (uint32) xact; \
+} while (0)
/*
* The Meta page is always the first page in the btree index.
diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h
index aa5c48f219..052789a12d 100644
--- a/src/include/access/rewriteheap.h
+++ b/src/include/access/rewriteheap.h
@@ -51,7 +51,7 @@ typedef struct LogicalRewriteMappingData
* 6) xid of the xact performing the mapping
* ---
*/
-#define LOGICAL_REWRITE_FORMAT "map-%x-%x-%X_%X-%x-%x"
+#define LOGICAL_REWRITE_FORMAT "map-%x-%x-%X_%X-%x_%x-%x_%x"
void CheckPointLogicalRewriteHeap(void);
#endif /* REWRITE_HEAP_H */
diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index cf8b6d4819..affddf1419 100644
--- a/src/include/access/rmgrlist.h
+++ b/src/include/access/rmgrlist.h
@@ -47,3 +47,4 @@ PG_RMGR(RM_COMMIT_TS_ID, "CommitTs", commit_ts_redo, commit_ts_desc, commit_ts_i
PG_RMGR(RM_REPLORIGIN_ID, "ReplicationOrigin", replorigin_redo, replorigin_desc, replorigin_identify, NULL, NULL, NULL, NULL)
PG_RMGR(RM_GENERIC_ID, "Generic", generic_redo, generic_desc, generic_identify, NULL, NULL, generic_mask, NULL)
PG_RMGR(RM_LOGICALMSG_ID, "LogicalMessage", logicalmsg_redo, logicalmsg_desc, logicalmsg_identify, NULL, NULL, NULL, logicalmsg_decode)
+PG_RMGR(RM_HEAP3_ID, "Heap3", heap3_redo, heap3_desc, heap3_identify, NULL, NULL, heap_mask, NULL)
diff --git a/src/include/access/slru.h b/src/include/access/slru.h
index 130c41c863..bd87d08bee 100644
--- a/src/include/access/slru.h
+++ b/src/include/access/slru.h
@@ -31,7 +31,7 @@
* take no explicit notice of that fact in slru.c, except when comparing
* segment and page numbers in SimpleLruTruncate (see PagePrecedes()).
*/
-#define SLRU_PAGES_PER_SEGMENT 32
+#define SLRU_PAGES_PER_SEGMENT 2048
/*
* Page status codes. Note that these do not include the "dirty" bit.
@@ -64,7 +64,7 @@ typedef struct SlruSharedData
char **page_buffer;
SlruPageStatus *page_status;
bool *page_dirty;
- int *page_number;
+ int64 *page_number;
int *page_lru_count;
LWLockPadded *buffer_locks;
@@ -95,7 +95,7 @@ typedef struct SlruSharedData
* this is not critical data, since we use it only to avoid swapping out
* the latest page.
*/
- int latest_page_number;
+ int64 latest_page_number;
/* SLRU's index for statistics purposes (might not be unique) */
int slru_stats_idx;
@@ -127,7 +127,7 @@ typedef struct SlruCtlData
* the behavior of this callback has no functional implications.) Use
* SlruPagePrecedesUnitTests() in SLRUs meeting its criteria.
*/
- bool (*PagePrecedes) (int, int);
+ bool (*PagePrecedes) (int64, int64);
/*
* Dir is set during SimpleLruInit and does not change thereafter. Since
@@ -143,10 +143,10 @@ extern Size SimpleLruShmemSize(int nslots, int nlsns);
extern void SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
LWLock *ctllock, const char *subdir, int tranche_id,
SyncRequestHandler sync_handler);
-extern int SimpleLruZeroPage(SlruCtl ctl, int pageno);
-extern int SimpleLruReadPage(SlruCtl ctl, int pageno, bool write_ok,
+extern int SimpleLruZeroPage(SlruCtl ctl, int64 pageno);
+extern int SimpleLruReadPage(SlruCtl ctl, int64 pageno, bool write_ok,
TransactionId xid);
-extern int SimpleLruReadPage_ReadOnly(SlruCtl ctl, int pageno,
+extern int SimpleLruReadPage_ReadOnly(SlruCtl ctl, int64 pageno,
TransactionId xid);
extern void SimpleLruWritePage(SlruCtl ctl, int slotno);
extern void SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied);
@@ -155,20 +155,20 @@ extern void SlruPagePrecedesUnitTests(SlruCtl ctl, int per_page);
#else
#define SlruPagePrecedesUnitTests(ctl, per_page) do {} while (0)
#endif
-extern void SimpleLruTruncate(SlruCtl ctl, int cutoffPage);
-extern bool SimpleLruDoesPhysicalPageExist(SlruCtl ctl, int pageno);
+extern void SimpleLruTruncate(SlruCtl ctl, int64 cutoffPage);
+extern bool SimpleLruDoesPhysicalPageExist(SlruCtl ctl, int64 pageno);
-typedef bool (*SlruScanCallback) (SlruCtl ctl, char *filename, int segpage,
- void *data);
+typedef bool (*SlruScanCallback) (SlruCtl ctl, char *filename, int64 segpage,
+ void *data);
extern bool SlruScanDirectory(SlruCtl ctl, SlruScanCallback callback, void *data);
-extern void SlruDeleteSegment(SlruCtl ctl, int segno);
+extern void SlruDeleteSegment(SlruCtl ctl, int64 segno);
extern int SlruSyncFileTag(SlruCtl ctl, const FileTag *ftag, char *path);
/* SlruScanDirectory public callbacks */
extern bool SlruScanDirCbReportPresence(SlruCtl ctl, char *filename,
- int segpage, void *data);
-extern bool SlruScanDirCbDeleteAll(SlruCtl ctl, char *filename, int segpage,
+ int64 segpage, void *data);
+extern bool SlruScanDirCbDeleteAll(SlruCtl ctl, char *filename, int64 segpage,
void *data);
#endif /* SLRU_H */
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index bb365736b7..15c43e110b 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -118,7 +118,7 @@ typedef enum TM_Result
* cmax is the outdating command's CID, but only when the failure code is
* TM_SelfModified (i.e., something in the current transaction outdated the
* tuple); otherwise cmax is zero. (We make this restriction because
- * HeapTupleHeaderGetCmax doesn't work for tuples outdated in other
+ * HeapTupleGetCmax doesn't work for tuples outdated in other
* transactions.)
*/
typedef struct TM_FailureData
diff --git a/src/include/access/transam.h b/src/include/access/transam.h
index 9a2816de51..77ef0b5d52 100644
--- a/src/include/access/transam.h
+++ b/src/include/access/transam.h
@@ -17,6 +17,10 @@
#include "access/xlogdefs.h"
+#ifndef FRONTEND
+#include "utils/elog.h"
+#endif
+
/* ----------------
* Special transaction ID values
*
@@ -28,11 +32,15 @@
* Note: if you need to change it, you must change pg_class.h as well.
* ----------------
*/
-#define InvalidTransactionId ((TransactionId) 0)
-#define BootstrapTransactionId ((TransactionId) 1)
-#define FrozenTransactionId ((TransactionId) 2)
-#define FirstNormalTransactionId ((TransactionId) 3)
-#define MaxTransactionId ((TransactionId) 0xFFFFFFFF)
+#define InvalidTransactionId UINT64CONST(0)
+#define BootstrapTransactionId UINT64CONST(1)
+#define FrozenTransactionId UINT64CONST(2)
+#define FirstNormalTransactionId UINT64CONST(3)
+#define MaxTransactionId UINT64CONST(0xFFFFFFFFFFFFFFFF)
+#define MaxShortTransactionId ((TransactionId) 0x7FFFFFFF)
+
+/* First TransactionId after upgrade from 32bit xid */
+#define FirstUpgradedTransactionId ((TransactionId) 1 << 32)
/* ----------------
* transaction ID manipulation macros
@@ -44,8 +52,37 @@
#define TransactionIdStore(xid, dest) (*(dest) = (xid))
#define StoreInvalidTransactionId(dest) (*(dest) = InvalidTransactionId)
-#define EpochFromFullTransactionId(x) ((uint32) ((x).value >> 32))
-#define XidFromFullTransactionId(x) ((uint32) (x).value)
+/*
+ * Convert short xid from/to full xid. Assertion should fail if we full xid
+ * doesn't fit to xid base.
+ */
+#define ShortTransactionIdToNormal(base, xid) \
+ (TransactionIdIsNormal(xid) ? (TransactionId)(xid) + (base) : (TransactionId)(xid))
+
+#ifdef USE_ASSERT_CHECKING
+static inline ShortTransactionId
+NormalTransactionIdToShort(TransactionId base, TransactionId xid)
+{
+ if (!TransactionIdIsNormal(xid))
+ return (ShortTransactionId)(xid);
+
+#ifndef FRONTEND
+ if (xid < base + FirstNormalTransactionId ||
+ xid > base + MaxShortTransactionId)
+ elog(PANIC, "Xid " XID_FMT " does not fit into valid range for base " XID_FMT, xid, base);
+#endif
+
+ return (ShortTransactionId)(xid - base);
+}
+#else
+#define NormalTransactionIdToShort(base, xid) \
+ (TransactionIdIsNormal(xid) ? (ShortTransactionId)( \
+ AssertMacro((xid) >= (base) + FirstNormalTransactionId), \
+ AssertMacro((xid) <= (base) + MaxShortTransactionId), \
+ (xid) - (base)) : (ShortTransactionId)(xid))
+#endif
+
+#define XidFromFullTransactionId(x) ((x).value)
#define U64FromFullTransactionId(x) ((x).value)
#define FullTransactionIdEquals(a, b) ((a).value == (b).value)
#define FullTransactionIdPrecedes(a, b) ((a).value < (b).value)
@@ -53,8 +90,8 @@
#define FullTransactionIdFollows(a, b) ((a).value > (b).value)
#define FullTransactionIdFollowsOrEquals(a, b) ((a).value >= (b).value)
#define FullTransactionIdIsValid(x) TransactionIdIsValid(XidFromFullTransactionId(x))
-#define InvalidFullTransactionId FullTransactionIdFromEpochAndXid(0, InvalidTransactionId)
-#define FirstNormalFullTransactionId FullTransactionIdFromEpochAndXid(0, FirstNormalTransactionId)
+#define InvalidFullTransactionId FullTransactionIdFromXid(InvalidTransactionId)
+#define FirstNormalFullTransactionId FullTransactionIdFromXid(FirstNormalTransactionId)
#define FullTransactionIdIsNormal(x) FullTransactionIdFollowsOrEquals(x, FirstNormalFullTransactionId)
/*
@@ -68,11 +105,11 @@ typedef struct FullTransactionId
} FullTransactionId;
static inline FullTransactionId
-FullTransactionIdFromEpochAndXid(uint32 epoch, TransactionId xid)
+FullTransactionIdFromXid(TransactionId xid)
{
FullTransactionId result;
- result.value = ((uint64) epoch) << 32 | xid;
+ result.value = xid;
return result;
}
@@ -91,8 +128,7 @@ FullTransactionIdFromU64(uint64 value)
#define TransactionIdAdvance(dest) \
do { \
(dest)++; \
- if ((dest) < FirstNormalTransactionId) \
- (dest) = FirstNormalTransactionId; \
+ Assert((dest) > FirstNormalTransactionId); \
} while(0)
/*
@@ -140,18 +176,19 @@ FullTransactionIdAdvance(FullTransactionId *dest)
/* back up a transaction ID variable, handling wraparound correctly */
#define TransactionIdRetreat(dest) \
do { \
+ Assert((dest) > FirstNormalTransactionId); \
(dest)--; \
- } while ((dest) < FirstNormalTransactionId)
+ } while(0)
/* compare two XIDs already known to be normal; this is a macro for speed */
#define NormalTransactionIdPrecedes(id1, id2) \
(AssertMacro(TransactionIdIsNormal(id1) && TransactionIdIsNormal(id2)), \
- (int32) ((id1) - (id2)) < 0)
+ (int64) ((id1) - (id2)) < 0)
/* compare two XIDs already known to be normal; this is a macro for speed */
#define NormalTransactionIdFollows(id1, id2) \
(AssertMacro(TransactionIdIsNormal(id1) && TransactionIdIsNormal(id2)), \
- (int32) ((id1) - (id2)) > 0)
+ (int64) ((id1) - (id2)) > 0)
/* ----------
* Object ID (OID) zero is InvalidOid.
@@ -225,9 +262,6 @@ typedef struct VariableCacheData
TransactionId oldestXid; /* cluster-wide minimum datfrozenxid */
TransactionId xidVacLimit; /* start forcing autovacuums here */
- TransactionId xidWarnLimit; /* start complaining here */
- TransactionId xidStopLimit; /* refuse to advance nextXid beyond here */
- TransactionId xidWrapLimit; /* where the world ends */
Oid oldestXidDB; /* database with minimum datfrozenxid */
/*
@@ -281,10 +315,6 @@ extern bool TransactionIdIsKnownCompleted(TransactionId transactionId);
extern void TransactionIdCommitTree(TransactionId xid, int nxids, TransactionId *xids);
extern void TransactionIdAsyncCommitTree(TransactionId xid, int nxids, TransactionId *xids, XLogRecPtr lsn);
extern void TransactionIdAbortTree(TransactionId xid, int nxids, TransactionId *xids);
-extern bool TransactionIdPrecedes(TransactionId id1, TransactionId id2);
-extern bool TransactionIdPrecedesOrEquals(TransactionId id1, TransactionId id2);
-extern bool TransactionIdFollows(TransactionId id1, TransactionId id2);
-extern bool TransactionIdFollowsOrEquals(TransactionId id1, TransactionId id2);
extern TransactionId TransactionIdLatest(TransactionId mainxid,
int nxids, const TransactionId *xids);
extern XLogRecPtr TransactionIdGetCommitLSN(TransactionId xid);
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 062cc7e17d..e257baf8fd 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -234,7 +234,7 @@ typedef struct xl_xact_xinfo
* four so following records don't have to care about alignment. Commit
* records can be large, so copying large portions isn't attractive.
*/
- uint32 xinfo;
+ uint64 xinfo;
} xl_xact_xinfo;
typedef struct xl_xact_dbinfo
@@ -266,7 +266,12 @@ typedef struct xl_xact_invals
typedef struct xl_xact_twophase
{
- TransactionId xid;
+ /*
+ * TransactionId is split into 32-bit parts because
+ * xl_xact_twophase is only int-aligned.
+ */
+ uint32 xid_lo;
+ uint32 xid_hi;
} xl_xact_twophase;
typedef struct xl_xact_origin
@@ -284,7 +289,7 @@ typedef struct xl_xact_commit
/* xl_xact_subxacts follows if XINFO_HAS_SUBXACT */
/* xl_xact_relfilenodes follows if XINFO_HAS_RELFILENODES */
/* xl_xact_invals follows if XINFO_HAS_INVALS */
- /* xl_xact_twophase follows if XINFO_HAS_TWOPHASE */
+ /* xl_xact_twophase follows if XINFO_HAS_TWOPHASE (xid is int-aligned!) */
/* twophase_gid follows if XINFO_HAS_GID. As a null-terminated string. */
/* xl_xact_origin follows if XINFO_HAS_ORIGIN, stored unaligned! */
} xl_xact_commit;
@@ -299,7 +304,7 @@ typedef struct xl_xact_abort
/* xl_xact_subxacts follows if XINFO_HAS_SUBXACT */
/* xl_xact_relfilenodes follows if XINFO_HAS_RELFILENODES */
/* No invalidation messages needed. */
- /* xl_xact_twophase follows if XINFO_HAS_TWOPHASE */
+ /* xl_xact_twophase follows if XINFO_HAS_TWOPHASE (xid is int-aligned!) */
/* twophase_gid follows if XINFO_HAS_GID. As a null-terminated string. */
/* xl_xact_origin follows if XINFO_HAS_ORIGIN, stored unaligned! */
} xl_xact_abort;
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 4b45ac64db..d283c4159c 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -88,6 +88,14 @@ typedef enum RecoveryState
extern PGDLLIMPORT int wal_level;
+/*
+ * these parameters specifies starting xid, multixact id and multixact offset
+ * for testing 64 bit xids
+ */
+extern TransactionId start_xid;
+extern MultiXactId start_mx_id;
+extern MultiXactOffset start_mx_offset;
+
/* Is WAL archiving enabled (always or only while server is running normally)? */
#define XLogArchivingActive() \
(AssertMacro(XLogArchiveMode == ARCHIVE_MODE_OFF || wal_level >= WAL_LEVEL_REPLICA), XLogArchiveMode > ARCHIVE_MODE_OFF)
diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h
index 5fc340c434..8fca607fa3 100644
--- a/src/include/access/xloginsert.h
+++ b/src/include/access/xloginsert.h
@@ -37,6 +37,7 @@
* will be skipped) */
#define REGBUF_KEEP_DATA 0x10 /* include data even if a full-page image
* is taken */
+#define REGBUF_CONVERTED 0x20 /* buffer had format convertion */
/* prototypes for public functions in xloginsert.c: */
extern void XLogBeginInsert(void);
diff --git a/src/include/access/xlogreader.h b/src/include/access/xlogreader.h
index 477f0efe26..945e9eb66e 100644
--- a/src/include/access/xlogreader.h
+++ b/src/include/access/xlogreader.h
@@ -327,10 +327,6 @@ extern bool DecodeXLogRecord(XLogReaderState *state, XLogRecord *record,
#define XLogRecBlockImageApply(decoder, block_id) \
((decoder)->blocks[block_id].apply_image)
-#ifndef FRONTEND
-extern FullTransactionId XLogRecGetFullXid(XLogReaderState *record);
-#endif
-
extern bool RestoreBlockImage(XLogReaderState *record, uint8 block_id, char *page);
extern char *XLogRecGetBlockData(XLogReaderState *record, uint8 block_id, Size *len);
extern bool XLogRecGetBlockTag(XLogReaderState *record, uint8 block_id,
diff --git a/src/include/c.h b/src/include/c.h
index 4f16e589b3..cb3c4cdd84 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -75,6 +75,10 @@
#include <libintl.h>
#endif
+#if HAVE_INTTYPES_H
+#include "inttypes.h"
+#endif
+
/* ----------------------------------------------------------------
* Section 1: compiler characteristics
@@ -483,6 +487,16 @@ typedef unsigned long long int uint64;
#define INT64_FORMAT "%" INT64_MODIFIER "d"
#define UINT64_FORMAT "%" INT64_MODIFIER "u"
+/*
+ * Used to make translatable strings.
+ * Also on macOS externally defined PRIu64 doesn't match postgres int64
+ * definition causing strom of printf warnings. So redefine it anyway. -- sk.
+ */
+#ifdef PRIu64
+#undef PRIu64
+#endif
+#define PRIu64 INT64_MODIFIER "u"
+
/*
* 128-bit signed and unsigned integers
* There currently is only limited support for such types.
@@ -584,19 +598,41 @@ typedef double float8;
typedef Oid regproc;
typedef regproc RegProcedure;
-typedef uint32 TransactionId;
+#define MAX_START_XID UINT64CONST(0x3fffffffffffffff)
+
+typedef uint64 TransactionId;
-typedef uint32 LocalTransactionId;
+#define TransactionIdPrecedes(id1, id2) ((id1) < (id2))
+#define TransactionIdPrecedesOrEquals(id1, id2) ((id1) <= (id2))
+#define TransactionIdFollows(id1, id2) ((id1) > (id2))
+#define TransactionIdFollowsOrEquals(id1, id2) ((id1) >= (id2))
-typedef uint32 SubTransactionId;
+#define StartTransactionIdIsValid(start_xid) ((start_xid) <= MAX_START_XID)
+
+typedef uint32 ShortTransactionId;
+
+typedef uint64 LocalTransactionId;
+
+typedef uint64 SubTransactionId;
#define InvalidSubTransactionId ((SubTransactionId) 0)
#define TopSubTransactionId ((SubTransactionId) 1)
+#define XID_FMT UINT64_FORMAT
+
/* MultiXactId must be equivalent to TransactionId, to fit in t_xmax */
typedef TransactionId MultiXactId;
-typedef uint32 MultiXactOffset;
+#define MultiXactIdPrecedes(id1, id2) ((id1) < (id2))
+#define MultiXactIdPrecedesOrEquals(id1, id2) ((id1) <= (id2))
+#define MultiXactIdFollows(id1, id2) ((id1) > (id2))
+#define MultiXactIdFollowsOrEquals(id1, id2) ((id1) >= (id2))
+
+#define StartMultiXactIdIsValid(start_mx_id) ((start_mx_id) <= MAX_START_XID)
+
+typedef uint64 MultiXactOffset;
+
+#define StartMultiXactOffsetIsValid(start_mx_offset) ((start_mx_offset) <= MAX_START_XID)
typedef uint32 CommandId;
diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat
index 4cc129bebd..4f20977635 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -403,9 +403,9 @@
amprocrighttype => 'bytea', amprocnum => '2',
amproc => 'hashvarlenaextended' },
{ amprocfamily => 'hash/xid_ops', amproclefttype => 'xid',
- amprocrighttype => 'xid', amprocnum => '1', amproc => 'hashint4' },
+ amprocrighttype => 'xid', amprocnum => '1', amproc => 'hashint8' },
{ amprocfamily => 'hash/xid_ops', amproclefttype => 'xid',
- amprocrighttype => 'xid', amprocnum => '2', amproc => 'hashint4extended' },
+ amprocrighttype => 'xid', amprocnum => '2', amproc => 'hashint8extended' },
{ amprocfamily => 'hash/xid8_ops', amproclefttype => 'xid8',
amprocrighttype => 'xid8', amprocnum => '1', amproc => 'hashint8' },
{ amprocfamily => 'hash/xid8_ops', amproclefttype => 'xid8',
diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h
index 1f3dc24ac1..8ca122777d 100644
--- a/src/include/catalog/pg_control.h
+++ b/src/include/catalog/pg_control.h
@@ -249,4 +249,10 @@ typedef struct ControlFileData
*/
#define PG_CONTROL_FILE_SIZE 8192
+#define CONTROLFILE_GET_OLDEDITION(control) \
+ ((control)->pg_old_version >> 16)
+
+#define CONTROLFILE_SET_OLDEDITION(control, v) \
+ (control)->pg_old_version = ((v) << 16)
+
#endif /* PG_CONTROL_H */
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index bc5f8213f3..912ab48ecf 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -183,16 +183,16 @@
oprresult => 'bool', oprcom => '=(xid,xid)', oprnegate => '<>(xid,xid)',
oprcode => 'xideq', oprrest => 'eqsel', oprjoin => 'eqjoinsel' },
{ oid => '353', descr => 'equal',
- oprname => '=', oprleft => 'xid', oprright => 'int4', oprresult => 'bool',
- oprnegate => '<>(xid,int4)', oprcode => 'xideqint4', oprrest => 'eqsel',
+ oprname => '=', oprleft => 'xid', oprright => 'int8', oprresult => 'bool',
+ oprnegate => '<>(xid,int8)', oprcode => 'xideqint8', oprrest => 'eqsel',
oprjoin => 'eqjoinsel' },
{ oid => '3315', descr => 'not equal',
oprname => '<>', oprleft => 'xid', oprright => 'xid', oprresult => 'bool',
oprcom => '<>(xid,xid)', oprnegate => '=(xid,xid)', oprcode => 'xidneq',
oprrest => 'neqsel', oprjoin => 'neqjoinsel' },
{ oid => '3316', descr => 'not equal',
- oprname => '<>', oprleft => 'xid', oprright => 'int4', oprresult => 'bool',
- oprnegate => '=(xid,int4)', oprcode => 'xidneqint4', oprrest => 'neqsel',
+ oprname => '<>', oprleft => 'xid', oprright => 'int8', oprresult => 'bool',
+ oprnegate => '=(xid,int8)', oprcode => 'xidneqint8', oprrest => 'neqsel',
oprjoin => 'neqjoinsel' },
{ oid => '5068', descr => 'equal',
oprname => '=', oprcanmerge => 't', oprcanhash => 't', oprleft => 'xid8',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d8e8715ed1..0fa0e0a77a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -2366,10 +2366,10 @@
{ oid => '1181',
descr => 'age of a transaction ID, in transactions before current transaction',
proname => 'age', provolatile => 's', proparallel => 'r',
- prorettype => 'int4', proargtypes => 'xid', prosrc => 'xid_age' },
+ prorettype => 'int8', proargtypes => 'xid', prosrc => 'xid_age' },
{ oid => '3939',
descr => 'age of a multi-transaction ID, in multi-transactions before current multi-transaction',
- proname => 'mxid_age', provolatile => 's', prorettype => 'int4',
+ proname => 'mxid_age', provolatile => 's', prorettype => 'int8',
proargtypes => 'xid', prosrc => 'mxid_age' },
{ oid => '1188',
@@ -2704,11 +2704,11 @@
prosrc => 'bpcharlen' },
{ oid => '1319',
- proname => 'xideqint4', proleakproof => 't', prorettype => 'bool',
- proargtypes => 'xid int4', prosrc => 'xideq' },
+ proname => 'xideqint8', proleakproof => 't', prorettype => 'bool',
+ proargtypes => 'xid int8', prosrc => 'xideq' },
{ oid => '3309',
- proname => 'xidneqint4', proleakproof => 't', prorettype => 'bool',
- proargtypes => 'xid int4', prosrc => 'xidneq' },
+ proname => 'xidneqint8', proleakproof => 't', prorettype => 'bool',
+ proargtypes => 'xid int8', prosrc => 'xidneq' },
{ oid => '1326',
proname => 'interval_div', prorettype => 'interval',
diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index df45879463..48e864eeb9 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -95,9 +95,9 @@
typinput => 'tidin', typoutput => 'tidout', typreceive => 'tidrecv',
typsend => 'tidsend', typalign => 's' },
{ oid => '28', array_type_oid => '1011', descr => 'transaction id',
- typname => 'xid', typlen => '4', typbyval => 't', typcategory => 'U',
+ typname => 'xid', typlen => '8', typbyval => 'FLOAT8PASSBYVAL', typcategory => 'U',
typinput => 'xidin', typoutput => 'xidout', typreceive => 'xidrecv',
- typsend => 'xidsend', typalign => 'i' },
+ typsend => 'xidsend', typalign => 'd' },
{ oid => '29', array_type_oid => '1012',
descr => 'command identifier type, sequence in transaction id',
typname => 'cid', typlen => '4', typbyval => 't', typcategory => 'U',
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index d64f6268f2..6b2be2c642 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -213,12 +213,12 @@ typedef enum VacOptValue
*/
typedef struct VacuumParams
{
- bits32 options; /* bitmask of VACOPT_* */
- int freeze_min_age; /* min freeze age, -1 to use default */
- int freeze_table_age; /* age at which to scan whole table */
- int multixact_freeze_min_age; /* min multixact freeze age, -1 to
+ bits32 options; /* bitmask of VacuumOption */
+ int64 freeze_min_age; /* min freeze age, -1 to use default */
+ int64 freeze_table_age; /* age at which to scan whole table */
+ int64 multixact_freeze_min_age; /* min multixact freeze age, -1 to
* use default */
- int multixact_freeze_table_age; /* multixact age at which to scan
+ int64 multixact_freeze_table_age; /* multixact age at which to scan
* whole table */
bool is_wraparound; /* force a for-wraparound vacuum */
int log_min_duration; /* minimum execution threshold in ms at
@@ -252,12 +252,12 @@ typedef struct VacDeadItems
/* GUC parameters */
extern PGDLLIMPORT int default_statistics_target; /* PGDLLIMPORT for PostGIS */
-extern int vacuum_freeze_min_age;
-extern int vacuum_freeze_table_age;
-extern int vacuum_multixact_freeze_min_age;
-extern int vacuum_multixact_freeze_table_age;
-extern int vacuum_failsafe_age;
-extern int vacuum_multixact_failsafe_age;
+extern int64 vacuum_freeze_min_age;
+extern int64 vacuum_freeze_table_age;
+extern int64 vacuum_multixact_freeze_min_age;
+extern int64 vacuum_multixact_freeze_table_age;
+extern int64 vacuum_failsafe_age;
+extern int64 vacuum_multixact_failsafe_age;
/* Variables for cost-based parallel vacuum */
extern pg_atomic_uint32 *VacuumSharedCostBalance;
@@ -287,9 +287,9 @@ extern void vac_update_relstats(Relation relation,
bool *minmulti_updated,
bool in_outer_xact);
extern bool vacuum_set_xid_limits(Relation rel,
- int freeze_min_age, int freeze_table_age,
- int multixact_freeze_min_age,
- int multixact_freeze_table_age,
+ int64 freeze_min_age, int64 freeze_table_age,
+ int64 multixact_freeze_min_age,
+ int64 multixact_freeze_table_age,
TransactionId *oldestXmin,
TransactionId *freezeLimit,
MultiXactId *multiXactCutoff);
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 6560e462d6..0eee14afd7 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -281,6 +281,7 @@ extern struct varlena *pg_detoast_datum_packed(struct varlena *datum);
#define PG_GETARG_FLOAT4(n) DatumGetFloat4(PG_GETARG_DATUM(n))
#define PG_GETARG_FLOAT8(n) DatumGetFloat8(PG_GETARG_DATUM(n))
#define PG_GETARG_INT64(n) DatumGetInt64(PG_GETARG_DATUM(n))
+#define PG_GETARG_TRANSACTIONID(n) DatumGetTransactionId(PG_GETARG_DATUM(n))
/* use this if you want the raw, possibly-toasted input datum: */
#define PG_GETARG_RAW_VARLENA_P(n) ((struct varlena *) PG_GETARG_POINTER(n))
/* use this if you want the input datum de-toasted: */
@@ -367,6 +368,7 @@ extern struct varlena *pg_detoast_datum_packed(struct varlena *datum);
#define PG_RETURN_FLOAT8(x) return Float8GetDatum(x)
#define PG_RETURN_INT64(x) return Int64GetDatum(x)
#define PG_RETURN_UINT64(x) return UInt64GetDatum(x)
+#define PG_RETURN_TRANSACTIONID(x) return TransactionIdGetDatum(x)
/* RETURN macros for other pass-by-ref types will typically look like this: */
#define PG_RETURN_BYTEA_P(x) PG_RETURN_POINTER(x)
#define PG_RETURN_TEXT_P(x) PG_RETURN_POINTER(x)
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 635fbb2181..025594a252 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -973,6 +973,9 @@
# endif
#endif
+/* Postgres Pro use 64bit xids */
+#undef XID_IS_64BIT
+
/* Size of a WAL file block. This need have no particular relation to BLCKSZ.
XLOG_BLCKSZ must be a power of 2, and if your system supports O_DIRECT I/O,
XLOG_BLCKSZ must be a multiple of the alignment requirement for direct-I/O
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 31358110dc..e8b4b1f9df 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -555,21 +555,21 @@ typedef struct NullableDatum
* Returns transaction identifier value of a datum.
*/
-#define DatumGetTransactionId(X) ((TransactionId) (X))
+#define DatumGetTransactionId(X) (DatumGetUInt64(X))
/*
* TransactionIdGetDatum
* Returns datum representation for a transaction identifier.
*/
-#define TransactionIdGetDatum(X) ((Datum) (X))
+#define TransactionIdGetDatum(X) (UInt64GetDatum(X))
/*
* MultiXactIdGetDatum
* Returns datum representation for a multixact identifier.
*/
-#define MultiXactIdGetDatum(X) ((Datum) (X))
+#define MultiXactIdGetDatum(X) (UInt64GetDatum(X))
/*
* DatumGetCommandId
diff --git a/src/include/postmaster/autovacuum.h b/src/include/postmaster/autovacuum.h
index 30a2b3274f..099bd827ba 100644
--- a/src/include/postmaster/autovacuum.h
+++ b/src/include/postmaster/autovacuum.h
@@ -37,8 +37,8 @@ extern int autovacuum_vac_ins_thresh;
extern double autovacuum_vac_ins_scale;
extern int autovacuum_anl_thresh;
extern double autovacuum_anl_scale;
-extern int autovacuum_freeze_max_age;
-extern int autovacuum_multixact_freeze_max_age;
+extern int64 autovacuum_freeze_max_age;
+extern int64 autovacuum_multixact_freeze_max_age;
extern double autovacuum_vac_cost_delay;
extern int autovacuum_vac_cost_limit;
diff --git a/src/include/storage/buf_internals.h b/src/include/storage/buf_internals.h
index b903d2bcaf..bbb8c43163 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -40,10 +40,10 @@
*/
#define BUF_REFCOUNT_ONE 1
#define BUF_REFCOUNT_MASK ((1U << 18) - 1)
-#define BUF_USAGECOUNT_MASK 0x003C0000U
+#define BUF_USAGECOUNT_MASK 0x001C0000U
#define BUF_USAGECOUNT_ONE (1U << 18)
#define BUF_USAGECOUNT_SHIFT 18
-#define BUF_FLAG_MASK 0xFFC00000U
+#define BUF_FLAG_MASK 0xFFE00000U
/* Get refcount and usagecount from buffer state */
#define BUF_STATE_GET_REFCOUNT(state) ((state) & BUF_REFCOUNT_MASK)
@@ -55,6 +55,7 @@
* Note: BM_TAG_VALID essentially means that there is a buffer hashtable
* entry associated with the buffer's tag.
*/
+#define BM_CONVERTED (1U << 21) /* buffer were converted to 64xid */
#define BM_LOCKED (1U << 22) /* buffer header is locked */
#define BM_DIRTY (1U << 23) /* data needs writing */
#define BM_VALID (1U << 24) /* data is valid */
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index dd01841c30..2e826865bb 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -223,6 +223,8 @@ extern void BufferGetTag(Buffer buffer, RelFileNode *rnode,
ForkNumber *forknum, BlockNumber *blknum);
extern void MarkBufferDirtyHint(Buffer buffer, bool buffer_std);
+extern void MarkBufferConverted(Buffer buffer, bool converted);
+extern bool IsBufferConverted(Buffer buffer);
extern void UnlockBuffers(void);
extern void LockBuffer(Buffer buffer, int mode);
@@ -245,6 +247,8 @@ extern void TestForOldSnapshot_impl(Snapshot snapshot, Relation relation);
extern BufferAccessStrategy GetAccessStrategy(BufferAccessStrategyType btype);
extern void FreeAccessStrategy(BufferAccessStrategy strategy);
+/* old tuple format support */
+extern void convert_page(Relation rel, Page orig_page, Buffer buf, BlockNumber blkno);
/* inline functions */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index e9f253f2c8..63837e8641 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -159,12 +159,84 @@ typedef struct PageHeaderData
LocationIndex pd_upper; /* offset to end of free space */
LocationIndex pd_special; /* offset to start of special space */
uint16 pd_pagesize_version;
- TransactionId pd_prune_xid; /* oldest prunable XID, or zero if none */
+ ShortTransactionId pd_prune_xid; /* oldest prunable XID, or zero if none */
ItemIdData pd_linp[FLEXIBLE_ARRAY_MEMBER]; /* line pointer array */
} PageHeaderData;
typedef PageHeaderData *PageHeader;
+
+/*
+ * HeapPageSpecialData -- data that stored at the end of each heap page.
+ *
+ * pd_xid_base - base value for transaction IDs on page
+ * pd_multi_base - base value for multixact IDs on page
+ *
+ * pd_xid_base and pd_multi_base are base values for calculation of transaction
+ * identifiers from t_xmin and t_xmax in each heap tuple header on the page.
+ */
+typedef struct HeapPageSpecialData
+{
+ TransactionId pd_xid_base; /* base value for transaction IDs on page */
+ TransactionId pd_multi_base; /* base value for multixact IDs on page */
+} HeapPageSpecialData;
+
+#define SizeOfPageSpecial MAXALIGN(sizeof(HeapPageSpecialData))
+
+typedef HeapPageSpecialData *HeapPageSpecial;
+
+extern PGDLLIMPORT HeapPageSpecial doubleXmaxSpecial;
+
+/*
+ * Get pointer to HeapPageSpecialData without using pd_special of the page
+ * (for the sake of speed) assuming all heap pages have same size of special
+ * data.
+ *
+ * Return doubleXmaxSpecial when pd_special == BLCKSZ. See comment in bufpage.c
+ * for details.
+ */
+#define HeapPageGetSpecial(page) ( \
+ (((PageHeader) (page))->pd_special == BLCKSZ) ? \
+ ((HeapPageSpecial) doubleXmaxSpecial) : \
+ (AssertMacro(((PageHeader) (page))->pd_special == BLCKSZ - MAXALIGN(sizeof(HeapPageSpecialData))), \
+ (HeapPageSpecial) ((Pointer) (page) + BLCKSZ - MAXALIGN(sizeof(HeapPageSpecialData)))) \
+)
+
+/*
+ * Version of HeapPageGetSpecial() without assertions about pd_special. Used
+ * for non-consistent reads from non-locked pages.
+ */
+#define HeapPageGetSpecialNoAssert(page) ( \
+ (((PageHeader) (page))->pd_special == BLCKSZ) ? \
+ ((HeapPageSpecial) doubleXmaxSpecial) : \
+ (HeapPageSpecial) ((Pointer) (page) + BLCKSZ - MAXALIGN(sizeof(HeapPageSpecialData))) \
+)
+
+ShortTransactionId HeapPageSetPruneXidInternal(Page page, TransactionId xid);
+
+#define HeapPageSetPruneXid(page, xid) \
+ HeapPageSetPruneXidInternal((Page)(page), xid)
+
+#define HeapPageGetPruneXid(page) \
+( \
+ ShortTransactionIdToNormal(HeapPageGetSpecial(page)->pd_xid_base, ((PageHeader) (page))->pd_prune_xid) \
+)
+
+/*
+ * Read pd_prune_xid from non-locked page. May return invalid value, but doen't
+ * causes assert failures.
+ */
+#define HeapPageGetPruneXidNoAssert(page) \
+( \
+ ShortTransactionIdToNormal(HeapPageGetSpecialNoAssert(page)->pd_xid_base, ((PageHeader) (page))->pd_prune_xid) \
+)
+
+#define XidFitsPage(page, xid) \
+( \
+ (xid) >= HeapPageGetSpecial(page)->pd_xid_base + FirstNormalTransactionId && \
+ (xid) <= HeapPageGetSpecial(page)->pd_xid_base + MaxShortTransactionId \
+)
+
/*
* pd_flags contains the following flag bits. Undefined bits are initialized
* to zero and may be used in the future.
@@ -192,11 +264,13 @@ typedef PageHeaderData *PageHeader;
* Release 8.3 uses 4; it changed the HeapTupleHeader layout again, and
* added the pd_flags field (by stealing some bits from pd_tli),
* as well as adding the pd_prune_xid field (which enlarges the header).
+ * PgPro Enterprise 10 uses version number (0x00FF - 1), and should not
+ * collide with vanilla versions due to page conversion after pg_upgrade.
*
* As of Release 9.3, the checksum version must also be considered when
* handling pages.
*/
-#define PG_PAGE_LAYOUT_VERSION 4
+#define PG_PAGE_LAYOUT_VERSION 5
#define PG_DATA_CHECKSUM_VERSION 1
/* ----------------------------------------------------------------
@@ -389,16 +463,18 @@ PageValidateSpecialPointer(Page page)
#define PageClearAllVisible(page) \
(((PageHeader) (page))->pd_flags &= ~PD_ALL_VISIBLE)
+/* Check if page is in "double xmax" format */
+#define HeapPageIsDoubleXmax(page) \
+ (((PageHeader) (page))->pd_special == BLCKSZ)
+
#define PageSetPrunable(page, xid) \
do { \
Assert(TransactionIdIsNormal(xid)); \
- if (!TransactionIdIsValid(((PageHeader) (page))->pd_prune_xid) || \
- TransactionIdPrecedes(xid, ((PageHeader) (page))->pd_prune_xid)) \
- ((PageHeader) (page))->pd_prune_xid = (xid); \
+ if (!HeapPageIsDoubleXmax(page) && \
+ (!TransactionIdIsValid(HeapPageGetPruneXid(page)) || \
+ TransactionIdPrecedes(xid, HeapPageGetPruneXid(page)))) \
+ HeapPageSetPruneXid(page, xid); \
} while (0)
-#define PageClearPrunable(page) \
- (((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
-
/* ----------------------------------------------------------------
* extern declarations
@@ -432,6 +508,19 @@ do { \
StaticAssertDecl(BLCKSZ == ((BLCKSZ / sizeof(size_t)) * sizeof(size_t)),
"BLCKSZ has to be a multiple of sizeof(size_t)");
+/*
+ * Tuple defrag support for PageRepairFragmentation and PageIndexMultiDelete
+ */
+typedef struct itemIdCompactData
+{
+ uint16 offsetindex; /* linp array index */
+ int16 itemoff; /* page offset of item data */
+ uint16 alignedlen; /* MAXALIGN(item data len) */
+} itemIdCompactData;
+typedef itemIdCompactData *itemIdCompact;
+
+extern int itemoffcompare(const void *item1, const void *item2);
+
extern void PageInit(Page page, Size pageSize, Size specialSize);
extern bool PageIsVerifiedExtended(Page page, BlockNumber blkno, int flags);
extern OffsetNumber PageAddItemExtended(Page page, Item item, Size size,
diff --git a/src/include/storage/itemid.h b/src/include/storage/itemid.h
index e33637ff21..442a72d658 100644
--- a/src/include/storage/itemid.h
+++ b/src/include/storage/itemid.h
@@ -78,6 +78,8 @@ typedef uint16 ItemLength;
#define ItemIdGetRedirect(itemId) \
((itemId)->lp_off)
+#define ItemIdGetTupleEnd(itemId) \
+ (MAXALIGN(ItemIdGetLength((itemId))) + ItemIdGetOffset((itemId)))
/*
* ItemIdIsValid
* True iff item identifier is valid.
diff --git a/src/include/storage/lock.h b/src/include/storage/lock.h
index dc537e20f2..c4d2d8a122 100644
--- a/src/include/storage/lock.h
+++ b/src/include/storage/lock.h
@@ -227,8 +227,8 @@ typedef struct LOCKTAG
/* ID info for a transaction is its TransactionId */
#define SET_LOCKTAG_TRANSACTION(locktag,xid) \
- ((locktag).locktag_field1 = (xid), \
- (locktag).locktag_field2 = 0, \
+ ((locktag).locktag_field1 = (uint32)((xid) & 0xFFFFFFFF), \
+ (locktag).locktag_field2 = (uint32)((xid) >> 32), \
(locktag).locktag_field3 = 0, \
(locktag).locktag_field4 = 0, \
(locktag).locktag_type = LOCKTAG_TRANSACTION, \
@@ -237,8 +237,8 @@ typedef struct LOCKTAG
/* ID info for a virtual transaction is its VirtualTransactionId */
#define SET_LOCKTAG_VIRTUALTRANSACTION(locktag,vxid) \
((locktag).locktag_field1 = (vxid).backendId, \
- (locktag).locktag_field2 = (vxid).localTransactionId, \
- (locktag).locktag_field3 = 0, \
+ (locktag).locktag_field2 = (uint32)((vxid).localTransactionId & 0xFFFFFFFF), \
+ (locktag).locktag_field3 = (uint32)((vxid).localTransactionId >> 32), \
(locktag).locktag_field4 = 0, \
(locktag).locktag_type = LOCKTAG_VIRTUALTRANSACTION, \
(locktag).locktag_lockmethodid = DEFAULT_LOCKMETHOD)
@@ -248,9 +248,9 @@ typedef struct LOCKTAG
* its speculative insert counter.
*/
#define SET_LOCKTAG_SPECULATIVE_INSERTION(locktag,xid,token) \
- ((locktag).locktag_field1 = (xid), \
- (locktag).locktag_field2 = (token), \
- (locktag).locktag_field3 = 0, \
+ ((locktag).locktag_field1 = (uint32)((xid) & 0xFFFFFFFF), \
+ (locktag).locktag_field2 = (uint32)((xid) >> 32), \
+ (locktag).locktag_field3 = (token), \
(locktag).locktag_field4 = 0, \
(locktag).locktag_type = LOCKTAG_SPECULATIVE_TOKEN, \
(locktag).locktag_lockmethodid = DEFAULT_LOCKMETHOD)
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index a58888f9e9..e86edd66a6 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -238,7 +238,7 @@ struct PGPROC
TransactionId clogGroupMemberXid; /* transaction id of clog group member */
XidStatus clogGroupMemberXidStatus; /* transaction status of clog
* group member */
- int clogGroupMemberPage; /* clog page corresponding to
+ int64 clogGroupMemberPage; /* clog page corresponding to
* transaction id of clog group member */
XLogRecPtr clogGroupMemberLsn; /* WAL location of commit record for clog
* group member */
diff --git a/src/include/storage/standby.h b/src/include/storage/standby.h
index 58ea21aa13..5094c50a53 100644
--- a/src/include/storage/standby.h
+++ b/src/include/storage/standby.h
@@ -21,7 +21,7 @@
#include "storage/standbydefs.h"
/* User-settable GUC parameters */
-extern int vacuum_defer_cleanup_age;
+extern int64 vacuum_defer_cleanup_age;
extern int max_standby_archive_delay;
extern int max_standby_streaming_delay;
extern bool log_recovery_conflict_waits;
diff --git a/src/include/storage/sync.h b/src/include/storage/sync.h
index 9737e1eb67..35fc1dff1a 100644
--- a/src/include/storage/sync.h
+++ b/src/include/storage/sync.h
@@ -52,7 +52,7 @@ typedef struct FileTag
int16 handler; /* SyncRequestHandler value, saving space */
int16 forknum; /* ForkNumber, saving space */
RelFileNode rnode;
- uint32 segno;
+ uint64 segno;
} FileTag;
extern void InitSync(void);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 666e545496..4b89f36d64 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -53,6 +53,7 @@ extern int pg_ltoa(int32 l, char *a);
extern int pg_lltoa(int64 ll, char *a);
extern char *pg_ultostr_zeropad(char *str, uint32 value, int32 minwidth);
extern char *pg_ultostr(char *str, uint32 value);
+extern uint64 pg_strtouint64(const char *s, char **endptr, int base);
/* oid.c */
extern oidvector *buildoidvector(const Oid *oids, int n);
diff --git a/src/include/utils/combocid.h b/src/include/utils/combocid.h
index 80fe6d2cea..8465768b6f 100644
--- a/src/include/utils/combocid.h
+++ b/src/include/utils/combocid.h
@@ -15,7 +15,7 @@
#define COMBOCID_H
/*
- * HeapTupleHeaderGetCmin and HeapTupleHeaderGetCmax function prototypes
+ * HeapTupleGetCmin and HeapTupleGetCmax function prototypes
* are in access/htup.h, because that's where the macro definitions that
* those functions replaced used to be.
*/
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 3b4ab65ae2..a53217bbb3 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -295,12 +295,12 @@ typedef struct AutoVacOpts
int vacuum_ins_threshold;
int analyze_threshold;
int vacuum_cost_limit;
- int freeze_min_age;
- int freeze_max_age;
- int freeze_table_age;
- int multixact_freeze_min_age;
- int multixact_freeze_max_age;
- int multixact_freeze_table_age;
+ int64 freeze_min_age;
+ int64 freeze_max_age;
+ int64 freeze_table_age;
+ int64 multixact_freeze_min_age;
+ int64 multixact_freeze_max_age;
+ int64 multixact_freeze_table_age;
int log_min_duration;
float8 vacuum_cost_delay;
float8 vacuum_scale_factor;
diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c
index edb93ec1c4..b29ef18645 100644
--- a/src/pl/plperl/plperl.c
+++ b/src/pl/plperl/plperl.c
@@ -2667,7 +2667,7 @@ validate_plperl_function(plperl_proc_ptr *proc_ptr, HeapTuple procTup)
* This is needed because CREATE OR REPLACE FUNCTION can modify the
* function's pg_proc entry without changing its OID.
************************************************************/
- uptodate = (prodesc->fn_xmin == HeapTupleHeaderGetRawXmin(procTup->t_data) &&
+ uptodate = (prodesc->fn_xmin == HeapTupleGetRawXmin(procTup) &&
ItemPointerEquals(&prodesc->fn_tid, &procTup->t_self));
if (uptodate)
@@ -2791,7 +2791,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger, bool is_event_trigger)
MemoryContextSetIdentifier(proc_cxt, prodesc->proname);
prodesc->fn_cxt = proc_cxt;
prodesc->fn_refcount = 0;
- prodesc->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data);
+ prodesc->fn_xmin = HeapTupleGetRawXmin(procTup);
prodesc->fn_tid = procTup->t_self;
prodesc->nargs = procStruct->pronargs;
prodesc->arg_out_func = (FmgrInfo *) palloc0(prodesc->nargs * sizeof(FmgrInfo));
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index b791c23f06..820db4c0e4 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -171,7 +171,7 @@ recheck:
if (function)
{
/* We have a compiled function, but is it still valid? */
- if (function->fn_xmin == HeapTupleHeaderGetRawXmin(procTup->t_data) &&
+ if (function->fn_xmin == HeapTupleGetRawXmin(procTup) &&
ItemPointerEquals(&function->fn_tid, &procTup->t_self))
function_valid = true;
else
@@ -348,7 +348,7 @@ do_compile(FunctionCallInfo fcinfo,
function->fn_signature = format_procedure(fcinfo->flinfo->fn_oid);
MemoryContextSetIdentifier(func_cxt, function->fn_signature);
function->fn_oid = fcinfo->flinfo->fn_oid;
- function->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data);
+ function->fn_xmin = HeapTupleGetRawXmin(procTup);
function->fn_tid = procTup->t_self;
function->fn_input_collation = fcinfo->fncollation;
function->fn_cxt = func_cxt;
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 915139378e..631b88e320 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -7352,6 +7352,7 @@ deconstruct_composite_datum(Datum value, HeapTupleData *tmptup)
tmptup->t_len = HeapTupleHeaderGetDatumLength(td);
ItemPointerSetInvalid(&(tmptup->t_self));
tmptup->t_tableOid = InvalidOid;
+ HeapTupleSetZeroBase(tmptup);
tmptup->t_data = td;
/* Extract rowtype info and find a tupdesc */
@@ -7526,6 +7527,7 @@ exec_move_row_from_datum(PLpgSQL_execstate *estate,
tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
ItemPointerSetInvalid(&(tmptup.t_self));
tmptup.t_tableOid = InvalidOid;
+ HeapTupleSetZeroBase(&tmptup);
tmptup.t_data = td;
/* Extract rowtype info */
diff --git a/src/pl/plpython/plpy_procedure.c b/src/pl/plpython/plpy_procedure.c
index 494f109b32..9884f74fa7 100644
--- a/src/pl/plpython/plpy_procedure.c
+++ b/src/pl/plpython/plpy_procedure.c
@@ -178,7 +178,7 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
proc->proname = pstrdup(NameStr(procStruct->proname));
MemoryContextSetIdentifier(cxt, proc->proname);
proc->pyname = pstrdup(procName);
- proc->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data);
+ proc->fn_xmin = HeapTupleGetRawXmin(procTup);
proc->fn_tid = procTup->t_self;
proc->fn_readonly = (procStruct->provolatile != PROVOLATILE_VOLATILE);
proc->is_setof = procStruct->proretset;
@@ -419,7 +419,7 @@ PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup)
return false;
/* If the pg_proc tuple has changed, it's not valid */
- if (!(proc->fn_xmin == HeapTupleHeaderGetRawXmin(procTup->t_data) &&
+ if (!(proc->fn_xmin == HeapTupleGetRawXmin(procTup) &&
ItemPointerEquals(&proc->fn_tid, &procTup->t_self)))
return false;
diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c
index 68c9bd1970..4022e11e8e 100644
--- a/src/pl/tcl/pltcl.c
+++ b/src/pl/tcl/pltcl.c
@@ -1430,7 +1430,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
* function's pg_proc entry without changing its OID.
************************************************************/
if (prodesc != NULL &&
- prodesc->fn_xmin == HeapTupleHeaderGetRawXmin(procTup->t_data) &&
+ prodesc->fn_xmin == HeapTupleGetRawXmin(procTup) &&
ItemPointerEquals(&prodesc->fn_tid, &procTup->t_self))
{
/* It's still up-to-date, so we can use it */
@@ -1497,7 +1497,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
prodesc->internal_proname = pstrdup(internal_proname);
prodesc->fn_cxt = proc_cxt;
prodesc->fn_refcount = 0;
- prodesc->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data);
+ prodesc->fn_xmin = HeapTupleGetRawXmin(procTup);
prodesc->fn_tid = procTup->t_self;
prodesc->nargs = procStruct->pronargs;
prodesc->arg_out_func = (FmgrInfo *) palloc0(prodesc->nargs * sizeof(FmgrInfo));
diff --git a/src/test/Makefile b/src/test/Makefile
index 46275915ff..ec6558e33b 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -12,7 +12,8 @@ subdir = src/test
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
-SUBDIRS = perl regress isolation modules authentication recovery subscription
+SUBDIRS = perl regress isolation modules authentication recovery subscription \
+ xid-64
# Test suites that are not safe by default but can be run if selected
# by the user via the whitespace-separated list in variable
diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index be05845248..53add835c1 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -456,6 +456,7 @@ sub init
mkdir $self->archive_dir;
PostgreSQL::Test::Utils::system_or_bail('initdb', '-D', $pgdata, '-A', 'trust', '-N',
+ '-x', '1249835483136', '-m', '2422361554944', '-o', '3594887626752',
@{ $params{extra} });
PostgreSQL::Test::Utils::system_or_bail($ENV{PG_REGRESS}, '--config-auth', $pgdata,
@{ $params{auth_extra} });
diff --git a/src/test/recovery/t/003_recovery_targets.pl b/src/test/recovery/t/003_recovery_targets.pl
index 25dd5ee7ec..360f882ee4 100644
--- a/src/test/recovery/t/003_recovery_targets.pl
+++ b/src/test/recovery/t/003_recovery_targets.pl
@@ -57,7 +57,7 @@ $node_primary->init(has_archiving => 1, allows_streaming => 1);
# Bump the transaction ID epoch. This is useful to stress the portability
# of recovery_target_xid parsing.
-system_or_bail('pg_resetwal', '--epoch', '1', $node_primary->data_dir);
+system_or_bail('pg_resetwal', $node_primary->data_dir);
# Start it
$node_primary->start;
diff --git a/src/test/regress/expected/indirect_toast.out b/src/test/regress/expected/indirect_toast.out
index 44b54dc37f..e9235af1ed 100644
--- a/src/test/regress/expected/indirect_toast.out
+++ b/src/test/regress/expected/indirect_toast.out
@@ -161,6 +161,14 @@ SELECT substring(indtoasttest::text, 1, 200) FROM indtoasttest;
("one-toasted,one-null, via indirect",0,1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
(5 rows)
+create or replace function random_string(len integer) returns text as $$
+select substr((select string_agg(r,'') from (select random()::text as r from generate_series(1,(len+15)/16)) s1), 1, len);
+$$ language sql;
+create table toasttest_main(t text);
+alter table toasttest_main alter column t set storage main;
+insert into toasttest_main (select random_string(len) from generate_series(8000,9000) len);
DROP TABLE indtoasttest;
+DROP TABLE toasttest_main;
DROP FUNCTION update_using_indirect();
+DROP FUNCTION random_string(integer);
RESET default_toast_compression;
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index dd4354fc7d..d52545b443 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -100,7 +100,7 @@ SELECT pg_size_pretty(pg_relation_size('large_tuple_test'::regclass, 'main'));
INSERT INTO large_tuple_test (select 3, NULL);
-- now this tuple won't fit on the second page, but the insert should
-- still succeed by extending the relation
-INSERT INTO large_tuple_test (select 4, repeat('a', 8126));
+INSERT INTO large_tuple_test (select 4, repeat('a', 8112));
DROP TABLE large_tuple_test;
--
-- check indirection (field/array assignment), cf bug #14265
@@ -980,3 +980,17 @@ insert into returningwrtest values (2, 'foo') returning returningwrtest;
(1 row)
drop table returningwrtest;
+-- Check for MaxHeapTupleSize
+create table maxheaptuplesize_test(value text);
+alter table maxheaptuplesize_test alter column value set storage external;
+insert into maxheaptuplesize_test values (repeat('x', 8104));
+insert into maxheaptuplesize_test values (repeat('x', 8112));
+insert into maxheaptuplesize_test values (repeat('x', 8120));
+insert into maxheaptuplesize_test values (repeat('x', 8128));
+insert into maxheaptuplesize_test values (repeat('x', 8136));
+insert into maxheaptuplesize_test values (repeat('x', 8144));
+insert into maxheaptuplesize_test values (repeat('x', 8152));
+insert into maxheaptuplesize_test values (repeat('x', 8160));
+insert into maxheaptuplesize_test values (repeat('x', 8168));
+insert into maxheaptuplesize_test values (repeat('x', 8176));
+drop table maxheaptuplesize_test;
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 4ce6c039b4..6d2ca72567 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -197,7 +197,7 @@ WHERE p1.oid != p2.oid AND
ORDER BY 1, 2;
proargtypes | proargtypes
-----------------------------+--------------------------
- integer | xid
+ bigint | xid
timestamp without time zone | timestamp with time zone
bit | bit varying
txid_snapshot | pg_snapshot
@@ -704,7 +704,7 @@ int8(oid)
tideq(tid,tid)
timestamptz_cmp(timestamp with time zone,timestamp with time zone)
interval_cmp(interval,interval)
-xideqint4(xid,integer)
+xideqint8(xid,bigint)
timetz_eq(time with time zone,time with time zone)
timetz_ne(time with time zone,time with time zone)
timetz_lt(time with time zone,time with time zone)
@@ -818,7 +818,7 @@ pg_lsn_gt(pg_lsn,pg_lsn)
pg_lsn_ne(pg_lsn,pg_lsn)
pg_lsn_cmp(pg_lsn,pg_lsn)
xidneq(xid,xid)
-xidneqint4(xid,integer)
+xidneqint8(xid,bigint)
sha224(bytea)
sha256(bytea)
sha384(bytea)
diff --git a/src/test/regress/expected/select_views.out b/src/test/regress/expected/select_views.out
index 1aeed8452b..d3be84754c 100644
--- a/src/test/regress/expected/select_views.out
+++ b/src/test/regress/expected/select_views.out
@@ -2,9 +2,22 @@
-- SELECT_VIEWS
-- test the views defined in CREATE_VIEWS
--
-SELECT * FROM street;
+SELECT * FROM street ORDER BY name COLLATE "C", thepath::text COLLATE "C";
name | thepath | cname
------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------
+ 100th Ave | [(-122.1657,37.429),(-122.1647,37.432)] | Oakland
+ 107th Ave | [(-122.1555,37.403),(-122.1531,37.41)] | Oakland
+ 14th St | [(-122.299,37.147),(-122.3,37.148)] | Lafayette
+ 19th Ave | [(-122.2366,37.897),(-122.2359,37.905)] | Berkeley
+ 1st St | [(-121.75508,37.89294),(-121.753581,37.90031)] | Oakland
+ 5th St | [(-122.278,37),(-122.2792,37.005),(-122.2803,37.009)] | Lafayette
+ 5th St | [(-122.296,37.615),(-122.2953,37.598)] | Berkeley
+ 82nd Ave | [(-122.1695,37.596),(-122.1681,37.603)] | Berkeley
+ 85th Ave | [(-122.1877,37.466),(-122.186,37.476)] | Oakland
+ 89th Ave | [(-122.1822,37.459),(-122.1803,37.471)] | Oakland
+ 98th Ave | [(-122.1568,37.498),(-122.1558,37.502)] | Oakland
+ 98th Ave | [(-122.1693,37.438),(-122.1682,37.444)] | Oakland
+ 98th Ave | [(-122.2001,37.258),(-122.1974,37.27)] | Lafayette
Access Rd 25 | [(-121.9283,37.894),(-121.9283,37.9)] | Oakland
Ada St | [(-122.2487,37.398),(-122.2496,37.401)] | Lafayette
Agua Fria Creek | [(-121.9254,37.922),(-121.9281,37.889)] | Oakland
@@ -22,8 +35,8 @@ SELECT * FROM street;
Arroyo Las Positas | [(-121.7973,37.997),(-121.7957,37.005)] | Oakland
Arroyo Seco | [(-121.7073,37.766),(-121.6997,37.729)] | Oakland
Ash St | [(-122.0408,37.31),(-122.04,37.292)] | Oakland
- Avenue 134th | [(-122.1823,37.002),(-122.1851,37.992)] | Oakland
Avenue 134th | [(-122.1823,37.002),(-122.1851,37.992)] | Berkeley
+ Avenue 134th | [(-122.1823,37.002),(-122.1851,37.992)] | Oakland
Avenue 140th | [(-122.1656,37.003),(-122.1691,37.988)] | Oakland
Avenue 140th | [(-122.1656,37.003),(-122.1691,37.988)] | Berkeley
Avenue D | [(-122.298,37.848),(-122.3024,37.849)] | Berkeley
@@ -37,14 +50,14 @@ SELECT * FROM street;
Broadmore Ave | [(-122.095,37.522),(-122.0936,37.497)] | Oakland
Broadway | [(-122.2409,37.586),(-122.2395,37.601)] | Berkeley
Buckingham Blvd | [(-122.2231,37.59),(-122.2214,37.606)] | Berkeley
+ Butterfield Dr | [(-122.0838,37.002),(-122.0834,37.987)] | Berkeley
Butterfield Dr | [(-122.0838,37.002),(-122.0834,37.987)] | Oakland
Butterfield Dr | [(-122.0838,37.002),(-122.0834,37.987)] | Oakland
- Butterfield Dr | [(-122.0838,37.002),(-122.0834,37.987)] | Berkeley
C St | [(-122.1768,37.46),(-122.1749,37.435)] | Oakland
Calaveras Creek | [(-121.8203,37.035),(-121.8207,37.931)] | Oakland
Calaveras Creek | [(-121.8203,37.035),(-121.8207,37.931)] | Oakland
- California St | [(-122.2032,37.005),(-122.2016,37.996)] | Berkeley
California St | [(-122.2032,37.005),(-122.2016,37.996)] | Lafayette
+ California St | [(-122.2032,37.005),(-122.2016,37.996)] | Berkeley
Cameron Ave | [(-122.1316,37.502),(-122.1327,37.481)] | Oakland
Campus Dr | [(-122.1704,37.905),(-122.1678,37.868),(-122.1671,37.865)] | Berkeley
Capricorn Ave | [(-122.2176,37.404),(-122.2164,37.384)] | Lafayette
@@ -55,8 +68,8 @@ SELECT * FROM street;
Central Ave | [(-122.2343,37.602),(-122.2331,37.595)] | Berkeley
Chambers Dr | [(-122.2004,37.352),(-122.1972,37.368)] | Lafayette
Chambers Lane | [(-122.2001,37.359),(-122.1975,37.371)] | Lafayette
- Champion St | [(-122.214,37.991),(-122.2147,37.002)] | Berkeley
Champion St | [(-122.214,37.991),(-122.2147,37.002)] | Lafayette
+ Champion St | [(-122.214,37.991),(-122.2147,37.002)] | Berkeley
Chapman Dr | [(-122.0421,37.504),(-122.0414,37.498)] | Oakland
Charles St | [(-122.0255,37.505),(-122.0252,37.499)] | Oakland
Cherry St | [(-122.0437,37.42),(-122.0434,37.413)] | Oakland
@@ -77,9 +90,9 @@ SELECT * FROM street;
Cull Canyon Road | [(-122.0536,37.435),(-122.0499,37.315)] | Oakland
Cull Creek | [(-122.0624,37.875),(-122.0582,37.527)] | Berkeley
D St | [(-122.1811,37.505),(-122.1805,37.497)] | Oakland
+ Decoto Road | [(-122.0159,37.006),(-122.016,37.002),(-122.0164,37.993)] | Berkeley
Decoto Road | [(-122.0159,37.006),(-122.016,37.002),(-122.0164,37.993)] | Oakland
Decoto Road | [(-122.0159,37.006),(-122.016,37.002),(-122.0164,37.993)] | Oakland
- Decoto Road | [(-122.0159,37.006),(-122.016,37.002),(-122.0164,37.993)] | Berkeley
Deering St | [(-122.2146,37.904),(-122.2126,37.897)] | Berkeley
Dimond Ave | [(-122.2167,37.994),(-122.2162,37.006)] | Berkeley
Dimond Ave | [(-122.2167,37.994),(-122.2162,37.006)] | Lafayette
@@ -117,12 +130,12 @@ SELECT * FROM street;
I- 580 | [(-121.9322,37.989),(-121.9243,37.006),(-121.9217,37.014)] | Oakland
I- 580 | [(-122.018,37.019),(-122.0009,37.032),(-121.9787,37.983),(-121.958,37.984),(-121.9571,37.986)] | Oakland
I- 580 | [(-122.018,37.019),(-122.0009,37.032),(-121.9787,37.983),(-121.958,37.984),(-121.9571,37.986)] | Oakland
- I- 580 | [(-122.1108,37.023),(-122.1101,37.02),(-122.108103,37.00764),(-122.108,37.007),(-122.1069,37.998),(-122.1064,37.994),(-122.1053,37.982),(-122.1048,37.977),(-122.1032,37.958),(-122.1026,37.953),(-122.1013,37.938),(-122.0989,37.911),(-122.0984,37.91),(-122.098,37.908)] | Oakland
I- 580 | [(-122.1108,37.023),(-122.1101,37.02),(-122.108103,37.00764),(-122.108,37.007),(-122.1069,37.998),(-122.1064,37.994),(-122.1053,37.982),(-122.1048,37.977),(-122.1032,37.958),(-122.1026,37.953),(-122.1013,37.938),(-122.0989,37.911),(-122.0984,37.91),(-122.098,37.908)] | Berkeley
+ I- 580 | [(-122.1108,37.023),(-122.1101,37.02),(-122.108103,37.00764),(-122.108,37.007),(-122.1069,37.998),(-122.1064,37.994),(-122.1053,37.982),(-122.1048,37.977),(-122.1032,37.958),(-122.1026,37.953),(-122.1013,37.938),(-122.0989,37.911),(-122.0984,37.91),(-122.098,37.908)] | Oakland
I- 580 | [(-122.1543,37.703),(-122.1535,37.694),(-122.1512,37.655),(-122.1475,37.603),(-122.1468,37.583),(-122.1472,37.569),(-122.149044,37.54874),(-122.1493,37.546),(-122.1501,37.532),(-122.1506,37.509),(-122.1495,37.482),(-122.1487,37.467),(-122.1477,37.447),(-122.1414,37.383),(-122.1404,37.376),(-122.1398,37.372),(-122.139,37.356),(-122.1388,37.353),(-122.1385,37.34),(-122.1382,37.33),(-122.1378,37.316)] | Oakland
I- 580 | [(-122.1543,37.703),(-122.1535,37.694),(-122.1512,37.655),(-122.1475,37.603),(-122.1468,37.583),(-122.1472,37.569),(-122.149044,37.54874),(-122.1493,37.546),(-122.1501,37.532),(-122.1506,37.509),(-122.1495,37.482),(-122.1487,37.467),(-122.1477,37.447),(-122.1414,37.383),(-122.1404,37.376),(-122.1398,37.372),(-122.139,37.356),(-122.1388,37.353),(-122.1385,37.34),(-122.1382,37.33),(-122.1378,37.316)] | Berkeley
- I- 580 | [(-122.2197,37.99),(-122.22,37.99),(-122.222092,37.99523),(-122.2232,37.998),(-122.224146,37.99963),(-122.2261,37.003),(-122.2278,37.007),(-122.2302,37.026),(-122.2323,37.043),(-122.2344,37.059),(-122.235405,37.06427),(-122.2365,37.07)] | Berkeley
I- 580 | [(-122.2197,37.99),(-122.22,37.99),(-122.222092,37.99523),(-122.2232,37.998),(-122.224146,37.99963),(-122.2261,37.003),(-122.2278,37.007),(-122.2302,37.026),(-122.2323,37.043),(-122.2344,37.059),(-122.235405,37.06427),(-122.2365,37.07)] | Lafayette
+ I- 580 | [(-122.2197,37.99),(-122.22,37.99),(-122.222092,37.99523),(-122.2232,37.998),(-122.224146,37.99963),(-122.2261,37.003),(-122.2278,37.007),(-122.2302,37.026),(-122.2323,37.043),(-122.2344,37.059),(-122.235405,37.06427),(-122.2365,37.07)] | Berkeley
I- 580 Ramp | [(-121.8521,37.011),(-121.8479,37.999),(-121.8476,37.999),(-121.8456,37.01),(-121.8455,37.011)] | Oakland
I- 580 Ramp | [(-121.8521,37.011),(-121.8479,37.999),(-121.8476,37.999),(-121.8456,37.01),(-121.8455,37.011)] | Oakland
I- 580 Ramp | [(-121.8743,37.014),(-121.8722,37.999),(-121.8714,37.999)] | Oakland
@@ -136,8 +149,8 @@ SELECT * FROM street;
I- 580 Ramp | [(-122.0941,37.897),(-122.0943,37.902)] | Berkeley
I- 580 Ramp | [(-122.096,37.888),(-122.0962,37.891),(-122.0964,37.9)] | Berkeley
I- 580 Ramp | [(-122.101,37.898),(-122.1005,37.902),(-122.0989,37.911)] | Berkeley
- I- 580 Ramp | [(-122.1086,37.003),(-122.1068,37.993),(-122.1066,37.992),(-122.1053,37.982)] | Oakland
I- 580 Ramp | [(-122.1086,37.003),(-122.1068,37.993),(-122.1066,37.992),(-122.1053,37.982)] | Berkeley
+ I- 580 Ramp | [(-122.1086,37.003),(-122.1068,37.993),(-122.1066,37.992),(-122.1053,37.982)] | Oakland
I- 580 Ramp | [(-122.1414,37.383),(-122.1407,37.376),(-122.1403,37.372),(-122.139,37.356)] | Oakland
I- 580/I-680 Ramp | ((-121.9207,37.988),(-121.9192,37.016)) | Oakland
I- 580/I-680 Ramp | ((-121.9207,37.988),(-121.9192,37.016)) | Oakland
@@ -158,16 +171,16 @@ SELECT * FROM street;
I- 880 | ((-121.9669,37.075),(-121.9663,37.071),(-121.9656,37.065),(-121.9618,37.037),(-121.95689,37),(-121.948,37.933)) | Oakland
I- 880 | [(-121.948,37.933),(-121.9471,37.925),(-121.9467,37.923),(-121.946,37.918),(-121.9452,37.912),(-121.937,37.852)] | Oakland
I- 880 | [(-122.0219,37.466),(-122.0205,37.447),(-122.020331,37.44447),(-122.020008,37.43962),(-122.0195,37.432),(-122.0193,37.429),(-122.0164,37.393),(-122.010219,37.34771),(-122.0041,37.313)] | Oakland
- I- 880 | [(-122.0375,37.632),(-122.0359,37.619),(-122.0358,37.616),(-122.034514,37.60409),(-122.031876,37.57965),(-122.031193,37.57332),(-122.03016,37.56375),(-122.02943,37.55698),(-122.028689,37.54929),(-122.027833,37.53908),(-122.025979,37.51698),(-122.0238,37.491)] | Oakland
I- 880 | [(-122.0375,37.632),(-122.0359,37.619),(-122.0358,37.616),(-122.034514,37.60409),(-122.031876,37.57965),(-122.031193,37.57332),(-122.03016,37.56375),(-122.02943,37.55698),(-122.028689,37.54929),(-122.027833,37.53908),(-122.025979,37.51698),(-122.0238,37.491)] | Berkeley
- I- 880 | [(-122.0612,37.003),(-122.0604,37.991),(-122.0596,37.982),(-122.0585,37.967),(-122.0583,37.961),(-122.0553,37.918),(-122.053635,37.89475),(-122.050759,37.8546),(-122.05,37.844),(-122.0485,37.817),(-122.0483,37.813),(-122.0482,37.811)] | Oakland
+ I- 880 | [(-122.0375,37.632),(-122.0359,37.619),(-122.0358,37.616),(-122.034514,37.60409),(-122.031876,37.57965),(-122.031193,37.57332),(-122.03016,37.56375),(-122.02943,37.55698),(-122.028689,37.54929),(-122.027833,37.53908),(-122.025979,37.51698),(-122.0238,37.491)] | Oakland
I- 880 | [(-122.0612,37.003),(-122.0604,37.991),(-122.0596,37.982),(-122.0585,37.967),(-122.0583,37.961),(-122.0553,37.918),(-122.053635,37.89475),(-122.050759,37.8546),(-122.05,37.844),(-122.0485,37.817),(-122.0483,37.813),(-122.0482,37.811)] | Oakland
I- 880 | [(-122.0612,37.003),(-122.0604,37.991),(-122.0596,37.982),(-122.0585,37.967),(-122.0583,37.961),(-122.0553,37.918),(-122.053635,37.89475),(-122.050759,37.8546),(-122.05,37.844),(-122.0485,37.817),(-122.0483,37.813),(-122.0482,37.811)] | Berkeley
+ I- 880 | [(-122.0612,37.003),(-122.0604,37.991),(-122.0596,37.982),(-122.0585,37.967),(-122.0583,37.961),(-122.0553,37.918),(-122.053635,37.89475),(-122.050759,37.8546),(-122.05,37.844),(-122.0485,37.817),(-122.0483,37.813),(-122.0482,37.811)] | Oakland
I- 880 | [(-122.0831,37.312),(-122.0819,37.296),(-122.081,37.285),(-122.0786,37.248),(-122.078,37.24),(-122.077642,37.23496),(-122.076983,37.22567),(-122.076599,37.22026),(-122.076229,37.21505),(-122.0758,37.209)] | Oakland
I- 880 | [(-122.0978,37.528),(-122.096,37.496),(-122.0931,37.453),(-122.09277,37.4496),(-122.090189,37.41442),(-122.0896,37.405),(-122.085,37.34)] | Oakland
I- 880 | [(-122.1365,37.902),(-122.1358,37.898),(-122.1333,37.881),(-122.1323,37.874),(-122.1311,37.866),(-122.1308,37.865),(-122.1307,37.864),(-122.1289,37.851),(-122.1277,37.843),(-122.1264,37.834),(-122.1231,37.812),(-122.1165,37.766),(-122.1104,37.72),(-122.109695,37.71094),(-122.109,37.702),(-122.108312,37.69168),(-122.1076,37.681)] | Berkeley
- I- 880 | [(-122.1755,37.185),(-122.1747,37.178),(-122.1742,37.173),(-122.1692,37.126),(-122.167792,37.11594),(-122.16757,37.11435),(-122.1671,37.111),(-122.1655,37.1),(-122.165169,37.09811),(-122.1641,37.092),(-122.1596,37.061),(-122.158381,37.05275),(-122.155991,37.03657),(-122.1531,37.017),(-122.1478,37.98),(-122.1407,37.932),(-122.1394,37.924),(-122.1389,37.92),(-122.1376,37.91)] | Oakland
I- 880 | [(-122.1755,37.185),(-122.1747,37.178),(-122.1742,37.173),(-122.1692,37.126),(-122.167792,37.11594),(-122.16757,37.11435),(-122.1671,37.111),(-122.1655,37.1),(-122.165169,37.09811),(-122.1641,37.092),(-122.1596,37.061),(-122.158381,37.05275),(-122.155991,37.03657),(-122.1531,37.017),(-122.1478,37.98),(-122.1407,37.932),(-122.1394,37.924),(-122.1389,37.92),(-122.1376,37.91)] | Berkeley
+ I- 880 | [(-122.1755,37.185),(-122.1747,37.178),(-122.1742,37.173),(-122.1692,37.126),(-122.167792,37.11594),(-122.16757,37.11435),(-122.1671,37.111),(-122.1655,37.1),(-122.165169,37.09811),(-122.1641,37.092),(-122.1596,37.061),(-122.158381,37.05275),(-122.155991,37.03657),(-122.1531,37.017),(-122.1478,37.98),(-122.1407,37.932),(-122.1394,37.924),(-122.1389,37.92),(-122.1376,37.91)] | Oakland
I- 880 | [(-122.2214,37.711),(-122.2202,37.699),(-122.2199,37.695),(-122.219,37.682),(-122.2184,37.672),(-122.2173,37.652),(-122.2159,37.638),(-122.2144,37.616),(-122.2138,37.612),(-122.2135,37.609),(-122.212,37.592),(-122.2116,37.586),(-122.2111,37.581)] | Berkeley
I- 880 | [(-122.2707,37.975),(-122.2693,37.972),(-122.2681,37.966),(-122.267,37.962),(-122.2659,37.957),(-122.2648,37.952),(-122.2636,37.946),(-122.2625,37.935),(-122.2617,37.927),(-122.2607,37.921),(-122.2593,37.916),(-122.258,37.911),(-122.2536,37.898),(-122.2432,37.858),(-122.2408,37.845),(-122.2386,37.827),(-122.2374,37.811)] | Berkeley
I- 880 Ramp | [(-122.0019,37.301),(-122.002,37.293)] | Oakland
@@ -202,28 +215,28 @@ SELECT * FROM street;
Laguna Ave | [(-122.2099,37.989),(-122.2089,37)] | Berkeley
Laguna Ave | [(-122.2099,37.989),(-122.2089,37)] | Lafayette
Lakehurst Cir | [(-122.284729,37.89025),(-122.286096,37.90364)] | Berkeley
- Lakeshore Ave | [(-122.2586,37.99),(-122.2556,37.006)] | Berkeley
Lakeshore Ave | [(-122.2586,37.99),(-122.2556,37.006)] | Lafayette
+ Lakeshore Ave | [(-122.2586,37.99),(-122.2556,37.006)] | Berkeley
Las Positas Road | [(-121.764488,37.99199),(-121.75569,37.02022)] | Oakland
Las Positas Road | [(-121.764488,37.99199),(-121.75569,37.02022)] | Oakland
- Linden St | [(-122.2867,37.998),(-122.2864,37.008)] | Berkeley
Linden St | [(-122.2867,37.998),(-122.2864,37.008)] | Lafayette
+ Linden St | [(-122.2867,37.998),(-122.2864,37.008)] | Berkeley
Livermore Ave | [(-121.7687,37.448),(-121.769,37.375)] | Oakland
Livermore Ave | [(-121.7687,37.448),(-121.769,37.375)] | Oakland
Livermore Ave | [(-121.772719,37.99085),(-121.7728,37.001)] | Oakland
Livermore Ave | [(-121.772719,37.99085),(-121.7728,37.001)] | Oakland
- Locust St | [(-122.1606,37.007),(-122.1593,37.987)] | Oakland
Locust St | [(-122.1606,37.007),(-122.1593,37.987)] | Berkeley
+ Locust St | [(-122.1606,37.007),(-122.1593,37.987)] | Oakland
Logan Ct | [(-122.0053,37.492),(-122.0061,37.484)] | Oakland
Magnolia St | [(-122.0971,37.5),(-122.0962,37.484)] | Oakland
Mandalay Road | [(-122.2322,37.397),(-122.2321,37.403)] | Lafayette
Marin Ave | [(-122.2741,37.894),(-122.272,37.901)] | Berkeley
Martin Luther King Jr Way | [(-122.2712,37.608),(-122.2711,37.599)] | Berkeley
Mattos Dr | [(-122.0005,37.502),(-122.000898,37.49683)] | Oakland
- Maubert Ave | [(-122.1114,37.009),(-122.1096,37.995)] | Oakland
Maubert Ave | [(-122.1114,37.009),(-122.1096,37.995)] | Berkeley
- McClure Ave | [(-122.1431,37.001),(-122.1436,37.998)] | Oakland
+ Maubert Ave | [(-122.1114,37.009),(-122.1096,37.995)] | Oakland
McClure Ave | [(-122.1431,37.001),(-122.1436,37.998)] | Berkeley
+ McClure Ave | [(-122.1431,37.001),(-122.1436,37.998)] | Oakland
Medlar Dr | [(-122.0627,37.378),(-122.0625,37.375)] | Oakland
Mildred Ct | [(-122.0002,37.388),(-121.9998,37.386)] | Oakland
Miller Road | [(-122.0902,37.645),(-122.0865,37.545)] | Berkeley
@@ -242,8 +255,8 @@ SELECT * FROM street;
Parkridge Dr | [(-122.1438,37.884),(-122.1428,37.9)] | Berkeley
Parkside Dr | [(-122.0475,37.603),(-122.0443,37.596)] | Berkeley
Paseo Padre Pkwy | [(-121.9143,37.005),(-121.913522,37)] | Oakland
- Paseo Padre Pkwy | [(-122.0021,37.639),(-121.996,37.628)] | Oakland
Paseo Padre Pkwy | [(-122.0021,37.639),(-121.996,37.628)] | Berkeley
+ Paseo Padre Pkwy | [(-122.0021,37.639),(-121.996,37.628)] | Oakland
Pearl St | [(-122.2383,37.594),(-122.2366,37.615)] | Berkeley
Periwinkle Road | [(-122.0451,37.301),(-122.044758,37.29844)] | Oakland
Pimlico Dr | [(-121.8616,37.998),(-121.8618,37.008)] | Oakland
@@ -254,11 +267,11 @@ SELECT * FROM street;
Railroad Ave | [(-122.0245,37.013),(-122.0234,37.003),(-122.0223,37.993)] | Oakland
Railroad Ave | [(-122.0245,37.013),(-122.0234,37.003),(-122.0223,37.993)] | Berkeley
Ranspot Dr | [(-122.0972,37.999),(-122.0959,37)] | Oakland
- Ranspot Dr | [(-122.0972,37.999),(-122.0959,37)] | Oakland
Ranspot Dr | [(-122.0972,37.999),(-122.0959,37)] | Berkeley
+ Ranspot Dr | [(-122.0972,37.999),(-122.0959,37)] | Oakland
Redding St | [(-122.1978,37.901),(-122.1975,37.895)] | Berkeley
- Redwood Road | [(-122.1493,37.98),(-122.1437,37.001)] | Oakland
Redwood Road | [(-122.1493,37.98),(-122.1437,37.001)] | Berkeley
+ Redwood Road | [(-122.1493,37.98),(-122.1437,37.001)] | Oakland
Roca Dr | [(-122.0335,37.609),(-122.0314,37.599)] | Berkeley
Rosedale Ct | [(-121.9232,37.9),(-121.924,37.897)] | Oakland
Sacramento St | [(-122.2799,37.606),(-122.2797,37.597)] | Berkeley
@@ -266,8 +279,8 @@ SELECT * FROM street;
Saginaw Ct | [(-121.8803,37.898),(-121.8806,37.901)] | Oakland
San Andreas Dr | [(-122.0609,37.9),(-122.0614,37.895)] | Berkeley
Santa Maria Ave | [(-122.0773,37),(-122.0773,37.98)] | Oakland
- Santa Maria Ave | [(-122.0773,37),(-122.0773,37.98)] | Oakland
Santa Maria Ave | [(-122.0773,37),(-122.0773,37.98)] | Berkeley
+ Santa Maria Ave | [(-122.0773,37),(-122.0773,37.98)] | Oakland
Shattuck Ave | [(-122.2686,37.904),(-122.2686,37.897)] | Berkeley
Sheridan Road | [(-122.2279,37.425),(-122.2253,37.411),(-122.2223,37.377)] | Lafayette
Shoreline Dr | [(-122.2657,37.603),(-122.2648,37.6)] | Berkeley
@@ -317,27 +330,14 @@ SELECT * FROM street;
Welch Creek Road | [(-121.7695,37.386),(-121.7737,37.413)] | Oakland
West Loop Road | [(-122.0576,37.604),(-122.0602,37.586)] | Berkeley
Western Pacific Railroad Spur | [(-122.0394,37.018),(-122.0394,37.961)] | Oakland
- Western Pacific Railroad Spur | [(-122.0394,37.018),(-122.0394,37.961)] | Oakland
Western Pacific Railroad Spur | [(-122.0394,37.018),(-122.0394,37.961)] | Berkeley
+ Western Pacific Railroad Spur | [(-122.0394,37.018),(-122.0394,37.961)] | Oakland
Whitlock Creek | [(-121.74683,37.91276),(-121.733107,37)] | Oakland
Whitlock Creek | [(-121.74683,37.91276),(-121.733107,37)] | Oakland
Willimet Way | [(-122.0964,37.517),(-122.0949,37.493)] | Oakland
Wisconsin St | [(-122.1994,37.017),(-122.1975,37.998),(-122.1971,37.994)] | Oakland
Wisconsin St | [(-122.1994,37.017),(-122.1975,37.998),(-122.1971,37.994)] | Berkeley
Wp Railroad | [(-122.254,37.902),(-122.2506,37.891)] | Berkeley
- 100th Ave | [(-122.1657,37.429),(-122.1647,37.432)] | Oakland
- 107th Ave | [(-122.1555,37.403),(-122.1531,37.41)] | Oakland
- 14th St | [(-122.299,37.147),(-122.3,37.148)] | Lafayette
- 19th Ave | [(-122.2366,37.897),(-122.2359,37.905)] | Berkeley
- 1st St | [(-121.75508,37.89294),(-121.753581,37.90031)] | Oakland
- 5th St | [(-122.278,37),(-122.2792,37.005),(-122.2803,37.009)] | Lafayette
- 5th St | [(-122.296,37.615),(-122.2953,37.598)] | Berkeley
- 82nd Ave | [(-122.1695,37.596),(-122.1681,37.603)] | Berkeley
- 85th Ave | [(-122.1877,37.466),(-122.186,37.476)] | Oakland
- 89th Ave | [(-122.1822,37.459),(-122.1803,37.471)] | Oakland
- 98th Ave | [(-122.1568,37.498),(-122.1558,37.502)] | Oakland
- 98th Ave | [(-122.1693,37.438),(-122.1682,37.444)] | Oakland
- 98th Ave | [(-122.2001,37.258),(-122.1974,37.27)] | Lafayette
(333 rows)
SELECT name, #thepath FROM iexit ORDER BY name COLLATE "C", 2;
diff --git a/src/test/regress/expected/txid.out b/src/test/regress/expected/txid.out
index 95ba66e95e..2ea4434f51 100644
--- a/src/test/regress/expected/txid.out
+++ b/src/test/regress/expected/txid.out
@@ -238,9 +238,11 @@ SELECT txid_snapshot '1:9223372036854775807:3';
(1 row)
SELECT txid_snapshot '1:9223372036854775808:3';
-ERROR: invalid input syntax for type pg_snapshot: "1:9223372036854775808:3"
-LINE 1: SELECT txid_snapshot '1:9223372036854775808:3';
- ^
+ txid_snapshot
+-------------------------
+ 1:9223372036854775808:3
+(1 row)
+
-- test txid_current_if_assigned
BEGIN;
SELECT txid_current_if_assigned() IS NULL;
diff --git a/src/test/regress/expected/xid.out b/src/test/regress/expected/xid.out
index d8e76f3321..3252bdc28d 100644
--- a/src/test/regress/expected/xid.out
+++ b/src/test/regress/expected/xid.out
@@ -8,9 +8,9 @@ select '010'::xid,
'42'::xid8,
'0xffffffffffffffff'::xid8,
'-1'::xid8;
- xid | xid | xid | xid | xid8 | xid8 | xid8 | xid8
------+-----+------------+------------+------+------+----------------------+----------------------
- 8 | 42 | 4294967295 | 4294967295 | 8 | 42 | 18446744073709551615 | 18446744073709551615
+ xid | xid | xid | xid | xid8 | xid8 | xid8 | xid8
+-----+-----+------------+----------------------+------+------+----------------------+----------------------
+ 8 | 42 | 4294967295 | 18446744073709551615 | 8 | 42 | 18446744073709551615 | 18446744073709551615
(1 row)
-- garbage values are not yet rejected (perhaps they should be)
@@ -381,9 +381,11 @@ SELECT pg_snapshot '1:9223372036854775807:3';
(1 row)
SELECT pg_snapshot '1:9223372036854775808:3';
-ERROR: invalid input syntax for type pg_snapshot: "1:9223372036854775808:3"
-LINE 1: SELECT pg_snapshot '1:9223372036854775808:3';
- ^
+ pg_snapshot
+-------------------------
+ 1:9223372036854775808:3
+(1 row)
+
-- test pg_current_xact_id_if_assigned
BEGIN;
SELECT pg_current_xact_id_if_assigned() IS NULL;
diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c
index db8427dd9b..0f6ca0c2cb 100644
--- a/src/test/regress/pg_regress.c
+++ b/src/test/regress/pg_regress.c
@@ -2261,7 +2261,7 @@ regression_main(int argc, char *argv[],
/* initdb */
header(_("initializing database system"));
snprintf(buf, sizeof(buf),
- "\"%s%sinitdb\" -D \"%s/data\" --no-clean --no-sync%s%s > \"%s/log/initdb.log\" 2>&1",
+ "\"%s%sinitdb\" -D \"%s/data\" -x 1249835483136 -m 2422361554944 -o 3594887626752 --no-clean --no-sync%s%s > \"%s/log/initdb.log\" 2>&1",
bindir ? bindir : "",
bindir ? "/" : "",
temp_instance,
diff --git a/src/test/regress/sql/indirect_toast.sql b/src/test/regress/sql/indirect_toast.sql
index 3e2f6c0237..0444867f66 100644
--- a/src/test/regress/sql/indirect_toast.sql
+++ b/src/test/regress/sql/indirect_toast.sql
@@ -76,7 +76,18 @@ SELECT substring(indtoasttest::text, 1, 200) FROM indtoasttest;
VACUUM FREEZE indtoasttest;
SELECT substring(indtoasttest::text, 1, 200) FROM indtoasttest;
+create or replace function random_string(len integer) returns text as $$
+select substr((select string_agg(r,'') from (select random()::text as r from generate_series(1,(len+15)/16)) s1), 1, len);
+$$ language sql;
+
+create table toasttest_main(t text);
+alter table toasttest_main alter column t set storage main;
+
+insert into toasttest_main (select random_string(len) from generate_series(8000,9000) len);
+
DROP TABLE indtoasttest;
+DROP TABLE toasttest_main;
DROP FUNCTION update_using_indirect();
+DROP FUNCTION random_string(integer);
RESET default_toast_compression;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index bdcffd0314..7ada0801eb 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -55,7 +55,7 @@ INSERT INTO large_tuple_test (select 3, NULL);
-- now this tuple won't fit on the second page, but the insert should
-- still succeed by extending the relation
-INSERT INTO large_tuple_test (select 4, repeat('a', 8126));
+INSERT INTO large_tuple_test (select 4, repeat('a', 8112));
DROP TABLE large_tuple_test;
@@ -597,3 +597,18 @@ alter table returningwrtest2 drop c;
alter table returningwrtest attach partition returningwrtest2 for values in (2);
insert into returningwrtest values (2, 'foo') returning returningwrtest;
drop table returningwrtest;
+
+-- Check for MaxHeapTupleSize
+create table maxheaptuplesize_test(value text);
+alter table maxheaptuplesize_test alter column value set storage external;
+insert into maxheaptuplesize_test values (repeat('x', 8104));
+insert into maxheaptuplesize_test values (repeat('x', 8112));
+insert into maxheaptuplesize_test values (repeat('x', 8120));
+insert into maxheaptuplesize_test values (repeat('x', 8128));
+insert into maxheaptuplesize_test values (repeat('x', 8136));
+insert into maxheaptuplesize_test values (repeat('x', 8144));
+insert into maxheaptuplesize_test values (repeat('x', 8152));
+insert into maxheaptuplesize_test values (repeat('x', 8160));
+insert into maxheaptuplesize_test values (repeat('x', 8168));
+insert into maxheaptuplesize_test values (repeat('x', 8176));
+drop table maxheaptuplesize_test;
diff --git a/src/test/regress/sql/select_views.sql b/src/test/regress/sql/select_views.sql
index e742f13699..70e663e350 100644
--- a/src/test/regress/sql/select_views.sql
+++ b/src/test/regress/sql/select_views.sql
@@ -3,7 +3,7 @@
-- test the views defined in CREATE_VIEWS
--
-SELECT * FROM street;
+SELECT * FROM street ORDER BY name COLLATE "C", thepath::text COLLATE "C";
SELECT name, #thepath FROM iexit ORDER BY name COLLATE "C", 2;
diff --git a/src/test/xid-64/Makefile b/src/test/xid-64/Makefile
new file mode 100644
index 0000000000..3b1e50dfc0
--- /dev/null
+++ b/src/test/xid-64/Makefile
@@ -0,0 +1,22 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/test/xid-64
+#
+# Copyright (c) 2018, Postgres Professional
+#
+# src/test/xid-64/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/test/xid-64
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+check:
+ $(prove_check)
+
+installcheck:
+ $(prove_installcheck)
+
+clean distclean maintainer-clean:
+ rm -rf tmp_check
diff --git a/src/test/xid-64/README b/src/test/xid-64/README
new file mode 100644
index 0000000000..01c0a1a1f7
--- /dev/null
+++ b/src/test/xid-64/README
@@ -0,0 +1,16 @@
+src/test/xid-64/README
+
+Regression tests for 64-bit XIDs
+=============================================
+
+This directory contains a test suite for 64-bit xids.
+
+Running the tests
+=================
+
+ make check
+
+NOTE: This creates a temporary installation, and some tests may
+create one or multiple nodes.
+
+NOTE: This requires the --enable-tap-tests argument to configure.
diff --git a/src/test/xid-64/t/001_test_large_xids.pl b/src/test/xid-64/t/001_test_large_xids.pl
new file mode 100644
index 0000000000..6a039d60d0
--- /dev/null
+++ b/src/test/xid-64/t/001_test_large_xids.pl
@@ -0,0 +1,53 @@
+# Tests for large xid values
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More tests => 9;
+
+use bigint;
+
+sub command_output
+{
+ my ($cmd) = @_;
+ my ($stdout, $stderr);
+ print("# Running: " . join(" ", @{$cmd}) . "\n");
+ my $result = IPC::Run::run $cmd, '>', \$stdout, '2>', \$stderr;
+ ok($result, "@$cmd exit code 0");
+ is($stderr, '', "@$cmd no stderr");
+ return $stdout;
+}
+
+my $START_VAL = 2**32;
+my $MAX_VAL = 2**62;
+
+my $ixid = $START_VAL + int(rand($MAX_VAL - $START_VAL));
+my $imxid = $START_VAL + int(rand($MAX_VAL - $START_VAL));
+my $imoff = $START_VAL + int(rand($MAX_VAL - $START_VAL));
+
+# Initialize master node with the random xid-related parameters
+my $node = PostgreSQL::Test::Cluster->new('master');
+$node->init(extra => [ "--xid=$ixid", "--multixact-id=$imxid", "--multixact-offset=$imoff" ]);
+$node->start;
+
+# Initialize master node and check the xid-related parameters
+my $pgcd_output = command_output(
+ [ 'pg_controldata', '-D', $node->data_dir ] );
+print($pgcd_output); print('\n');
+ok($pgcd_output =~ qr/Latest checkpoint's NextXID:\s*(\d+)/, "XID found");
+my ($nextxid) = ($1);
+ok($nextxid >= $ixid && $nextxid < $ixid + 1000,
+ "Latest checkpoint's NextXID ($nextxid) is close to the initial xid ($ixid).");
+ok($pgcd_output =~ qr/Latest checkpoint's NextMultiXactId:\s*(\d+)/, "MultiXactId found");
+my ($nextmxid) = ($1);
+ok($nextmxid >= $imxid && $nextmxid < $imxid + 1000,
+ "Latest checkpoint's NextMultiXactId ($nextmxid) is close to the initial multiXactId ($imxid).");
+ok($pgcd_output =~ qr/Latest checkpoint's NextMultiOffset:\s*(\d+)/, "MultiOffset found");
+my ($nextmoff) = ($1);
+ok($nextmoff >= $imoff && $nextmoff < $imoff + 1000,
+ "Latest checkpoint's NextMultiOffset ($nextmoff) is close to the initial multiOffset ($imoff).");
+
+# Run pgbench to check whether the database is working properly
+$node->command_ok(
+ [ qw(pgbench --initialize --no-vacuum --scale=10) ],
+ 'pgbench finished without errors');
diff --git a/src/test/xid-64/t/002_test_gucs.pl b/src/test/xid-64/t/002_test_gucs.pl
new file mode 100644
index 0000000000..2b76374f01
--- /dev/null
+++ b/src/test/xid-64/t/002_test_gucs.pl
@@ -0,0 +1,78 @@
+# Tests for guc boundary values
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More tests => 19;
+
+use bigint;
+
+sub command_output
+{
+ my ($cmd) = @_;
+ my ($stdout, $stderr);
+ print("# Running: " . join(" ", @{$cmd}) . "\n");
+ my $result = IPC::Run::run $cmd, '>', \$stdout, '2>', \$stderr;
+ ok($result, "@$cmd exit code 0");
+ is($stderr, '', "@$cmd no stderr");
+ return $stdout;
+}
+
+sub set_guc
+{
+ my ($node, $guc, $val) = @_;
+ print("SET $guc = $val\n");
+ $node->safe_psql('postgres', "ALTER SYSTEM SET $guc = $val");
+ $node->restart();
+}
+
+sub test_pgbench
+{
+ my ($node) = @_;
+ $node->command_ok(
+ [ qw(pgbench --progress=5 --transactions=1000 --jobs=5 --client=5) ],
+ 'pgbench finished without errors');
+}
+
+my @guc_vals = (
+ [ "autovacuum_freeze_max_age", 100000, 2**63 - 1 ],
+ [ "autovacuum_multixact_freeze_max_age", 10000, 2**63 - 1 ],
+ [ "vacuum_freeze_min_age", 0, 2**63 - 1 ],
+ [ "vacuum_freeze_table_age", 0, 2**63 - 1 ],
+ [ "vacuum_multixact_freeze_min_age", 0, 2**63 - 1 ],
+ [ "vacuum_multixact_freeze_table_age", 0, 2**63 -1 ]
+);
+
+my $START_VAL = 2**32;
+my $MAX_VAL = 2**62;
+
+my $ixid = $START_VAL + int(rand($MAX_VAL - $START_VAL));
+my $imxid = $START_VAL + int(rand($MAX_VAL - $START_VAL));
+my $imoff = $START_VAL + int(rand($MAX_VAL - $START_VAL));
+
+# Initialize master node
+my $node = PostgreSQL::Test::Cluster->new('master');
+$node->init(extra => [ "--xid=$ixid", "--multixact-id=$imxid", "--multixact-offset=$imoff" ]);
+# Disable logging of all statements to avoid log bloat during pgbench
+$node->append_conf('postgresql.conf', "log_statement = none");
+$node->start;
+
+# Fill the test database with the pgbench data
+$node->command_ok(
+ [ qw(pgbench --initialize --scale=10) ],
+ 'pgbench finished without errors');
+
+# Test all GUCs with minimum, maximum and random value inbetween
+# (run pgbench for every configuration setting)
+foreach my $gi (0 .. $#guc_vals) {
+ print($guc_vals[$gi][0]); print("\n");
+ my $guc = $guc_vals[$gi][0];
+ my $minval = $guc_vals[$gi][1];
+ my $maxval = $guc_vals[$gi][2];
+ set_guc($node, $guc, $minval);
+ test_pgbench($node);
+ set_guc($node, $guc, $maxval);
+ test_pgbench($node);
+ set_guc($node, $guc, $minval + int(rand($maxval - $minval)));
+ test_pgbench($node);
+}
diff --git a/src/test/xid-64/t/003_test_integrity.pl b/src/test/xid-64/t/003_test_integrity.pl
new file mode 100644
index 0000000000..d984907aee
--- /dev/null
+++ b/src/test/xid-64/t/003_test_integrity.pl
@@ -0,0 +1,56 @@
+# Check integrity after dump/restore with different xids
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More tests => 5;
+use File::Compare;
+
+my $tempdir = PostgreSQL::Test::Utils::tempdir;
+use bigint;
+
+my $START_VAL = 2**32;
+my $MAX_VAL = 2**62;
+
+my $ixid = $START_VAL + int(rand($MAX_VAL - $START_VAL));
+my $imxid = $START_VAL + int(rand($MAX_VAL - $START_VAL));
+my $imoff = $START_VAL + int(rand($MAX_VAL - $START_VAL));
+
+# Initialize master node
+my $node = PostgreSQL::Test::Cluster->new('master');
+$node->init();
+$node->start;
+
+# Create a database and fill it with the pgbench data
+$node->safe_psql('postgres', "CREATE DATABASE pgbench_db");
+$node->command_ok(
+ [ qw(pgbench --initialize --scale=2 pgbench_db) ],
+ 'pgbench finished without errors');
+# Dump the database (cluster the main table to put data in a determined order)
+$node->safe_psql('pgbench_db', qq(
+ CREATE INDEX pa_aid_idx ON pgbench_accounts (aid);
+ CLUSTER pgbench_accounts USING pa_aid_idx));
+$node->command_ok(
+ [ "pg_dump", "-w", "--inserts", "--file=$tempdir/pgbench.sql", "pgbench_db" ],
+ 'pgdump finished without errors');
+$node->stop('fast');
+
+# Initialize second node
+my $node2 = PostgreSQL::Test::Cluster->new('master2');
+$node2->init(extra => [ "--xid=$ixid", "--multixact-id=$imxid", "--multixact-offset=$imoff" ]);
+# Disable logging of all statements to avoid log bloat during restore
+$node2->append_conf('postgresql.conf', "log_statement = none");
+$node2->start;
+
+# Create a database and restore the previous dump
+$node2->safe_psql('postgres', "CREATE DATABASE pgbench_db");
+my $txid0 = $node2->safe_psql('pgbench_db', 'SELECT txid_current()');
+print("# Initial txid_current: $txid0\n");
+$node2->command_ok(["psql", "-q", "-f", "$tempdir/pgbench.sql", "pgbench_db"]);
+
+# Dump the database and compare the dumped content with the previous one
+$node2->safe_psql('pgbench_db', 'CLUSTER pgbench_accounts');
+$node2->command_ok(
+ [ "pg_dump", "-w", "--inserts", "--file=$tempdir/pgbench2.sql", "pgbench_db" ],
+ 'pgdump finished without errors');
+ok(File::Compare::compare_text("$tempdir/pgbench.sql", "$tempdir/pgbench2.sql") == 0, "no differences detected");
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index a21ea9bef9..bc879dde9f 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -462,6 +462,7 @@ sub GenerateFiles
PACKAGE_TARNAME => lc qq{"$package_name"},
PACKAGE_URL => qq{"$package_url"},
PACKAGE_VERSION => qq{"$package_version"},
+ XID_IS_64BIT => 1,
PG_INT128_TYPE => undef,
PG_INT64_TYPE => 'long long int',
PG_KRB_SRVNAM => qq{"postgres"},
--
2.35.1
^ permalink raw reply [nested|flat] 7+ messages in thread
* [PATCH v17] Add CopyFromRoutine/CopyToRountine
@ 2024-03-04 04:52 Sutou Kouhei <[email protected]>
0 siblings, 0 replies; 7+ messages in thread
From: Sutou Kouhei @ 2024-03-04 04:52 UTC (permalink / raw)
They are for implementing custom COPY FROM/TO format. But this is not
enough to implement custom COPY FROM/TO format yet. We'll export some
APIs to receive/send data and add "format" option to COPY FROM/TO
later.
Existing text/csv/binary format implementations don't use
CopyFromRoutine/CopyToRoutine for now. We have a patch for it but we
defer it. Because there are some mysterious profile results in spite
of we get faster runtimes. See [1] for details.
[1] https://www.postgresql.org/message-id/ZdbtQJ-p5H1_EDwE%40paquier.xyz
Note that this doesn't change existing text/csv/binary format
implementations.
---
src/backend/commands/copyfrom.c | 24 +++++-
src/backend/commands/copyfromparse.c | 5 ++
src/backend/commands/copyto.c | 25 +++++-
src/include/commands/copyapi.h | 100 +++++++++++++++++++++++
src/include/commands/copyfrom_internal.h | 4 +
src/tools/pgindent/typedefs.list | 2 +
6 files changed, 155 insertions(+), 5 deletions(-)
create mode 100644 src/include/commands/copyapi.h
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index c3bc897028..9bf2f6497e 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -1623,12 +1623,22 @@ BeginCopyFrom(ParseState *pstate,
/* Fetch the input function and typioparam info */
if (cstate->opts.binary)
+ {
getTypeBinaryInputInfo(att->atttypid,
&in_func_oid, &typioparams[attnum - 1]);
+ fmgr_info(in_func_oid, &in_functions[attnum - 1]);
+ }
+ else if (cstate->routine)
+ cstate->routine->CopyFromInFunc(cstate, att->atttypid,
+ &in_functions[attnum - 1],
+ &typioparams[attnum - 1]);
+
else
+ {
getTypeInputInfo(att->atttypid,
&in_func_oid, &typioparams[attnum - 1]);
- fmgr_info(in_func_oid, &in_functions[attnum - 1]);
+ fmgr_info(in_func_oid, &in_functions[attnum - 1]);
+ }
/* Get default info if available */
defexprs[attnum - 1] = NULL;
@@ -1768,10 +1778,13 @@ BeginCopyFrom(ParseState *pstate,
/* Read and verify binary header */
ReceiveCopyBinaryHeader(cstate);
}
-
- /* create workspace for CopyReadAttributes results */
- if (!cstate->opts.binary)
+ else if (cstate->routine)
{
+ cstate->routine->CopyFromStart(cstate, tupDesc);
+ }
+ else
+ {
+ /* create workspace for CopyReadAttributes results */
AttrNumber attr_count = list_length(cstate->attnumlist);
cstate->max_fields = attr_count;
@@ -1789,6 +1802,9 @@ BeginCopyFrom(ParseState *pstate,
void
EndCopyFrom(CopyFromState cstate)
{
+ if (cstate->routine)
+ cstate->routine->CopyFromEnd(cstate);
+
/* No COPY FROM related resources except memory. */
if (cstate->is_program)
{
diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c
index 7cacd0b752..8b15080585 100644
--- a/src/backend/commands/copyfromparse.c
+++ b/src/backend/commands/copyfromparse.c
@@ -978,6 +978,11 @@ NextCopyFrom(CopyFromState cstate, ExprContext *econtext,
Assert(fieldno == attr_count);
}
+ else if (cstate->routine)
+ {
+ if (!cstate->routine->CopyFromOneRow(cstate, econtext, values, nulls))
+ return false;
+ }
else
{
/* binary */
diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c
index 20ffc90363..b4a7c9c8b9 100644
--- a/src/backend/commands/copyto.c
+++ b/src/backend/commands/copyto.c
@@ -24,6 +24,7 @@
#include "access/xact.h"
#include "access/xlog.h"
#include "commands/copy.h"
+#include "commands/copyapi.h"
#include "commands/progress.h"
#include "executor/execdesc.h"
#include "executor/executor.h"
@@ -71,6 +72,9 @@ typedef enum CopyDest
*/
typedef struct CopyToStateData
{
+ /* format routine */
+ const CopyToRoutine *routine;
+
/* low-level state data */
CopyDest copy_dest; /* type of copy source/destination */
FILE *copy_file; /* used if copy_dest == COPY_FILE */
@@ -777,14 +781,22 @@ DoCopyTo(CopyToState cstate)
Form_pg_attribute attr = TupleDescAttr(tupDesc, attnum - 1);
if (cstate->opts.binary)
+ {
getTypeBinaryOutputInfo(attr->atttypid,
&out_func_oid,
&isvarlena);
+ fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]);
+ }
+ else if (cstate->routine)
+ cstate->routine->CopyToOutFunc(cstate, attr->atttypid,
+ &cstate->out_functions[attnum - 1]);
else
+ {
getTypeOutputInfo(attr->atttypid,
&out_func_oid,
&isvarlena);
- fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]);
+ fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]);
+ }
}
/*
@@ -811,6 +823,8 @@ DoCopyTo(CopyToState cstate)
tmp = 0;
CopySendInt32(cstate, tmp);
}
+ else if (cstate->routine)
+ cstate->routine->CopyToStart(cstate, tupDesc);
else
{
/*
@@ -892,6 +906,8 @@ DoCopyTo(CopyToState cstate)
/* Need to flush out the trailer */
CopySendEndOfRow(cstate);
}
+ else if (cstate->routine)
+ cstate->routine->CopyToEnd(cstate);
MemoryContextDelete(cstate->rowcontext);
@@ -916,6 +932,13 @@ CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot)
MemoryContextReset(cstate->rowcontext);
oldcontext = MemoryContextSwitchTo(cstate->rowcontext);
+ if (cstate->routine)
+ {
+ cstate->routine->CopyToOneRow(cstate, slot);
+ MemoryContextSwitchTo(oldcontext);
+ return;
+ }
+
if (cstate->opts.binary)
{
/* Binary per-tuple header */
diff --git a/src/include/commands/copyapi.h b/src/include/commands/copyapi.h
new file mode 100644
index 0000000000..635c4cbff2
--- /dev/null
+++ b/src/include/commands/copyapi.h
@@ -0,0 +1,100 @@
+/*-------------------------------------------------------------------------
+ *
+ * copyapi.h
+ * API for COPY TO/FROM handlers
+ *
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/copyapi.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COPYAPI_H
+#define COPYAPI_H
+
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
+
+/* These are private in commands/copy[from|to].c */
+typedef struct CopyFromStateData *CopyFromState;
+typedef struct CopyToStateData *CopyToState;
+
+/*
+ * API structure for a COPY FROM format implementation. Note this must be
+ * allocated in a server-lifetime manner, typically as a static const struct.
+ */
+typedef struct CopyFromRoutine
+{
+ /*
+ * Called when COPY FROM is started to set up the input functions
+ * associated to the relation's attributes writing to. `finfo` can be
+ * optionally filled to provide the catalog information of the input
+ * function. `typioparam` can be optionally filled to define the OID of
+ * the type to pass to the input function. `atttypid` is the OID of data
+ * type used by the relation's attribute.
+ */
+ void (*CopyFromInFunc) (CopyFromState cstate, Oid atttypid,
+ FmgrInfo *finfo, Oid *typioparam);
+
+ /*
+ * Called when COPY FROM is started.
+ *
+ * `tupDesc` is the tuple descriptor of the relation where the data needs
+ * to be copied. This can be used for any initialization steps required
+ * by a format.
+ */
+ void (*CopyFromStart) (CopyFromState cstate, TupleDesc tupDesc);
+
+ /*
+ * Copy one row to a set of `values` and `nulls` of size tupDesc->natts.
+ *
+ * 'econtext' is used to evaluate default expression for each column that
+ * is either not read from the file or is using the DEFAULT option of COPY
+ * FROM. It is NULL if no default values are used.
+ *
+ * Returns false if there are no more tuples to copy.
+ */
+ bool (*CopyFromOneRow) (CopyFromState cstate, ExprContext *econtext,
+ Datum *values, bool *nulls);
+
+ /* Called when COPY FROM has ended. */
+ void (*CopyFromEnd) (CopyFromState cstate);
+} CopyFromRoutine;
+
+/*
+ * API structure for a COPY TO format implementation. Note this must be
+ * allocated in a server-lifetime manner, typically as a static const struct.
+ */
+typedef struct CopyToRoutine
+{
+ /*
+ * Called when COPY TO is started to set up the output functions
+ * associated to the relation's attributes reading from. `finfo` can be
+ * optionally filled. `atttypid` is the OID of data type used by the
+ * relation's attribute.
+ */
+ void (*CopyToOutFunc) (CopyToState cstate, Oid atttypid,
+ FmgrInfo *finfo);
+
+ /*
+ * Called when COPY TO is started.
+ *
+ * `tupDesc` is the tuple descriptor of the relation from where the data
+ * is read.
+ */
+ void (*CopyToStart) (CopyToState cstate, TupleDesc tupDesc);
+
+ /*
+ * Copy one row for COPY TO.
+ *
+ * `slot` is the tuple slot where the data is emitted.
+ */
+ void (*CopyToOneRow) (CopyToState cstate, TupleTableSlot *slot);
+
+ /* Called when COPY TO has ended */
+ void (*CopyToEnd) (CopyToState cstate);
+} CopyToRoutine;
+
+#endif /* COPYAPI_H */
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index cad52fcc78..509b9e92a1 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -15,6 +15,7 @@
#define COPYFROM_INTERNAL_H
#include "commands/copy.h"
+#include "commands/copyapi.h"
#include "commands/trigger.h"
#include "nodes/miscnodes.h"
@@ -58,6 +59,9 @@ typedef enum CopyInsertMethod
*/
typedef struct CopyFromStateData
{
+ /* format routine */
+ const CopyFromRoutine *routine;
+
/* low-level state data */
CopySource copy_src; /* type of copy source */
FILE *copy_file; /* used if copy_src == COPY_FILE */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index ee40a341d3..a5ae161ca5 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -475,6 +475,7 @@ ConvertRowtypeExpr
CookedConstraint
CopyDest
CopyFormatOptions
+CopyFromRoutine
CopyFromState
CopyFromStateData
CopyHeaderChoice
@@ -484,6 +485,7 @@ CopyMultiInsertInfo
CopyOnErrorChoice
CopySource
CopyStmt
+CopyToRoutine
CopyToState
CopyToStateData
Cost
--
2.43.0
----Next_Part(Tue_Mar__5_17_18_08_2024_468)----
^ permalink raw reply [nested|flat] 7+ messages in thread
end of thread, other threads:[~2024-03-04 04:52 UTC | newest]
Thread overview: 7+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2022-02-02 15:10 Re: Add 64-bit XIDs into PostgreSQL 15 Pavel Borisov <[email protected]>
2022-03-02 13:25 ` Aleksander Alekseev <[email protected]>
2022-03-02 14:43 ` Pavel Borisov <[email protected]>
2022-03-02 16:35 ` Justin Pryzby <[email protected]>
2022-03-02 21:22 ` Andres Freund <[email protected]>
2022-03-03 11:07 ` Aleksander Alekseev <[email protected]>
2024-03-04 04:52 [PATCH v17] Add CopyFromRoutine/CopyToRountine Sutou Kouhei <[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