diff options
Diffstat (limited to 'src')
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 |
