diff --git a/doc/src/sgml/ref/replace.sgmlin b/doc/src/sgml/ref/replace.sgmlin
new file mode 100644
index 000000000..c2a0350c9
--- /dev/null
+++ b/doc/src/sgml/ref/replace.sgmlin
@@ -0,0 +1,30 @@
+
+
+REPLACE
+7
+SQL - Language Statements
+
+
+REPLACE
+insert new row or replace rows in a table
+
+
+
+[NOTICE:REPLACE only has gained support in MySQL compatibility!]
+REPLACE [LOW_PRIORITY | DELAYED]
+ [INTO] tbl_name [(col_name,...)]
+ {VALUES | VALUE ({expr},...),...},...
+OR:
+REPLACE [LOW_PRIORITY | DELAYED]
+ [INTO] tbl_name
+ SET col_name={expr},...
+OR:
+REPLACE [LOW_PRIORITY | DELAYED]
+ [INTO] tbl_name [(col_name,...)]
+ SELECT...
+
+REPLACE works exactly like INSERT, except that if an old row in the table has the same value as a new row for a PRIMARY KEY or a UNIQUE index, the old row is deleted before the new row is inserted.
+
+
+
+
\ No newline at end of file
diff --git a/src/common/backend/nodes/copyfuncs.cpp b/src/common/backend/nodes/copyfuncs.cpp
index 9455d87da..9ed4221fe 100644
--- a/src/common/backend/nodes/copyfuncs.cpp
+++ b/src/common/backend/nodes/copyfuncs.cpp
@@ -299,6 +299,9 @@ static ModifyTable* _copyModifyTable(const ModifyTable* from)
COPY_NODE_FIELD(rowMarks);
COPY_SCALAR_FIELD(epqParam);
COPY_SCALAR_FIELD(partKeyUpdated);
+ if (t_thrd.proc->workingVersionNum >= REPLACE_INTO_VERSION_NUM) {
+ COPY_SCALAR_FIELD(isReplace);
+ }
#ifdef PGXC
COPY_NODE_FIELD(remote_plans);
COPY_NODE_FIELD(remote_insert_plans);
@@ -4618,7 +4621,11 @@ static InsertStmt* _copyInsertStmt(const InsertStmt* from)
COPY_NODE_FIELD(withClause);
COPY_NODE_FIELD(upsertClause);
COPY_NODE_FIELD(hintState);
- COPY_SCALAR_FIELD(isRewritten);
+ if (t_thrd.proc->workingVersionNum >= REPLACE_INTO_VERSION_NUM) {
+ COPY_NODE_FIELD(targetList);
+ COPY_SCALAR_FIELD(isReplace);
+ }
+ COPY_SCALAR_FIELD(isRewritten);
COPY_SCALAR_FIELD(hasIgnore);
return newnode;
}
diff --git a/src/common/backend/nodes/equalfuncs.cpp b/src/common/backend/nodes/equalfuncs.cpp
index 1360946cd..ed352168c 100644
--- a/src/common/backend/nodes/equalfuncs.cpp
+++ b/src/common/backend/nodes/equalfuncs.cpp
@@ -905,7 +905,10 @@ static bool _equalInsertStmt(const InsertStmt* a, const InsertStmt* b)
COMPARE_NODE_FIELD(selectStmt);
COMPARE_NODE_FIELD(returningList);
COMPARE_NODE_FIELD(withClause);
- COMPARE_NODE_FIELD(upsertClause);
+ if (t_thrd.proc->workingVersionNum >= REPLACE_INTO_VERSION_NUM) {
+ COMPARE_NODE_FIELD(targetList);
+ }
+ COMPARE_NODE_FIELD(upsertClause);
COMPARE_SCALAR_FIELD(hasIgnore);
return true;
diff --git a/src/common/backend/nodes/outfuncs.cpp b/src/common/backend/nodes/outfuncs.cpp
index e6410d3c5..cb9551aa6 100755
--- a/src/common/backend/nodes/outfuncs.cpp
+++ b/src/common/backend/nodes/outfuncs.cpp
@@ -773,6 +773,9 @@ static void _outModifyTable(StringInfo str, ModifyTable* node)
WRITE_NODE_FIELD(rowMarks);
WRITE_INT_FIELD(epqParam);
WRITE_BOOL_FIELD(partKeyUpdated);
+ if (t_thrd.proc->workingVersionNum >= REPLACE_INTO_VERSION_NUM) {
+ WRITE_BOOL_FIELD(isReplace);
+ }
#ifdef PGXC
WRITE_NODE_FIELD(remote_plans);
WRITE_NODE_FIELD(remote_insert_plans);
@@ -3693,6 +3696,9 @@ static void _outInsertStmt(StringInfo str, InsertStmt* node)
WRITE_NODE_FIELD(selectStmt);
WRITE_NODE_FIELD(returningList);
WRITE_NODE_FIELD(withClause);
+ if (t_thrd.proc->workingVersionNum >= REPLACE_INTO_VERSION_NUM) {
+ WRITE_NODE_FIELD(targetList);
+ }
#ifdef ENABLE_MULTIPLE_NODES
if (t_thrd.proc->workingVersionNum >= UPSERT_ROW_STORE_VERSION_NUM) {
WRITE_NODE_FIELD(upsertClause);
diff --git a/src/common/backend/nodes/readfuncs.cpp b/src/common/backend/nodes/readfuncs.cpp
index ca57c821f..46987f774 100755
--- a/src/common/backend/nodes/readfuncs.cpp
+++ b/src/common/backend/nodes/readfuncs.cpp
@@ -3996,6 +3996,9 @@ static ModifyTable* _readModifyTable(ModifyTable* local_node)
READ_NODE_FIELD(rowMarks);
READ_INT_FIELD(epqParam);
READ_BOOL_FIELD(partKeyUpdated);
+ if (t_thrd.proc->workingVersionNum >= REPLACE_INTO_VERSION_NUM) {
+ READ_BOOL_FIELD(isReplace);
+ }
#ifdef PGXC
READ_NODE_FIELD(remote_plans);
READ_NODE_FIELD(remote_insert_plans);
diff --git a/src/common/backend/parser/analyze.cpp b/src/common/backend/parser/analyze.cpp
index 589847b39..fc863aed9 100644
--- a/src/common/backend/parser/analyze.cpp
+++ b/src/common/backend/parser/analyze.cpp
@@ -1567,6 +1567,32 @@ static Query* transformInsertStmt(ParseState* pstate, InsertStmt* stmt)
/* set io state for backend status for the thread, we will use it to check user space */
pgstat_set_io_state(IOSTATE_WRITE);
+ qry->isReplace = stmt->isReplace;
+ if (stmt->isReplace) {
+ targetPerms |= ACL_DELETE;
+ /* transform set clause in REPLACE INTO stmt */
+ if (selectStmt == NULL && stmt->cols == NULL && stmt->targetList != NULL) {
+ stmt->selectStmt = (Node *)makeNode(SelectStmt);
+ selectStmt = (SelectStmt *)stmt->selectStmt;
+ ListCell* o_target = NULL;
+ List *ctext_expr_list = NULL;
+
+ foreach (o_target, stmt->targetList) {
+ ResTarget* res = (ResTarget*)lfirst(o_target);
+
+ ctext_expr_list = lappend(ctext_expr_list, res->val);
+ }
+ selectStmt->valuesLists = list_make1(ctext_expr_list);
+
+ stmt->cols = (List *)copyObject(stmt->targetList);
+ foreach (o_target, stmt->cols) {
+ ResTarget* res = (ResTarget*)lfirst(o_target);
+ res->val = NULL;
+ }
+
+ }
+ }
+
/*
* Insert into relation pg_auth_history is not allowed.
* We update it only when some user's password has been changed.
diff --git a/src/common/backend/parser/gram.y b/src/common/backend/parser/gram.y
index 7c8edd90f..1bd6cdeb7 100644
--- a/src/common/backend/parser/gram.y
+++ b/src/common/backend/parser/gram.y
@@ -19779,10 +19779,62 @@ InsertStmt: opt_with_clause INSERT hint_string INTO insert_target insert_rest re
$6->relation = $5;
$6->returningList = $7;
$6->withClause = $1;
+ $6->isReplace = false;
$6->hintState = create_hintstate($3);
$6->hasIgnore = ($6->hintState != NULL && $6->hintState->sql_ignore_hint && DB_IS_CMPT(B_FORMAT));
$$ = (Node *) $6;
}
+ | REPLACE hint_string INTO insert_target insert_rest returning_clause
+ {
+#ifndef ENABLE_MULTIPLE_NODES
+ if (u_sess->attr.attr_sql.sql_compatibility == B_FORMAT)
+ {
+ $5->relation = $4;
+ $5->returningList = $6;
+ $5->hintState = create_hintstate($2);
+ $5->isReplace = true;
+ $$ = (Node *) $5;
+ }
+ else
+#endif
+ {
+ const char* message = "REPLACE INTO syntax is not supported.";
+ InsertErrorMessage(message, u_sess->plsql_cxt.plpgsql_yylloc);
+ ereport(errstate,
+ (errmodule(MOD_PARSER),
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("REPLACE INTO syntax is not supported."),
+ parser_errposition(@1)));
+ $$ = NULL;/* not reached */
+ }
+
+ }
+ | REPLACE hint_string INTO insert_target SET set_clause_list
+ {
+#ifndef ENABLE_MULTIPLE_NODES
+ if (u_sess->attr.attr_sql.sql_compatibility == B_FORMAT)
+ {
+ InsertStmt* n = makeNode(InsertStmt);
+ n->relation = $4;
+ n->targetList = $6;
+ n->hintState = create_hintstate($2);
+ n->isReplace = true;
+ $$ = (Node*)n;
+ }
+ else
+#endif
+ {
+ const char* message = "REPLACE INTO syntax is not supported.";
+ InsertErrorMessage(message, u_sess->plsql_cxt.plpgsql_yylloc);
+ ereport(errstate,
+ (errmodule(MOD_PARSER),
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("REPLACE INTO syntax is not supported."),
+ parser_errposition(@1)));
+ $$ = NULL;/* not reached */
+ }
+
+ }
| opt_with_clause INSERT hint_string INTO insert_target insert_rest upsert_clause returning_clause
{
if ($8 != NIL) {
@@ -19834,6 +19886,7 @@ InsertStmt: opt_with_clause INSERT hint_string INTO insert_target insert_rest re
/* for UPSERT, keep the INSERT statement as well */
$6->relation = $5;
$6->returningList = $8;
+ $6->isReplace = false;
$6->withClause = $1;
$6->hintState = create_hintstate($3);
$6->hasIgnore = ($6->hintState != NULL && $6->hintState->sql_ignore_hint && DB_IS_CMPT(B_FORMAT));
@@ -19878,7 +19931,8 @@ InsertStmt: opt_with_clause INSERT hint_string INTO insert_target insert_rest re
$6->returningList = $8;
$6->withClause = $1;
$6->upsertClause = (UpsertClause *)$7;
- $6->hintState = create_hintstate($3);
+ $6->isReplace = false;
+ $6->hintState = create_hintstate($3);
$6->hasIgnore = ($6->hintState != NULL && $6->hintState->sql_ignore_hint && DB_IS_CMPT(B_FORMAT));
$$ = (Node *) $6;
}
diff --git a/src/common/backend/utils/adt/ruleutils.cpp b/src/common/backend/utils/adt/ruleutils.cpp
index ac4400aca..d561e2d93 100644
--- a/src/common/backend/utils/adt/ruleutils.cpp
+++ b/src/common/backend/utils/adt/ruleutils.cpp
@@ -12473,3 +12473,75 @@ static void replace_cl_types_in_argtypes(Oid func_id, int numargs, Oid* argtypes
ReleaseSysCache(gs_oldtup);
}
}
+
+Oid pg_get_serial_sequence_oid(text* tablename, text* columnname)
+{
+ RangeVar* tablerv = NULL;
+ Oid tableOid;
+ char* column = NULL;
+ AttrNumber attnum;
+ Oid sequenceId = InvalidOid;
+ Relation depRel;
+ ScanKeyData key[3];
+ SysScanDesc scan;
+ HeapTuple tup;
+ List* names = NIL;
+
+ /* Look up table name. */
+ names = textToQualifiedNameList(tablename);
+ tablerv = makeRangeVarFromNameList(names);
+ tableOid = RangeVarGetRelid(tablerv, NoLock, false);
+
+ /* Get the number of the column */
+ column = text_to_cstring(columnname);
+ attnum = get_attnum(tableOid, column);
+ if (attnum == InvalidAttrNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist", column, tablerv->relname)));
+
+ /* Search the dependency table for the dependent sequence */
+ depRel = heap_open(DependRelationId, AccessShareLock);
+
+ ScanKeyInit(
+ &key[0], Anum_pg_depend_refclassid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationRelationId));
+ ScanKeyInit(&key[1], Anum_pg_depend_refobjid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(tableOid));
+ ScanKeyInit(&key[2], Anum_pg_depend_refobjsubid, BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum(attnum));
+
+ scan = systable_beginscan(depRel, DependReferenceIndexId, true, NULL, 3, key);
+
+ while (HeapTupleIsValid(tup = systable_getnext(scan))) {
+ Form_pg_depend deprec = (Form_pg_depend)GETSTRUCT(tup);
+
+ if (deprec->classid == RelationRelationId && deprec->objsubid == 0 && deprec->deptype == DEPENDENCY_AUTO &&
+ get_rel_relkind(deprec->objid) == RELKIND_SEQUENCE) {
+ sequenceId = deprec->objid;
+ break;
+ }
+ }
+ systable_endscan(scan);
+ heap_close(depRel, AccessShareLock);
+
+ if (OidIsValid(sequenceId)) {
+ HeapTuple classtup;
+ Form_pg_class classtuple;
+ char* nspname = NULL;
+
+ classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(sequenceId));
+ if (!HeapTupleIsValid(classtup))
+ return InvalidOid;
+
+ classtuple = (Form_pg_class)GETSTRUCT(classtup);
+
+ nspname = get_namespace_name(classtuple->relnamespace);
+
+ if (nspname == NULL) {
+ ReleaseSysCache(classtup);
+ return InvalidOid;
+ }
+
+ ReleaseSysCache(classtup);
+ return sequenceId;
+ }
+ return InvalidOid;
+}
diff --git a/src/common/backend/utils/init/globals.cpp b/src/common/backend/utils/init/globals.cpp
index cf4c3aff6..d912e4c3e 100644
--- a/src/common/backend/utils/init/globals.cpp
+++ b/src/common/backend/utils/init/globals.cpp
@@ -59,7 +59,7 @@ bool open_join_children = true;
bool will_shutdown = false;
/* hard-wired binary version number */
-const uint32 GRAND_VERSION_NUM = 92777;
+const uint32 GRAND_VERSION_NUM = 92778;
const uint32 SQL_PATCH_VERSION_NUM = 92625;
const uint32 PREDPUSH_SAME_LEVEL_VERSION_NUM = 92522;
@@ -112,6 +112,7 @@ const uint32 ADVANCE_CATALOG_XMIN_VERSION_NUM = 92609;
const uint32 LOGICAL_DECODE_FLATTEN_TOAST_VERSION_NUM = 92614;
const uint32 SWITCH_ROLE_VERSION_NUM = 92618;
const uint32 PLAN_SELECT_VERSION_NUM = 92776;
+const uint32 REPLACE_INTO_VERSION_NUM = 92778;
/* Version number of the guc parameter backend_version added in V500R001C20 */
diff --git a/src/gausskernel/optimizer/plan/planner.cpp b/src/gausskernel/optimizer/plan/planner.cpp
index f64a1162e..eb7586d1f 100755
--- a/src/gausskernel/optimizer/plan/planner.cpp
+++ b/src/gausskernel/optimizer/plan/planner.cpp
@@ -1838,6 +1838,7 @@ Plan* subquery_planner(PlannerGlobal* glob, Query* parse, PlannerInfo* parent_ro
#ifdef PGXC
plan = pgxc_make_modifytable(root, plan);
#endif
+ ((ModifyTable*)plan)->isReplace = parse->isReplace;
}
}
diff --git a/src/gausskernel/optimizer/util/optcommon.cpp b/src/gausskernel/optimizer/util/optcommon.cpp
index e4ce13e96..7233c2a91 100755
--- a/src/gausskernel/optimizer/util/optcommon.cpp
+++ b/src/gausskernel/optimizer/util/optcommon.cpp
@@ -42,7 +42,10 @@ void GetPlanNodePlainText(
*pt_operation = "MODIFY TABLE";
switch (((ModifyTable*)plan)->operation) {
case CMD_INSERT:
- *pname = *operation = *pt_options = "Insert";
+ if (((ModifyTable*)plan)->isReplace)
+ *pname = *operation = *pt_options = "Replace";
+ else
+ *pname = *operation = *pt_options = "Insert";
break;
case CMD_UPDATE:
*pname = *operation = *pt_options = "Update";
diff --git a/src/gausskernel/process/tcop/pquery.cpp b/src/gausskernel/process/tcop/pquery.cpp
index 0a20cd5af..b135b4340 100644
--- a/src/gausskernel/process/tcop/pquery.cpp
+++ b/src/gausskernel/process/tcop/pquery.cpp
@@ -313,12 +313,17 @@ static void ProcessQuery(
lastOid = queryDesc->estate->es_lastoid;
else
lastOid = InvalidOid;
- ret = snprintf_s(completionTag,
- COMPLETION_TAG_BUFSIZE,
- COMPLETION_TAG_BUFSIZE - 1,
- "INSERT %u %lu",
- lastOid,
- queryDesc->estate->es_processed);
+ if (((ModifyTableState*)queryDesc->planstate)->isReplace) {
+ ret = snprintf_s(completionTag, COMPLETION_TAG_BUFSIZE, COMPLETION_TAG_BUFSIZE - 1,
+ "REPLACE %u %lu", lastOid, queryDesc->estate->es_processed);
+ } else {
+ ret = snprintf_s(completionTag,
+ COMPLETION_TAG_BUFSIZE,
+ COMPLETION_TAG_BUFSIZE - 1,
+ "INSERT %u %lu",
+ lastOid,
+ queryDesc->estate->es_processed);
+ }
securec_check_ss(ret, "\0", "\0");
break;
case CMD_UPDATE:
diff --git a/src/gausskernel/runtime/executor/nodeModifyTable.cpp b/src/gausskernel/runtime/executor/nodeModifyTable.cpp
index 246e58473..f76600441 100644
--- a/src/gausskernel/runtime/executor/nodeModifyTable.cpp
+++ b/src/gausskernel/runtime/executor/nodeModifyTable.cpp
@@ -107,6 +107,7 @@ extern void FlushInsertSelectBulk(
extern void FlushErrorInfo(Relation rel, EState* estate, ErrorCacheEntry* cache);
extern void HeapInsertCStore(Relation relation, ResultRelInfo* resultRelInfo, HeapTuple tup, int option);
extern void HeapDeleteCStore(Relation relation, ItemPointer tid, Oid tableOid, Snapshot snapshot);
+extern Oid pg_get_serial_sequence_oid(text* tablename, text* columnname);
#ifdef ENABLE_MULTIPLE_NODES
extern void HeapInsertTsStore(Relation relation, ResultRelInfo* resultRelInfo, HeapTuple tup, int option);
#endif /* ENABLE_MULTIPLE_NODES */
@@ -1465,13 +1466,35 @@ TupleTableSlot* ExecInsertT(ModifyTableState* state, TupleTableSlot* slot, Tuple
* insert index entries for tuple
*/
pTSelf = tableam_tops_get_t_self(result_relation_desc, tuple);
- if (result_rel_info->ri_NumIndices > 0 && !RelationIsColStore(result_relation_desc))
- recheck_indexes = ExecInsertIndexTuples(slot,
+ if (result_rel_info->ri_NumIndices > 0 && !RelationIsColStore(result_relation_desc)) {
+ if (state->isReplace) {
+ bool specConflict = false;
+ recheck_indexes = ExecInsertIndexTuples(slot,
pTSelf,
estate,
RELATION_IS_PARTITIONED(result_relation_desc) ? heap_rel : NULL,
RELATION_IS_PARTITIONED(result_relation_desc) ? partition : NULL,
- bucket_id, NULL, NULL);
+ bucket_id, &specConflict, NULL);
+ if (specConflict) {
+ state->isConflict = true;
+ tableam_tuple_abort_speculative(target_rel, tuple);
+ list_free(recheck_indexes);
+ return slot;
+ } else {
+ state->isConflict = false;
+ }
+ } else {
+ recheck_indexes = ExecInsertIndexTuples(slot,
+ pTSelf,
+ estate,
+ RELATION_IS_PARTITIONED(result_relation_desc) ? heap_rel : NULL,
+ RELATION_IS_PARTITIONED(result_relation_desc) ? partition : NULL,
+ bucket_id, NULL, NULL);
+ }
+ } else {
+ state->isConflict = false;
+ }
+
}
}
if (pTSelf == NULL) {
@@ -1594,7 +1617,7 @@ TupleTableSlot* ExecDelete(ItemPointer tupleid, Oid deletePartitionOid, int2 buc
oldtuple,
#endif
tupleid);
- if (!dodelete) /* "do nothing" */
+ if (!dodelete && !node->isReplace) /* "do nothing" */
return NULL;
}
@@ -3120,6 +3143,127 @@ uint64 GetDeleteLimitCount(ExprContext* econtext, PlanState* scan, Limit *limitP
return (uint64)iCount;
}
+static TupleTableSlot* ExecReplace(EState* estate, ModifyTableState* node, TupleTableSlot* slot, TupleTableSlot* plan_slot, int2 bucketid, int hi_options, List* partition_list)
+{
+ Form_pg_attribute att_tup;
+ Oid seqOid = InvalidOid;
+ Relation rel = estate->es_result_relation_info->ri_RelationDesc;
+ TupleTableSlot* (*ExecInsert)(
+ ModifyTableState* state, TupleTableSlot*, TupleTableSlot*, EState*, bool, int, List**) = NULL;
+
+ ExecInsert = ExecInsertT;
+
+ /* REPLACE INTO not support sequence, check columns to report ERROR */
+ for (int attrno=1; attrno<=rel->rd_att->natts; ++attrno) {
+ att_tup = rel->rd_att->attrs[attrno-1];
+ errno_t rc;
+ char *nspname = get_namespace_name(rel->rd_rel->relnamespace);
+ char tableName[NAMEDATALEN*2+2] = {0};
+ text *tbname;
+ text *attname;
+ rc = memset_s(tableName, NAMEDATALEN*2, 0, NAMEDATALEN*2);
+ securec_check(rc, "\0", "\0");
+
+ if (nspname != NULL) {
+ const char *sym = ".";
+ int nspnameLen = strlen(nspname);
+ int symLen = strlen(sym);
+ int tbnameLen = strlen(rel->rd_rel->relname.data);
+ rc = memcpy_s(&tableName[0], NAMEDATALEN*2+2, nspname, nspnameLen);
+ securec_check(rc, "\0","\0");
+ rc = memcpy_s(&tableName[nspnameLen], NAMEDATALEN*2+2, sym, symLen);
+ securec_check(rc, "\0","\0");
+ rc = memcpy_s(&tableName[nspnameLen+symLen], NAMEDATALEN*2+2, rel->rd_rel->relname.data, tbnameLen);
+ securec_check(rc, "\0","\0");
+ tableName[nspnameLen+symLen+tbnameLen] = 0;
+ tbname = cstring_to_text(&tableName[0]);
+ } else {
+ tbname = cstring_to_text(rel->rd_rel->relname.data);
+ }
+
+ attname = cstring_to_text(att_tup->attname.data);
+ seqOid = pg_get_serial_sequence_oid(tbname, attname);
+
+ if (OidIsValid(seqOid))
+ elog(ERROR, "REPLACE can not work on sequence!");
+ }
+
+ /* set flag to start loop */
+ node->isConflict = true;
+
+ /* we assume there is conflict by default, try to delete the conflict tuple then insert new one */
+ while (node->isConflict) {
+ ConflictInfoData conflictInfo;
+ Oid conflictPartOid = InvalidOid;
+ int2 conflictBucketid = InvalidBktId;
+ bool isgpi = false;
+ Oid partitionid = InvalidOid;
+ Oid subPartitionId = InvalidOid;
+ Relation targetrel = NULL;
+ Relation heaprel = NULL;
+ Relation subPartRel = NULL;
+ Partition partition = NULL;
+ Partition subPart = NULL;
+ Tuple tuple = NULL;
+ ResultRelInfo* resultRelInfo = NULL;
+
+ slot = plan_slot;
+ resultRelInfo = estate->es_result_relation_info;
+ targetrel = resultRelInfo->ri_RelationDesc;
+ heaprel = targetrel;
+ tuple = tableam_tslot_get_tuple_from_slot(targetrel, slot);
+ if (RelationIsPartitioned(targetrel)) {
+ partitionid = heapTupleGetPartitionId(targetrel, tuple);
+ searchFakeReationForPartitionOid(estate->esfRelations,
+ estate->es_query_cxt,
+ targetrel,
+ partitionid,
+ heaprel,
+ partition,
+ RowExclusiveLock);
+
+ if (RelationIsSubPartitioned(targetrel)) {
+ subPartitionId = heapTupleGetPartitionId(heaprel, tuple);
+ searchFakeReationForPartitionOid(estate->esfRelations,
+ estate->es_query_cxt,
+ heaprel,
+ subPartitionId,
+ subPartRel,
+ subPart,
+ RowExclusiveLock);
+ partitionid = subPartitionId;
+ heaprel = subPartRel;
+ partition = subPart;
+ }
+ }
+
+ targetrel = heaprel;
+ if (!ExecCheckIndexConstraints(plan_slot, estate, targetrel,
+ partition, &isgpi, bucketid, &conflictInfo,
+ &conflictPartOid, &conflictBucketid)) {
+
+ ExecDelete(&(&conflictInfo)->conflictTid, conflictPartOid,
+ conflictBucketid, NULL, plan_slot, &node->mt_epqstate,
+ node, node->canSetTag);
+ InstrCountFiltered2(&node->ps, 1);
+ } else {
+ slot = ExecInsert(node, slot, plan_slot, estate, node->canSetTag, hi_options, &partition_list);
+ }
+ }
+
+}
+
+static TupleTableSlot* FetchPlanSlot(ModifyTableState* node, PlanState* subPlanState)
+{
+ EState* estate = node->ps.state;
+
+ if (estate->result_rel_index > 0) {
+ return ExecProject(node->mt_ProjInfos[estate->result_rel_index], NULL);
+ } else {
+ return ExecProcNode(subPlanState);
+ }
+}
+
/* ----------------------------------------------------------------
* ExecModifyTable
*
@@ -3224,7 +3368,7 @@ TupleTableSlot* ExecModifyTable(ModifyTableState* node)
#endif
if (operation == CMD_INSERT) {
- if (node->ps.type == T_ModifyTableState || node->mt_upsert->us_action != UPSERT_NONE ||
+ if (node->ps.type == T_ModifyTableState || node->mt_upsert->us_action != UPSERT_NONE || node->isReplace ||
(result_rel_info->ri_TrigDesc != NULL && (result_rel_info->ri_TrigDesc->trig_insert_before_row ||
result_rel_info->ri_TrigDesc->trig_insert_instead_row)))
ExecInsert = ExecInsertT;
@@ -3456,7 +3600,11 @@ TupleTableSlot* ExecModifyTable(ModifyTableState* node)
#endif
switch (operation) {
case CMD_INSERT:
- slot = ExecInsert(node, slot, plan_slot, estate, node->canSetTag, hi_options, &partition_list);
+ if (!node->isReplace) {
+ slot = ExecInsert(node, slot, plan_slot, estate, node->canSetTag, hi_options, &partition_list);
+ } else {
+ slot = ExecReplace(estate, node, slot, plan_slot, bucketid, hi_options, partition_list);
+ }
break;
case CMD_UPDATE:
slot = ExecUpdate(tuple_id,
@@ -3650,6 +3798,7 @@ ModifyTableState* ExecInitModifyTable(ModifyTable* node, EState* estate, int efl
upsertState->us_updateproj = NULL;
upsertState->us_updateWhere = NIL;
mt_state->mt_upsert = upsertState;
+ mt_state->isReplace = node->isReplace;
mt_state->fireBSTriggers = true;
@@ -3722,7 +3871,7 @@ ModifyTableState* ExecInitModifyTable(ModifyTable* node, EState* estate, int efl
result_rel_info->ri_FdwRoutine->GetFdwType() != MOT_ORC) {
#endif
if (RelationIsUstoreFormat(result_rel_info->ri_RelationDesc) || operation != CMD_DELETE) {
- ExecOpenIndices(result_rel_info, (node->upsertAction != UPSERT_NONE ||
+ ExecOpenIndices(result_rel_info, (node->upsertAction != UPSERT_NONE || node->isReplace ||
(estate->es_plannedstmt && estate->es_plannedstmt->hasIgnore)));
}
#ifdef ENABLE_MOT
@@ -3854,7 +4003,7 @@ ModifyTableState* ExecInitModifyTable(ModifyTable* node, EState* estate, int efl
* If needed, Initialize target list, projection and qual for DUPLICATE KEY UPDATE
*/
result_rel_info = mt_state->resultRelInfo;
- if (node->upsertAction == UPSERT_UPDATE) {
+ if (node->upsertAction == UPSERT_UPDATE ||node->isReplace) {
ExprContext* econtext = NULL;
ExprState* setexpr = NULL;
TupleDesc tupDesc;
diff --git a/src/gausskernel/runtime/opfusion/opfusion_util.cpp b/src/gausskernel/runtime/opfusion/opfusion_util.cpp
index 62d7ad0d6..67e5d6508 100644
--- a/src/gausskernel/runtime/opfusion/opfusion_util.cpp
+++ b/src/gausskernel/runtime/opfusion/opfusion_util.cpp
@@ -222,6 +222,11 @@ const char* getBypassReason(FusionType result)
return "Bypass not executed because it's Var type allowed for target in sort query";
break;
}
+
+ case NOBYPASS_REPLACE_NOT_SUPPORT: {
+ return "Bypass not support REPLACE INTO statement";
+ break;
+ }
case NOBYPASS_UPSERT_NOT_SUPPORT: {
return "Bypass not support INSERT INTO ... ON DUPLICATE KEY UPDATE statement";
@@ -904,6 +909,9 @@ FusionType checkBaseResult(Plan* top_plan)
if (node->upsertAction != UPSERT_NONE) {
return NOBYPASS_UPSERT_NOT_SUPPORT;
}
+ if (node->isReplace) {
+ return NOBYPASS_REPLACE_NOT_SUPPORT;
+ }
return result;
}
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index aabff18f9..8aee5f90e 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -97,6 +97,7 @@ extern const uint32 ANALYZER_HOOK_VERSION_NUM;
extern const uint32 SUPPORT_HASH_XLOG_VERSION_NUM;
extern const uint32 PITR_INIT_VERSION_NUM;
extern const uint32 PUBLICATION_INITIAL_DATA_VERSION_NAME;
+extern const uint32 REPLACE_INTO_VERSION_NUM;
extern const uint32 CREATE_FUNCTION_DEFINER_VERSION;
extern const uint32 KEYWORD_IGNORE_COMPART_VERSION_NUM;
extern const uint32 CSN_TIME_BARRIER_VERSION;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 43d76c83c..d75a8dabc 100755
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1377,6 +1377,8 @@ typedef struct ModifyTableState {
CmdType operation; /* INSERT, UPDATE, or DELETE */
bool canSetTag; /* do we set the command tag/es_processed? */
bool mt_done; /* are we done? */
+ bool isReplace;
+ bool isConflict;
PlanState** mt_plans; /* subplans (one per target rel) */
#ifdef PGXC
PlanState** mt_remoterels; /* per-target remote query node */
diff --git a/src/include/nodes/parsenodes_common.h b/src/include/nodes/parsenodes_common.h
index 34796423d..bac33ee47 100644
--- a/src/include/nodes/parsenodes_common.h
+++ b/src/include/nodes/parsenodes_common.h
@@ -454,7 +454,9 @@ typedef struct InsertStmt {
WithClause *withClause; /* WITH clause */
UpsertClause *upsertClause; /* DUPLICATE KEY UPDATE clause */
HintState *hintState;
- bool isRewritten; /* is this Stmt created by rewritter or end user? */
+ bool isReplace;
+ List *targetList;
+ bool isRewritten; /* is this Stmt created by rewritter or end user? */
bool hasIgnore; /* is this Stmt containing ignore keyword? */
} InsertStmt;
@@ -1960,7 +1962,7 @@ typedef struct Query {
List* mergeActionList; /* list of actions for MERGE (only) */
Query* upsertQuery; /* insert query for INSERT ON DUPLICATE KEY UPDATE (only) */
UpsertExpr* upsertClause; /* DUPLICATE KEY UPDATE [NOTHING | ...] */
-
+ bool isReplace;
bool isRowTriggerShippable; /* true if all row triggers are shippable. */
bool use_star_targets; /* true if use * for targetlist. */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 21ad78f4d..d5faaff65 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -422,6 +422,7 @@ typedef struct ModifyTable {
List* updateTlist; /* List of UPDATE target */
List* exclRelTlist; /* target list of the EXECLUDED pseudo relation */
Index exclRelRTIndex; /* RTI of the EXCLUDED pseudo relation */
+ bool isReplace;
Node* upsertWhere; /* Qualifiers for upsert's update clause to check */
OpMemInfo mem_info; /* Memory info for modify node */
diff --git a/src/include/opfusion/opfusion_util.h b/src/include/opfusion/opfusion_util.h
index a77e624fc..1ffd343b3 100644
--- a/src/include/opfusion/opfusion_util.h
+++ b/src/include/opfusion/opfusion_util.h
@@ -113,6 +113,7 @@ enum FusionType {
NOBYPASS_PARTITION_BYPASS_NOT_OPEN,
NOBYPASS_VERSION_SCAN_PLAN,
NOBYPASS_PARTITION_TYPE_NOT_SUPPORT,
+ NOBYPASS_REPLACE_NOT_SUPPORT,
NOBYPASS_GPC_NOT_SUPPORT_PARTITION_BYPASS
};
@@ -266,4 +267,4 @@ void InitPartitionRelationInFusion(Oid partOid, Relation parentRel, Partition *p
void ExeceDoneInIndexFusionConstruct(bool isPartTbl, Relation *parentRel, Partition *part, Relation *index,
Relation *rel);
-#endif /* SRC_INCLUDE_OPFUSION_OPFUSION_UTIL_H_ */
\ No newline at end of file
+#endif /* SRC_INCLUDE_OPFUSION_OPFUSION_UTIL_H_ */
diff --git a/src/test/regress/expected/replaceinto.out b/src/test/regress/expected/replaceinto.out
new file mode 100644
index 000000000..869bda51f
--- /dev/null
+++ b/src/test/regress/expected/replaceinto.out
@@ -0,0 +1,421 @@
+replace into replace_test values(1,'aaaaa','aaaaa');
+ERROR: REPLACE INTO syntax is not supported.
+LINE 1: replace into replace_test values(1,'aaaaa','aaaaa');
+ ^
+create database db_replaceinto dbcompatibility='B';
+\c db_replaceinto
+CREATE TABLE replace_test(
+ id int NOT NULL,
+ name varchar(20) DEFAULT NULL,
+ key varchar(20) DEFAULT NULL,
+ PRIMARY KEY(id)
+);
+NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "replace_test_pkey" for table "replace_test"
+create unique index reidx on replace_test(key);
+create or replace function before_tri() returns trigger as $$
+begin
+ if TG_OP = 'INSERT' then
+ raise notice 'before insert trigger';
+ end if;
+ if TG_OP = 'DELETE' then
+ raise notice 'before delete trigger';
+ end if;
+ return new;
+end $$ language plpgsql;
+create trigger replace_before before insert or delete on
+replace_test for each row execute procedure before_tri();
+create or replace function after_tri() returns trigger as $$
+begin
+ if TG_OP = 'INSERT' then
+ raise notice 'after insert trigger';
+ end if;
+ if TG_OP = 'DELETE' then
+ raise notice 'after delete trigger';
+ end if;
+ return new;
+end $$ language plpgsql;
+create trigger replace_after after insert or delete on
+replace_test for each row execute procedure after_tri();
+select * from replace_test;
+ id | name | key
+----+------+-----
+(0 rows)
+
+replace into replace_test values(1,'aaaaa','aaaaa');
+NOTICE: before insert trigger
+NOTICE: after insert trigger
+select * from replace_test;
+ id | name | key
+----+-------+-------
+ 1 | aaaaa | aaaaa
+(1 row)
+
+replace into replace_test values(2,'bbbbb','bbbbb');
+NOTICE: before insert trigger
+NOTICE: after insert trigger
+select * from replace_test;
+ id | name | key
+----+-------+-------
+ 1 | aaaaa | aaaaa
+ 2 | bbbbb | bbbbb
+(2 rows)
+
+replace into replace_test values(3,'ccccc','ccccc'),(4,'ddddd','ddddd'),(5,'eeeee','eeeee');
+NOTICE: before insert trigger
+NOTICE: before insert trigger
+NOTICE: before insert trigger
+NOTICE: after insert trigger
+NOTICE: after insert trigger
+NOTICE: after insert trigger
+select * from replace_test;
+ id | name | key
+----+-------+-------
+ 1 | aaaaa | aaaaa
+ 2 | bbbbb | bbbbb
+ 3 | ccccc | ccccc
+ 4 | ddddd | ddddd
+ 5 | eeeee | eeeee
+(5 rows)
+
+replace into replace_test values(1,'aaaaa','ddddd');
+NOTICE: before delete trigger
+NOTICE: before delete trigger
+NOTICE: before insert trigger
+NOTICE: after delete trigger
+NOTICE: after delete trigger
+NOTICE: after insert trigger
+select * from replace_test;
+ id | name | key
+----+-------+-------
+ 2 | bbbbb | bbbbb
+ 3 | ccccc | ccccc
+ 5 | eeeee | eeeee
+ 1 | aaaaa | ddddd
+(4 rows)
+
+truncate replace_test;
+select * from replace_test;
+ id | name | key
+----+------+-----
+(0 rows)
+
+replace into replace_test set id=1,name='aaaaa', key='bbbbb';
+NOTICE: before insert trigger
+NOTICE: after insert trigger
+select * from replace_test;
+ id | name | key
+----+-------+-------
+ 1 | aaaaa | bbbbb
+(1 row)
+
+replace into replace_test set id=2,name=default, key='bbbbb';
+NOTICE: before delete trigger
+NOTICE: before insert trigger
+NOTICE: after delete trigger
+NOTICE: after insert trigger
+select * from replace_test;
+ id | name | key
+----+------+-------
+ 2 | | bbbbb
+(1 row)
+
+replace into replace_test values(3,default,'ccccc'),(4,'ddddd','ddddd'),(5,'eeeee','eeeee');
+NOTICE: before insert trigger
+NOTICE: before insert trigger
+NOTICE: before insert trigger
+NOTICE: after insert trigger
+NOTICE: after insert trigger
+NOTICE: after insert trigger
+select * from replace_test;
+ id | name | key
+----+-------+-------
+ 2 | | bbbbb
+ 3 | | ccccc
+ 4 | ddddd | ddddd
+ 5 | eeeee | eeeee
+(4 rows)
+
+replace into replace_test values(1,'aaaaa','ddddd');
+NOTICE: before delete trigger
+NOTICE: before insert trigger
+NOTICE: after delete trigger
+NOTICE: after insert trigger
+select * from replace_test;
+ id | name | key
+----+-------+-------
+ 2 | | bbbbb
+ 3 | | ccccc
+ 5 | eeeee | eeeee
+ 1 | aaaaa | ddddd
+(4 rows)
+
+replace into replace_test select * from replace_test;
+NOTICE: before delete trigger
+NOTICE: before insert trigger
+NOTICE: before delete trigger
+NOTICE: before insert trigger
+NOTICE: before delete trigger
+NOTICE: before insert trigger
+NOTICE: before delete trigger
+NOTICE: before insert trigger
+NOTICE: after delete trigger
+NOTICE: after insert trigger
+NOTICE: after delete trigger
+NOTICE: after insert trigger
+NOTICE: after delete trigger
+NOTICE: after insert trigger
+NOTICE: after delete trigger
+NOTICE: after insert trigger
+select * from replace_test;
+ id | name | key
+----+-------+-------
+ 2 | | bbbbb
+ 3 | | ccccc
+ 5 | eeeee | eeeee
+ 1 | aaaaa | ddddd
+(4 rows)
+
+replace into replace_test set id=1,name = (select name from replace_test where id=1);
+NOTICE: before delete trigger
+NOTICE: before insert trigger
+NOTICE: after delete trigger
+NOTICE: after insert trigger
+drop table replace_test;
+CREATE TABLE replace_partition_test(
+ id int NOT NULL,
+ name varchar(20) DEFAULT NULL,
+ key varchar(20) DEFAULT NULL,
+ PRIMARY KEY(id)
+)
+partition by range(id)
+(
+ partition replace_p1 values less than (5),
+ partition replace_p2 values less than (10),
+ partition replace_p3 values less than (20)
+);
+NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "replace_partition_test_pkey" for table "replace_partition_test"
+create unique index repartidx on replace_partition_test(key);
+replace into replace_partition_test values(1,'aaaaa','aaaaa');
+replace into replace_partition_test values(6,'bbbbb','bbbbb');
+replace into replace_partition_test values(15,'ccccc','ccccc');
+select * from replace_partition_test;
+ id | name | key
+----+-------+-------
+ 1 | aaaaa | aaaaa
+ 6 | bbbbb | bbbbb
+ 15 | ccccc | ccccc
+(3 rows)
+
+replace into replace_partition_test values(7,'ddddd','ddddd');
+select * from replace_partition_test;
+ id | name | key
+----+-------+-------
+ 1 | aaaaa | aaaaa
+ 6 | bbbbb | bbbbb
+ 7 | ddddd | ddddd
+ 15 | ccccc | ccccc
+(4 rows)
+
+replace into replace_partition_test values(7,'ddddd','aaaaa');
+select * from replace_partition_test;
+ id | name | key
+----+-------+-------
+ 6 | bbbbb | bbbbb
+ 7 | ddddd | aaaaa
+ 15 | ccccc | ccccc
+(3 rows)
+
+select * from replace_partition_test partition(replace_p1);
+ id | name | key
+----+------+-----
+(0 rows)
+
+select * from replace_partition_test partition(replace_p2);
+ id | name | key
+----+-------+-------
+ 6 | bbbbb | bbbbb
+ 7 | ddddd | aaaaa
+(2 rows)
+
+drop table replace_partition_test;
+create table replace_test1(a serial primary key, b int);
+NOTICE: CREATE TABLE will create implicit sequence "replace_test1_a_seq" for serial column "replace_test1.a"
+NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "replace_test1_pkey" for table "replace_test1"
+replace into replace_test1 values(1,2);
+ERROR: REPLACE can not work on sequence!
+select * from replace_test1;
+ a | b
+---+---
+(0 rows)
+
+drop table replace_test1;
+create table replace_test2(id int, name char(20));
+replace into replace_test2 values(1,'aaaaa');
+replace into replace_test2 values(1,'aaaaa');
+replace into replace_test2 values(2,'bbbbb');
+select * from replace_test2;
+ id | name
+----+----------------------
+ 1 | aaaaa
+ 1 | aaaaa
+ 2 | bbbbb
+(3 rows)
+
+drop table replace_test2;
+create table t_range_list
+( id number PRIMARY KEY not null,
+partition_key int,
+subpartition_key int,
+col2 varchar2(10) )
+partition by range(partition_key)
+subpartition by list(subpartition_key)(
+partition p1 values less than (100)
+(subpartition sub_1_1 values (10),
+subpartition sub_1_2 values (20)),
+partition p2 values less than(200)
+(subpartition sub_2_1 values (10),
+subpartition sub_2_2 values (20)),
+partition p3 values less than (300)
+(subpartition sub_3_1 values (10),
+subpartition sub_3_2 values (20)));
+NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t_range_list_pkey" for table "t_range_list"
+REPLACE INTO t_range_list VALUES(1,50,10,'sub_1_1');
+REPLACE INTO t_range_list VALUES(2,150,20,'sub_2_2');
+REPLACE INTO t_range_list VALUES(3,250,10,'sub_3_1');
+select * from t_range_list partition(p1);
+ id | partition_key | subpartition_key | col2
+----+---------------+------------------+---------
+ 1 | 50 | 10 | sub_1_1
+(1 row)
+
+select * from t_range_list partition(p2);
+ id | partition_key | subpartition_key | col2
+----+---------------+------------------+---------
+ 2 | 150 | 20 | sub_2_2
+(1 row)
+
+select * from t_range_list partition(p3);
+ id | partition_key | subpartition_key | col2
+----+---------------+------------------+---------
+ 3 | 250 | 10 | sub_3_1
+(1 row)
+
+CREATE TABLE t1(id int,p int,sub int);
+INSERT INTO t1 VALUES(2,60,10);
+select * from t_range_list;
+ id | partition_key | subpartition_key | col2
+----+---------------+------------------+---------
+ 1 | 50 | 10 | sub_1_1
+ 2 | 150 | 20 | sub_2_2
+ 3 | 250 | 10 | sub_3_1
+(3 rows)
+
+REPLACE INTO t_range_list VALUES(1,210,20,'sub_3_2');
+REPLACE INTO t_range_list(id,partition_key,subpartition_key ) SELECT * FROM t1;
+REPLACE INTO t_range_list SET id=3,partition_key=190,subpartition_key=20,col2='sub_2_2';
+select * from t_range_list;
+ id | partition_key | subpartition_key | col2
+----+---------------+------------------+---------
+ 2 | 60 | 10 |
+ 3 | 190 | 20 | sub_2_2
+ 1 | 210 | 20 | sub_3_2
+(3 rows)
+
+REPLACE INTO t_range_list partition(p2) VALUES(1,50,10,'fail');
+ERROR: inserted partition key does not map to the table partition
+DETAIL: N/A.
+select * from t_range_list;
+ id | partition_key | subpartition_key | col2
+----+---------------+------------------+---------
+ 2 | 60 | 10 |
+ 3 | 190 | 20 | sub_2_2
+ 1 | 210 | 20 | sub_3_2
+(3 rows)
+
+REPLACE INTO t_range_list subpartition(sub_2_1) VALUES(2,150,20,'fail');
+ERROR: inserted subpartition key does not map to the table subpartition
+DETAIL: N/A.
+select * from t_range_list;
+ id | partition_key | subpartition_key | col2
+----+---------------+------------------+---------
+ 2 | 60 | 10 |
+ 3 | 190 | 20 | sub_2_2
+ 1 | 210 | 20 | sub_3_2
+(3 rows)
+
+REPLACE INTO t_range_list subpartition(sub_3_1) VALUES(3,250,10,'new');
+select * from t_range_list;
+ id | partition_key | subpartition_key | col2
+----+---------------+------------------+---------
+ 2 | 60 | 10 |
+ 1 | 210 | 20 | sub_3_2
+ 3 | 250 | 10 | new
+(3 rows)
+
+drop table t_range_list;
+drop table t1;
+CREATE TABLE ustore_test (col1 int PRIMARY KEY, col2 INT) with(storage_type=ustore);
+NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "ustore_test_pkey" for table "ustore_test"
+REPLACE INTO ustore_test values(1,2);
+REPLACE INTO ustore_test values(2,3);
+REPLACE INTO ustore_test values(3,4);
+REPLACE INTO ustore_test values(4,5);
+select * from ustore_test;
+ col1 | col2
+------+------
+ 1 | 2
+ 2 | 3
+ 3 | 4
+ 4 | 5
+(4 rows)
+
+REPLACE INTO ustore_test values(1,5);
+REPLACE INTO ustore_test values(2,6);
+select * from ustore_test;
+ col1 | col2
+------+------
+ 3 | 4
+ 4 | 5
+ 1 | 5
+ 2 | 6
+(4 rows)
+
+drop table ustore_test;
+CREATE TABLE replace_segment_test(
+ id int NOT NULL,
+ name varchar(20) DEFAULT NULL,
+ key varchar(20) DEFAULT NULL,
+ PRIMARY KEY(id)
+)with (parallel_workers=10, SEGMENT=ON)
+partition by range(id)
+(
+ partition replace_p1 values less than (5),
+ partition replace_p2 values less than (10),
+ partition replace_p3 values less than (20)
+);
+NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "replace_segment_test_pkey" for table "replace_segment_test"
+create unique index repartidx on replace_segment_test(key);
+replace into replace_segment_test values(1,'aaaaa','aaaaa');
+replace into replace_segment_test values(6,'bbbbb','bbbbb');
+replace into replace_segment_test values(15,'ccccc','ccccc');
+select * from replace_segment_test;
+ id | name | key
+----+-------+-------
+ 1 | aaaaa | aaaaa
+ 6 | bbbbb | bbbbb
+ 15 | ccccc | ccccc
+(3 rows)
+
+replace into replace_segment_test values(7,'ddddd','ddddd');
+select * from replace_segment_test;
+ id | name | key
+----+-------+-------
+ 1 | aaaaa | aaaaa
+ 6 | bbbbb | bbbbb
+ 7 | ddddd | ddddd
+ 15 | ccccc | ccccc
+(4 rows)
+
+drop table replace_segment_test;
+\c postgres
+drop database db_replaceinto;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index b9274eba8..8f30ed8c1 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -5,7 +5,6 @@
# By convention, we put no more than twenty tests in any one parallel group;
# this limits the number of connections needed to run the tests.
# ----------
-
test: memory_check
test: directory_test
@@ -58,6 +57,9 @@ test: create_enumtype enum select_nest_views
#test add_views
test: add_views
+#test replace into
+test: replaceinto
+
test: udf_crem
test: create_c_function
# test on inplace upgrade
diff --git a/src/test/regress/sql/replaceinto.sql b/src/test/regress/sql/replaceinto.sql
new file mode 100644
index 000000000..33357a577
--- /dev/null
+++ b/src/test/regress/sql/replaceinto.sql
@@ -0,0 +1,188 @@
+replace into replace_test values(1,'aaaaa','aaaaa');
+
+create database db_replaceinto dbcompatibility='B';
+\c db_replaceinto
+
+CREATE TABLE replace_test(
+ id int NOT NULL,
+ name varchar(20) DEFAULT NULL,
+ key varchar(20) DEFAULT NULL,
+ PRIMARY KEY(id)
+);
+create unique index reidx on replace_test(key);
+
+create or replace function before_tri() returns trigger as $$
+begin
+ if TG_OP = 'INSERT' then
+ raise notice 'before insert trigger';
+ end if;
+ if TG_OP = 'DELETE' then
+ raise notice 'before delete trigger';
+ end if;
+ return new;
+end $$ language plpgsql;
+
+create trigger replace_before before insert or delete on
+replace_test for each row execute procedure before_tri();
+
+
+create or replace function after_tri() returns trigger as $$
+begin
+ if TG_OP = 'INSERT' then
+ raise notice 'after insert trigger';
+ end if;
+ if TG_OP = 'DELETE' then
+ raise notice 'after delete trigger';
+ end if;
+ return new;
+end $$ language plpgsql;
+
+create trigger replace_after after insert or delete on
+replace_test for each row execute procedure after_tri();
+
+select * from replace_test;
+replace into replace_test values(1,'aaaaa','aaaaa');
+select * from replace_test;
+replace into replace_test values(2,'bbbbb','bbbbb');
+select * from replace_test;
+replace into replace_test values(3,'ccccc','ccccc'),(4,'ddddd','ddddd'),(5,'eeeee','eeeee');
+select * from replace_test;
+replace into replace_test values(1,'aaaaa','ddddd');
+select * from replace_test;
+truncate replace_test;
+
+select * from replace_test;
+replace into replace_test set id=1,name='aaaaa', key='bbbbb';
+select * from replace_test;
+replace into replace_test set id=2,name=default, key='bbbbb';
+select * from replace_test;
+replace into replace_test values(3,default,'ccccc'),(4,'ddddd','ddddd'),(5,'eeeee','eeeee');
+select * from replace_test;
+replace into replace_test values(1,'aaaaa','ddddd');
+select * from replace_test;
+replace into replace_test select * from replace_test;
+select * from replace_test;
+replace into replace_test set id=1,name = (select name from replace_test where id=1);
+drop table replace_test;
+
+CREATE TABLE replace_partition_test(
+ id int NOT NULL,
+ name varchar(20) DEFAULT NULL,
+ key varchar(20) DEFAULT NULL,
+ PRIMARY KEY(id)
+)
+partition by range(id)
+(
+ partition replace_p1 values less than (5),
+ partition replace_p2 values less than (10),
+ partition replace_p3 values less than (20)
+);
+create unique index repartidx on replace_partition_test(key);
+
+
+replace into replace_partition_test values(1,'aaaaa','aaaaa');
+replace into replace_partition_test values(6,'bbbbb','bbbbb');
+replace into replace_partition_test values(15,'ccccc','ccccc');
+select * from replace_partition_test;
+replace into replace_partition_test values(7,'ddddd','ddddd');
+select * from replace_partition_test;
+replace into replace_partition_test values(7,'ddddd','aaaaa');
+select * from replace_partition_test;
+select * from replace_partition_test partition(replace_p1);
+select * from replace_partition_test partition(replace_p2);
+drop table replace_partition_test;
+
+create table replace_test1(a serial primary key, b int);
+
+replace into replace_test1 values(1,2);
+
+select * from replace_test1;
+
+drop table replace_test1;
+
+create table replace_test2(id int, name char(20));
+
+replace into replace_test2 values(1,'aaaaa');
+replace into replace_test2 values(1,'aaaaa');
+replace into replace_test2 values(2,'bbbbb');
+select * from replace_test2;
+drop table replace_test2;
+
+create table t_range_list
+( id number PRIMARY KEY not null,
+partition_key int,
+subpartition_key int,
+col2 varchar2(10) )
+partition by range(partition_key)
+subpartition by list(subpartition_key)(
+partition p1 values less than (100)
+(subpartition sub_1_1 values (10),
+subpartition sub_1_2 values (20)),
+partition p2 values less than(200)
+(subpartition sub_2_1 values (10),
+subpartition sub_2_2 values (20)),
+partition p3 values less than (300)
+(subpartition sub_3_1 values (10),
+subpartition sub_3_2 values (20)));
+REPLACE INTO t_range_list VALUES(1,50,10,'sub_1_1');
+REPLACE INTO t_range_list VALUES(2,150,20,'sub_2_2');
+REPLACE INTO t_range_list VALUES(3,250,10,'sub_3_1');
+
+select * from t_range_list partition(p1);
+select * from t_range_list partition(p2);
+select * from t_range_list partition(p3);
+
+CREATE TABLE t1(id int,p int,sub int);
+INSERT INTO t1 VALUES(2,60,10);
+
+select * from t_range_list;
+REPLACE INTO t_range_list VALUES(1,210,20,'sub_3_2');
+REPLACE INTO t_range_list(id,partition_key,subpartition_key ) SELECT * FROM t1;
+REPLACE INTO t_range_list SET id=3,partition_key=190,subpartition_key=20,col2='sub_2_2';
+select * from t_range_list;
+
+REPLACE INTO t_range_list partition(p2) VALUES(1,50,10,'fail');
+select * from t_range_list;
+REPLACE INTO t_range_list subpartition(sub_2_1) VALUES(2,150,20,'fail');
+select * from t_range_list;
+REPLACE INTO t_range_list subpartition(sub_3_1) VALUES(3,250,10,'new');
+select * from t_range_list;
+
+drop table t_range_list;
+drop table t1;
+
+CREATE TABLE ustore_test (col1 int PRIMARY KEY, col2 INT) with(storage_type=ustore);
+REPLACE INTO ustore_test values(1,2);
+REPLACE INTO ustore_test values(2,3);
+REPLACE INTO ustore_test values(3,4);
+REPLACE INTO ustore_test values(4,5);
+select * from ustore_test;
+REPLACE INTO ustore_test values(1,5);
+REPLACE INTO ustore_test values(2,6);
+select * from ustore_test;
+
+drop table ustore_test;
+
+CREATE TABLE replace_segment_test(
+ id int NOT NULL,
+ name varchar(20) DEFAULT NULL,
+ key varchar(20) DEFAULT NULL,
+ PRIMARY KEY(id)
+)with (parallel_workers=10, SEGMENT=ON)
+partition by range(id)
+(
+ partition replace_p1 values less than (5),
+ partition replace_p2 values less than (10),
+ partition replace_p3 values less than (20)
+);
+create unique index repartidx on replace_segment_test(key);
+replace into replace_segment_test values(1,'aaaaa','aaaaa');
+replace into replace_segment_test values(6,'bbbbb','bbbbb');
+replace into replace_segment_test values(15,'ccccc','ccccc');
+select * from replace_segment_test;
+replace into replace_segment_test values(7,'ddddd','ddddd');
+select * from replace_segment_test;
+
+drop table replace_segment_test;
+\c postgres
+drop database db_replaceinto;