From bdd6a6d93376db41ef7257ac465b3912ce38b194 Mon Sep 17 00:00:00 2001 From: zongwei Date: Thu, 8 Sep 2022 01:37:47 -0400 Subject: [PATCH] REPLACE INTO feature for M gram mode commit all files for replace into --- doc/src/sgml/ref/replace.sgmlin | 30 ++ src/common/backend/nodes/copyfuncs.cpp | 9 +- src/common/backend/nodes/equalfuncs.cpp | 5 +- src/common/backend/nodes/outfuncs.cpp | 6 + src/common/backend/nodes/readfuncs.cpp | 3 + src/common/backend/parser/analyze.cpp | 26 ++ src/common/backend/parser/gram.y | 56 ++- src/common/backend/utils/adt/ruleutils.cpp | 72 +++ src/common/backend/utils/init/globals.cpp | 3 +- src/gausskernel/optimizer/plan/planner.cpp | 1 + src/gausskernel/optimizer/util/optcommon.cpp | 5 +- src/gausskernel/process/tcop/pquery.cpp | 17 +- .../runtime/executor/nodeModifyTable.cpp | 165 ++++++- .../runtime/opfusion/opfusion_util.cpp | 8 + src/include/miscadmin.h | 1 + src/include/nodes/execnodes.h | 2 + src/include/nodes/parsenodes_common.h | 6 +- src/include/nodes/plannodes.h | 1 + src/include/opfusion/opfusion_util.h | 3 +- src/test/regress/expected/replaceinto.out | 421 ++++++++++++++++++ src/test/regress/parallel_schedule | 4 +- src/test/regress/sql/replaceinto.sql | 188 ++++++++ 22 files changed, 1009 insertions(+), 23 deletions(-) create mode 100644 doc/src/sgml/ref/replace.sgmlin create mode 100644 src/test/regress/expected/replaceinto.out create mode 100644 src/test/regress/sql/replaceinto.sql 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 f022cd0af..d346ee545 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 c968060a3..2d5cb8ef1 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 770e84bea..b62034628 100644 --- a/src/common/backend/parser/gram.y +++ b/src/common/backend/parser/gram.y @@ -19653,10 +19653,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) { @@ -19708,6 +19760,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)); @@ -19752,7 +19805,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 55205125c..4aa0f2caf 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;