mirror of https://github.com/postgres/postgres
This patch provides the additional logging information in the following conflict scenarios while applying changes: insert_exists: Inserting a row that violates a NOT DEFERRABLE unique constraint. update_differ: Updating a row that was previously modified by another origin. update_exists: The updated row value violates a NOT DEFERRABLE unique constraint. update_missing: The tuple to be updated is missing. delete_differ: Deleting a row that was previously modified by another origin. delete_missing: The tuple to be deleted is missing. For insert_exists and update_exists conflicts, the log can include the origin and commit timestamp details of the conflicting key with track_commit_timestamp enabled. update_differ and delete_differ conflicts can only be detected when track_commit_timestamp is enabled on the subscriber. We do not offer additional logging for exclusion constraint violations because these constraints can specify rules that are more complex than simple equality checks. Resolving such conflicts won't be straightforward. This area can be further enhanced if required. Author: Hou Zhijie Reviewed-by: Shveta Malik, Amit Kapila, Nisha Moond, Hayato Kuroda, Dilip Kumar Discussion: https://postgr.es/m/OS0PR01MB5716352552DFADB8E9AD1D8994C92@OS0PR01MB5716.jpnprd01.prod.outlook.compull/175/head
parent
adf97c1562
commit
9758174e2e
@ -0,0 +1,488 @@ |
|||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
* conflict.c |
||||||
|
* Support routines for logging conflicts. |
||||||
|
* |
||||||
|
* Copyright (c) 2024, PostgreSQL Global Development Group |
||||||
|
* |
||||||
|
* IDENTIFICATION |
||||||
|
* src/backend/replication/logical/conflict.c |
||||||
|
* |
||||||
|
* This file contains the code for logging conflicts on the subscriber during |
||||||
|
* logical replication. |
||||||
|
*------------------------------------------------------------------------- |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "postgres.h" |
||||||
|
|
||||||
|
#include "access/commit_ts.h" |
||||||
|
#include "access/tableam.h" |
||||||
|
#include "executor/executor.h" |
||||||
|
#include "replication/conflict.h" |
||||||
|
#include "replication/logicalrelation.h" |
||||||
|
#include "storage/lmgr.h" |
||||||
|
#include "utils/lsyscache.h" |
||||||
|
|
||||||
|
static const char *const ConflictTypeNames[] = { |
||||||
|
[CT_INSERT_EXISTS] = "insert_exists", |
||||||
|
[CT_UPDATE_DIFFER] = "update_differ", |
||||||
|
[CT_UPDATE_EXISTS] = "update_exists", |
||||||
|
[CT_UPDATE_MISSING] = "update_missing", |
||||||
|
[CT_DELETE_DIFFER] = "delete_differ", |
||||||
|
[CT_DELETE_MISSING] = "delete_missing" |
||||||
|
}; |
||||||
|
|
||||||
|
static int errcode_apply_conflict(ConflictType type); |
||||||
|
static int errdetail_apply_conflict(EState *estate, |
||||||
|
ResultRelInfo *relinfo, |
||||||
|
ConflictType type, |
||||||
|
TupleTableSlot *searchslot, |
||||||
|
TupleTableSlot *localslot, |
||||||
|
TupleTableSlot *remoteslot, |
||||||
|
Oid indexoid, TransactionId localxmin, |
||||||
|
RepOriginId localorigin, |
||||||
|
TimestampTz localts); |
||||||
|
static char *build_tuple_value_details(EState *estate, ResultRelInfo *relinfo, |
||||||
|
ConflictType type, |
||||||
|
TupleTableSlot *searchslot, |
||||||
|
TupleTableSlot *localslot, |
||||||
|
TupleTableSlot *remoteslot, |
||||||
|
Oid indexoid); |
||||||
|
static char *build_index_value_desc(EState *estate, Relation localrel, |
||||||
|
TupleTableSlot *slot, Oid indexoid); |
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the xmin and commit timestamp data (origin and timestamp) associated |
||||||
|
* with the provided local tuple. |
||||||
|
* |
||||||
|
* Return true if the commit timestamp data was found, false otherwise. |
||||||
|
*/ |
||||||
|
bool |
||||||
|
GetTupleTransactionInfo(TupleTableSlot *localslot, TransactionId *xmin, |
||||||
|
RepOriginId *localorigin, TimestampTz *localts) |
||||||
|
{ |
||||||
|
Datum xminDatum; |
||||||
|
bool isnull; |
||||||
|
|
||||||
|
xminDatum = slot_getsysattr(localslot, MinTransactionIdAttributeNumber, |
||||||
|
&isnull); |
||||||
|
*xmin = DatumGetTransactionId(xminDatum); |
||||||
|
Assert(!isnull); |
||||||
|
|
||||||
|
/*
|
||||||
|
* The commit timestamp data is not available if track_commit_timestamp is |
||||||
|
* disabled. |
||||||
|
*/ |
||||||
|
if (!track_commit_timestamp) |
||||||
|
{ |
||||||
|
*localorigin = InvalidRepOriginId; |
||||||
|
*localts = 0; |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
return TransactionIdGetCommitTsData(*xmin, localts, localorigin); |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* This function is used to report a conflict while applying replication |
||||||
|
* changes. |
||||||
|
* |
||||||
|
* 'searchslot' should contain the tuple used to search the local tuple to be |
||||||
|
* updated or deleted. |
||||||
|
* |
||||||
|
* 'localslot' should contain the existing local tuple, if any, that conflicts |
||||||
|
* with the remote tuple. 'localxmin', 'localorigin', and 'localts' provide the |
||||||
|
* transaction information related to this existing local tuple. |
||||||
|
* |
||||||
|
* 'remoteslot' should contain the remote new tuple, if any. |
||||||
|
* |
||||||
|
* The 'indexoid' represents the OID of the unique index that triggered the |
||||||
|
* constraint violation error. We use this to report the key values for |
||||||
|
* conflicting tuple. |
||||||
|
* |
||||||
|
* The caller must ensure that the index with the OID 'indexoid' is locked so |
||||||
|
* that we can fetch and display the conflicting key value. |
||||||
|
*/ |
||||||
|
void |
||||||
|
ReportApplyConflict(EState *estate, ResultRelInfo *relinfo, int elevel, |
||||||
|
ConflictType type, TupleTableSlot *searchslot, |
||||||
|
TupleTableSlot *localslot, TupleTableSlot *remoteslot, |
||||||
|
Oid indexoid, TransactionId localxmin, |
||||||
|
RepOriginId localorigin, TimestampTz localts) |
||||||
|
{ |
||||||
|
Relation localrel = relinfo->ri_RelationDesc; |
||||||
|
|
||||||
|
Assert(!OidIsValid(indexoid) || |
||||||
|
CheckRelationOidLockedByMe(indexoid, RowExclusiveLock, true)); |
||||||
|
|
||||||
|
ereport(elevel, |
||||||
|
errcode_apply_conflict(type), |
||||||
|
errmsg("conflict detected on relation \"%s.%s\": conflict=%s", |
||||||
|
get_namespace_name(RelationGetNamespace(localrel)), |
||||||
|
RelationGetRelationName(localrel), |
||||||
|
ConflictTypeNames[type]), |
||||||
|
errdetail_apply_conflict(estate, relinfo, type, searchslot, |
||||||
|
localslot, remoteslot, indexoid, |
||||||
|
localxmin, localorigin, localts)); |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Find all unique indexes to check for a conflict and store them into |
||||||
|
* ResultRelInfo. |
||||||
|
*/ |
||||||
|
void |
||||||
|
InitConflictIndexes(ResultRelInfo *relInfo) |
||||||
|
{ |
||||||
|
List *uniqueIndexes = NIL; |
||||||
|
|
||||||
|
for (int i = 0; i < relInfo->ri_NumIndices; i++) |
||||||
|
{ |
||||||
|
Relation indexRelation = relInfo->ri_IndexRelationDescs[i]; |
||||||
|
|
||||||
|
if (indexRelation == NULL) |
||||||
|
continue; |
||||||
|
|
||||||
|
/* Detect conflict only for unique indexes */ |
||||||
|
if (!relInfo->ri_IndexRelationInfo[i]->ii_Unique) |
||||||
|
continue; |
||||||
|
|
||||||
|
/* Don't support conflict detection for deferrable index */ |
||||||
|
if (!indexRelation->rd_index->indimmediate) |
||||||
|
continue; |
||||||
|
|
||||||
|
uniqueIndexes = lappend_oid(uniqueIndexes, |
||||||
|
RelationGetRelid(indexRelation)); |
||||||
|
} |
||||||
|
|
||||||
|
relInfo->ri_onConflictArbiterIndexes = uniqueIndexes; |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Add SQLSTATE error code to the current conflict report. |
||||||
|
*/ |
||||||
|
static int |
||||||
|
errcode_apply_conflict(ConflictType type) |
||||||
|
{ |
||||||
|
switch (type) |
||||||
|
{ |
||||||
|
case CT_INSERT_EXISTS: |
||||||
|
case CT_UPDATE_EXISTS: |
||||||
|
return errcode(ERRCODE_UNIQUE_VIOLATION); |
||||||
|
case CT_UPDATE_DIFFER: |
||||||
|
case CT_UPDATE_MISSING: |
||||||
|
case CT_DELETE_DIFFER: |
||||||
|
case CT_DELETE_MISSING: |
||||||
|
return errcode(ERRCODE_T_R_SERIALIZATION_FAILURE); |
||||||
|
} |
||||||
|
|
||||||
|
Assert(false); |
||||||
|
return 0; /* silence compiler warning */ |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Add an errdetail() line showing conflict detail. |
||||||
|
* |
||||||
|
* The DETAIL line comprises of two parts: |
||||||
|
* 1. Explanation of the conflict type, including the origin and commit |
||||||
|
* timestamp of the existing local tuple. |
||||||
|
* 2. Display of conflicting key, existing local tuple, remote new tuple, and |
||||||
|
* replica identity columns, if any. The remote old tuple is excluded as its |
||||||
|
* information is covered in the replica identity columns. |
||||||
|
*/ |
||||||
|
static int |
||||||
|
errdetail_apply_conflict(EState *estate, ResultRelInfo *relinfo, |
||||||
|
ConflictType type, TupleTableSlot *searchslot, |
||||||
|
TupleTableSlot *localslot, TupleTableSlot *remoteslot, |
||||||
|
Oid indexoid, TransactionId localxmin, |
||||||
|
RepOriginId localorigin, TimestampTz localts) |
||||||
|
{ |
||||||
|
StringInfoData err_detail; |
||||||
|
char *val_desc; |
||||||
|
char *origin_name; |
||||||
|
|
||||||
|
initStringInfo(&err_detail); |
||||||
|
|
||||||
|
/* First, construct a detailed message describing the type of conflict */ |
||||||
|
switch (type) |
||||||
|
{ |
||||||
|
case CT_INSERT_EXISTS: |
||||||
|
case CT_UPDATE_EXISTS: |
||||||
|
Assert(OidIsValid(indexoid)); |
||||||
|
|
||||||
|
if (localts) |
||||||
|
{ |
||||||
|
if (localorigin == InvalidRepOriginId) |
||||||
|
appendStringInfo(&err_detail, _("Key already exists in unique index \"%s\", modified locally in transaction %u at %s."), |
||||||
|
get_rel_name(indexoid), |
||||||
|
localxmin, timestamptz_to_str(localts)); |
||||||
|
else if (replorigin_by_oid(localorigin, true, &origin_name)) |
||||||
|
appendStringInfo(&err_detail, _("Key already exists in unique index \"%s\", modified by origin \"%s\" in transaction %u at %s."), |
||||||
|
get_rel_name(indexoid), origin_name, |
||||||
|
localxmin, timestamptz_to_str(localts)); |
||||||
|
|
||||||
|
/*
|
||||||
|
* The origin that modified this row has been removed. This |
||||||
|
* can happen if the origin was created by a different apply |
||||||
|
* worker and its associated subscription and origin were |
||||||
|
* dropped after updating the row, or if the origin was |
||||||
|
* manually dropped by the user. |
||||||
|
*/ |
||||||
|
else |
||||||
|
appendStringInfo(&err_detail, _("Key already exists in unique index \"%s\", modified by a non-existent origin in transaction %u at %s."), |
||||||
|
get_rel_name(indexoid), |
||||||
|
localxmin, timestamptz_to_str(localts)); |
||||||
|
} |
||||||
|
else |
||||||
|
appendStringInfo(&err_detail, _("Key already exists in unique index \"%s\", modified in transaction %u."), |
||||||
|
get_rel_name(indexoid), localxmin); |
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
case CT_UPDATE_DIFFER: |
||||||
|
if (localorigin == InvalidRepOriginId) |
||||||
|
appendStringInfo(&err_detail, _("Updating the row that was modified locally in transaction %u at %s."), |
||||||
|
localxmin, timestamptz_to_str(localts)); |
||||||
|
else if (replorigin_by_oid(localorigin, true, &origin_name)) |
||||||
|
appendStringInfo(&err_detail, _("Updating the row that was modified by a different origin \"%s\" in transaction %u at %s."), |
||||||
|
origin_name, localxmin, timestamptz_to_str(localts)); |
||||||
|
|
||||||
|
/* The origin that modified this row has been removed. */ |
||||||
|
else |
||||||
|
appendStringInfo(&err_detail, _("Updating the row that was modified by a non-existent origin in transaction %u at %s."), |
||||||
|
localxmin, timestamptz_to_str(localts)); |
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
case CT_UPDATE_MISSING: |
||||||
|
appendStringInfo(&err_detail, _("Could not find the row to be updated.")); |
||||||
|
break; |
||||||
|
|
||||||
|
case CT_DELETE_DIFFER: |
||||||
|
if (localorigin == InvalidRepOriginId) |
||||||
|
appendStringInfo(&err_detail, _("Deleting the row that was modified locally in transaction %u at %s."), |
||||||
|
localxmin, timestamptz_to_str(localts)); |
||||||
|
else if (replorigin_by_oid(localorigin, true, &origin_name)) |
||||||
|
appendStringInfo(&err_detail, _("Deleting the row that was modified by a different origin \"%s\" in transaction %u at %s."), |
||||||
|
origin_name, localxmin, timestamptz_to_str(localts)); |
||||||
|
|
||||||
|
/* The origin that modified this row has been removed. */ |
||||||
|
else |
||||||
|
appendStringInfo(&err_detail, _("Deleting the row that was modified by a non-existent origin in transaction %u at %s."), |
||||||
|
localxmin, timestamptz_to_str(localts)); |
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
case CT_DELETE_MISSING: |
||||||
|
appendStringInfo(&err_detail, _("Could not find the row to be deleted.")); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
Assert(err_detail.len > 0); |
||||||
|
|
||||||
|
val_desc = build_tuple_value_details(estate, relinfo, type, searchslot, |
||||||
|
localslot, remoteslot, indexoid); |
||||||
|
|
||||||
|
/*
|
||||||
|
* Next, append the key values, existing local tuple, remote tuple and |
||||||
|
* replica identity columns after the message. |
||||||
|
*/ |
||||||
|
if (val_desc) |
||||||
|
appendStringInfo(&err_detail, "\n%s", val_desc); |
||||||
|
|
||||||
|
return errdetail_internal("%s", err_detail.data); |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper function to build the additional details for conflicting key, |
||||||
|
* existing local tuple, remote tuple, and replica identity columns. |
||||||
|
* |
||||||
|
* If the return value is NULL, it indicates that the current user lacks |
||||||
|
* permissions to view the columns involved. |
||||||
|
*/ |
||||||
|
static char * |
||||||
|
build_tuple_value_details(EState *estate, ResultRelInfo *relinfo, |
||||||
|
ConflictType type, |
||||||
|
TupleTableSlot *searchslot, |
||||||
|
TupleTableSlot *localslot, |
||||||
|
TupleTableSlot *remoteslot, |
||||||
|
Oid indexoid) |
||||||
|
{ |
||||||
|
Relation localrel = relinfo->ri_RelationDesc; |
||||||
|
Oid relid = RelationGetRelid(localrel); |
||||||
|
TupleDesc tupdesc = RelationGetDescr(localrel); |
||||||
|
StringInfoData tuple_value; |
||||||
|
char *desc = NULL; |
||||||
|
|
||||||
|
Assert(searchslot || localslot || remoteslot); |
||||||
|
|
||||||
|
initStringInfo(&tuple_value); |
||||||
|
|
||||||
|
/*
|
||||||
|
* Report the conflicting key values in the case of a unique constraint |
||||||
|
* violation. |
||||||
|
*/ |
||||||
|
if (type == CT_INSERT_EXISTS || type == CT_UPDATE_EXISTS) |
||||||
|
{ |
||||||
|
Assert(OidIsValid(indexoid) && localslot); |
||||||
|
|
||||||
|
desc = build_index_value_desc(estate, localrel, localslot, indexoid); |
||||||
|
|
||||||
|
if (desc) |
||||||
|
appendStringInfo(&tuple_value, _("Key %s"), desc); |
||||||
|
} |
||||||
|
|
||||||
|
if (localslot) |
||||||
|
{ |
||||||
|
/*
|
||||||
|
* The 'modifiedCols' only applies to the new tuple, hence we pass |
||||||
|
* NULL for the existing local tuple. |
||||||
|
*/ |
||||||
|
desc = ExecBuildSlotValueDescription(relid, localslot, tupdesc, |
||||||
|
NULL, 64); |
||||||
|
|
||||||
|
if (desc) |
||||||
|
{ |
||||||
|
if (tuple_value.len > 0) |
||||||
|
{ |
||||||
|
appendStringInfoString(&tuple_value, "; "); |
||||||
|
appendStringInfo(&tuple_value, _("existing local tuple %s"), |
||||||
|
desc); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
appendStringInfo(&tuple_value, _("Existing local tuple %s"), |
||||||
|
desc); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (remoteslot) |
||||||
|
{ |
||||||
|
Bitmapset *modifiedCols; |
||||||
|
|
||||||
|
/*
|
||||||
|
* Although logical replication doesn't maintain the bitmap for the |
||||||
|
* columns being inserted, we still use it to create 'modifiedCols' |
||||||
|
* for consistency with other calls to ExecBuildSlotValueDescription. |
||||||
|
* |
||||||
|
* Note that generated columns are formed locally on the subscriber. |
||||||
|
*/ |
||||||
|
modifiedCols = bms_union(ExecGetInsertedCols(relinfo, estate), |
||||||
|
ExecGetUpdatedCols(relinfo, estate)); |
||||||
|
desc = ExecBuildSlotValueDescription(relid, remoteslot, tupdesc, |
||||||
|
modifiedCols, 64); |
||||||
|
|
||||||
|
if (desc) |
||||||
|
{ |
||||||
|
if (tuple_value.len > 0) |
||||||
|
{ |
||||||
|
appendStringInfoString(&tuple_value, "; "); |
||||||
|
appendStringInfo(&tuple_value, _("remote tuple %s"), desc); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
appendStringInfo(&tuple_value, _("Remote tuple %s"), desc); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (searchslot) |
||||||
|
{ |
||||||
|
/*
|
||||||
|
* Note that while index other than replica identity may be used (see |
||||||
|
* IsIndexUsableForReplicaIdentityFull for details) to find the tuple |
||||||
|
* when applying update or delete, such an index scan may not result |
||||||
|
* in a unique tuple and we still compare the complete tuple in such |
||||||
|
* cases, thus such indexes are not used here. |
||||||
|
*/ |
||||||
|
Oid replica_index = GetRelationIdentityOrPK(localrel); |
||||||
|
|
||||||
|
Assert(type != CT_INSERT_EXISTS); |
||||||
|
|
||||||
|
/*
|
||||||
|
* If the table has a valid replica identity index, build the index |
||||||
|
* key value string. Otherwise, construct the full tuple value for |
||||||
|
* REPLICA IDENTITY FULL cases. |
||||||
|
*/ |
||||||
|
if (OidIsValid(replica_index)) |
||||||
|
desc = build_index_value_desc(estate, localrel, searchslot, replica_index); |
||||||
|
else |
||||||
|
desc = ExecBuildSlotValueDescription(relid, searchslot, tupdesc, NULL, 64); |
||||||
|
|
||||||
|
if (desc) |
||||||
|
{ |
||||||
|
if (tuple_value.len > 0) |
||||||
|
{ |
||||||
|
appendStringInfoString(&tuple_value, "; "); |
||||||
|
appendStringInfo(&tuple_value, OidIsValid(replica_index) |
||||||
|
? _("replica identity %s") |
||||||
|
: _("replica identity full %s"), desc); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
appendStringInfo(&tuple_value, OidIsValid(replica_index) |
||||||
|
? _("Replica identity %s") |
||||||
|
: _("Replica identity full %s"), desc); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (tuple_value.len == 0) |
||||||
|
return NULL; |
||||||
|
|
||||||
|
appendStringInfoChar(&tuple_value, '.'); |
||||||
|
return tuple_value.data; |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper functions to construct a string describing the contents of an index |
||||||
|
* entry. See BuildIndexValueDescription for details. |
||||||
|
* |
||||||
|
* The caller must ensure that the index with the OID 'indexoid' is locked so |
||||||
|
* that we can fetch and display the conflicting key value. |
||||||
|
*/ |
||||||
|
static char * |
||||||
|
build_index_value_desc(EState *estate, Relation localrel, TupleTableSlot *slot, |
||||||
|
Oid indexoid) |
||||||
|
{ |
||||||
|
char *index_value; |
||||||
|
Relation indexDesc; |
||||||
|
Datum values[INDEX_MAX_KEYS]; |
||||||
|
bool isnull[INDEX_MAX_KEYS]; |
||||||
|
TupleTableSlot *tableslot = slot; |
||||||
|
|
||||||
|
if (!tableslot) |
||||||
|
return NULL; |
||||||
|
|
||||||
|
Assert(CheckRelationOidLockedByMe(indexoid, RowExclusiveLock, true)); |
||||||
|
|
||||||
|
indexDesc = index_open(indexoid, NoLock); |
||||||
|
|
||||||
|
/*
|
||||||
|
* If the slot is a virtual slot, copy it into a heap tuple slot as |
||||||
|
* FormIndexDatum only works with heap tuple slots. |
||||||
|
*/ |
||||||
|
if (TTS_IS_VIRTUAL(slot)) |
||||||
|
{ |
||||||
|
tableslot = table_slot_create(localrel, &estate->es_tupleTable); |
||||||
|
tableslot = ExecCopySlot(tableslot, slot); |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialize ecxt_scantuple for potential use in FormIndexDatum when |
||||||
|
* index expressions are present. |
||||||
|
*/ |
||||||
|
GetPerTupleExprContext(estate)->ecxt_scantuple = tableslot; |
||||||
|
|
||||||
|
/*
|
||||||
|
* The values/nulls arrays passed to BuildIndexValueDescription should be |
||||||
|
* the results of FormIndexDatum, which are the "raw" input to the index |
||||||
|
* AM. |
||||||
|
*/ |
||||||
|
FormIndexDatum(BuildIndexInfo(indexDesc), tableslot, estate, values, isnull); |
||||||
|
|
||||||
|
index_value = BuildIndexValueDescription(indexDesc, values, isnull); |
||||||
|
|
||||||
|
index_close(indexDesc, NoLock); |
||||||
|
|
||||||
|
return index_value; |
||||||
|
} |
||||||
@ -0,0 +1,58 @@ |
|||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
* conflict.h |
||||||
|
* Exports for conflicts logging. |
||||||
|
* |
||||||
|
* Copyright (c) 2024, PostgreSQL Global Development Group |
||||||
|
* |
||||||
|
*------------------------------------------------------------------------- |
||||||
|
*/ |
||||||
|
#ifndef CONFLICT_H |
||||||
|
#define CONFLICT_H |
||||||
|
|
||||||
|
#include "nodes/execnodes.h" |
||||||
|
#include "utils/timestamp.h" |
||||||
|
|
||||||
|
/*
|
||||||
|
* Conflict types that could occur while applying remote changes. |
||||||
|
*/ |
||||||
|
typedef enum |
||||||
|
{ |
||||||
|
/* The row to be inserted violates unique constraint */ |
||||||
|
CT_INSERT_EXISTS, |
||||||
|
|
||||||
|
/* The row to be updated was modified by a different origin */ |
||||||
|
CT_UPDATE_DIFFER, |
||||||
|
|
||||||
|
/* The updated row value violates unique constraint */ |
||||||
|
CT_UPDATE_EXISTS, |
||||||
|
|
||||||
|
/* The row to be updated is missing */ |
||||||
|
CT_UPDATE_MISSING, |
||||||
|
|
||||||
|
/* The row to be deleted was modified by a different origin */ |
||||||
|
CT_DELETE_DIFFER, |
||||||
|
|
||||||
|
/* The row to be deleted is missing */ |
||||||
|
CT_DELETE_MISSING, |
||||||
|
|
||||||
|
/*
|
||||||
|
* Other conflicts, such as exclusion constraint violations, involve more |
||||||
|
* complex rules than simple equality checks. These conflicts are left for |
||||||
|
* future improvements. |
||||||
|
*/ |
||||||
|
} ConflictType; |
||||||
|
|
||||||
|
extern bool GetTupleTransactionInfo(TupleTableSlot *localslot, |
||||||
|
TransactionId *xmin, |
||||||
|
RepOriginId *localorigin, |
||||||
|
TimestampTz *localts); |
||||||
|
extern void ReportApplyConflict(EState *estate, ResultRelInfo *relinfo, |
||||||
|
int elevel, ConflictType type, |
||||||
|
TupleTableSlot *searchslot, |
||||||
|
TupleTableSlot *localslot, |
||||||
|
TupleTableSlot *remoteslot, |
||||||
|
Oid indexoid, TransactionId localxmin, |
||||||
|
RepOriginId localorigin, TimestampTz localts); |
||||||
|
extern void InitConflictIndexes(ResultRelInfo *relInfo); |
||||||
|
|
||||||
|
#endif |
||||||
Loading…
Reference in new issue