summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/access/rmgrdesc/seqdesc.c2
-rw-r--r--src/backend/access/transam/rmgr.c2
-rw-r--r--src/backend/access/transam/xlogfuncs.c2
-rw-r--r--src/backend/commands/Makefile1
-rw-r--r--src/backend/commands/meson.build1
-rw-r--r--src/backend/commands/sequence.c76
-rw-r--r--src/backend/commands/sequence_xlog.c80
-rw-r--r--src/backend/optimizer/util/clauses.c4
-rw-r--r--src/backend/optimizer/util/plancat.c187
-rw-r--r--src/backend/regex/regc_pg_locale.c18
-rw-r--r--src/backend/replication/logical/origin.c15
-rw-r--r--src/backend/utils/adt/cash.c2
-rw-r--r--src/backend/utils/adt/date.c209
-rw-r--r--src/backend/utils/adt/pg_locale_libc.c2
-rw-r--r--src/backend/utils/adt/timestamp.c121
-rw-r--r--src/bin/pg_waldump/rmgrdesc.c2
-rw-r--r--src/include/commands/sequence.h18
-rw-r--r--src/include/commands/sequence_xlog.h45
-rw-r--r--src/include/utils/date.h8
-rw-r--r--src/include/utils/pg_locale.h6
-rw-r--r--src/include/utils/timestamp.h8
-rw-r--r--src/test/modules/injection_points/Makefile1
-rw-r--r--src/test/modules/injection_points/expected/reindex-concurrently-upsert-on-constraint.out238
-rw-r--r--src/test/modules/injection_points/meson.build1
-rw-r--r--src/test/modules/injection_points/specs/reindex-concurrently-upsert-on-constraint.spec110
25 files changed, 775 insertions, 384 deletions
diff --git a/src/backend/access/rmgrdesc/seqdesc.c b/src/backend/access/rmgrdesc/seqdesc.c
index 0d289d77fcf..a0edb78856b 100644
--- a/src/backend/access/rmgrdesc/seqdesc.c
+++ b/src/backend/access/rmgrdesc/seqdesc.c
@@ -14,7 +14,7 @@
*/
#include "postgres.h"
-#include "commands/sequence.h"
+#include "commands/sequence_xlog.h"
void
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 1b7499726eb..4fda03a3cfc 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -33,7 +33,7 @@
#include "access/xact.h"
#include "catalog/storage_xlog.h"
#include "commands/dbcommands_xlog.h"
-#include "commands/sequence.h"
+#include "commands/sequence_xlog.h"
#include "commands/tablespace.h"
#include "replication/decode.h"
#include "replication/message.h"
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index 3e45fce43ed..a50345f9bf7 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -479,7 +479,7 @@ pg_split_walfile_name(PG_FUNCTION_ARGS)
/* Capitalize WAL file name. */
for (p = fname_upper; *p; p++)
- *p = pg_toupper((unsigned char) *p);
+ *p = pg_ascii_toupper((unsigned char) *p);
if (!IsXLogFileName(fname_upper))
ereport(ERROR,
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index f99acfd2b4b..64cb6278409 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -53,6 +53,7 @@ OBJS = \
schemacmds.o \
seclabel.o \
sequence.o \
+ sequence_xlog.o \
statscmds.o \
subscriptioncmds.o \
tablecmds.o \
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index 9f640ad4810..5fc35826b1c 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -41,6 +41,7 @@ backend_sources += files(
'schemacmds.c',
'seclabel.c',
'sequence.c',
+ 'sequence_xlog.c',
'statscmds.c',
'subscriptioncmds.c',
'tablecmds.c',
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 8d671b7a29d..51567994126 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -14,7 +14,6 @@
*/
#include "postgres.h"
-#include "access/bufmask.h"
#include "access/htup_details.h"
#include "access/multixact.h"
#include "access/relation.h"
@@ -22,9 +21,7 @@
#include "access/table.h"
#include "access/transam.h"
#include "access/xact.h"
-#include "access/xlog.h"
#include "access/xloginsert.h"
-#include "access/xlogutils.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
@@ -34,11 +31,13 @@
#include "catalog/storage_xlog.h"
#include "commands/defrem.h"
#include "commands/sequence.h"
+#include "commands/sequence_xlog.h"
#include "commands/tablecmds.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "parser/parse_type.h"
+#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/proc.h"
#include "storage/smgr.h"
@@ -59,16 +58,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
* rely on the relcache, since it's only, well, a cache, and may decide to
@@ -1907,56 +1896,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
}
-
-void
-seq_redo(XLogReaderState *record)
-{
- XLogRecPtr lsn = record->EndRecPtr;
- uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
- Buffer buffer;
- Page page;
- Page localpage;
- 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);
-
- buffer = XLogInitBufferForRedo(record, 0);
- page = BufferGetPage(buffer);
-
- /*
- * We always reinit the page. However, since this WAL record type is also
- * used for updating sequences, it's possible that a hot-standby backend
- * is examining the page concurrently; so we mustn't transiently trash the
- * buffer. The solution is to build the correct new page contents in
- * local workspace and then memcpy into the buffer. Then only bytes that
- * are supposed to change will change, even transiently. We must palloc
- * the local page for alignment reasons.
- */
- localpage = (Page) palloc(BufferGetPageSize(buffer));
-
- PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
- sm = (sequence_magic *) PageGetSpecialPointer(localpage);
- sm->magic = SEQ_MAGIC;
-
- item = (char *) xlrec + sizeof(xl_seq_rec);
- itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
-
- if (PageAddItem(localpage, item, itemsz, FirstOffsetNumber, false, false) == InvalidOffsetNumber)
- elog(PANIC, "seq_redo: failed to add item to page");
-
- PageSetLSN(localpage, lsn);
-
- memcpy(page, localpage, BufferGetPageSize(buffer));
- MarkBufferDirty(buffer);
- UnlockReleaseBuffer(buffer);
-
- pfree(localpage);
-}
-
/*
* Flush cached sequence information.
*/
@@ -1971,14 +1910,3 @@ ResetSequenceCaches(void)
last_used_seq = NULL;
}
-
-/*
- * Mask a Sequence page before performing consistency checks on it.
- */
-void
-seq_mask(char *page, BlockNumber blkno)
-{
- mask_page_lsn_and_checksum(page);
-
- mask_unused_space(page);
-}
diff --git a/src/backend/commands/sequence_xlog.c b/src/backend/commands/sequence_xlog.c
new file mode 100644
index 00000000000..ffbd9820416
--- /dev/null
+++ b/src/backend/commands/sequence_xlog.c
@@ -0,0 +1,80 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequence.c
+ * RMGR WAL routines for sequences.
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/commands/sequence_xlog.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/bufmask.h"
+#include "access/xlogutils.h"
+#include "commands/sequence_xlog.h"
+#include "storage/bufmgr.h"
+
+void
+seq_redo(XLogReaderState *record)
+{
+ XLogRecPtr lsn = record->EndRecPtr;
+ uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+ Buffer buffer;
+ Page page;
+ Page localpage;
+ 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);
+
+ buffer = XLogInitBufferForRedo(record, 0);
+ page = BufferGetPage(buffer);
+
+ /*
+ * We always reinit the page. However, since this WAL record type is also
+ * used for updating sequences, it's possible that a hot-standby backend
+ * is examining the page concurrently; so we mustn't transiently trash the
+ * buffer. The solution is to build the correct new page contents in
+ * local workspace and then memcpy into the buffer. Then only bytes that
+ * are supposed to change will change, even transiently. We must palloc
+ * the local page for alignment reasons.
+ */
+ localpage = (Page) palloc(BufferGetPageSize(buffer));
+
+ PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
+ sm = (sequence_magic *) PageGetSpecialPointer(localpage);
+ sm->magic = SEQ_MAGIC;
+
+ item = (char *) xlrec + sizeof(xl_seq_rec);
+ itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
+
+ if (PageAddItem(localpage, item, itemsz, FirstOffsetNumber, false, false) == InvalidOffsetNumber)
+ elog(PANIC, "seq_redo: failed to add item to page");
+
+ PageSetLSN(localpage, lsn);
+
+ memcpy(page, localpage, BufferGetPageSize(buffer));
+ MarkBufferDirty(buffer);
+ UnlockReleaseBuffer(buffer);
+
+ pfree(localpage);
+}
+
+/*
+ * Mask a Sequence page before performing consistency checks on it.
+ */
+void
+seq_mask(char *page, BlockNumber blkno)
+{
+ mask_page_lsn_and_checksum(page);
+
+ mask_unused_space(page);
+}
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 9975185934b..bda4c4eb292 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2638,7 +2638,9 @@ eval_const_expressions_mutator(Node *node,
}
case T_Aggref:
node = ece_generic_processing(node);
- return simplify_aggref((Aggref *) node, context);
+ if (context->root != NULL)
+ return simplify_aggref((Aggref *) node, context);
+ return node;
case T_OpExpr:
{
OpExpr *expr = (OpExpr *) node;
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 7af9a2064e3..4367f107e9c 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -806,9 +806,15 @@ infer_arbiter_indexes(PlannerInfo *root)
Relation relation;
Oid indexOidFromConstraint = InvalidOid;
List *indexList;
- ListCell *l;
+ List *indexRelList = NIL;
- /* Normalized inference attributes and inference expressions: */
+ /*
+ * Required attributes and expressions used to match indexes to the clause
+ * given by the user. In the ON CONFLICT ON CONSTRAINT case, we compute
+ * these from that constraint's index to match all other indexes, to
+ * account for the case where that index is being concurrently reindexed.
+ */
+ List *inferIndexExprs = (List *) onconflict->arbiterWhere;
Bitmapset *inferAttrs = NULL;
List *inferElems = NIL;
@@ -841,12 +847,14 @@ infer_arbiter_indexes(PlannerInfo *root)
* well as a separate list of expression items. This simplifies matching
* the cataloged definition of indexes.
*/
- foreach(l, onconflict->arbiterElems)
+ foreach_ptr(InferenceElem, elem, onconflict->arbiterElems)
{
- InferenceElem *elem = (InferenceElem *) lfirst(l);
Var *var;
int attno;
+ /* we cannot also have a constraint name, per grammar */
+ Assert(!OidIsValid(onconflict->constraint));
+
if (!IsA(elem->expr, Var))
{
/* If not a plain Var, just shove it in inferElems for now */
@@ -867,45 +875,96 @@ infer_arbiter_indexes(PlannerInfo *root)
}
/*
- * Lookup named constraint's index. This is not immediately returned
- * because some additional sanity checks are required.
+ * Next, open all the indexes. We need this list for two things: first,
+ * if an ON CONSTRAINT clause was given, and that constraint's index is
+ * undergoing REINDEX CONCURRENTLY, then we need to consider all matches
+ * for that index. Second, if an attribute list was specified in the ON
+ * CONFLICT clause, we use the list to find the indexes whose attributes
+ * match that list.
+ */
+ indexList = RelationGetIndexList(relation);
+ foreach_oid(indexoid, indexList)
+ {
+ Relation idxRel;
+
+ /* obtain the same lock type that the executor will ultimately use */
+ idxRel = index_open(indexoid, rte->rellockmode);
+ indexRelList = lappend(indexRelList, idxRel);
+ }
+
+ /*
+ * If a constraint was named in the command, look up its index. We don't
+ * return it immediately because we need some additional sanity checks,
+ * and also because we need to include other indexes as arbiters to
+ * account for REINDEX CONCURRENTLY processing it.
*/
if (onconflict->constraint != InvalidOid)
{
- indexOidFromConstraint = get_constraint_index(onconflict->constraint);
+ /* we cannot also have an explicit list of elements, per grammar */
+ Assert(onconflict->arbiterElems == NIL);
+ indexOidFromConstraint = get_constraint_index(onconflict->constraint);
if (indexOidFromConstraint == InvalidOid)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("constraint in ON CONFLICT clause has no associated index")));
+
+ /*
+ * Find the named constraint index to extract its attributes and
+ * predicates.
+ */
+ foreach_ptr(RelationData, idxRel, indexRelList)
+ {
+ Form_pg_index idxForm = idxRel->rd_index;
+
+ if (indexOidFromConstraint == idxForm->indexrelid)
+ {
+ /* Found it. */
+ Assert(idxForm->indisready);
+
+ /*
+ * Set up inferElems and inferPredExprs to match the
+ * constraint index, so that we can match them in the loop
+ * below.
+ */
+ for (int natt = 0; natt < idxForm->indnkeyatts; natt++)
+ {
+ int attno;
+
+ attno = idxRel->rd_index->indkey.values[natt];
+ if (attno != InvalidAttrNumber)
+ inferAttrs =
+ bms_add_member(inferAttrs,
+ attno - FirstLowInvalidHeapAttributeNumber);
+ }
+
+ inferElems = RelationGetIndexExpressions(idxRel);
+ inferIndexExprs = RelationGetIndexPredicate(idxRel);
+ break;
+ }
+ }
}
/*
* Using that representation, iterate through the list of indexes on the
- * target relation to try and find a match
+ * target relation to find matches.
*/
- indexList = RelationGetIndexList(relation);
-
- foreach(l, indexList)
+ foreach_ptr(RelationData, idxRel, indexRelList)
{
- Oid indexoid = lfirst_oid(l);
- Relation idxRel;
Form_pg_index idxForm;
Bitmapset *indexedAttrs;
List *idxExprs;
List *predExprs;
AttrNumber natt;
- ListCell *el;
+ bool match;
/*
- * Extract info from the relation descriptor for the index. Obtain
- * the same lock type that the executor will ultimately use.
+ * Extract info from the relation descriptor for the index.
*
* Let executor complain about !indimmediate case directly, because
* enforcement needs to occur there anyway when an inference clause is
* omitted.
*/
- idxRel = index_open(indexoid, rte->rellockmode);
idxForm = idxRel->rd_index;
/*
@@ -924,7 +983,7 @@ infer_arbiter_indexes(PlannerInfo *root)
* indexes at least one index that is marked valid.
*/
if (!idxForm->indisready)
- goto next;
+ continue;
/*
* Note that we do not perform a check against indcheckxmin (like e.g.
@@ -934,7 +993,7 @@ infer_arbiter_indexes(PlannerInfo *root)
*/
/*
- * Look for match on "ON constraint_name" variant, which may not be
+ * Look for match for "ON constraint_name" variant, which may not be a
* unique constraint. This can only be a constraint name.
*/
if (indexOidFromConstraint == idxForm->indexrelid)
@@ -944,31 +1003,37 @@ infer_arbiter_indexes(PlannerInfo *root)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("ON CONFLICT DO UPDATE not supported with exclusion constraints")));
+ /* Consider this one a match already */
results = lappend_oid(results, idxForm->indexrelid);
foundValid |= idxForm->indisvalid;
- index_close(idxRel, NoLock);
- break;
+ continue;
}
else if (indexOidFromConstraint != InvalidOid)
{
- /* No point in further work for index in named constraint case */
- goto next;
+ /*
+ * In the case of "ON constraint_name DO UPDATE" we need to skip
+ * non-unique candidates.
+ */
+ if (!idxForm->indisunique && onconflict->action == ONCONFLICT_UPDATE)
+ continue;
+ }
+ else
+ {
+ /*
+ * Only considering conventional inference at this point (not
+ * named constraints), so index under consideration can be
+ * immediately skipped if it's not unique.
+ */
+ if (!idxForm->indisunique)
+ continue;
}
-
- /*
- * Only considering conventional inference at this point (not named
- * constraints), so index under consideration can be immediately
- * skipped if it's not unique
- */
- if (!idxForm->indisunique)
- goto next;
/*
* So-called unique constraints with WITHOUT OVERLAPS are really
* exclusion constraints, so skip those too.
*/
if (idxForm->indisexclusion)
- goto next;
+ continue;
/* Build BMS representation of plain (non expression) index attrs */
indexedAttrs = NULL;
@@ -983,17 +1048,20 @@ infer_arbiter_indexes(PlannerInfo *root)
/* Non-expression attributes (if any) must match */
if (!bms_equal(indexedAttrs, inferAttrs))
- goto next;
+ continue;
/* Expression attributes (if any) must match */
idxExprs = RelationGetIndexExpressions(idxRel);
if (idxExprs && varno != 1)
ChangeVarNodes((Node *) idxExprs, 1, varno, 0);
- foreach(el, onconflict->arbiterElems)
+ /*
+ * If arbiterElems are present, check them. (Note that if a
+ * constraint name was given in the command line, this list is NIL.)
+ */
+ match = true;
+ foreach_ptr(InferenceElem, elem, onconflict->arbiterElems)
{
- InferenceElem *elem = (InferenceElem *) lfirst(el);
-
/*
* Ensure that collation/opclass aspects of inference expression
* element match. Even though this loop is primarily concerned
@@ -1002,7 +1070,10 @@ infer_arbiter_indexes(PlannerInfo *root)
* attributes appearing as inference elements.
*/
if (!infer_collation_opclass_match(elem, idxRel, idxExprs))
- goto next;
+ {
+ match = false;
+ break;
+ }
/*
* Plain Vars don't factor into count of expression elements, and
@@ -1023,37 +1094,59 @@ infer_arbiter_indexes(PlannerInfo *root)
list_member(idxExprs, elem->expr))
continue;
- goto next;
+ match = false;
+ break;
}
+ if (!match)
+ continue;
/*
- * Now that all inference elements were matched, ensure that the
+ * In case of inference from an attribute list, ensure that the
* expression elements from inference clause are not missing any
* cataloged expressions. This does the right thing when unique
* indexes redundantly repeat the same attribute, or if attributes
* redundantly appear multiple times within an inference clause.
+ *
+ * In case a constraint was named, ensure the candidate has an equal
+ * set of expressions as the named constraint's index.
*/
if (list_difference(idxExprs, inferElems) != NIL)
- goto next;
+ continue;
- /*
- * If it's a partial index, its predicate must be implied by the ON
- * CONFLICT's WHERE clause.
- */
predExprs = RelationGetIndexPredicate(idxRel);
if (predExprs && varno != 1)
ChangeVarNodes((Node *) predExprs, 1, varno, 0);
- if (!predicate_implied_by(predExprs, (List *) onconflict->arbiterWhere, false))
- goto next;
+ /*
+ * Partial indexes affect each form of ON CONFLICT differently: if a
+ * constraint was named, then the predicates must be identical. In
+ * conventional inference, the index's predicate must be implied by
+ * the WHERE clause.
+ */
+ if (OidIsValid(indexOidFromConstraint))
+ {
+ if (list_difference(predExprs, inferIndexExprs) != NIL)
+ continue;
+ }
+ else
+ {
+ if (!predicate_implied_by(predExprs, inferIndexExprs, false))
+ continue;
+ }
+ /* All good -- consider this index a match */
results = lappend_oid(results, idxForm->indexrelid);
foundValid |= idxForm->indisvalid;
-next:
+ }
+
+ /* Close all indexes */
+ foreach_ptr(RelationData, idxRel, indexRelList)
+ {
index_close(idxRel, NoLock);
}
list_free(indexList);
+ list_free(indexRelList);
table_close(relation, NoLock);
/* We require at least one indisvalid index */
diff --git a/src/backend/regex/regc_pg_locale.c b/src/backend/regex/regc_pg_locale.c
index 4698f110a0c..bb0e3f1d139 100644
--- a/src/backend/regex/regc_pg_locale.c
+++ b/src/backend/regex/regc_pg_locale.c
@@ -320,16 +320,18 @@ regc_ctype_get_cache(regc_wc_probefunc probefunc, int cclasscode)
max_chr = (pg_wchar) MAX_SIMPLE_CHR;
#endif
}
+ else if (GetDatabaseEncoding() == PG_UTF8)
+ {
+ max_chr = (pg_wchar) MAX_SIMPLE_CHR;
+ }
else
{
- if (pg_regex_locale->ctype->max_chr != 0 &&
- pg_regex_locale->ctype->max_chr <= MAX_SIMPLE_CHR)
- {
- max_chr = pg_regex_locale->ctype->max_chr;
- pcc->cv.cclasscode = -1;
- }
- else
- max_chr = (pg_wchar) MAX_SIMPLE_CHR;
+#if MAX_SIMPLE_CHR >= UCHAR_MAX
+ max_chr = (pg_wchar) UCHAR_MAX;
+ pcc->cv.cclasscode = -1;
+#else
+ max_chr = (pg_wchar) MAX_SIMPLE_CHR;
+#endif
}
/*
diff --git a/src/backend/replication/logical/origin.c b/src/backend/replication/logical/origin.c
index 4632aa8115d..2380f369578 100644
--- a/src/backend/replication/logical/origin.c
+++ b/src/backend/replication/logical/origin.c
@@ -789,14 +789,6 @@ StartupReplicationOrigin(void)
readBytes = read(fd, &disk_state, sizeof(disk_state));
- /* no further data */
- if (readBytes == sizeof(crc))
- {
- /* not pretty, but simple ... */
- file_crc = *(pg_crc32c *) &disk_state;
- break;
- }
-
if (readBytes < 0)
{
ereport(PANIC,
@@ -805,6 +797,13 @@ StartupReplicationOrigin(void)
path)));
}
+ /* no further data */
+ if (readBytes == sizeof(crc))
+ {
+ memcpy(&file_crc, &disk_state, sizeof(file_crc));
+ break;
+ }
+
if (readBytes != sizeof(disk_state))
{
ereport(PANIC,
diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c
index 611d23f3cb0..623f6eec056 100644
--- a/src/backend/utils/adt/cash.c
+++ b/src/backend/utils/adt/cash.c
@@ -1035,7 +1035,7 @@ cash_words(PG_FUNCTION_ARGS)
appendStringInfoString(&buf, m0 == 1 ? " cent" : " cents");
/* capitalize output */
- buf.data[0] = pg_toupper((unsigned char) buf.data[0]);
+ buf.data[0] = pg_ascii_toupper((unsigned char) buf.data[0]);
/* return as text datum */
res = cstring_to_text_with_len(buf.data, buf.len);
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 344f58b92f7..c4b8125dd66 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -27,6 +27,7 @@
#include "common/int.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
+#include "nodes/miscnodes.h"
#include "nodes/supportnodes.h"
#include "parser/scansup.h"
#include "utils/array.h"
@@ -615,24 +616,21 @@ date_mii(PG_FUNCTION_ARGS)
/*
* Promote date to timestamp.
*
- * On successful conversion, *overflow is set to zero if it's not NULL.
+ * If the date falls out of the valid range for the timestamp type, error
+ * handling proceeds based on escontext.
*
- * If the date is finite but out of the valid range for timestamp, then:
- * if overflow is NULL, we throw an out-of-range error.
- * if overflow is not NULL, we store +1 or -1 there to indicate the sign
- * of the overflow, and return the appropriate timestamp infinity.
+ * If escontext is NULL, we throw an out-of-range error (hard error).
+ * If escontext is not NULL, we return NOBEGIN or NOEND for lower bound or
+ * upper bound overflow, respectively, and record a soft error.
*
- * Note: *overflow = -1 is actually not possible currently, since both
- * datatypes have the same lower bound, Julian day zero.
+ * Note: Lower bound overflow is currently not possible, as both date and
+ * timestamp datatypes share the same lower boundary: Julian day zero.
*/
Timestamp
-date2timestamp_opt_overflow(DateADT dateVal, int *overflow)
+date2timestamp_safe(DateADT dateVal, Node *escontext)
{
Timestamp result;
- if (overflow)
- *overflow = 0;
-
if (DATE_IS_NOBEGIN(dateVal))
TIMESTAMP_NOBEGIN(result);
else if (DATE_IS_NOEND(dateVal))
@@ -645,18 +643,10 @@ date2timestamp_opt_overflow(DateADT dateVal, int *overflow)
*/
if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
{
- if (overflow)
- {
- *overflow = 1;
- TIMESTAMP_NOEND(result);
- return result;
- }
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("date out of range for timestamp")));
- }
+ TIMESTAMP_NOEND(result);
+ ereturn(escontext, result,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range for timestamp")));
}
/* date is days since 2000, timestamp is microseconds since same... */
@@ -672,30 +662,27 @@ date2timestamp_opt_overflow(DateADT dateVal, int *overflow)
static TimestampTz
date2timestamp(DateADT dateVal)
{
- return date2timestamp_opt_overflow(dateVal, NULL);
+ return date2timestamp_safe(dateVal, NULL);
}
/*
* Promote date to timestamp with time zone.
*
- * On successful conversion, *overflow is set to zero if it's not NULL.
+ * If the date falls out of the valid range for the timestamp type, error
+ * handling proceeds based on escontext.
*
- * If the date is finite but out of the valid range for timestamptz, then:
- * if overflow is NULL, we throw an out-of-range error.
- * if overflow is not NULL, we store +1 or -1 there to indicate the sign
- * of the overflow, and return the appropriate timestamptz infinity.
+ * If escontext is NULL, we throw an out-of-range error (hard error).
+ * If escontext is not NULL, we return NOBEGIN or NOEND for lower bound or
+ * upper bound overflow, respectively, and record a soft error.
*/
TimestampTz
-date2timestamptz_opt_overflow(DateADT dateVal, int *overflow)
+date2timestamptz_safe(DateADT dateVal, Node *escontext)
{
TimestampTz result;
struct pg_tm tt,
*tm = &tt;
int tz;
- if (overflow)
- *overflow = 0;
-
if (DATE_IS_NOBEGIN(dateVal))
TIMESTAMP_NOBEGIN(result);
else if (DATE_IS_NOEND(dateVal))
@@ -708,18 +695,10 @@ date2timestamptz_opt_overflow(DateADT dateVal, int *overflow)
*/
if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
{
- if (overflow)
- {
- *overflow = 1;
- TIMESTAMP_NOEND(result);
- return result;
- }
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("date out of range for timestamp")));
- }
+ TIMESTAMP_NOEND(result);
+ ereturn(escontext, result,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range for timestamp")));
}
j2date(dateVal + POSTGRES_EPOCH_JDATE,
@@ -737,25 +716,14 @@ date2timestamptz_opt_overflow(DateADT dateVal, int *overflow)
*/
if (!IS_VALID_TIMESTAMP(result))
{
- if (overflow)
- {
- if (result < MIN_TIMESTAMP)
- {
- *overflow = -1;
- TIMESTAMP_NOBEGIN(result);
- }
- else
- {
- *overflow = 1;
- TIMESTAMP_NOEND(result);
- }
- }
+ if (result < MIN_TIMESTAMP)
+ TIMESTAMP_NOBEGIN(result);
else
- {
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("date out of range for timestamp")));
- }
+ TIMESTAMP_NOEND(result);
+
+ ereturn(escontext, result,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range for timestamp")));
}
}
@@ -768,7 +736,7 @@ date2timestamptz_opt_overflow(DateADT dateVal, int *overflow)
static TimestampTz
date2timestamptz(DateADT dateVal)
{
- return date2timestamptz_opt_overflow(dateVal, NULL);
+ return date2timestamptz_safe(dateVal, NULL);
}
/*
@@ -808,15 +776,16 @@ int32
date_cmp_timestamp_internal(DateADT dateVal, Timestamp dt2)
{
Timestamp dt1;
- int overflow;
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
- dt1 = date2timestamp_opt_overflow(dateVal, &overflow);
- if (overflow > 0)
+ dt1 = date2timestamp_safe(dateVal, (Node *) &escontext);
+ if (escontext.error_occurred)
{
+ Assert(TIMESTAMP_IS_NOEND(dt1)); /* NOBEGIN case cannot occur */
+
/* dt1 is larger than any finite timestamp, but less than infinity */
return TIMESTAMP_IS_NOEND(dt2) ? -1 : +1;
}
- Assert(overflow == 0); /* -1 case cannot occur */
return timestamp_cmp_internal(dt1, dt2);
}
@@ -888,18 +857,22 @@ int32
date_cmp_timestamptz_internal(DateADT dateVal, TimestampTz dt2)
{
TimestampTz dt1;
- int overflow;
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
- dt1 = date2timestamptz_opt_overflow(dateVal, &overflow);
- if (overflow > 0)
- {
- /* dt1 is larger than any finite timestamp, but less than infinity */
- return TIMESTAMP_IS_NOEND(dt2) ? -1 : +1;
- }
- if (overflow < 0)
+ dt1 = date2timestamptz_safe(dateVal, (Node *) &escontext);
+
+ if (escontext.error_occurred)
{
- /* dt1 is less than any finite timestamp, but more than -infinity */
- return TIMESTAMP_IS_NOBEGIN(dt2) ? +1 : -1;
+ if (TIMESTAMP_IS_NOEND(dt1))
+ {
+ /* dt1 is larger than any finite timestamp, but less than infinity */
+ return TIMESTAMP_IS_NOEND(dt2) ? -1 : +1;
+ }
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ /* dt1 is less than any finite timestamp, but more than -infinity */
+ return TIMESTAMP_IS_NOBEGIN(dt2) ? +1 : -1;
+ }
}
return timestamptz_cmp_internal(dt1, dt2);
@@ -1364,34 +1337,31 @@ timestamp_date(PG_FUNCTION_ARGS)
Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamp2date_opt_overflow(timestamp, NULL);
+ result = timestamp2date_safe(timestamp, NULL);
PG_RETURN_DATEADT(result);
}
/*
* Convert timestamp to date.
*
- * On successful conversion, *overflow is set to zero if it's not NULL.
+ * If the timestamp falls out of the valid range for the date type, error
+ * handling proceeds based on escontext.
*
- * If the timestamp is finite but out of the valid range for date, then:
- * if overflow is NULL, we throw an out-of-range error.
- * if overflow is not NULL, we store +1 or -1 there to indicate the sign
- * of the overflow, and return the appropriate date infinity.
+ * If escontext is NULL, we throw an out-of-range error (hard error).
+ * If escontext is not NULL, we return NOBEGIN or NOEND for lower bound or
+ * upper bound overflow, respectively, and record a soft error.
*
* Note: given the ranges of the types, overflow is only possible at
- * the minimum end of the range, but we don't assume that in this code.
+ * the lower bound of the range, but we don't assume that in this code.
*/
DateADT
-timestamp2date_opt_overflow(Timestamp timestamp, int *overflow)
+timestamp2date_safe(Timestamp timestamp, Node *escontext)
{
DateADT result;
struct pg_tm tt,
*tm = &tt;
fsec_t fsec;
- if (overflow)
- *overflow = 0;
-
if (TIMESTAMP_IS_NOBEGIN(timestamp))
DATE_NOBEGIN(result);
else if (TIMESTAMP_IS_NOEND(timestamp))
@@ -1400,21 +1370,12 @@ timestamp2date_opt_overflow(Timestamp timestamp, int *overflow)
{
if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
{
- if (overflow)
- {
- if (timestamp < 0)
- {
- *overflow = -1;
- DATE_NOBEGIN(result);
- }
- else
- {
- *overflow = 1; /* not actually reachable */
- DATE_NOEND(result);
- }
- return result;
- }
- ereport(ERROR,
+ if (timestamp < 0)
+ DATE_NOBEGIN(result);
+ else
+ DATE_NOEND(result); /* not actually reachable */
+
+ ereturn(escontext, result,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
}
@@ -1450,25 +1411,25 @@ timestamptz_date(PG_FUNCTION_ARGS)
TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamptz2date_opt_overflow(timestamp, NULL);
+ result = timestamptz2date_safe(timestamp, NULL);
PG_RETURN_DATEADT(result);
}
/*
* Convert timestamptz to date.
*
- * On successful conversion, *overflow is set to zero if it's not NULL.
+ * If the timestamp falls out of the valid range for the date type, error
+ * handling proceeds based on escontext.
*
- * If the timestamptz is finite but out of the valid range for date, then:
- * if overflow is NULL, we throw an out-of-range error.
- * if overflow is not NULL, we store +1 or -1 there to indicate the sign
- * of the overflow, and return the appropriate date infinity.
+ * If escontext is NULL, we throw an out-of-range error (hard error).
+ * If escontext is not NULL, we return NOBEGIN or NOEND for lower bound or
+ * upper bound overflow, respectively, and record a soft error.
*
* Note: given the ranges of the types, overflow is only possible at
- * the minimum end of the range, but we don't assume that in this code.
+ * the lower bound of the range, but we don't assume that in this code.
*/
DateADT
-timestamptz2date_opt_overflow(TimestampTz timestamp, int *overflow)
+timestamptz2date_safe(TimestampTz timestamp, Node *escontext)
{
DateADT result;
struct pg_tm tt,
@@ -1476,9 +1437,6 @@ timestamptz2date_opt_overflow(TimestampTz timestamp, int *overflow)
fsec_t fsec;
int tz;
- if (overflow)
- *overflow = 0;
-
if (TIMESTAMP_IS_NOBEGIN(timestamp))
DATE_NOBEGIN(result);
else if (TIMESTAMP_IS_NOEND(timestamp))
@@ -1487,21 +1445,12 @@ timestamptz2date_opt_overflow(TimestampTz timestamp, int *overflow)
{
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
{
- if (overflow)
- {
- if (timestamp < 0)
- {
- *overflow = -1;
- DATE_NOBEGIN(result);
- }
- else
- {
- *overflow = 1; /* not actually reachable */
- DATE_NOEND(result);
- }
- return result;
- }
- ereport(ERROR,
+ if (timestamp < 0)
+ DATE_NOBEGIN(result);
+ else
+ DATE_NOEND(result); /* not actually reachable */
+
+ ereturn(escontext, result,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
}
diff --git a/src/backend/utils/adt/pg_locale_libc.c b/src/backend/utils/adt/pg_locale_libc.c
index e2beee44335..6ad3f93b543 100644
--- a/src/backend/utils/adt/pg_locale_libc.c
+++ b/src/backend/utils/adt/pg_locale_libc.c
@@ -342,7 +342,6 @@ static const struct ctype_methods ctype_methods_libc_sb = {
.char_tolower = char_tolower_libc,
.wc_toupper = toupper_libc_sb,
.wc_tolower = tolower_libc_sb,
- .max_chr = UCHAR_MAX,
};
/*
@@ -369,7 +368,6 @@ static const struct ctype_methods ctype_methods_libc_other_mb = {
.char_tolower = char_tolower_libc,
.wc_toupper = toupper_libc_sb,
.wc_tolower = tolower_libc_sb,
- .max_chr = UCHAR_MAX,
};
static const struct ctype_methods ctype_methods_libc_utf8 = {
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 156a4830ffd..af48527d436 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -2363,18 +2363,21 @@ int32
timestamp_cmp_timestamptz_internal(Timestamp timestampVal, TimestampTz dt2)
{
TimestampTz dt1;
- int overflow;
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
- dt1 = timestamp2timestamptz_opt_overflow(timestampVal, &overflow);
- if (overflow > 0)
+ dt1 = timestamp2timestamptz_safe(timestampVal, (Node *) &escontext);
+ if (escontext.error_occurred)
{
- /* dt1 is larger than any finite timestamp, but less than infinity */
- return TIMESTAMP_IS_NOEND(dt2) ? -1 : +1;
- }
- if (overflow < 0)
- {
- /* dt1 is less than any finite timestamp, but more than -infinity */
- return TIMESTAMP_IS_NOBEGIN(dt2) ? +1 : -1;
+ if (TIMESTAMP_IS_NOEND(dt1))
+ {
+ /* dt1 is larger than any finite timestamp, but less than infinity */
+ return TIMESTAMP_IS_NOEND(dt2) ? -1 : +1;
+ }
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ /* dt1 is less than any finite timestamp, but more than -infinity */
+ return TIMESTAMP_IS_NOBEGIN(dt2) ? +1 : -1;
+ }
}
return timestamptz_cmp_internal(dt1, dt2);
@@ -6434,15 +6437,15 @@ timestamp_timestamptz(PG_FUNCTION_ARGS)
/*
* Convert timestamp to timestamp with time zone.
*
- * On successful conversion, *overflow is set to zero if it's not NULL.
+ * If the timestamp is finite but out of the valid range for timestamptz,
+ * error handling proceeds based on escontext.
*
- * If the timestamp is finite but out of the valid range for timestamptz, then:
- * if overflow is NULL, we throw an out-of-range error.
- * if overflow is not NULL, we store +1 or -1 there to indicate the sign
- * of the overflow, and return the appropriate timestamptz infinity.
+ * If escontext is NULL, we throw an out-of-range error (hard error).
+ * If escontext is not NULL, we return NOBEGIN or NOEND for lower bound or
+ * upper bound overflow, respectively, and record a soft error.
*/
TimestampTz
-timestamp2timestamptz_opt_overflow(Timestamp timestamp, int *overflow)
+timestamp2timestamptz_safe(Timestamp timestamp, Node *escontext)
{
TimestampTz result;
struct pg_tm tt,
@@ -6450,9 +6453,6 @@ timestamp2timestamptz_opt_overflow(Timestamp timestamp, int *overflow)
fsec_t fsec;
int tz;
- if (overflow)
- *overflow = 0;
-
if (TIMESTAMP_NOT_FINITE(timestamp))
return timestamp;
@@ -6467,26 +6467,14 @@ timestamp2timestamptz_opt_overflow(Timestamp timestamp, int *overflow)
return result;
}
- if (overflow)
- {
- if (timestamp < 0)
- {
- *overflow = -1;
- TIMESTAMP_NOBEGIN(result);
- }
- else
- {
- *overflow = 1;
- TIMESTAMP_NOEND(result);
- }
- return result;
- }
+ if (timestamp < 0)
+ TIMESTAMP_NOBEGIN(result);
+ else
+ TIMESTAMP_NOEND(result);
- ereport(ERROR,
+ ereturn(escontext, result,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
-
- return 0;
}
/*
@@ -6495,7 +6483,7 @@ timestamp2timestamptz_opt_overflow(Timestamp timestamp, int *overflow)
static TimestampTz
timestamp2timestamptz(Timestamp timestamp)
{
- return timestamp2timestamptz_opt_overflow(timestamp, NULL);
+ return timestamp2timestamptz_safe(timestamp, NULL);
}
/* timestamptz_timestamp()
@@ -6515,21 +6503,21 @@ timestamptz_timestamp(PG_FUNCTION_ARGS)
static Timestamp
timestamptz2timestamp(TimestampTz timestamp)
{
- return timestamptz2timestamp_opt_overflow(timestamp, NULL);
+ return timestamptz2timestamp_safe(timestamp, NULL);
}
/*
* Convert timestamp with time zone to timestamp.
*
- * On successful conversion, *overflow is set to zero if it's not NULL.
+ * If the timestamptz is finite but out of the valid range for timestamp,
+ * error handling proceeds based on escontext.
*
- * If the timestamptz is finite but out of the valid range for timestamp, then:
- * if overflow is NULL, we throw an out-of-range error.
- * if overflow is not NULL, we store +1 or -1 there to indicate the sign
- * of the overflow, and return the appropriate timestamp infinity.
+ * If escontext is NULL, we throw an out-of-range error (hard error).
+ * If escontext is not NULL, we return NOBEGIN or NOEND for lower bound or
+ * upper bound overflow, respectively, and record a soft error.
*/
Timestamp
-timestamptz2timestamp_opt_overflow(TimestampTz timestamp, int *overflow)
+timestamptz2timestamp_safe(TimestampTz timestamp, Node *escontext)
{
Timestamp result;
struct pg_tm tt,
@@ -6537,50 +6525,29 @@ timestamptz2timestamp_opt_overflow(TimestampTz timestamp, int *overflow)
fsec_t fsec;
int tz;
- if (overflow)
- *overflow = 0;
-
if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
{
- if (overflow)
- {
- if (timestamp < 0)
- {
- *overflow = -1;
- TIMESTAMP_NOBEGIN(result);
- }
- else
- {
- *overflow = 1;
- TIMESTAMP_NOEND(result);
- }
- return result;
- }
- ereport(ERROR,
+ if (timestamp < 0)
+ TIMESTAMP_NOBEGIN(result);
+ else
+ TIMESTAMP_NOEND(result);
+
+ ereturn(escontext, result,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
}
if (tm2timestamp(tm, fsec, NULL, &result) != 0)
{
- if (overflow)
- {
- if (timestamp < 0)
- {
- *overflow = -1;
- TIMESTAMP_NOBEGIN(result);
- }
- else
- {
- *overflow = 1;
- TIMESTAMP_NOEND(result);
- }
- return result;
- }
- ereport(ERROR,
+ if (timestamp < 0)
+ TIMESTAMP_NOBEGIN(result);
+ else
+ TIMESTAMP_NOEND(result);
+
+ ereturn(escontext, result,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
}
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index fac509ed134..931ab8b979e 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -24,7 +24,7 @@
#include "access/xlog_internal.h"
#include "catalog/storage_xlog.h"
#include "commands/dbcommands_xlog.h"
-#include "commands/sequence.h"
+#include "commands/sequence_xlog.h"
#include "commands/tablespace.h"
#include "replication/message.h"
#include "replication/origin.h"
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 46b4d89dd6e..3f8d353c49e 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -13,14 +13,10 @@
#ifndef SEQUENCE_H
#define SEQUENCE_H
-#include "access/xlogreader.h"
#include "catalog/objectaddress.h"
#include "fmgr.h"
-#include "lib/stringinfo.h"
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "storage/relfilelocator.h"
-
typedef struct FormData_pg_sequence_data
{
@@ -42,15 +38,6 @@ typedef FormData_pg_sequence_data *Form_pg_sequence_data;
#define SEQ_COL_FIRSTCOL SEQ_COL_LASTVAL
#define SEQ_COL_LASTCOL SEQ_COL_CALLED
-/* XLOG stuff */
-#define XLOG_SEQ_LOG 0x00
-
-typedef struct xl_seq_rec
-{
- RelFileLocator locator;
- /* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
-} xl_seq_rec;
-
extern int64 nextval_internal(Oid relid, bool check_permissions);
extern Datum nextval(PG_FUNCTION_ARGS);
extern List *sequence_options(Oid relid);
@@ -63,9 +50,4 @@ extern void ResetSequence(Oid seq_relid);
extern void SetSequence(Oid relid, int64 next, bool is_called);
extern void ResetSequenceCaches(void);
-extern void seq_redo(XLogReaderState *record);
-extern void seq_desc(StringInfo buf, XLogReaderState *record);
-extern const char *seq_identify(uint8 info);
-extern void seq_mask(char *page, BlockNumber blkno);
-
#endif /* SEQUENCE_H */
diff --git a/src/include/commands/sequence_xlog.h b/src/include/commands/sequence_xlog.h
new file mode 100644
index 00000000000..c8cf0112c38
--- /dev/null
+++ b/src/include/commands/sequence_xlog.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequence_xlog.h
+ * Sequence WAL definitions.
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/sequence_xlog.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef SEQUENCE_XLOG_H
+#define SEQUENCE_XLOG_H
+
+#include "access/xlogreader.h"
+#include "lib/stringinfo.h"
+
+/* Record identifier */
+#define XLOG_SEQ_LOG 0x00
+
+/*
+ * The "special area" of a sequence's buffer page looks like this.
+ */
+#define SEQ_MAGIC 0x1717
+
+typedef struct sequence_magic
+{
+ uint32 magic;
+} sequence_magic;
+
+/* Sequence WAL record */
+typedef struct xl_seq_rec
+{
+ RelFileLocator locator;
+ /* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
+} xl_seq_rec;
+
+extern void seq_redo(XLogReaderState *record);
+extern void seq_desc(StringInfo buf, XLogReaderState *record);
+extern const char *seq_identify(uint8 info);
+extern void seq_mask(char *page, BlockNumber blkno);
+
+#endif /* SEQUENCE_XLOG_H */
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index abfda0b1ae9..7316ac0ff17 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -98,10 +98,10 @@ TimeTzADTPGetDatum(const TimeTzADT *X)
/* date.c */
extern int32 anytime_typmod_check(bool istz, int32 typmod);
extern double date2timestamp_no_overflow(DateADT dateVal);
-extern Timestamp date2timestamp_opt_overflow(DateADT dateVal, int *overflow);
-extern TimestampTz date2timestamptz_opt_overflow(DateADT dateVal, int *overflow);
-extern DateADT timestamp2date_opt_overflow(Timestamp timestamp, int *overflow);
-extern DateADT timestamptz2date_opt_overflow(TimestampTz timestamp, int *overflow);
+extern Timestamp date2timestamp_safe(DateADT dateVal, Node *escontext);
+extern TimestampTz date2timestamptz_safe(DateADT dateVal, Node *escontext);
+extern DateADT timestamp2date_safe(Timestamp timestamp, Node *escontext);
+extern DateADT timestamptz2date_safe(TimestampTz timestamp, Node *escontext);
extern int32 date_cmp_timestamp_internal(DateADT dateVal, Timestamp dt2);
extern int32 date_cmp_timestamptz_internal(DateADT dateVal, TimestampTz dt2);
diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h
index 54193a17a90..42e21e7fb8a 100644
--- a/src/include/utils/pg_locale.h
+++ b/src/include/utils/pg_locale.h
@@ -134,12 +134,6 @@ struct ctype_methods
* pg_strlower().
*/
char (*char_tolower) (unsigned char ch, pg_locale_t locale);
-
- /*
- * For regex and pattern matching efficiency, the maximum char value
- * supported by the above methods. If zero, limit is set by regex code.
- */
- pg_wchar max_chr;
};
/*
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index 93531732b08..f1a85c7d9eb 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -142,10 +142,10 @@ extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
-extern TimestampTz timestamp2timestamptz_opt_overflow(Timestamp timestamp,
- int *overflow);
-extern Timestamp timestamptz2timestamp_opt_overflow(TimestampTz timestamp,
- int *overflow);
+extern TimestampTz timestamp2timestamptz_safe(Timestamp timestamp,
+ Node *escontext);
+extern Timestamp timestamptz2timestamp_safe(TimestampTz timestamp,
+ Node *escontext);
extern int32 timestamp_cmp_timestamptz_internal(Timestamp timestampVal,
TimestampTz dt2);
diff --git a/src/test/modules/injection_points/Makefile b/src/test/modules/injection_points/Makefile
index 7b3c0c4b716..0a9716db27c 100644
--- a/src/test/modules/injection_points/Makefile
+++ b/src/test/modules/injection_points/Makefile
@@ -19,6 +19,7 @@ ISOLATION = basic \
syscache-update-pruned \
index-concurrently-upsert \
reindex-concurrently-upsert \
+ reindex-concurrently-upsert-on-constraint \
index-concurrently-upsert-predicate
TAP_TESTS = 1
diff --git a/src/test/modules/injection_points/expected/reindex-concurrently-upsert-on-constraint.out b/src/test/modules/injection_points/expected/reindex-concurrently-upsert-on-constraint.out
new file mode 100644
index 00000000000..c1ac1f77c61
--- /dev/null
+++ b/src/test/modules/injection_points/expected/reindex-concurrently-upsert-on-constraint.out
@@ -0,0 +1,238 @@
+Parsed test spec with 4 sessions
+
+starting permutation: s3_setup_wait_before_set_dead s3_start_reindex s1_start_upsert s4_wakeup_to_set_dead s2_start_upsert s4_wakeup_s1 s4_wakeup_s2
+injection_points_attach
+-----------------------
+
+(1 row)
+
+injection_points_attach
+-----------------------
+
+(1 row)
+
+injection_points_set_local
+--------------------------
+
+(1 row)
+
+step s3_setup_wait_before_set_dead:
+ SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
+
+injection_points_attach
+-----------------------
+
+(1 row)
+
+step s3_start_reindex:
+ REINDEX INDEX CONCURRENTLY test.tbl_pkey;
+ <waiting ...>
+step s1_start_upsert:
+ INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
+ <waiting ...>
+step s4_wakeup_to_set_dead:
+ SELECT injection_points_detach('reindex-relation-concurrently-before-set-dead');
+ SELECT injection_points_wakeup('reindex-relation-concurrently-before-set-dead');
+
+injection_points_detach
+-----------------------
+
+(1 row)
+
+injection_points_wakeup
+-----------------------
+
+(1 row)
+
+step s2_start_upsert:
+ INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
+ <waiting ...>
+step s4_wakeup_s1:
+ SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
+ SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
+
+injection_points_detach
+-----------------------
+
+(1 row)
+
+injection_points_wakeup
+-----------------------
+
+(1 row)
+
+step s1_start_upsert: <... completed>
+step s4_wakeup_s2:
+ SELECT injection_points_detach('exec-insert-before-insert-speculative');
+ SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
+
+injection_points_detach
+-----------------------
+
+(1 row)
+
+injection_points_wakeup
+-----------------------
+
+(1 row)
+
+step s2_start_upsert: <... completed>
+step s3_start_reindex: <... completed>
+
+starting permutation: s3_setup_wait_before_swap s3_start_reindex s1_start_upsert s4_wakeup_to_swap s2_start_upsert s4_wakeup_s2 s4_wakeup_s1
+injection_points_attach
+-----------------------
+
+(1 row)
+
+injection_points_attach
+-----------------------
+
+(1 row)
+
+injection_points_set_local
+--------------------------
+
+(1 row)
+
+step s3_setup_wait_before_swap:
+ SELECT injection_points_attach('reindex-relation-concurrently-before-swap', 'wait');
+
+injection_points_attach
+-----------------------
+
+(1 row)
+
+step s3_start_reindex:
+ REINDEX INDEX CONCURRENTLY test.tbl_pkey;
+ <waiting ...>
+step s1_start_upsert:
+ INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
+ <waiting ...>
+step s4_wakeup_to_swap:
+ SELECT injection_points_detach('reindex-relation-concurrently-before-swap');
+ SELECT injection_points_wakeup('reindex-relation-concurrently-before-swap');
+
+injection_points_detach
+-----------------------
+
+(1 row)
+
+injection_points_wakeup
+-----------------------
+
+(1 row)
+
+step s2_start_upsert:
+ INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
+ <waiting ...>
+step s4_wakeup_s2:
+ SELECT injection_points_detach('exec-insert-before-insert-speculative');
+ SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
+
+injection_points_detach
+-----------------------
+
+(1 row)
+
+injection_points_wakeup
+-----------------------
+
+(1 row)
+
+step s4_wakeup_s1:
+ SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
+ SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
+
+injection_points_detach
+-----------------------
+
+(1 row)
+
+injection_points_wakeup
+-----------------------
+
+(1 row)
+
+step s1_start_upsert: <... completed>
+step s2_start_upsert: <... completed>
+step s3_start_reindex: <... completed>
+
+starting permutation: s3_setup_wait_before_set_dead s3_start_reindex s1_start_upsert s2_start_upsert s4_wakeup_s1 s4_wakeup_to_set_dead s4_wakeup_s2
+injection_points_attach
+-----------------------
+
+(1 row)
+
+injection_points_attach
+-----------------------
+
+(1 row)
+
+injection_points_set_local
+--------------------------
+
+(1 row)
+
+step s3_setup_wait_before_set_dead:
+ SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
+
+injection_points_attach
+-----------------------
+
+(1 row)
+
+step s3_start_reindex:
+ REINDEX INDEX CONCURRENTLY test.tbl_pkey;
+ <waiting ...>
+step s1_start_upsert:
+ INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
+ <waiting ...>
+step s2_start_upsert:
+ INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
+ <waiting ...>
+step s4_wakeup_s1:
+ SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
+ SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
+
+injection_points_detach
+-----------------------
+
+(1 row)
+
+injection_points_wakeup
+-----------------------
+
+(1 row)
+
+step s1_start_upsert: <... completed>
+step s4_wakeup_to_set_dead:
+ SELECT injection_points_detach('reindex-relation-concurrently-before-set-dead');
+ SELECT injection_points_wakeup('reindex-relation-concurrently-before-set-dead');
+
+injection_points_detach
+-----------------------
+
+(1 row)
+
+injection_points_wakeup
+-----------------------
+
+(1 row)
+
+step s4_wakeup_s2:
+ SELECT injection_points_detach('exec-insert-before-insert-speculative');
+ SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
+
+injection_points_detach
+-----------------------
+
+(1 row)
+
+injection_points_wakeup
+-----------------------
+
+(1 row)
+
+step s2_start_upsert: <... completed>
+step s3_start_reindex: <... completed>
diff --git a/src/test/modules/injection_points/meson.build b/src/test/modules/injection_points/meson.build
index 485b483e3ca..3bbbc0bf7b3 100644
--- a/src/test/modules/injection_points/meson.build
+++ b/src/test/modules/injection_points/meson.build
@@ -50,6 +50,7 @@ tests += {
'syscache-update-pruned',
'index-concurrently-upsert',
'reindex-concurrently-upsert',
+ 'reindex-concurrently-upsert-on-constraint',
'index-concurrently-upsert-predicate',
],
'runningcheck': false, # see syscache-update-pruned
diff --git a/src/test/modules/injection_points/specs/reindex-concurrently-upsert-on-constraint.spec b/src/test/modules/injection_points/specs/reindex-concurrently-upsert-on-constraint.spec
new file mode 100644
index 00000000000..8126256460c
--- /dev/null
+++ b/src/test/modules/injection_points/specs/reindex-concurrently-upsert-on-constraint.spec
@@ -0,0 +1,110 @@
+# Test race conditions involving:
+#
+# - s1: UPSERT a tuple
+# - s2: UPSERT the same tuple
+# - s3: concurrently REINDEX the primary key
+#
+# - s4: operations with injection points
+
+setup
+{
+ CREATE EXTENSION injection_points;
+ CREATE SCHEMA test;
+ CREATE UNLOGGED TABLE test.tbl(i int primary key, updated_at timestamp);
+ ALTER TABLE test.tbl SET (parallel_workers=0);
+}
+
+teardown
+{
+ DROP SCHEMA test CASCADE;
+ DROP EXTENSION injection_points;
+}
+
+session s1
+setup
+{
+ SELECT injection_points_set_local();
+ SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
+}
+step s1_start_upsert
+{
+ INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
+}
+
+session s2
+setup
+{
+ SELECT injection_points_set_local();
+ SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
+}
+step s2_start_upsert
+{
+ INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
+}
+
+session s3
+setup
+{
+ SELECT injection_points_set_local();
+}
+step s3_setup_wait_before_set_dead
+{
+ SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
+}
+step s3_setup_wait_before_swap
+{
+ SELECT injection_points_attach('reindex-relation-concurrently-before-swap', 'wait');
+}
+step s3_start_reindex
+{
+ REINDEX INDEX CONCURRENTLY test.tbl_pkey;
+}
+
+session s4
+step s4_wakeup_to_swap
+{
+ SELECT injection_points_detach('reindex-relation-concurrently-before-swap');
+ SELECT injection_points_wakeup('reindex-relation-concurrently-before-swap');
+}
+step s4_wakeup_s1
+{
+ SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
+ SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
+}
+step s4_wakeup_s2
+{
+ SELECT injection_points_detach('exec-insert-before-insert-speculative');
+ SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
+}
+step s4_wakeup_to_set_dead
+{
+ SELECT injection_points_detach('reindex-relation-concurrently-before-set-dead');
+ SELECT injection_points_wakeup('reindex-relation-concurrently-before-set-dead');
+}
+
+permutation
+ s3_setup_wait_before_set_dead
+ s3_start_reindex(s1_start_upsert, s2_start_upsert)
+ s1_start_upsert
+ s4_wakeup_to_set_dead
+ s2_start_upsert(s1_start_upsert)
+ s4_wakeup_s1
+ s4_wakeup_s2
+
+permutation
+ s3_setup_wait_before_swap
+ s3_start_reindex(s1_start_upsert, s2_start_upsert)
+ s1_start_upsert
+ s4_wakeup_to_swap
+ s2_start_upsert(s1_start_upsert)
+ s4_wakeup_s2
+ s4_wakeup_s1
+
+permutation
+ s3_setup_wait_before_set_dead
+ s3_start_reindex(s1_start_upsert, s2_start_upsert)
+ s1_start_upsert
+ s2_start_upsert(s1_start_upsert)
+ s4_wakeup_s1
+ s4_wakeup_to_set_dead
+ s4_wakeup_s2