diff --git a/src/bin/gs_guc/cluster_guc.conf b/src/bin/gs_guc/cluster_guc.conf index b9ad7f149..43564816f 100644 --- a/src/bin/gs_guc/cluster_guc.conf +++ b/src/bin/gs_guc/cluster_guc.conf @@ -154,6 +154,7 @@ enable_copy_server_files|bool|0,0|NULL|NULL| enable_sonic_hashjoin|bool|0,0|NULL|NULL| enable_sonic_hashagg|bool|0,0|NULL|NULL| enable_sonic_optspill|bool|0,0|NULL|NULL| +enable_upsert_to_merge|bool|0,0|NULL|Enable transform INSERT ON DUPLICATE KEY UDPATE statement to MERGE statement.| enable_codegen|bool|0,0|NULL|NULL| enable_codegen_print|bool|0,0|NULL|Enable dump for llvm function| enable_delta_store|bool|0,0|NULL|NULL| diff --git a/src/common/backend/catalog/index.cpp b/src/common/backend/catalog/index.cpp index d642a72b1..6be9454f3 100755 --- a/src/common/backend/catalog/index.cpp +++ b/src/common/backend/catalog/index.cpp @@ -1814,7 +1814,13 @@ IndexInfo* BuildIndexInfo(Relation index) /* fetch exclusion constraint info if any */ if (indexStruct->indisexclusion) { RelationGetExclusionInfo(index, &ii->ii_ExclusionOps, &ii->ii_ExclusionProcs, &ii->ii_ExclusionStrats); - } + } + + /* not doing speculative insertion here */ + ii->ii_UniqueOps = NULL; + ii->ii_UniqueProcs = NULL; + ii->ii_UniqueStrats = NULL; + return ii; } @@ -1865,6 +1871,42 @@ IndexInfo* BuildDummyIndexInfo(Relation index) return ii; } +void BuildSpeculativeIndexInfo(Relation index, IndexInfo* ii) +{ + int ncols = index->rd_rel->relnatts; + int i; + + /* + * fetch info for checking unique indexes + */ + Assert(ii->ii_Unique); + + if (index->rd_rel->relam != BTREE_AM_OID) { + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("unexpected non-btree speculative unique index"))); + } + + ii->ii_UniqueOps = (Oid*) palloc(sizeof(Oid) * ncols); + ii->ii_UniqueProcs = (Oid*) palloc(sizeof(Oid) * ncols); + ii->ii_UniqueStrats = (uint16*) palloc(sizeof(uint16) * ncols); + + /* + * We have to look up the operator's strategy number. This + * provides a cross-check that the operator does match the index. + * + * We need the func OIDs and strategy numbers too + */ + for (i = 0; i < ncols; i++) { + ii->ii_UniqueStrats[i] = BTEqualStrategyNumber; + ii->ii_UniqueOps[i] = get_opfamily_member(index->rd_opfamily[i], + index->rd_opcintype[i], + index->rd_opcintype[i], + ii->ii_UniqueStrats[i]); + ii->ii_UniqueProcs[i] = get_opcode(ii->ii_UniqueOps[i]); + } +} + /* ---------------- * FormIndexDatum * Construct values[] and isnull[] arrays for a new index tuple. diff --git a/src/common/backend/catalog/indexing.cpp b/src/common/backend/catalog/indexing.cpp index ea710c878..16f911de0 100644 --- a/src/common/backend/catalog/indexing.cpp +++ b/src/common/backend/catalog/indexing.cpp @@ -45,7 +45,7 @@ CatalogIndexState CatalogOpenIndexes(Relation heapRel) resultRelInfo->ri_RelationDesc = heapRel; resultRelInfo->ri_TrigDesc = NULL; /* we don't fire triggers */ - ExecOpenIndices(resultRelInfo); + ExecOpenIndices(resultRelInfo, false); return resultRelInfo; } diff --git a/src/common/backend/catalog/storage_gtt.cpp b/src/common/backend/catalog/storage_gtt.cpp index 9fab21762..4300e82e4 100644 --- a/src/common/backend/catalog/storage_gtt.cpp +++ b/src/common/backend/catalog/storage_gtt.cpp @@ -1427,7 +1427,7 @@ void gtt_create_storage_files(Oid relid) InitResultRelInfo(resultRelInfo, rel, 1, 0); if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex && resultRelInfo->ri_IndexRelationDescs == NULL) { - ExecOpenIndices(resultRelInfo); + ExecOpenIndices(resultRelInfo, false); } init_gtt_storage(CMD_UTILITY, resultRelInfo); relation_close(rel, NoLock); diff --git a/src/common/backend/nodes/copyfuncs.cpp b/src/common/backend/nodes/copyfuncs.cpp index 22ab1ce20..8dc1db1f2 100644 --- a/src/common/backend/nodes/copyfuncs.cpp +++ b/src/common/backend/nodes/copyfuncs.cpp @@ -283,6 +283,11 @@ static ModifyTable* _copyModifyTable(const ModifyTable* from) COPY_NODE_FIELD(mergeSourceTargetList); COPY_NODE_FIELD(mergeActionList); + COPY_SCALAR_FIELD(upsertAction); + COPY_NODE_FIELD(updateTlist); + COPY_NODE_FIELD(exclRelTlist); + COPY_SCALAR_FIELD(exclRelRTIndex); + return newnode; } @@ -3264,6 +3269,7 @@ static RangeTblEntry* _copyRangeTblEntry(const RangeTblEntry* from) COPY_SCALAR_FIELD(relhasbucket); COPY_SCALAR_FIELD(isbucket); COPY_NODE_FIELD(buckets); + COPY_SCALAR_FIELD(isexcluded); return newnode; } @@ -3341,6 +3347,28 @@ static WithClause* _copyWithClause(const WithClause* from) return newnode; } +static UpsertClause* _copyUpsertClause(const UpsertClause* from) +{ + UpsertClause* newnode = makeNode(UpsertClause); + + COPY_NODE_FIELD(targetList); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +static UpsertExpr* _copyUpsertExpr(const UpsertExpr* from) +{ + UpsertExpr* newnode = makeNode(UpsertExpr); + + COPY_SCALAR_FIELD(upsertAction); + COPY_NODE_FIELD(updateTlist); + COPY_NODE_FIELD(exclRelTlist); + COPY_SCALAR_FIELD(exclRelIndex); + + return newnode; +} + static CommonTableExpr* _copyCommonTableExpr(const CommonTableExpr* from) { CommonTableExpr* newnode = makeNode(CommonTableExpr); @@ -3981,6 +4009,7 @@ static Query* _copyQuery(const Query* from) COPY_NODE_FIELD(mergeSourceTargetList); COPY_NODE_FIELD(mergeActionList); COPY_NODE_FIELD(upsertQuery); + COPY_NODE_FIELD(upsertClause); COPY_SCALAR_FIELD(isRowTriggerShippable); COPY_SCALAR_FIELD(use_star_targets); COPY_SCALAR_FIELD(is_from_full_join_rewrite); @@ -3998,6 +4027,7 @@ static InsertStmt* _copyInsertStmt(const InsertStmt* from) COPY_NODE_FIELD(selectStmt); COPY_NODE_FIELD(returningList); COPY_NODE_FIELD(withClause); + COPY_NODE_FIELD(upsertClause); return newnode; } @@ -6164,6 +6194,9 @@ void* copyObject(const void* from) case T_FromExpr: retval = _copyFromExpr((FromExpr*)from); break; + case T_UpsertExpr: + retval = _copyUpsertExpr((UpsertExpr *)from); + break; case T_PartitionState: retval = _copyPartitionState((PartitionState*)from); break; @@ -6701,6 +6734,9 @@ void* copyObject(const void* from) case T_WithClause: retval = _copyWithClause((WithClause*)from); break; + case T_UpsertClause: + retval = _copyUpsertClause((UpsertClause *)from); + break; case T_CommonTableExpr: retval = _copyCommonTableExpr((CommonTableExpr*)from); break; diff --git a/src/common/backend/nodes/equalfuncs.cpp b/src/common/backend/nodes/equalfuncs.cpp index 41c52a839..2e9cbe382 100755 --- a/src/common/backend/nodes/equalfuncs.cpp +++ b/src/common/backend/nodes/equalfuncs.cpp @@ -680,6 +680,15 @@ static bool _equalMergeAction(const MergeAction* a, const MergeAction* b) return true; } +static bool _equalUpsertExpr(const UpsertExpr* a, const UpsertExpr* b) +{ + COMPARE_SCALAR_FIELD(upsertAction); + COMPARE_NODE_FIELD(updateTlist); + COMPARE_NODE_FIELD(exclRelTlist); + COMPARE_SCALAR_FIELD(exclRelIndex); + + return true; +} /* * Stuff from relation.h */ @@ -818,6 +827,7 @@ static bool _equalQuery(const Query* a, const Query* b) COMPARE_NODE_FIELD(mergeSourceTargetList); COMPARE_NODE_FIELD(mergeActionList); COMPARE_NODE_FIELD(upsertQuery); + COMPARE_NODE_FIELD(upsertClause); COMPARE_SCALAR_FIELD(isRowTriggerShippable); COMPARE_SCALAR_FIELD(use_star_targets); COMPARE_SCALAR_FIELD(is_from_full_join_rewrite); @@ -832,6 +842,7 @@ 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); return true; } @@ -2310,6 +2321,7 @@ static bool _equalRangeTblEntry(const RangeTblEntry* a, const RangeTblEntry* b) COMPARE_SCALAR_FIELD(relhasbucket); COMPARE_SCALAR_FIELD(isbucket); COMPARE_NODE_FIELD(buckets); + COMPARE_SCALAR_FIELD(isexcluded); return true; } @@ -2387,6 +2399,13 @@ static bool _equalWithClause(const WithClause* a, const WithClause* b) return true; } +static bool _equalUpsertClause(const UpsertClause* a, const UpsertClause* b) +{ + COMPARE_NODE_FIELD(targetList); + COMPARE_LOCATION_FIELD(location); + + return true; +} static bool _equalCommonTableExpr(const CommonTableExpr* a, const CommonTableExpr* b) { COMPARE_STRING_FIELD(ctename); @@ -2892,6 +2911,9 @@ bool equal(const void* a, const void* b) case T_FromExpr: retval = _equalFromExpr((FromExpr*)a, (FromExpr*)b); break; + case T_UpsertExpr: + retval = _equalUpsertExpr((UpsertExpr*)a, (UpsertExpr*)b); + break; case T_JoinExpr: retval = _equalJoinExpr((JoinExpr*)a, (JoinExpr*)b); break; @@ -3429,6 +3451,9 @@ bool equal(const void* a, const void* b) case T_WithClause: retval = _equalWithClause((WithClause*)a, (WithClause*)b); break; + case T_UpsertClause: + retval = _equalUpsertClause((UpsertClause*)a, (UpsertClause*)b); + break; case T_CommonTableExpr: retval = _equalCommonTableExpr((CommonTableExpr*)a, (CommonTableExpr*)b); break; diff --git a/src/common/backend/nodes/nodeFuncs.cpp b/src/common/backend/nodes/nodeFuncs.cpp index 513774d1a..698fa8ca1 100755 --- a/src/common/backend/nodes/nodeFuncs.cpp +++ b/src/common/backend/nodes/nodeFuncs.cpp @@ -1372,6 +1372,9 @@ int exprLocation(const Node* expr) case T_WithClause: loc = ((const WithClause*)expr)->location; break; + case T_UpsertClause: + loc = ((const UpsertClause*)expr)->location; + break; case T_CommonTableExpr: loc = ((const CommonTableExpr*)expr)->location; break; @@ -1781,6 +1784,11 @@ bool expression_tree_walker(Node* node, bool (*walker)(), void* context) return true; } } break; + case T_UpsertExpr: { + UpsertExpr* upsertClause = (UpsertExpr*)node; + if (p2walker(upsertClause->updateTlist, context)) + return true; + } break; case T_JoinExpr: { JoinExpr* join = (JoinExpr*)node; @@ -1880,6 +1888,9 @@ bool query_tree_walker(Query* query, bool (*walker)(), void* context, int flags) if (p2walker((Node*)query->mergeActionList, context)) { return true; } + if (p2walker((Node*)query->upsertClause, context)) { + return true; + } if (p2walker((Node*)query->returningList, context)) { return true; } @@ -2468,6 +2479,14 @@ Node* expression_tree_mutator(Node* node, Node* (*mutator)(Node*, void*), void* } return (Node*)resultlist; } break; + case T_UpsertExpr: { + UpsertExpr* upsertClause = (UpsertExpr*)node; + UpsertExpr* newnode = NULL; + + FLATCOPY(newnode, upsertClause, UpsertExpr, isCopy); + MUTATE(newnode->updateTlist, upsertClause->updateTlist, List*); + return (Node*)newnode; + } break; case T_FromExpr: { FromExpr* from = (FromExpr*)node; FromExpr* newnode = NULL; @@ -2586,6 +2605,7 @@ Query* query_tree_mutator(Query* query, Node* (*mutator)(Node*, void*), void* co MUTATE(query->targetList, query->targetList, List*); MUTATE(query->mergeSourceTargetList, query->mergeSourceTargetList, List*); MUTATE(query->mergeActionList, query->mergeActionList, List*); + MUTATE(query->upsertClause, query->upsertClause, UpsertExpr*); MUTATE(query->returningList, query->returningList, List*); MUTATE(query->jointree, query->jointree, FromExpr*); MUTATE(query->setOperations, query->setOperations, Node*); @@ -2853,6 +2873,9 @@ bool raw_expression_tree_walker(Node* node, bool (*walker)(), void* context) if (p2walker(stmt->withClause, context)) { return true; } + if (p2walker(stmt->upsertClause, context)) { + return true; + } } break; case T_DeleteStmt: { DeleteStmt* stmt = (DeleteStmt*)node; @@ -3144,6 +3167,8 @@ bool raw_expression_tree_walker(Node* node, bool (*walker)(), void* context) } break; case T_WithClause: return p2walker(((WithClause*)node)->ctes, context); + case T_UpsertClause: + return p2walker(((UpsertClause*)node)->targetList, context); case T_CommonTableExpr: return p2walker(((CommonTableExpr*)node)->ctequery, context); default: diff --git a/src/common/backend/nodes/outfuncs.cpp b/src/common/backend/nodes/outfuncs.cpp index af0ea3ccb..41c48689d 100755 --- a/src/common/backend/nodes/outfuncs.cpp +++ b/src/common/backend/nodes/outfuncs.cpp @@ -735,8 +735,30 @@ static void _outModifyTable(StringInfo str, ModifyTable* node) WRITE_INT_FIELD(mergeTargetRelation); WRITE_NODE_FIELD(mergeSourceTargetList); WRITE_NODE_FIELD(mergeActionList); + + WRITE_ENUM_FIELD(upsertAction, UpsertAction); + WRITE_NODE_FIELD(updateTlist); + WRITE_NODE_FIELD(exclRelTlist); + WRITE_INT_FIELD(exclRelRTIndex); } +static void _outUpsertClause(StringInfo str, const UpsertClause* node) +{ + WRITE_NODE_TYPE("UPSERTCLAUSE"); + + WRITE_NODE_FIELD(targetList); + WRITE_INT_FIELD(location); +} + +static void _outUpsertExpr(StringInfo str, const UpsertExpr* node) +{ + WRITE_NODE_TYPE("UPSERTEXPR"); + + WRITE_ENUM_FIELD(upsertAction, UpsertAction); + WRITE_NODE_FIELD(updateTlist); + WRITE_NODE_FIELD(exclRelTlist); + WRITE_INT_FIELD(exclRelIndex); +} static void _outMergeWhenClause(StringInfo str, const MergeWhenClause* node) { WRITE_NODE_TYPE("MERGEWHENCLAUSE"); @@ -3317,6 +3339,8 @@ static void _outInsertStmt(StringInfo str, InsertStmt* node) WRITE_NODE_FIELD(selectStmt); WRITE_NODE_FIELD(returningList); WRITE_NODE_FIELD(withClause); + + WRITE_NODE_FIELD(upsertClause); } static void _outUpdateStmt(StringInfo str, UpdateStmt* node) @@ -3771,6 +3795,7 @@ static void _outQuery(StringInfo str, Query* node) WRITE_NODE_FIELD(mergeSourceTargetList); WRITE_NODE_FIELD(mergeActionList); WRITE_NODE_FIELD(upsertQuery); + WRITE_NODE_FIELD(upsertClause); WRITE_BOOL_FIELD(isRowTriggerShippable); WRITE_BOOL_FIELD(use_star_targets); WRITE_BOOL_FIELD(is_from_full_join_rewrite); @@ -3986,6 +4011,7 @@ static void _outRangeTblEntry(StringInfo str, RangeTblEntry* node) WRITE_BOOL_FIELD(isbucket); WRITE_NODE_FIELD(buckets); } + WRITE_BOOL_FIELD(isexcluded); } /* @@ -5116,7 +5142,9 @@ static void _outNode(StringInfo str, const void* obj) case T_MergeAction: _outMergeAction(str, (MergeAction*)obj); break; - + case T_UpsertExpr: + _outUpsertExpr(str, (UpsertExpr*)obj); + break; case T_Path: _outPath(str, (Path*)obj); break; @@ -5289,6 +5317,9 @@ static void _outNode(StringInfo str, const void* obj) case T_WithClause: _outWithClause(str, (WithClause*)obj); break; + case T_UpsertClause: + _outUpsertClause(str, (UpsertClause*)obj); + break; case T_CommonTableExpr: _outCommonTableExpr(str, (CommonTableExpr*)obj); break; diff --git a/src/common/backend/nodes/readfuncs.cpp b/src/common/backend/nodes/readfuncs.cpp index 7b913aa54..26493109e 100644 --- a/src/common/backend/nodes/readfuncs.cpp +++ b/src/common/backend/nodes/readfuncs.cpp @@ -1304,6 +1304,9 @@ static Query* _readQuery(void) IF_EXIST(upsertQuery) { READ_NODE_FIELD(upsertQuery); } + IF_EXIST(upsertClause) { + READ_NODE_FIELD(upsertClause); + } IF_EXIST(isRowTriggerShippable) { READ_BOOL_FIELD(isRowTriggerShippable); } @@ -2659,6 +2662,10 @@ static RangeTblEntry* _readRangeTblEntry(void) READ_NODE_FIELD(buckets); } + IF_EXIST(isexcluded) { + READ_BOOL_FIELD(isexcluded); + } + READ_DONE(); } @@ -3392,6 +3399,44 @@ static ModifyTable* _readModifyTable(ModifyTable* local_node) READ_NODE_FIELD(mergeActionList); } + IF_EXIST(upsertAction) { + READ_ENUM_FIELD(upsertAction, UpsertAction); + } + + IF_EXIST(updateTlist) { + READ_NODE_FIELD(updateTlist); + } + + IF_EXIST(exclRelTlist) { + READ_NODE_FIELD(exclRelTlist); + } + + IF_EXIST(exclRelRTIndex) { + READ_INT_FIELD(exclRelRTIndex); + } + + READ_DONE(); +} + +static UpsertExpr* _readUpsertExpr(void) +{ + READ_LOCALS(UpsertExpr); + + READ_ENUM_FIELD(upsertAction, UpsertAction); + READ_NODE_FIELD(updateTlist); + READ_NODE_FIELD(exclRelTlist); + READ_INT_FIELD(exclRelIndex); + + READ_DONE(); +} + +static UpsertClause* _readUpsertClause(void) +{ + READ_LOCALS(UpsertClause); + + READ_NODE_FIELD(targetList); + READ_INT_FIELD(location); + READ_DONE(); } @@ -5197,6 +5242,10 @@ Node* parseNodeString(void) return_value = _readAddPartitionState(); } else if (MATCH("ROWNUM", 6)) { return_value = _readRownum(); + } else if (MATCH("UPSERTEXPR", 10)) { + return_value = _readUpsertExpr(); + } else if (MATCH("UPSERTCLAUSE", 12)) { + return_value = _readUpsertClause(); } else { ereport(ERROR, (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), diff --git a/src/common/backend/parser/analyze.cpp b/src/common/backend/parser/analyze.cpp index 338693cfe..f0c3b62e2 100644 --- a/src/common/backend/parser/analyze.cpp +++ b/src/common/backend/parser/analyze.cpp @@ -82,6 +82,8 @@ THR_LOCAL post_parse_analyze_hook_type post_parse_analyze_hook = NULL; static Query* transformDeleteStmt(ParseState* pstate, DeleteStmt* stmt); static Query* transformInsertStmt(ParseState* pstate, InsertStmt* stmt); +static void checkUpsertTargetlist(Relation targetTable, List* updateTlist); +static UpsertExpr* transformUpsertClause(ParseState* pstate, UpsertClause* upsertClause, RangeVar* relation); static int count_rowexpr_columns(ParseState* pstate, Node* expr); static Query* transformSelectStmt( ParseState* pstate, SelectStmt* stmt, bool isFirstNode = true, bool isCreateView = false); @@ -90,6 +92,7 @@ static Query* transformSetOperationStmt(ParseState* pstate, SelectStmt* stmt); static Node* transformSetOperationTree(ParseState* pstate, SelectStmt* stmt, bool isTopLevel, List** targetlist); static void determineRecursiveColTypes(ParseState* pstate, Node* larg, List* nrtargetlist); static Query* transformUpdateStmt(ParseState* pstate, UpdateStmt* stmt); +static List* transformUpdateTargetList(ParseState* pstate, List* qryTlist, List* origTlist, RangeVar* stmtrel); static List* transformReturningList(ParseState* pstate, List* returningList); static Query* transformDeclareCursorStmt(ParseState* pstate, DeclareCursorStmt* stmt); static Query* transformExplainStmt(ParseState* pstate, ExplainStmt* stmt); @@ -1002,6 +1005,7 @@ static Query* transformInsertStmt(ParseState* pstate, InsertStmt* stmt) ListCell* icols = NULL; ListCell* attnos = NULL; ListCell* lc = NULL; + AclMode targetPerms = ACL_INSERT; /* There can't be any outer WITH to worry about */ AssertEreport(pstate->p_ctenamespace == NIL, MOD_OPT, "para should be NIL"); @@ -1068,7 +1072,10 @@ static Query* transformInsertStmt(ParseState* pstate, InsertStmt* stmt) * mentioned in the SELECT part. Note that the target table is not added * to the joinlist or namespace. */ - qry->resultRelation = setTargetTable(pstate, stmt->relation, false, false, ACL_INSERT); + if (stmt->upsertClause != NULL && stmt->upsertClause->targetList != NIL) { + targetPerms |= ACL_UPDATE; + } + qry->resultRelation = setTargetTable(pstate, stmt->relation, false, false, targetPerms); if (pstate->p_target_relation != NULL && ((unsigned int)RelationGetInternalMask(pstate->p_target_relation) & INTERNAL_MASK_DINSERT)) { ereport(ERROR, @@ -1076,6 +1083,23 @@ static Query* transformInsertStmt(ParseState* pstate, InsertStmt* stmt) errmsg("Un-support feature"), errdetail("internal relation doesn't allow INSERT"))); } + if (pstate->p_target_relation != NULL && stmt->upsertClause != NULL) { + /* non-supported upsert cases */ + if (!u_sess->attr.attr_sql.enable_upsert_to_merge && RelationIsColumnFormat(pstate->p_target_relation)) { + ereport(ERROR, ((errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("INSERT ON DUPLICATE KEY UPDATE is not supported on column orientated table.")))); + } + + if (RelationIsForeignTable(pstate->p_target_relation)) { + ereport(ERROR, ((errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("INSERT ON DUPLICATE KEY UPDATE is not supported on foreign table.")))); + } + + if (RelationIsView(pstate->p_target_relation)) { + ereport(ERROR, ((errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("INSERT ON DUPLICATE KEY UPDATE is not supported on VIEW.")))); + } + } /* data redistribution for DFS table. * check if the target relation is being redistributed(insert mode). @@ -1439,6 +1463,11 @@ static Query* transformInsertStmt(ParseState* pstate, InsertStmt* stmt) attnos = lnext(attnos); } + /* Process DUPLICATE KEY UPDATE, if any. */ + if (stmt->upsertClause) { + qry->upsertClause = transformUpsertClause(pstate, stmt->upsertClause, stmt->relation); + } + /* * If we have a RETURNING clause, we need to add the target relation to * the query namespace before processing it, so that Var references in @@ -1480,6 +1509,153 @@ static Query* transformInsertStmt(ParseState* pstate, InsertStmt* stmt) return qry; } +static void checkUpsertTargetlist(Relation targetTable, List* updateTlist) +{ + List* index_list = RelationGetIndexInfoList(targetTable); + if (check_unique_constraint(index_list)) { + ListCell* target = NULL; + ListCell* index = NULL; + IndexInfo* index_info = NULL; + Bitmapset* target_attrs = NULL; /* attr bitmap according to targetlist */ + Bitmapset* index_attrs = NULL; /* attr bitmap according to index */ + TargetEntry* tle = NULL; + + foreach (target, updateTlist) { + tle = (TargetEntry*)lfirst(target); + target_attrs = bms_add_member(target_attrs, tle->resno); + } + + foreach (index, index_list) { + index_info = (IndexInfo*)lfirst(index); + for (int i = 0; i < index_info->ii_NumIndexAttrs; i++) { + int attrno = index_info->ii_KeyAttrNumbers[i]; + + if (attrno > 0) { + index_attrs = bms_add_member(index_attrs, attrno); + } + } + } + + if (bms_overlap(index_attrs, target_attrs)) { + ereport(ERROR, ((errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key.")))); + } + } +} + +List* BuildExcludedTargetlist(Relation targetrel, Index exclRelIndex) +{ + List* result = NIL; + int attno; + Var* var; + TargetEntry* te; + + /* + * Note that resnos of the tlist must correspond to attnos of the + * underlying relation, hence we need entries for dropped columns too. + */ + for (attno = 0; attno < RelationGetNumberOfAttributes(targetrel); attno++) { + Form_pg_attribute attr = targetrel->rd_att->attrs[attno]; + char* name; + + if (attr->attisdropped) { + /* + * can't use atttypid here, but it doesn't really matter what type + * the Const claims to be. + */ + var = (Var*)makeNullConst(INT4OID, -1, InvalidOid); + name = NULL; + } else { + var = makeVar(exclRelIndex, attno + 1, attr->atttypid, attr->atttypmod, + attr->attcollation, 0); + name = pstrdup(NameStr(attr->attname)); + } + + te = makeTargetEntry((Expr*)var, attno + 1, name, false); + + result = lappend(result, te); + } + + /* + * Add a whole-row-Var entry to support references to "EXCLUDED.*". Like + * the other entries in the EXCLUDED tlist, its resno must match the Var's + * varattno, else the wrong things happen while resolving references in + * setrefs.c. This is against normal conventions for targetlists, but + * it's okay since we don't use this as a real tlist. + */ + var = makeVar(exclRelIndex, InvalidAttrNumber, targetrel->rd_rel->reltype, + -1, InvalidOid, 0); + te = makeTargetEntry((Expr*)var, InvalidAttrNumber, NULL, true); + result = lappend(result, te); + + return result; +} + +static UpsertExpr* transformUpsertClause(ParseState* pstate, UpsertClause* upsertClause, RangeVar* relation) +{ + UpsertExpr* result = NULL; + List* updateTlist = NIL; + RangeTblEntry* exclRte = NULL; + int exclRelIndex = 0; + List* exclRelTlist = NIL; + UpsertAction action = UPSERT_NOTHING; + Relation targetrel = pstate->p_target_relation; + +#ifdef ENABLE_MULTIPLE_NODES + if (targetrel->rd_rel->relhastriggers) { + ereport(WARNING, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("INSERT ON DUPLICATE KEY UPDATE will ignore triggers."))); + } +#endif + + if (upsertClause->targetList != NIL) { + pstate->p_is_insert = false; + action = UPSERT_UPDATE; + exclRte = addRangeTableEntryForRelation(pstate, targetrel, makeAlias("excluded", NIL), false, false); + exclRte->isexcluded = true; + exclRelIndex = list_length(pstate->p_rtable); + + /* + * Build a targetlist for the EXCLUDED pseudo relation. Out of + * simplicity we do that here, because expandRelAttrs() happens to + * nearly do the right thing; specifically it also works with views. + * It'd be more proper to instead scan some pseudo scan node, but it + * doesn't seem worth the amount of code required. + * + * The only caveat of this hack is that the permissions expandRelAttrs + * adds have to be reset. markVarForSelectPriv() will add the exact + * required permissions back. + */ + + exclRelTlist = BuildExcludedTargetlist(targetrel, exclRelIndex); + exclRte->requiredPerms = 0; + exclRte->selectedCols = NULL; + + /* + * Add EXCLUDED and the target RTE to the namespace, so that they can + * be used in the UPDATE statement. + */ + addRTEtoQuery(pstate, exclRte, false, true, true); + addRTEtoQuery(pstate, pstate->p_target_rangetblentry, false, true, true); + + updateTlist = transformTargetList(pstate, upsertClause->targetList); + updateTlist = transformUpdateTargetList(pstate, updateTlist, upsertClause->targetList, relation); + /* We can't update primary or unique key in upsert, check it here */ + if (IS_PGXC_COORDINATOR || IS_SINGLE_NODE) { + checkUpsertTargetlist(pstate->p_target_relation, updateTlist); + } + } + + /* Finally, build DUPLICATE KEY UPDATE [NOTHING | ... ] expression */ + result = makeNode(UpsertExpr); + result->updateTlist = updateTlist; + result->exclRelIndex = exclRelIndex; + result->exclRelTlist = exclRelTlist; + result->upsertAction = action; + return result; +} + /* * Prepare an INSERT row for assignment to the target table. * @@ -2656,13 +2832,10 @@ void fixResTargetListWithTableNameRef(Relation rd, RangeVar* rel, List* clause_l static Query* transformUpdateStmt(ParseState* pstate, UpdateStmt* stmt) { Query* qry = makeNode(Query); - RangeTblEntry* target_rte = NULL; Node* qual = NULL; - ListCell* origTargetList = NULL; - ListCell* tl = NULL; qry->commandType = CMD_UPDATE; - pstate->p_is_update = true; + pstate->p_is_insert = false; /* set io state for backend status for the thread, we will use it to check user space */ pgstat_set_io_state(IOSTATE_READ); @@ -2714,7 +2887,6 @@ static Query* transformUpdateStmt(ParseState* pstate, UpdateStmt* stmt) transformFromClause(pstate, stmt->fromClause); qry->targetList = transformTargetList(pstate, stmt->targetList); - qual = transformWhereClause(pstate, stmt->whereClause, "WHERE"); qry->returningList = transformReturningList(pstate, stmt->returningList); @@ -2752,6 +2924,24 @@ static Query* transformUpdateStmt(ParseState* pstate, UpdateStmt* stmt) * Now we are done with SELECT-like processing, and can get on with * transforming the target list to match the UPDATE target columns. */ + qry->targetList = transformUpdateTargetList(pstate, qry->targetList, stmt->targetList, stmt->relation); + + assign_query_collations(pstate, qry); + return qry; +} + +/* + * transformUpdateTargetList - + * handle SET clause in UPDATE/INSERT ... DUPLICATE KEY UPDATE + */ +static List* transformUpdateTargetList(ParseState* pstate, List* qryTlist, List* origTlist, RangeVar* stmtrel) +{ + List* tlist = NIL; + RangeTblEntry* target_rte = NULL; + ListCell* orig_tl = NULL; + ListCell* tl = NULL; + + tlist = qryTlist; /* Prepare to assign non-conflicting resnos to resjunk attributes */ if (pstate->p_next_resno <= pstate->p_target_relation->rd_rel->relnatts) { @@ -2760,9 +2950,9 @@ static Query* transformUpdateStmt(ParseState* pstate, UpdateStmt* stmt) /* Prepare non-junk columns for assignment to target table */ target_rte = pstate->p_target_rangetblentry; - origTargetList = list_head(stmt->targetList); + orig_tl = list_head(origTlist); - foreach (tl, qry->targetList) { + foreach (tl, tlist) { TargetEntry* tle = (TargetEntry*)lfirst(tl); ResTarget* origTarget = NULL; int attrno; @@ -2778,14 +2968,16 @@ static Query* transformUpdateStmt(ParseState* pstate, UpdateStmt* stmt) tle->resname = NULL; continue; } - if (origTargetList == NULL) { + + if (orig_tl == NULL) { ereport(ERROR, (errcode(ERRCODE_UNEXPECTED_NULL_VALUE), errmsg("UPDATE target count mismatch --- internal error"))); } - origTarget = (ResTarget*)lfirst(origTargetList); + origTarget = (ResTarget*)lfirst(orig_tl); AssertEreport(IsA(origTarget, ResTarget), MOD_OPT, "Node type inconsistant here"); - - fixResTargetNameWithTableNameRef(pstate->p_target_relation, stmt->relation, origTarget); + if (stmtrel != NULL) { + fixResTargetNameWithTableNameRef(pstate->p_target_relation, stmtrel, origTarget); + } attrno = attnameAttNum(pstate->p_target_relation, origTarget->name, true); if (attrno == InvalidAttrNumber) { @@ -2812,16 +3004,14 @@ static Query* transformUpdateStmt(ParseState* pstate, UpdateStmt* stmt) /* Mark the target column as requiring update permissions */ target_rte->updatedCols = bms_add_member(target_rte->updatedCols, attrno - FirstLowInvalidHeapAttributeNumber); - origTargetList = lnext(origTargetList); + orig_tl = lnext(orig_tl); } - if (origTargetList != NULL) { + if (orig_tl != NULL) { ereport( ERROR, (errcode(ERRCODE_NOT_NULL_VIOLATION), errmsg("UPDATE target count mismatch --- internal error"))); } - assign_query_collations(pstate, qry); - - return qry; + return tlist; } /* diff --git a/src/common/backend/parser/gram.y b/src/common/backend/parser/gram.y index 1a5359b9c..7f6c57db0 100755 --- a/src/common/backend/parser/gram.y +++ b/src/common/backend/parser/gram.y @@ -239,6 +239,7 @@ static void ParseUpdateMultiSet(List *set_target_list, SelectStmt *stmt, core_yy /* PGXC_END */ ForeignPartState *foreignpartby; MergeWhenClause *mergewhen; + UpsertClause *upsert; } %type stmt schema_stmt @@ -447,7 +448,7 @@ static void ParseUpdateMultiSet(List *set_target_list, SelectStmt *stmt, core_yy /* INSERT */ %type insert_rest -%type duplicate_update_clause +%type upsert_clause %type merge_insert merge_update @@ -640,7 +641,7 @@ static void ParseUpdateMultiSet(List *set_target_list, SelectStmt *stmt, core_yy DROP DUPLICATE DISCONNECT EACH ELASTIC ELSE ENABLE_P ENCODING ENCRYPTED END_P ENFORCED ENUM_P ERRORS ESCAPE EOL ESCAPING EVERY EXCEPT EXCHANGE - EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN + EXCLUDE EXCLUDED EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXTENSION EXTERNAL EXTRACT FALSE_P FAMILY FAST FENCED FETCH FILEHEADER_P FILL_MISSING_FIELDS FIRST_P FIXED_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORMATTER FORWARD @@ -13067,80 +13068,79 @@ InsertStmt: opt_with_clause INSERT INTO qualified_name insert_rest returning_cla $5->withClause = $1; $$ = (Node *) $5; } - | opt_with_clause INSERT INTO qualified_name insert_rest duplicate_update_clause returning_clause + | opt_with_clause INSERT INTO qualified_name insert_rest upsert_clause returning_clause { - /* It is a INSERT ON DUPLICATE KEY UPDATE statement */ - if ($7 != NIL) - { + if ($7 != NIL) { ereport(ERROR, (errmodule(MOD_PARSER), errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("RETURNING clause is not yet supported whithin INSERT ON DUPLICATE KEY UPDATE statement."))); } - if ($1 != NULL) - { + if ($1 != NULL) { ereport(ERROR, (errmodule(MOD_PARSER), errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("WITH clause is not yet supported whithin INSERT ON DUPLICATE KEY UPDATE statement."))); } - if ($5 != NULL && $5->cols != NIL) - { - ListCell *c = NULL; - List *cols = $5->cols; - foreach (c, cols) - { - ResTarget *rt = (ResTarget *)lfirst(c); - if (rt->indirection != NIL) - { - ereport(ERROR, - (errmodule(MOD_PARSER), - errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("Subfield name or array subscript of column \"%s\" " - "is not yet supported whithin INSERT ON DUPLICATE KEY UPDATE statement.", - rt->name), - errhint("Try assign a composite or an array expression to column \"%s\".", rt->name))); + if (unlikely(u_sess->attr.attr_sql.enable_upsert_to_merge)) { + + if ($5 != NULL && $5->cols != NIL) { + ListCell *c = NULL; + List *cols = $5->cols; + foreach (c, cols) { + ResTarget *rt = (ResTarget *)lfirst(c); + if (rt->indirection != NIL) { + ereport(ERROR, + (errmodule(MOD_PARSER), + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Subfield name or array subscript of column \"%s\" " + "is not yet supported whithin INSERT ON DUPLICATE KEY UPDATE statement.", + rt->name), + errhint("Try assign a composite or an array expression to column \"%s\".", rt->name))); + } } } + + MergeStmt *m = makeNode(MergeStmt); + m->is_insert_update = true; + + /* for UPSERT, keep the INSERT statement as well */ + $5->relation = $4; + $5->returningList = $7; + $5->withClause = $1; + m->insert_stmt = (Node *) copyObject($5); + + /* fill a MERGE statement*/ + m->relation = $4; + + Alias *a1 = makeAlias(($4->relname), NIL); + $4->alias = a1; + + Alias *a2 = makeAlias("excluded", NIL); + RangeSubselect *r = makeNode(RangeSubselect); + r->alias = a2; + r->subquery = (Node *) ($5->selectStmt); + m->source_relation = (Node *) r; + + MergeWhenClause *n = makeNode(MergeWhenClause); + n->matched = false; + n->commandType = CMD_INSERT; + n->cols = $5->cols; + n->values = NULL; + + m->mergeWhenClauses = list_make1((Node *) n); + if ($6 != NULL) + m->mergeWhenClauses = list_concat(list_make1($6), m->mergeWhenClauses); + + $$ = (Node *)m; + } else { + $5->relation = $4; + $5->returningList = $7; + $5->withClause = $1; + $5->upsertClause = (UpsertClause *)$6; + $$ = (Node *) $5; } - - MergeStmt *m = makeNode(MergeStmt); - m->is_insert_update = true; - - /* for UPSERT, keep the INSERT statement as well */ - $5->relation = $4; - $5->returningList = $7; - $5->withClause = $1; - m->insert_stmt = (Node *) copyObject($5); - - /* fill a MERGE statement*/ - m->relation = $4; - - Alias *a1 = makeAlias(($4->relname), NIL); - $4->alias = a1; - - Alias *a2 = makeAlias("__unnamed_subquery_source__", NIL); - RangeSubselect *r = makeNode(RangeSubselect); - r->alias = a2; - r->subquery = (Node *) ($5->selectStmt); - m->source_relation = (Node *) r; - - MergeWhenClause *n = makeNode(MergeWhenClause); - n->matched = false; - n->commandType = CMD_INSERT; - n->cols = $5->cols; - n->values = NULL; - - m->mergeWhenClauses = list_make2(($6), (Node *) n); - - /* for UPSERT, keep the INSERT statement as well */ - $5->relation = $4; - $5->returningList = $7; - $5->withClause = $1; - m->insert_stmt = (Node *) copyObject($5); - - $$ = (Node *)m; } ; @@ -13188,16 +13188,47 @@ returning_clause: | /* EMPTY */ { $$ = NIL; } ; -duplicate_update_clause: - ON DUPLICATE KEY UPDATE set_clause_list - { - MergeWhenClause *n = makeNode(MergeWhenClause); - n->matched = true; - n->commandType = CMD_UPDATE; - n->targetList = $5; - $$ = (Node *) n; - } - ; +upsert_clause: + ON DUPLICATE KEY UPDATE set_clause_list + { + if (unlikely(u_sess->attr.attr_sql.enable_upsert_to_merge)) { + MergeWhenClause *n = makeNode(MergeWhenClause); + n->matched = true; + n->commandType = CMD_UPDATE; + n->targetList = $5; + $$ = (Node *) n; + } else { + /* check subquery in set clause*/ + ListCell* cell = NULL; + ResTarget* res = NULL; + foreach (cell, $5) { + res = (ResTarget*)lfirst(cell); + if (IsA(res->val,SubLink)) { + ereport(ERROR, + (errmodule(MOD_PARSER), + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Update with subquery is not yet supported whithin INSERT ON DUPLICATE KEY UPDATE statement."))); + } + } + + UpsertClause *uc = makeNode(UpsertClause); + uc->targetList = $5; + uc->location = @1; + $$ = (Node *) uc; + } + } + | ON DUPLICATE KEY UPDATE NOTHING + { + if (unlikely(u_sess->attr.attr_sql.enable_upsert_to_merge)) { + $$ = NULL; + } else { + UpsertClause *uc = makeNode(UpsertClause); + uc->targetList = NIL; + uc->location = @1; + $$ = (Node *) uc; + } + } + ; /***************************************************************************** * @@ -13288,7 +13319,7 @@ UpdateStmt: opt_with_clause UPDATE relation_expr_opt_alias { UpdateStmt *n = makeNode(UpdateStmt); n->relation = $3; - n->targetList = $5; + n->targetList = $5; n->fromClause = $6; n->whereClause = $7; n->returningList = $8; @@ -13313,6 +13344,30 @@ single_set_clause: $$ = $1; $$->val = (Node *) $3; } + /* this is only used in ON DUPLICATE KEY UPDATE col = VALUES(col) case + * for mysql compatibility + */ + | set_target '=' VALUES '(' columnref ')' + { + ColumnRef *c = NULL; + int nfields = 0; + if (IsA($5, ColumnRef)) { + c = (ColumnRef *) $5; + } else if (IsA($5, A_Indirection)) { + c = (ColumnRef *)(((A_Indirection *)$5)->arg); + } + nfields = list_length(c->fields); + /* only allow col.*, col[...], col */ + if (nfields > 1) { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("only allow column name within VALUES"), parser_errposition(@5))); + } + + c->fields = lcons((Node *)makeString("excluded"), c->fields); + $$ = $1; + $$->val = (Node *) $5; + } ; multiple_set_clause: @@ -17755,6 +17810,7 @@ SignedIconst: Iconst { $$ = $1; } ColId: IDENT { $$ = $1; } | unreserved_keyword { $$ = pstrdup($1); } | col_name_keyword { $$ = pstrdup($1); } + | EXCLUDED { $$ = pstrdup($1); } ; /* Type/function identifier --- names that can be type or function names. @@ -18312,6 +18368,7 @@ reserved_keyword: | ELSE | END_P | EXCEPT + | EXCLUDED | FALSE_P | FETCH | FOR diff --git a/src/common/backend/parser/parse_collate.cpp b/src/common/backend/parser/parse_collate.cpp index 315f5ceb3..87584a22f 100755 --- a/src/common/backend/parser/parse_collate.cpp +++ b/src/common/backend/parser/parse_collate.cpp @@ -459,8 +459,8 @@ static bool assign_collations_walker(Node* node, assign_collations_context* cont case T_FromExpr: case T_SortGroupClause: case T_MergeAction: + case T_UpsertExpr: (void)expression_tree_walker(node, (bool (*)())assign_collations_walker, (void*)&loccontext); - /* * When we're invoked on a query's jointree, we don't need to do * anything with join nodes except recurse through them to process @@ -786,4 +786,4 @@ static void assign_ordered_set_collations(Aggref* aggref, assign_collations_cont assign_expr_collations(loccontext->pstate, (Node*)tle); } } -} \ No newline at end of file +} diff --git a/src/common/backend/parser/parse_merge.cpp b/src/common/backend/parser/parse_merge.cpp index bc054149b..ed6442efd 100644 --- a/src/common/backend/parser/parse_merge.cpp +++ b/src/common/backend/parser/parse_merge.cpp @@ -56,7 +56,8 @@ static void check_system_column_node(Node* node, bool is_insert_update); static void check_system_column_reference(List* joinVarList, List* mergeActionList, bool is_insert_update); static void checkTargetTableSystemCatalog(Relation targetRel); static void check_insert_action_targetlist(List* merge_action_list, List* source_targetlist); -static bool check_unique_constraint(List*& index_list); +static bool check_update_action_targetlist(List* update_action_list, List* source_targetlist); +bool check_unique_constraint(List*& index_list); static bool find_valid_unique_constraint(Relation relation, List* colnames, List*& index_list); static bool var_in_list(Var* var, List* list); static Bitmapset* get_relation_attno_bitmap_by_names(Relation relation, List* colnames); @@ -1180,6 +1181,16 @@ Query* transformMergeStmt(ParseState* pstate, MergeStmt* stmt) Assert(stmt->insert_stmt != NULL); Assert(IsA(stmt->insert_stmt, InsertStmt)); + if (check_update_action_targetlist(mergeActionList, qry->mergeSourceTargetList)) { + if (u_sess->attr.attr_sql.enable_upsert_to_merge) { + return qry; + } + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("Invalid column reference in the UPDATE target values"), + errhint("Only allow to reference target table's column in the UPDATE clause"))); + } + Query* insert_query = NULL; ParseState* insert_pstate = make_parsestate(NULL); insert_pstate->p_resolve_unknowns = pstate->p_resolve_unknowns; @@ -1768,7 +1779,7 @@ static int count_target_columns(Node* query) * @out index_list: unique index list * @return: true if there is any primary or unique index */ -static bool check_unique_constraint(List*& index_list) +bool check_unique_constraint(List*& index_list) { /* There are no indexes */ if (index_list == NIL) { @@ -1790,10 +1801,48 @@ static bool check_unique_constraint(List*& index_list) return true; } +bool check_action_targetlist_condition(List* action_targetlist, List* source_targetlist, bool condition_in) +{ + ListCell* var_cell = NULL; + /* oops, not insert action found, noting to do */ + if (action_targetlist == NIL) { + return true; + } + /* let's do the hard work */ + List* vars_list = + pull_var_clause((Node*)action_targetlist, PVC_RECURSE_AGGREGATES, PVC_RECURSE_PLACEHOLDERS); + List* source_vars_list = + pull_var_clause((Node*)source_targetlist, PVC_RECURSE_AGGREGATES, PVC_RECURSE_PLACEHOLDERS); + foreach (var_cell, vars_list) { + Var* var = (Var*)lfirst(var_cell); + if (var_in_list(var, source_vars_list) != condition_in) { + return false; + } + } + return true; +} + +bool check_update_action_targetlist(List* merge_action_list, List* source_targetlist) +{ + ListCell* action_cell = NULL; + List* update_action_targetlist = NIL; + + /* first let's locate the insert action */ + foreach (action_cell, merge_action_list) { + MergeAction* action = (MergeAction*)lfirst(action_cell); + if (action->commandType == CMD_UPDATE) { + update_action_targetlist = action->targetList; + + break; + } + } + + return !check_action_targetlist_condition(update_action_targetlist, source_targetlist, false); +} + /* report error if vars in insert_action_targetlist not found in source_targetlist */ void check_insert_action_targetlist(List* merge_action_list, List* source_targetlist) { - ListCell* var_cell = NULL; ListCell* action_cell = NULL; List* insert_action_targetlist = NIL; @@ -1806,25 +1855,10 @@ void check_insert_action_targetlist(List* merge_action_list, List* source_target break; } } - - /* oops, not insert action found, noting to do */ - if (insert_action_targetlist == NIL) { - return; - } - /* let's do the hard work */ - List* insert_vars_list = - pull_var_clause((Node*)insert_action_targetlist, PVC_RECURSE_AGGREGATES, PVC_RECURSE_PLACEHOLDERS); - List* source_vars_list = - pull_var_clause((Node*)source_targetlist, PVC_RECURSE_AGGREGATES, PVC_RECURSE_PLACEHOLDERS); - foreach (var_cell, insert_vars_list) { - Var* var = (Var*)lfirst(var_cell); - - /* INSERT can only reference source rel's targetlist */ - if (!var_in_list(var, source_vars_list)) { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_COLUMN), - errmsg("Invalid column reference in the INSERT VALUES Clause"), - errhint("You may have referenced target table's column"))); - } + if (!check_action_targetlist_condition(insert_action_targetlist, source_targetlist, true)) { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("Invalid column reference in the INSERT VALUES Clause"), + errhint("You may have referenced target table's column"))); } } diff --git a/src/common/backend/parser/parse_relation.cpp b/src/common/backend/parser/parse_relation.cpp index cc2c863b4..45d9960e8 100755 --- a/src/common/backend/parser/parse_relation.cpp +++ b/src/common/backend/parser/parse_relation.cpp @@ -479,7 +479,7 @@ CommonTableExpr* GetCTEForRTE(ParseState* pstate, RangeTblEntry* rte, int rtelev * Side effect: if we find a match, mark the RTE as requiring read access * for the column. */ -Node* scanRTEForColumn(ParseState* pstate, RangeTblEntry* rte, char* colname, int location) +Node* scanRTEForColumn(ParseState* pstate, RangeTblEntry* rte, char* colname, int location, bool omit_excluded) { Node* result = NULL; int attnum = 0; @@ -501,6 +501,9 @@ Node* scanRTEForColumn(ParseState* pstate, RangeTblEntry* rte, char* colname, in */ foreach (c, rte->eref->colnames) { attnum++; + if (omit_excluded && rte->isexcluded) { + continue; + } if (strcmp(strVal(lfirst(c)), colname) == 0) { if (result != NULL) { ereport(ERROR, @@ -607,7 +610,7 @@ Node* colNameToVar(ParseState* pstate, char* colname, bool localonly, int locati Node* newresult = NULL; /* use orig_pstate here to get the right sublevels_up */ - newresult = scanRTEForColumn(orig_pstate, rte, colname, location); + newresult = scanRTEForColumn(orig_pstate, rte, colname, location, true); if (newresult != NULL) { if (final_rte != NULL) { *final_rte = rte; @@ -1087,6 +1090,7 @@ RangeTblEntry* addRangeTableEntry(ParseState* pstate, RangeVar* relation, Alias* rte->rtekind = RTE_RELATION; rte->alias = alias; + rte->isexcluded = false; if (pstate == NULL) { ereport(ERROR, (errmodule(MOD_OPT), errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("pstate can not be NULL"))); @@ -1205,6 +1209,7 @@ RangeTblEntry* addRangeTableEntryForRelation(ParseState* pstate, Relation rel, A rte->relkind = rel->rd_rel->relkind; rte->ispartrel = RELATION_IS_PARTITIONED(rel); rte->relhasbucket = RELATION_HAS_BUCKET(rel); + rte->isexcluded = false; /* * In cases that target relation's rd_refSynOid is valid, it has been referenced from one synonym. * thus, alias name is used to take its raw relname away in order to form the refname. diff --git a/src/common/backend/parser/parse_target.cpp b/src/common/backend/parser/parse_target.cpp index 144ae7abd..34154c148 100755 --- a/src/common/backend/parser/parse_target.cpp +++ b/src/common/backend/parser/parse_target.cpp @@ -335,12 +335,12 @@ static void markTargetListOrigin(ParseState* pstate, TargetEntry* tle, Var* var, /* * transformAssignedExpr() - * This is used in INSERT and UPDATE statements only. It prepares an - * expression for assignment to a column of the target table. - * This includes coercing the given value to the target column's type - * (if necessary), and dealing with any subfield names or subscripts - * attached to the target column itself. The input expression has - * already been through transformExpr(). + * This is used in INSERT and UPDATE (including DUPLICATE KEY UPDATE) + * statements only. It prepares an expression for assignment to a column + * of the target table. This includes coercing the given value + * to the target column's type (if necessary), and dealing with + * any subfield names or subscripts attached to the target column itself + * The input expression has already been through transformExpr(). * * pstate parse state * expr expression to be modified @@ -496,11 +496,11 @@ Expr* transformAssignedExpr(ParseState* pstate, Expr* expr, char* colname, int a /* * updateTargetListEntry() - * This is used in UPDATE statements only. It prepares an UPDATE - * TargetEntry for assignment to a column of the target table. - * This includes coercing the given value to the target column's type - * (if necessary), and dealing with any subfield names or subscripts - * attached to the target column itself. + * This is used in UPDATE (and DUPLICATE KEY UPDATE) statements only. + * It prepares an UPDATE TargetEntry for assignment to a column of + * the target table. This includes coercing the given value to the + * target column's type (if necessary), and dealing with any subfield + * names or subscripts attached to the target column itself. * * pstate parse state * tle target list entry to be modified diff --git a/src/common/backend/utils/adt/ruleutils.cpp b/src/common/backend/utils/adt/ruleutils.cpp index f382978ca..b5ef2c888 100755 --- a/src/common/backend/utils/adt/ruleutils.cpp +++ b/src/common/backend/utils/adt/ruleutils.cpp @@ -222,6 +222,8 @@ static void get_with_clause(Query* query, deparse_context* context); static void get_select_query_def(Query* query, deparse_context* context, TupleDesc resultDesc); static void get_insert_query_def(Query* query, deparse_context* context); static void get_update_query_def(Query* query, deparse_context* context); +static void get_update_query_targetlist_def( + Query* query, List* targetList, RangeTblEntry* rte, deparse_context* context); static void get_delete_query_def(Query* query, deparse_context* context); static void get_utility_query_def(Query* query, deparse_context* context); static void get_basic_select_query(Query* query, deparse_context* context, TupleDesc resultDesc); @@ -3738,6 +3740,10 @@ static void set_deparse_planstate(deparse_namespace* dpns, PlanState* ps) * For a SubqueryScan, pretend the subplan is INNER referent. (We don't * use OUTER because that could someday conflict with the normal meaning.) * Likewise, for a CteScan, pretend the subquery's plan is INNER referent. + * For DUPLICATE KEY UPDATE we just need the inner tlist to point to the + * excluded expression's tlist. (Similar to the SubqueryScan we don't want + * to reuse OUTER, it's used for RETURNING in some modify table cases, + * although not INSERT ... ON DUPLICATE KEY UPDATE). */ if (IsA(ps, SubqueryScanState)) dpns->inner_planstate = ((SubqueryScanState*)ps)->subplan; @@ -3749,6 +3755,7 @@ static void set_deparse_planstate(deparse_namespace* dpns, PlanState* ps) ModifyTableState* mps = (ModifyTableState*)ps; dpns->outer_planstate = mps->mt_plans[0]; + dpns->inner_planstate = ps; /* * For merge into, we should deparse the inner plan, since the targetlist and qual will * reference sourceTargetList, which comes from outer plan of the join (source table) @@ -3762,6 +3769,11 @@ static void set_deparse_planstate(deparse_namespace* dpns, PlanState* ps) } else dpns->inner_planstate = innerPlanState(ps); +#ifdef ENABLE_MULTIPLE_NODEX + if (IsA(ps, ModifyTableState)) + dpns->inner_tlist = ((ModifyTableState*)ps)->mt_upsert->us_excludedtlist; + else +#endif if (dpns->inner_planstate != NULL) dpns->inner_tlist = dpns->inner_planstate->plan->targetlist; else @@ -5564,18 +5576,9 @@ static void get_insert_query_def(Query* query, deparse_context* context) } /* - * select_rte and values_rte are not required by INSERT queries in XC - * Both these should stay null for INSERT queries to work corretly - * Consider an example - * create table tt as values(1,'One'),(2,'Two'); - * This query uses values_rte, but we do not need them in XC - * because it gets broken down into two queries - * CREATE TABLE tt(column1 int4, column2 text) - * and - * INSERT INTO tt (column1, column2) VALUES ($1, $2) - * Note that the insert query does not need values_rte - * - * Now consider another example + * select_rte are not required by INSERT queries in XC + * it should stay null for INSERT queries to work corretly + * consider an example * insert into tt select * from tt * This query uses select_rte, but again that is not required in XC * Again here the query gets broken down into two queries @@ -5600,7 +5603,35 @@ static void get_insert_query_def(Query* query, deparse_context* context) } select_rte = rte; } + } +#ifdef PGXC + } +#endif + + /* + * values_rte will be required by INSERT queries in XC + * only when the relation is located on a single node + * requested by FQS + * Consider an example + * CREATE NODE GROUP ng WITH (datanode2); + * CREATE TABLE tt (column1 int4, column2 text) TO GROUP ng; + * INSERT INTO tt (column1) VALUES(1), (2) + * + * for other cases values_rte should stay null + * create table tt as values(1,'One'),(2,'Two'); + * This query uses values_rte, but we do not need them in XC + * because it gets broken down into two queries + * CREATE TABLE tt(column1 int4, column2 text) + * and + * INSERT INTO tt (column1, column2) VALUES ($1, $2) + * Note that the insert query does not need values_rte + */ +#ifdef PGXC + if (context->is_fqs || !(IS_PGXC_COORDINATOR && !IsConnFromCoord())) { +#endif + foreach (l, query->rtable) { + rte = (RangeTblEntry*)lfirst(l); if (rte->rtekind == RTE_VALUES) { if (values_rte != NULL) { ereport(ERROR, (errcode(ERRCODE_RESTRICT_VIOLATION), errmsg("too many values RTEs in INSERT"))); @@ -5611,6 +5642,7 @@ static void get_insert_query_def(Query* query, deparse_context* context) #ifdef PGXC } #endif + if ((select_rte != NULL) && (values_rte != NULL)) { ereport(ERROR, (errcode(ERRCODE_RESTRICT_VIOLATION), errmsg("both subquery and values RTEs in INSERT"))); } @@ -5758,21 +5790,37 @@ static void get_insert_query_def(Query* query, deparse_context* context) appendStringInfo(buf, "DEFAULT VALUES"); } - /* it is an UPSERT statement, add ON DUPLICATE KEY UPDATE expression */ + /* for MERGE INTO statement for UPSERT, add ON DUPLICATE KEY UPDATE expression */ if (query->mergeActionList) { appendStringInfo(buf, " ON DUPLICATE KEY UPDATE "); ListCell* l = NULL; + bool update = false; foreach (l, query->mergeActionList) { MergeAction* mc = (MergeAction*)lfirst(l); /* only deparse the update clause */ if (mc->commandType == CMD_UPDATE) { + update = true; get_set_target_list(mc->targetList, rte, context); } else { continue; } } + if (!update) { + appendStringInfoString(buf, "NOTHING"); + } + } + + /* for INSERT statement for UPSERT, add ON DUPLICATE KEY UPDATE expression */ + if (query->upsertClause != NULL && query->upsertClause->upsertAction != UPSERT_NONE) { + appendStringInfoString(buf, " ON DUPLICATE KEY UPDATE "); + UpsertExpr* upsertClause = query->upsertClause; + if (upsertClause->upsertAction == UPSERT_NOTHING) { + appendStringInfoString(buf, "NOTHING"); + } else { + get_update_query_targetlist_def(query, upsertClause->updateTlist, rte, context); + } } /* Add RETURNING if present */ @@ -5789,9 +5837,7 @@ static void get_insert_query_def(Query* query, deparse_context* context) static void get_update_query_def(Query* query, deparse_context* context) { StringInfo buf = context->buf; - char* sep = NULL; RangeTblEntry* rte = NULL; - ListCell* l = NULL; /* Insert the WITH clause if given */ get_with_clause(query, context); @@ -5810,10 +5856,38 @@ static void get_update_query_def(Query* query, deparse_context* context) appendStringInfo(buf, " %s", quote_identifier(rte->alias->aliasname)); } appendStringInfoString(buf, " SET "); + /* Deparse targetlist */ + get_update_query_targetlist_def(query, query->targetList, rte, context); + /* Add the FROM clause if needed */ + get_from_clause(query, " FROM ", context); + + /* Add a WHERE clause if given */ + if (query->jointree->quals != NULL) { + append_context_keyword(context, " WHERE ", PRETTYINDENT_STD, PRETTYINDENT_STD, 1); + get_rule_expr(query->jointree->quals, context, false); + } + + /* Add RETURNING if present */ + if (query->returningList) { + append_context_keyword(context, " RETURNING", PRETTYINDENT_STD, PRETTYINDENT_STD, 1); + get_target_list(query, query->returningList, context, NULL); + } +} + +/* ---------- + * get_update_query_targetlist_def - Parse back an UPDATE targetlist + * ---------- + */ +static void get_update_query_targetlist_def(Query* query, List* targetList, + RangeTblEntry* rte, deparse_context* context) +{ + StringInfo buf = context->buf; + ListCell* l; + const char* sep; /* Add the comma separated list of 'attname = value' */ sep = ""; - foreach (l, query->targetList) { + foreach (l, targetList) { TargetEntry* tle = (TargetEntry*)lfirst(l); Node* expr = NULL; @@ -5890,21 +5964,6 @@ static void get_update_query_def(Query* query, deparse_context* context) get_rule_expr((Node*)tle->expr, context, false); } } - - /* Add the FROM clause if needed */ - get_from_clause(query, " FROM ", context); - - /* Add a WHERE clause if given */ - if (query->jointree->quals != NULL) { - append_context_keyword(context, " WHERE ", -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); - get_rule_expr(query->jointree->quals, context, false); - } - - /* Add RETURNING if present */ - if (query->returningList) { - append_context_keyword(context, " RETURNING", -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); - get_target_list(query, query->returningList, context, NULL); - } } /* ---------- diff --git a/src/common/backend/utils/misc/guc.cpp b/src/common/backend/utils/misc/guc.cpp index e0d216dea..34a660a5e 100644 --- a/src/common/backend/utils/misc/guc.cpp +++ b/src/common/backend/utils/misc/guc.cpp @@ -1389,6 +1389,20 @@ static void init_configure_names_bool() NULL, NULL }, + { + { + "enable_upsert_to_merge", + PGC_USERSET, + QUERY_TUNING_METHOD, + gettext_noop("Enable transform INSERT ON DUPLICATE KEY UDPATE statement to MERGE statement."), + NULL + }, + &u_sess->attr.attr_sql.enable_upsert_to_merge, + false, + NULL, + NULL, + NULL + }, #ifdef ENABLE_MULTIPLE_NODES { { diff --git a/src/common/backend/utils/time/tqual.cpp b/src/common/backend/utils/time/tqual.cpp index 9ecb9446d..57f0c4b39 100755 --- a/src/common/backend/utils/time/tqual.cpp +++ b/src/common/backend/utils/time/tqual.cpp @@ -520,6 +520,14 @@ bool HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot, Buffer buffer) if (!HeapTupleHeaderXminCommitted(tuple)) { if (HeapTupleHeaderXminInvalid(tuple)) return false; + + /* + * An invalid Xmin can be left behind by a speculative insertion that + * is cancelled by super-deleting the tuple. We shouldn't see any of + * those in TOAST tables, but better safe than sorry. + */ + if (!TransactionIdIsValid(HeapTupleHeaderGetXmin(BufferGetPage(buffer), tuple))) + return false; } /* otherwise assume the tuple is valid for TOAST. */ @@ -552,8 +560,11 @@ bool HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot, Buffer buffer) * the case where the tuple is share-locked by a MultiXact, even if the * MultiXact includes the current transaction. Callers that want to * distinguish that case must test for it themselves.) + * + * HeapTupleSelfCreated: the tuple didn't exist at all when the scan started, it + * was created during the current CommandId (scan) */ -HTSU_Result HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, Buffer buffer) +HTSU_Result HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, Buffer buffer, bool self_visible) { bool needSync = false; HeapTupleHeader tuple = htup->t_data; @@ -580,8 +591,10 @@ restart: return HeapTupleInvisible; if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(page, tuple))) { - if (HeapTupleHeaderGetCmin(tuple, page) >= curcid) + if (HeapTupleHeaderGetCmin(tuple, page) > curcid) return HeapTupleInvisible; /* inserted after scan started */ + else if (HeapTupleHeaderGetCmin(tuple, page) == curcid && !self_visible) + return HeapTupleSelfCreated; /* inserted during the scan */ if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */ return HeapTupleMayBeUpdated; diff --git a/src/gausskernel/optimizer/commands/copy.cpp b/src/gausskernel/optimizer/commands/copy.cpp index 5d733e1f3..5c0e74c12 100644 --- a/src/gausskernel/optimizer/commands/copy.cpp +++ b/src/gausskernel/optimizer/commands/copy.cpp @@ -3490,7 +3490,7 @@ static uint64 CopyFrom(CopyState cstate) 1, /* dummy rangetable index */ 0); - ExecOpenIndices(resultRelInfo); + ExecOpenIndices(resultRelInfo, false); init_gtt_storage(CMD_INSERT, resultRelInfo); resultRelationDesc = resultRelInfo->ri_RelationDesc; @@ -4066,7 +4066,7 @@ static uint64 CopyFrom(CopyState cstate) estate, isPartitionRel ? heaprel : NULL, isPartitionRel ? partition : NULL, - bucketid); + bucketid, NULL); /* AFTER ROW INSERT Triggers */ ExecARInsertTriggers(estate, resultRelInfo, partitionid, bucketid, tuple, recheckIndexes); @@ -4413,7 +4413,7 @@ static void CopyFromUpdateIndexAndRunAfterRowTrigger(EState* estate, ResultRelIn estate, ispartitionedtable ? actualHeap : NULL, ispartitionedtable ? partition : NULL, - bucketid); + bucketid, NULL); ExecARInsertTriggers(estate, resultRelInfo, partitionOid, bucketid, bufferedTuples[i], recheckIndexes); list_free(recheckIndexes); } @@ -4484,7 +4484,7 @@ static void CopyFromInsertBatch(Relation rel, EState* estate, CommandId mycid, i estate, ispartitionedtable ? actualHeap : NULL, ispartitionedtable ? partition : NULL, - bucketId); + bucketId, NULL); ExecARInsertTriggers(estate, resultRelInfo, partitionOid, bucketId, bufferedTuples[i], recheckIndexes); list_free(recheckIndexes); } diff --git a/src/gausskernel/optimizer/commands/explain.cpp b/src/gausskernel/optimizer/commands/explain.cpp index a627fd21c..1b78fd1e6 100755 --- a/src/gausskernel/optimizer/commands/explain.cpp +++ b/src/gausskernel/optimizer/commands/explain.cpp @@ -187,6 +187,7 @@ static void ExplainProperty(const char* qlabel, const char* value, bool numeric, static void ExplainOpenGroup(const char* objtype, const char* labelname, bool labeled, ExplainState* es); static void ExplainCloseGroup(const char* objtype, const char* labelname, bool labeled, ExplainState* es); static void ExplainDummyGroup(const char* objtype, const char* labelname, ExplainState* es); +static void show_on_duplicate_info(ModifyTableState* mtstate, ExplainState* es); #ifdef PGXC static void ExplainExecNodes(const ExecNodes* en, ExplainState* es); static void ExplainRemoteQuery(RemoteQuery* plan, PlanState* planstate, List* ancestors, ExplainState* es); @@ -2326,6 +2327,12 @@ static void ExplainNode( } } } else { + /* upsert cases */ + ModifyTableState* mtstate = (ModifyTableState*)planstate; + if (mtstate->mt_upsert != NULL && + mtstate->mt_upsert->us_action != UPSERT_NONE && mtstate->resultRelInfo->ri_NumIndices > 0) { + show_on_duplicate_info(mtstate, es); + } /* non-merge cases */ foreach (elt, mt->remote_plans) { if (lfirst(elt)) { @@ -7912,6 +7919,39 @@ static void ExplainTargetRel(Plan* plan, Index rti, ExplainState* es) } } +/* + * Show extra information for upsert info + */ +static void show_on_duplicate_info(ModifyTableState* mtstate, ExplainState* es) +{ + ResultRelInfo* resultRelInfo = mtstate->resultRelInfo; + IndexInfo* indexInfo = NULL; + List* idxNames = NIL; + + /* Gather names of ON CONFLICT Arbiter indexes */ + for (int i = 0; i < resultRelInfo->ri_NumIndices; ++i) { + indexInfo = resultRelInfo->ri_IndexRelationInfo[i]; + if (!indexInfo->ii_Unique && !indexInfo->ii_ExclusionOps) { + continue; + } + + Relation indexRelation = resultRelInfo->ri_IndexRelationDescs[i]; + char* indexName = RelationGetRelationName(indexRelation); + idxNames = lappend(idxNames, indexName); + } + + ExplainPropertyText("Conflict Resolution", + mtstate->mt_upsert->us_action == UPSERT_NOTHING ? "NOTHING" : "UPDATE", + es); + /* + * Don't display arbiter indexes at all when DO NOTHING variant + * implicitly ignores all conflicts + */ + if (idxNames != NIL) { + ExplainPropertyList("Conflict Arbiter Indexes", idxNames, es); + } +} + #ifndef PGXC /* * Show extra information for a ModifyTable node diff --git a/src/gausskernel/optimizer/commands/tablecmds.cpp b/src/gausskernel/optimizer/commands/tablecmds.cpp index 4d3be9c3f..636166ffa 100644 --- a/src/gausskernel/optimizer/commands/tablecmds.cpp +++ b/src/gausskernel/optimizer/commands/tablecmds.cpp @@ -18804,7 +18804,7 @@ static void checkValidationForExchangeCStore(Relation partTableRel, Relation ord // init cstore partition insert resultRelInfo = makeNode(ResultRelInfo); InitResultRelInfo(resultRelInfo, partTableRel, 1, 0); - ExecOpenIndices(resultRelInfo); + ExecOpenIndices(resultRelInfo, false); resultRelInfo->ri_junkFilter = makeNode(JunkFilter); resultRelInfo->ri_junkFilter->jf_junkAttNo = tididx; resultRelInfo->ri_junkFilter->jf_xc_part_id = tableoidIdx; diff --git a/src/gausskernel/optimizer/commands/trigger.cpp b/src/gausskernel/optimizer/commands/trigger.cpp index 08c8c6e33..255c71be4 100644 --- a/src/gausskernel/optimizer/commands/trigger.cpp +++ b/src/gausskernel/optimizer/commands/trigger.cpp @@ -2616,6 +2616,11 @@ static HeapTuple GetTupleForTrigger(EState* estate, EPQState* epqstate, ResultRe LockTupleExclusive, false); switch (test) { + case HeapTupleSelfCreated: + ReleaseBuffer(buffer); + ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("attempted to lock invisible tuple"))); + break; case HeapTupleSelfUpdated: /* treat it as deleted; do not process */ ReleaseBuffer(buffer); diff --git a/src/gausskernel/optimizer/plan/createplan.cpp b/src/gausskernel/optimizer/plan/createplan.cpp index 6c3171404..d2391db52 100644 --- a/src/gausskernel/optimizer/plan/createplan.cpp +++ b/src/gausskernel/optimizer/plan/createplan.cpp @@ -8298,11 +8298,11 @@ static void deparallelize_modifytable(List* subplans) #ifdef STREAMPLAN Plan* make_modifytable(PlannerInfo* root, CmdType operation, bool canSetTag, List* resultRelations, List* subplans, List* returningLists, List* rowMarks, int epqParam, bool partKeyUpdated, Index mergeTargetRelation, - List* mergeSourceTargetList, List* mergeActionList, bool isDfsStore) + List* mergeSourceTargetList, List* mergeActionList, UpsertExpr* upsertClause, bool isDfsStore) #else ModifyTable* make_modifytable(CmdType operation, bool canSetTag, List* resultRelations, List* subplans, List* returningLists, List* rowMarks, int epqParam, bool partKeyUpdated, Index mergeTargetRelation, - List* mergeSourceTargetList, List* mergeActionList, bool isDfsStore) + List* mergeSourceTargetList, List* mergeActionList, UpsertExpr* upsertClause, bool isDfsStore) #endif { ModifyTable* node = makeNode(ModifyTable); @@ -8377,6 +8377,16 @@ ModifyTable* make_modifytable(CmdType operation, bool canSetTag, List* resultRel node->mergeTargetRelation = mergeTargetRelation; node->mergeSourceTargetList = mergeSourceTargetList; node->mergeActionList = mergeActionList; + if (upsertClause != NULL) { + node->upsertAction = upsertClause->upsertAction; + node->updateTlist = upsertClause->updateTlist; + node->exclRelTlist = upsertClause->exclRelTlist; + node->exclRelRTIndex = upsertClause->exclRelIndex; + } else { + node->upsertAction = UPSERT_NONE; + node->updateTlist = NIL; + node->exclRelTlist = NIL; + } #ifdef STREAMPLAN node->plan.exec_nodes = exec_nodes; @@ -8507,11 +8517,11 @@ ModifyTable* make_modifytable(CmdType operation, bool canSetTag, List* resultRel */ Plan* make_modifytables(PlannerInfo* root, CmdType operation, bool canSetTag, List* resultRelations, List* subplans, List* returningLists, List* rowMarks, int epqParam, bool partKeyUpdated, bool isDfsStore, Index mergeTargetRelation, - List* mergeSourceTargetList, List* mergeActionList) + List* mergeSourceTargetList, List* mergeActionList, UpsertExpr* upsertClause) #else ModifyTable* make_modifytables(CmdType operation, bool canSetTag, List* resultRelations, List* subplans, List* returningLists, List* rowMarks, int epqParam, bool partKeyUpdated, bool isDfsStore, Index mergeTargetRelation, - List* mergeSourceTargetList, List* mergeActionList) + List* mergeSourceTargetList, List* mergeActionList, UpsertExpr* upsertClause) #endif { if (isDfsStore) { @@ -8542,6 +8552,7 @@ ModifyTable* make_modifytables(CmdType operation, bool canSetTag, List* resultRe 0, NULL, NULL, + upsertClause, isDfsStore); /* * We must adjust the plan tree. Because the make_modifytable function would make a @@ -8593,6 +8604,7 @@ ModifyTable* make_modifytables(CmdType operation, bool canSetTag, List* resultRe 0, NULL, NULL, + upsertClause, isDfsStore); appendSubPlans = lappend(appendSubPlans, (void*)mtplan); #endif @@ -8627,6 +8639,7 @@ ModifyTable* make_modifytables(CmdType operation, bool canSetTag, List* resultRe mergeTargetRelation, mergeSourceTargetList, mergeActionList, + upsertClause, isDfsStore); if (IS_STREAM_PLAN) return mt_stream_plan; @@ -8644,6 +8657,7 @@ ModifyTable* make_modifytables(CmdType operation, bool canSetTag, List* resultRe mergeTargetRelation, mergeSourceTargetList, mergeActionList, + upsertClause isDfsStore); return pgxc_make_modifytable(root, (Plan*)mtplan); #endif @@ -8659,6 +8673,7 @@ ModifyTable* make_modifytables(CmdType operation, bool canSetTag, List* resultRe mergeTargetRelation, mergeSourceTargetList, mergeActionList, + upsertClause, isDfsStore); #endif } @@ -8952,7 +8967,7 @@ List* process_agg_targetlist(PlannerInfo* root, List** local_tlist) * We are about to change the local_tlist, check if we have already * copied original local_tlist, if not take a copy */ - if ((orig_local_tlist == NULL) && (IsA(expr, Aggref) || context.aggs)) + if ((orig_local_tlist == NIL) && (IsA(expr, Aggref) || context.aggs)) orig_local_tlist = (List*)copyObject(*local_tlist); /* diff --git a/src/gausskernel/optimizer/plan/planner.cpp b/src/gausskernel/optimizer/plan/planner.cpp index 8f202bd76..415acf524 100644 --- a/src/gausskernel/optimizer/plan/planner.cpp +++ b/src/gausskernel/optimizer/plan/planner.cpp @@ -1332,6 +1332,10 @@ Plan* subquery_planner(PlannerGlobal* glob, Query* parse, PlannerInfo* parent_ro parse->mergeSourceTargetList = (List*)preprocess_expression(root, (Node*)parse->mergeSourceTargetList, EXPRKIND_TARGET); + if (parse->upsertClause) { + parse->upsertClause->updateTlist = (List*) + preprocess_expression(root, (Node*)parse->upsertClause->updateTlist, EXPRKIND_TARGET); + } root->append_rel_list = (List*)preprocess_expression(root, (Node*)root->append_rel_list, EXPRKIND_APPINFO); /* Also need to preprocess expressions for function and values RTEs */ @@ -1521,6 +1525,7 @@ Plan* subquery_planner(PlannerGlobal* glob, Query* parse, PlannerInfo* parent_ro parse->mergeTarget_relation, parse->mergeSourceTargetList, parse->mergeActionList, + parse->upsertClause, isDfsStore); #else plan = (Plan*)make_modifytable(parse->commandType, @@ -1534,6 +1539,7 @@ Plan* subquery_planner(PlannerGlobal* glob, Query* parse, PlannerInfo* parent_ro parse->mergeTarget_relation, parse->mergeSourceTargetList, parse->mergeActionList, + parse->upsertClause, isDfsStore); #endif #ifdef PGXC @@ -2014,6 +2020,7 @@ static Plan* inheritance_planner(PlannerInfo* root) isDfsStore, 0, NULL, + NULL, NULL); #else return make_modifytables(parse->commandType, @@ -2027,6 +2034,7 @@ static Plan* inheritance_planner(PlannerInfo* root) isDfsStore, 0, NULL, + NULL, NULL); #endif } @@ -2445,6 +2453,11 @@ static Plan* grouping_planner(PlannerInfo* root, double tuple_fraction) /* Preprocess targetlist */ tlist = preprocess_targetlist(root, tlist); + if (parse->upsertClause) { + UpsertExpr* upsertClause = parse->upsertClause; + upsertClause->updateTlist = + preprocess_upsert_targetlist(upsertClause->updateTlist, parse->resultRelation, parse->rtable); + } /* * Locate any window functions in the tlist. (We don't need to look * anywhere else, since expressions used in ORDER BY will be in there diff --git a/src/gausskernel/optimizer/plan/setrefs.cpp b/src/gausskernel/optimizer/plan/setrefs.cpp index d9b0c9645..a64541b86 100755 --- a/src/gausskernel/optimizer/plan/setrefs.cpp +++ b/src/gausskernel/optimizer/plan/setrefs.cpp @@ -830,6 +830,15 @@ static Plan* set_plan_refs(PlannerInfo* root, Plan* plan, int rtoffset) } } + if (splan->updateTlist != NIL) { + indexed_tlist* itlist; + itlist = build_tlist_index(splan->exclRelTlist); + splan->updateTlist = fix_join_expr(root, splan->updateTlist, NULL, + itlist, linitial_int(splan->resultRelations), rtoffset); + } + + splan->exclRelRTIndex += rtoffset; + foreach (l, splan->resultRelations) { lfirst_int(l) += rtoffset; } diff --git a/src/gausskernel/optimizer/plan/subselect.cpp b/src/gausskernel/optimizer/plan/subselect.cpp index a021a792c..3ff05af07 100755 --- a/src/gausskernel/optimizer/plan/subselect.cpp +++ b/src/gausskernel/optimizer/plan/subselect.cpp @@ -2578,6 +2578,7 @@ static Bitmapset* finalize_plan(PlannerInfo* root, Plan* plan, Bitmapset* valid_ valid_params = bms_add_member(bms_copy(valid_params), locally_added_param); scan_params = bms_add_member(bms_copy(scan_params), locally_added_param); (void)finalize_primnode((Node*)mtplan->returningLists, &context); + (void)finalize_primnode((Node*)mtplan->updateTlist, &context); finalize_plans(root, &context, mtplan->plans, valid_params, scan_params); } break; #ifdef PGXC diff --git a/src/gausskernel/optimizer/prep/prepjointree.cpp b/src/gausskernel/optimizer/prep/prepjointree.cpp index e3dd08871..835f8ddb8 100755 --- a/src/gausskernel/optimizer/prep/prepjointree.cpp +++ b/src/gausskernel/optimizer/prep/prepjointree.cpp @@ -1056,6 +1056,10 @@ static Node* pull_up_simple_subquery(PlannerInfo* root, Node* jtnode, RangeTblEn */ parse->targetList = (List*)pullup_replace_vars((Node*)parse->targetList, &rvcontext); parse->returningList = (List*)pullup_replace_vars((Node*)parse->returningList, &rvcontext); + if (parse->upsertClause != NULL) { + parse->upsertClause->updateTlist = (List*) + pullup_replace_vars((Node*)parse->upsertClause->updateTlist, &rvcontext); + } replace_vars_in_jointree((Node*)parse->jointree, &rvcontext, lowest_outer_join); diff --git a/src/gausskernel/optimizer/prep/preptlist.cpp b/src/gausskernel/optimizer/prep/preptlist.cpp index b0b1de5a3..fa82ab05a 100644 --- a/src/gausskernel/optimizer/prep/preptlist.cpp +++ b/src/gausskernel/optimizer/prep/preptlist.cpp @@ -555,5 +555,10 @@ static List* add_distribute_column(List* tlist, Index result_relation, List* ran return tlist; } + +List* preprocess_upsert_targetlist(List* tlist, int result_relation, List* range_table) +{ + return expand_targetlist(tlist, CMD_UPDATE, result_relation, range_table); +} #endif diff --git a/src/gausskernel/optimizer/rewrite/rewriteHandler.cpp b/src/gausskernel/optimizer/rewrite/rewriteHandler.cpp index a6a345704..3d6ea7bf1 100644 --- a/src/gausskernel/optimizer/rewrite/rewriteHandler.cpp +++ b/src/gausskernel/optimizer/rewrite/rewriteHandler.cpp @@ -57,7 +57,8 @@ static bool acquireLocksOnSubLinks(Node* node, void* context); static Query* rewriteRuleAction( Query* parsetree, Query* rule_action, Node* rule_qual, int rt_index, CmdType event, bool* returning_flag); static List* adjustJoinTreeList(Query* parsetree, bool removert, int rt_index); -static void rewriteTargetListIU(Query* parsetree, Relation target_relation, List** attrno_list); +static List* rewriteTargetListIU(List* targetList, CmdType commandType, + Relation target_relation, int result_rtindex, List** attrno_list); static TargetEntry* process_matched_tle(TargetEntry* src_tle, TargetEntry* prior_tle, const char* attrName); static Node* get_assignment_input(Node* node); static void rewriteValuesRTE(RangeTblEntry* rte, Relation target_relation, List* attrnos); @@ -613,9 +614,9 @@ static List* adjustJoinTreeList(Query* parsetree, bool removert, int rt_index) * order of the original tlist's non-junk entries. This is needed for * processing VALUES RTEs. */ -static void rewriteTargetListIU(Query* parsetree, Relation target_relation, List** attrno_list) +static List* rewriteTargetListIU(List* targetList, CmdType commandType, Relation target_relation, + int result_rtindex, List** attrno_list) { - CmdType commandType = parsetree->commandType; TargetEntry** new_tles; List* new_tlist = NIL; List* junk_tlist = NIL; @@ -639,7 +640,7 @@ static void rewriteTargetListIU(Query* parsetree, Relation target_relation, List new_tles = (TargetEntry**)palloc0(numattrs * sizeof(TargetEntry*)); next_junk_attrno = numattrs + 1; - foreach (temp, parsetree->targetList) { + foreach (temp, targetList) { TargetEntry* old_tle = (TargetEntry*)lfirst(temp); if (!old_tle->resjunk) { @@ -737,8 +738,7 @@ static void rewriteTargetListIU(Query* parsetree, Relation target_relation, List Node* new_expr = NULL; new_expr = (Node*)makeVar( - (unsigned int)(parsetree->resultRelation), attrno, att_tup->atttypid, att_tup->atttypmod, - att_tup->attcollation, 0); + result_rtindex, attrno, att_tup->atttypid, att_tup->atttypmod, att_tup->attcollation, 0); new_tle = makeTargetEntry((Expr*)new_expr, (int16)attrno, pstrdup(NameStr(att_tup->attname)), false); } @@ -748,8 +748,8 @@ static void rewriteTargetListIU(Query* parsetree, Relation target_relation, List } pfree_ext(new_tles); - - parsetree->targetList = list_concat(new_tlist, junk_tlist); + targetList = list_concat(new_tlist, junk_tlist); + return targetList; } /* @@ -2303,15 +2303,28 @@ static List* RewriteQuery(Query* parsetree, List* rewrite_events) List* attrnos = NIL; /* Process the main targetlist ... */ - rewriteTargetListIU(parsetree, rt_entry_relation, &attrnos); + parsetree->targetList = + rewriteTargetListIU(parsetree->targetList, parsetree->commandType, + rt_entry_relation, parsetree->resultRelation, &attrnos); /* ... and the VALUES expression lists */ rewriteValuesRTE(values_rte, rt_entry_relation, attrnos); } else { /* Process just the main targetlist */ - rewriteTargetListIU(parsetree, rt_entry_relation, NULL); + parsetree->targetList = + rewriteTargetListIU(parsetree->targetList, parsetree->commandType, + rt_entry_relation, parsetree->resultRelation, NULL); + } + + if (parsetree->upsertClause != NULL && + parsetree->upsertClause->upsertAction == UPSERT_UPDATE) { + parsetree->upsertClause->updateTlist = + rewriteTargetListIU(parsetree->upsertClause->updateTlist, CMD_UPDATE, + rt_entry_relation, parsetree->resultRelation, NULL); } } else if (event == CMD_UPDATE) { - rewriteTargetListIU(parsetree, rt_entry_relation, NULL); + parsetree->targetList = + rewriteTargetListIU(parsetree->targetList, parsetree->commandType, + rt_entry_relation, parsetree->resultRelation, NULL); rewriteTargetListUD(parsetree, rt_entry, rt_entry_relation); } else if (event == CMD_MERGE) { /* diff --git a/src/gausskernel/runtime/executor/execMain.cpp b/src/gausskernel/runtime/executor/execMain.cpp index bce75c951..d9d527ad4 100644 --- a/src/gausskernel/runtime/executor/execMain.cpp +++ b/src/gausskernel/runtime/executor/execMain.cpp @@ -2762,6 +2762,11 @@ HeapTuple EvalPlanQualFetch(EState *estate, Relation relation, int lockmode, Ite ReleaseBuffer(buffer); switch (test) { + case HeapTupleSelfCreated: + ReleaseBuffer(buffer); + ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("attempted to lock invisible tuple"))); + break; case HeapTupleSelfUpdated: /* treat it as deleted; do not process */ ReleaseBuffer(buffer); diff --git a/src/gausskernel/runtime/executor/execUtils.cpp b/src/gausskernel/runtime/executor/execUtils.cpp index 30dad3e92..c624cfc35 100755 --- a/src/gausskernel/runtime/executor/execUtils.cpp +++ b/src/gausskernel/runtime/executor/execUtils.cpp @@ -64,6 +64,8 @@ static bool get_last_attnums(Node* node, ProjectionInfo* projInfo); static bool index_recheck_constraint( Relation index, Oid* constr_procs, Datum* existing_values, const bool* existing_isnull, Datum* new_values); static void ShutdownExprContext(ExprContext* econtext, bool isCommit); +static bool check_violation(Relation heap, Relation index, IndexInfo *indexInfo, ItemPointer tupleid, Datum *values, + const bool *isnull, EState *estate, bool newIndex, bool errorOK, CheckWaitMode waitMode, ItemPointer conflictTid); /* ---------------------------------------------------------------- * Executor state and memory management functions @@ -1100,7 +1102,7 @@ Partition ExecOpenScanParitition(EState* estate, Relation parent, PartitionIdent * resultRelInfo->ri_RelationDesc. * ---------------------------------------------------------------- */ -void ExecOpenIndices(ResultRelInfo* resultRelInfo) +void ExecOpenIndices(ResultRelInfo* resultRelInfo, bool speculative) { Relation resultRelation = resultRelInfo->ri_RelationDesc; List* indexoidlist = NIL; @@ -1156,6 +1158,13 @@ void ExecOpenIndices(ResultRelInfo* resultRelInfo) /* extract index key information from the index's pg_index info */ ii = BuildIndexInfo(indexDesc); + /* + * If the indexes are to be used for speculative insertion, add extra + * information required by unique index entries. + */ + if (speculative && ii->ii_Unique) { + BuildSpeculativeIndexInfo(indexDesc, ii); + } relationDescs[i] = indexDesc; indexInfoArray[i] = ii; i++; @@ -1195,6 +1204,164 @@ void ExecCloseIndices(ResultRelInfo* resultRelInfo) */ } +/* ---------------------------------------------------------------- + * ExecCheckIndexConstraints + * + * This routine checks if a tuple violates any unique or + * exclusion constraints. Returns true if there is no no conflict. + * Otherwise returns false, and the TID of the conflicting + * tuple is returned in *conflictTid. + * + * Note that this doesn't lock the values in any way, so it's + * possible that a conflicting tuple is inserted immediately + * after this returns. But this can be used for a pre-check + * before insertion. + * ---------------------------------------------------------------- + */ +bool ExecCheckIndexConstraints(TupleTableSlot* slot, EState* estate, + Relation targetRel, Partition p, int2 bucketId, ItemPointer conflictTid) +{ + ResultRelInfo* resultRelInfo = NULL; + RelationPtr relationDescs = NULL; + int i = 0; + int numIndices = 0; + IndexInfo** indexInfoArray = NULL; + Relation heapRelationDesc = NULL; + Relation actualHeap = NULL; + ExprContext* econtext = NULL; + Datum values[INDEX_MAX_KEYS]; + bool isnull[INDEX_MAX_KEYS]; + ItemPointerData invalidItemPtr; + bool isPartitioned = false; + List* partitionIndexOidList = NIL; + + ItemPointerSetInvalid(conflictTid); + ItemPointerSetInvalid(&invalidItemPtr); + + /* + * Get information from the result relation info structure. + */ + resultRelInfo = estate->es_result_relation_info; + numIndices = resultRelInfo->ri_NumIndices; + relationDescs = resultRelInfo->ri_IndexRelationDescs; + indexInfoArray = resultRelInfo->ri_IndexRelationInfo; + heapRelationDesc = resultRelInfo->ri_RelationDesc; + actualHeap = targetRel; + + if (RELATION_IS_PARTITIONED(heapRelationDesc)) { + Assert(p != NULL && p->pd_part != NULL); + isPartitioned = true; + + if (!p->pd_part->indisusable) { + return false; + } + } + + /* + * use the EState's per-tuple context for evaluating predicates + * and index expressions (creating it if it's not already there). + */ + econtext = GetPerTupleExprContext(estate); + + /* Arrange for econtext's scan tuple to be the tuple under test */ + econtext->ecxt_scantuple = slot; + + /* + * For each index, form index tuple and check if it satisfies the + * constraint. + */ + for (i = 0; i < numIndices; i++) { + Relation indexRelation = relationDescs[i]; + IndexInfo* indexInfo; + bool satisfiesConstraint; + Relation actualIndex = NULL; + Oid partitionedindexid = InvalidOid; + Oid indexpartitionid = InvalidOid; + Partition indexpartition = NULL; + + if (indexRelation == NULL) + continue; + + indexInfo = indexInfoArray[i]; + + if (!indexInfo->ii_Unique && !indexInfo->ii_ExclusionOps) + continue; + + /* If the index is marked as read-only, ignore it */ + if (!indexInfo->ii_ReadyForInserts) + continue; + + if (!indexRelation->rd_index->indimmediate) + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("INSERT ON DUPLICATE KEY UPDATE does not support deferrable" + " unique constraints/exclusion constraints."))); + if (isPartitioned) { + partitionedindexid = RelationGetRelid(indexRelation); + if (!PointerIsValid(partitionIndexOidList)) { + partitionIndexOidList = PartitionGetPartIndexList(p); + if (!PointerIsValid(partitionIndexOidList)) { + // no local indexes available + return false; + } + } + + indexpartitionid = searchPartitionIndexOid(partitionedindexid, partitionIndexOidList); + + searchFakeReationForPartitionOid(estate->esfRelations, + estate->es_query_cxt, + indexRelation, + indexpartitionid, + actualIndex, + indexpartition, + RowExclusiveLock); + /* skip unusable index */ + if (indexpartition->pd_part->indisusable == false) { + continue; + } + } else { + actualIndex = indexRelation; + } + + if (bucketId != InvalidBktId) { + searchHBucketFakeRelation(estate->esfRelations, estate->es_query_cxt, actualIndex, bucketId, actualIndex); + } + + /* Check for partial index */ + if (indexInfo->ii_Predicate != NIL) { + List* predicate; + + /* + * If predicate state not set up yet, create it (in the estate's + * per-query context) + */ + predicate = indexInfo->ii_PredicateState; + if (predicate == NIL) { + predicate = (List*)ExecPrepareExpr((Expr*)indexInfo->ii_Predicate, estate); + indexInfo->ii_PredicateState = predicate; + } + + /* Skip this index-update if the predicate isn't satisfied */ + if (!ExecQual(predicate, econtext, false)) { + continue; + } + } + + /* + * FormIndexDatum fills in its values and isnull parameters with the + * appropriate values for the column(s) of the index. + */ + FormIndexDatum(indexInfo, slot, estate, values, isnull); + + satisfiesConstraint = check_violation(actualHeap, actualIndex, indexInfo, &invalidItemPtr, values, isnull, + estate, false, true, CHECK_WAIT, conflictTid); + if (!satisfiesConstraint) { + return false; + } + } + + return true; +} + /* ---------------------------------------------------------------- * ExecInsertIndexTuples * @@ -1215,8 +1382,8 @@ void ExecCloseIndices(ResultRelInfo* resultRelInfo) * Should we change the API to make it safer? * ---------------------------------------------------------------- */ -List* ExecInsertIndexTuples( - TupleTableSlot* slot, ItemPointer tupleid, EState* estate, Relation targetPartRel, Partition p, int2 bucketId) +List* ExecInsertIndexTuples(TupleTableSlot* slot, ItemPointer tupleid, EState* estate, + Relation targetPartRel, Partition p, int2 bucketId, bool* conflict) { List* result = NIL; ResultRelInfo* resultRelInfo = NULL; @@ -1362,6 +1529,8 @@ List* ExecInsertIndexTuples( */ if (!indexRelation->rd_index->indisunique) { checkUnique = UNIQUE_CHECK_NO; + } else if (conflict != NULL) { + checkUnique = UNIQUE_CHECK_PARTIAL; } else if (indexRelation->rd_index->indimmediate) { checkUnique = UNIQUE_CHECK_YES; } else { @@ -1397,9 +1566,13 @@ List* ExecInsertIndexTuples( /* * The tuple potentially violates the uniqueness or exclusion * constraint, so make a note of the index so that we can re-check - * it later. + * it later. Speculative inserters are told if there was a + * speculative conflict, since that always requires a restart. */ result = lappend_oid(result, RelationGetRelid(indexRelation)); + if (conflict != NULL) { + *conflict = true; + } } } @@ -1433,6 +1606,13 @@ List* ExecInsertIndexTuples( */ bool check_exclusion_constraint(Relation heap, Relation index, IndexInfo* indexInfo, ItemPointer tupleid, Datum* values, const bool* isnull, EState* estate, bool newIndex, bool errorOK) +{ + return check_violation(heap, index, indexInfo, tupleid, values, isnull, + estate, newIndex, errorOK, errorOK ? CHECK_NOWAIT : CHECK_WAIT, NULL); +} + +bool check_violation(Relation heap, Relation index, IndexInfo* indexInfo, ItemPointer tupleid, Datum* values, + const bool* isnull, EState* estate, bool newIndex, bool errorOK, CheckWaitMode waitMode, ItemPointer conflictTid) { Oid* constr_procs = indexInfo->ii_ExclusionProcs; uint16* constr_strats = indexInfo->ii_ExclusionStrats; @@ -1459,6 +1639,13 @@ bool check_exclusion_constraint(Relation heap, Relation index, IndexInfo* indexI } } + if (indexInfo->ii_ExclusionOps) { + constr_procs = indexInfo->ii_ExclusionProcs; + constr_strats = indexInfo->ii_ExclusionStrats; + } else { + constr_procs = indexInfo->ii_UniqueProcs; + constr_strats = indexInfo->ii_UniqueStrats; + } /* * Search the tuples that are in the index for any violations, including * tuples that aren't visible yet. @@ -1503,7 +1690,7 @@ retry: /* * Ignore the entry for the tuple we're trying to check. */ - if (ItemPointerEquals(tupleid, &tup->t_self)) { + if (ItemPointerIsValid(tupleid) && ItemPointerEquals(tupleid, &tup->t_self)) { if (found_self) /* should not happen */ ereport(ERROR, (errcode(ERRCODE_FETCH_DATA_FAILED), @@ -1527,33 +1714,44 @@ retry: } /* - * At this point we have either a conflict or a potential conflict. If - * we're not supposed to raise error, just return the fact of the - * potential conflict without waiting to see if it's real. - */ - if (errorOK) { - conflict = true; - break; - } - - /* + * At this point we have either a conflict or a potential conflict. * If an in-progress transaction is affecting the visibility of this - * tuple, we need to wait for it to complete and then recheck. For - * simplicity we do rechecking by just restarting the whole scan --- - * this case probably doesn't happen often enough to be worth trying - * harder, and anyway we don't want to hold any index internal locks - * while waiting. + * tuple, we need to wait for it to complete and then recheck (unless + * the caller requested not to). For simplicity we do rechecking by + * just restarting the whole scan --- this case probably doesn't + * happen often enough to be worth trying harder, and anyway we don't + * want to hold any index internal locks while waiting. */ xwait = TransactionIdIsValid(DirtySnapshot.xmin) ? DirtySnapshot.xmin : DirtySnapshot.xmax; - if (TransactionIdIsValid(xwait)) { + if (TransactionIdIsValid(xwait) && waitMode == CHECK_WAIT) { index_endscan(index_scan); + + /* for speculative insertion (INSERT ON DUPLICATE KEY UPDATE), + * we only need to wait the speculative token lock to be release, + * which happens when the tuple is speculative inserted by other + * running transction, and has done it's insertion (eithter + * finished or aborted). + */ XactLockTableWait(xwait); goto retry; } /* - * We have a definite conflict. Report it. + * We have a definite conflict (or a potential one, but the caller + * didn't want to wait). If we're not supposed to raise error, just + * return to the caller. + */ + if (errorOK) { + conflict = true; + if (conflictTid != NULL) + *conflictTid = tup->t_self; + break; + } + + /* + * We have a definite conflict (or a potential one, but the caller + * didn't want to wait). Report it. */ error_new = BuildIndexValueDescription(index, values, isnull); error_existing = BuildIndexValueDescription(index, existing_values, existing_isnull); @@ -1578,7 +1776,7 @@ retry: /* * Ordinarily, at this point the search should have found the originally - * inserted tuple, unless we exited the loop early because of conflict. + * inserted tuple (if any), unless we exited the loop early because of conflict. * However, it is possible to define exclusion constraints for which that * wouldn't be true --- for instance, if the operator is <>. So we no * longer complain if found_self is still false. diff --git a/src/gausskernel/runtime/executor/nodeLockRows.cpp b/src/gausskernel/runtime/executor/nodeLockRows.cpp index cde8d6a48..e702ea46a 100755 --- a/src/gausskernel/runtime/executor/nodeLockRows.cpp +++ b/src/gausskernel/runtime/executor/nodeLockRows.cpp @@ -184,6 +184,10 @@ lnext: bucket_rel, &tuple, &buffer, &update_ctid, &update_xmax, estate->es_output_cid, lock_mode, erm->noWait); ReleaseBuffer(buffer); switch (test) { + case HeapTupleSelfCreated: + ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("attempted to lock invisible tuple"))); + break; case HeapTupleSelfUpdated: /* treat it as deleted; do not process */ goto lnext; diff --git a/src/gausskernel/runtime/executor/nodeModifyTable.cpp b/src/gausskernel/runtime/executor/nodeModifyTable.cpp index d40c94d5b..cdcbb5a44 100644 --- a/src/gausskernel/runtime/executor/nodeModifyTable.cpp +++ b/src/gausskernel/runtime/executor/nodeModifyTable.cpp @@ -248,6 +248,277 @@ static TupleTableSlot* ExecProcessReturning( return ExecProject(projectReturning, NULL); } +static void ExecCheckHeapTupleVisible(EState* estate, HeapTuple tuple, Buffer buffer) +{ + if (!IsolationUsesXactSnapshot()) + return; + + if (!HeapTupleSatisfiesVisibility(tuple, estate->es_snapshot, buffer)) + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("could not serialize access due to concurrent update"))); +} + +static void ExecCheckTIDVisible(EState* estate, Relation rel, ItemPointer tid) +{ + Buffer buffer; + HeapTupleData tuple; + + /* check isolation level to tell if tuple visibility check is needed */ + if (!IsolationUsesXactSnapshot()) { + return; + } + + tuple.t_self = *tid; + if (!heap_fetch(rel, SnapshotAny, &tuple, &buffer, false, NULL)) { + ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("failed to fetch conflicting tuple for DUPLICATE KEY UPDATE"))); + } + + ExecCheckHeapTupleVisible(estate, &tuple, buffer); + ReleaseBuffer(buffer); +} + +static bool ExecConflictUpdate(ModifyTableState* mtstate, ResultRelInfo* resultRelInfo, ItemPointer conflictTid, + TupleTableSlot* planSlot, TupleTableSlot* excludedSlot, EState* estate, Relation targetRel, + Oid oldPartitionOid, int2 bucketid, bool canSetTag, TupleTableSlot** returning) +{ + ExprContext* econtext = mtstate->ps.ps_ExprContext; + Relation relation = targetRel; + UpsertState* upsertState = mtstate->mt_upsert; + HeapTupleData tuple; + HTSU_Result test; + Buffer buffer; + ItemPointerData update_ctid; + TransactionId update_xmax; + + tuple.t_self = *conflictTid; + test = heap_lock_tuple(relation, &tuple, &buffer, + &update_ctid, &update_xmax, + estate->es_output_cid, LockTupleExclusive, false); +checktest: + switch (test) { + case HeapTupleMayBeUpdated: + /* success */ + break; + case HeapTupleSelfCreated: + /* + * This can occur when a just inserted tuple is updated again in + * the same command. E.g. because multiple rows with the same + * conflicting key values are inserted using STREAM: + * INSERT INTO t VALUES(1),(1) ON DUPLICATE KEY UPDATE ... + * + * This is somewhat similar to the ExecUpdate() + * HeapTupleSelfUpdated case. We do not want to proceed because + * it would lead to the same row being updated a second time in + * some unspecified order, and in contrast to plain UPDATEs + * there's no historical behavior to break. + * + * It is the user's responsibility to prevent this situation from + * occurring. These problems are why SQL-2003 similarly specifies + * that for SQL MERGE, an exception must be raised in the event of + * an attempt to update the same row twice. + * + * However, in order by be compatible with SQL, we have to break the + * rule and update the same row which is created within the command. + */ + ReleaseBuffer(buffer); +#ifdef ENABLE_MULTIPLE_NODES + if (!(u_sess->attr.attr_sql.sql_compatibility & DB_CMPT_C)) { + ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("ON DUPLICATE KEY UPDATE command cannot affect row a second time"), + errhint("Ensure that no rows proposed for insertion within" + "the same command have duplicate constrained values."))); + } +#endif + test = heap_lock_tuple(relation, &tuple, &buffer, &update_ctid, &update_xmax, + estate->es_output_cid, LockTupleExclusive, false, true); + Assert(test != HeapTupleSelfCreated); + goto checktest; + break; + case HeapTupleSelfUpdated: + ReleaseBuffer(buffer); + /* + * This state should never be reached. As a dirty snapshot is used + * to find conflicting tuples, speculative insertion wouldn't have + * seen this row to conflict with. + */ + ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("unexpected self-updated tuple"))); + break; + case HeapTupleUpdated: + ReleaseBuffer(buffer); + if (IsolationUsesXactSnapshot()) { + ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("could not serialize access due to concurrent update"))); + } + /* + * Tell caller to try again from the very start. + * It does not make sense to use the usual EvalPlanQual() style + * loop here, as the new version of the row might not conflict + * anymore, or the conflicting tuple has actually been deleted. + */ + return false; + case HeapTupleBeingUpdated: + ReleaseBuffer(buffer); + ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("unexpected concurrent update tuple"))); + break; + default: + ReleaseBuffer(buffer); + elog(ERROR, "unrecognized heap_lock_tuple status: %u", test); + break; + } + + /* + * Success, the tuple is locked. + * + * Reset per-tuple memory context to free any expression evaluation + * storage allocated in the previous cycle. + */ + ResetExprContext(econtext); + + /* NOTE: we rely on ExecUpdate() to do MVCC snapshot check, thus projection is + * done here although the final ExecUpdate might be failed. + */ + ExecCheckHeapTupleVisible(estate, &tuple, buffer); + + /* Store target's existing tuple in the state's dedicated slot */ + ExecStoreTuple(&tuple, upsertState->us_existing, buffer, false); + + /* + * Make tuple and any needed join variables available to ExecQual and + * ExecProject. The EXCLUDED tuple is installed in ecxt_innertuple, while + * the target's existing tuple is installed in the scantuple. EXCLUDED has + * been made to reference INNER_VAR in setrefs.c, but there is no other redirection. + */ + econtext->ecxt_scantuple = upsertState->us_existing; + econtext->ecxt_innertuple = excludedSlot; + econtext->ecxt_outertuple = NULL; + + ExecProject(resultRelInfo->ri_updateProj, NULL); + + *returning = ExecUpdate(conflictTid, oldPartitionOid, bucketid, NULL, + upsertState->us_updateproj, planSlot, &mtstate->mt_epqstate, + mtstate, canSetTag, false); + ReleaseBuffer(buffer); + return true; +} + + +static Oid ExecUpsert(ModifyTableState* state, TupleTableSlot* slot, TupleTableSlot* planSlot, EState* estate, + bool canSetTag, HeapTuple tuple, TupleTableSlot** returning, bool* updated) +{ + Oid newid = InvalidOid; + bool specConflict; + List* recheckIndexes = NIL; + ResultRelInfo* resultRelInfo = NULL; + Relation resultRelationDesc = NULL; + Relation heaprel = NULL; /* actual relation to upsert index */ + Relation targetrel = NULL; /* actual relation to upsert tuple */ + Oid partitionid = InvalidOid; /* bucket id for bucket hash table */ + Partition partition = NULL; /* partition info for partition table */ + int2 bucketid = InvalidBktId; + ItemPointerData conflictTid; + UpsertState* upsertState = state->mt_upsert; + *updated = false; + + /* + * get information on the (current) result relation + */ + resultRelInfo = estate->es_result_relation_info; + resultRelationDesc = resultRelInfo->ri_RelationDesc; + heaprel = resultRelationDesc; + + if (unlikely(RelationIsCUFormat(resultRelationDesc))) { + ereport(ERROR, + (errmodule(MOD_EXECUTOR), + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("ON DUPLICATE KEY UPDATE is not supported on column orientated table")))); + } + + if (unlikely(RelationIsPAXFormat(resultRelationDesc))) { + ereport(ERROR, + (errmodule(MOD_EXECUTOR), + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("ON DUPLICATE KEY UPDATE is not supported on DFS table")))); + } + + if (RelationIsPartitioned(resultRelationDesc)) { + partitionid = heapTupleGetPartitionId(resultRelationDesc, tuple); + searchFakeReationForPartitionOid(estate->esfRelations, + estate->es_query_cxt, + resultRelationDesc, + partitionid, + heaprel, + partition, + RowExclusiveLock); + } + + targetrel = heaprel; + if (RELATION_OWN_BUCKET(resultRelationDesc)) { + bucketid = computeTupleBucketId(resultRelationDesc, tuple); + if (unlikely(bucketid != InvalidBktId)) { + searchHBucketFakeRelation(estate->esfRelations, estate->es_query_cxt, heaprel, bucketid, targetrel); + } + } + + + vlock: + specConflict = false; + if (!ExecCheckIndexConstraints(slot, estate, targetrel, partition, bucketid, &conflictTid)) { + /* committed conflict tuple found */ + if (upsertState->us_action == UPSERT_UPDATE) { + /* + * In case of DUPLICATE KEY UPDATE, execute the UPDATE part. + * Be prepared to retry if the UPDATE fails because + * of another concurrent UPDATE/DELETE to the conflict tuple. + */ + *returning = NULL; + + if (ExecConflictUpdate(state, resultRelInfo, &conflictTid, planSlot, slot, estate, targetrel, partitionid, + bucketid, canSetTag, returning)) { + InstrCountFiltered2(&state->ps, 1); + *updated = true; + return InvalidOid; + } else { + goto vlock; + } + } else { + /* + * In case of DUPLICATE UPDATE NOTHING, do nothing. + * However, verify that the tuple is visible to the + * executor's MVCC snapshot at higher isolation levels. + */ + Assert(upsertState->us_action == UPSERT_NOTHING); + ExecCheckTIDVisible(estate, targetrel, &conflictTid); + InstrCountFiltered2(&state->ps, 1); + *updated = true; + return InvalidOid; + } + } + + /* insert the tuple */ + newid = heap_insert(targetrel, tuple, estate->es_output_cid, 0, NULL); + + /* insert index entries for tuple */ + recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self), estate, heaprel, + partition, bucketid, &specConflict); + + /* other transaction commit index insertion before us, + * then abort the tuple and try to find the conflict tuple again + */ + if (specConflict) { + heap_abort_speculative(targetrel, tuple); + + list_free(recheckIndexes); + goto vlock; + } + + return newid; +} + + /* ---------------------------------------------------------------- * ExecInsert * @@ -307,9 +578,12 @@ TupleTableSlot* ExecInsertT(ModifyTableState* state, TupleTableSlot* slot, Tuple if (result_relation_desc->rd_rel->relhasoids) HeapTupleSetOid(tuple, InvalidOid); - /* BEFORE ROW INSERT Triggers */ - if (state->operation != CMD_MERGE && result_rel_info->ri_TrigDesc && - result_rel_info->ri_TrigDesc->trig_insert_before_row) { + /* BEFORE ROW INSERT Triggers + * Note: We fire BEFORE ROW TRIGGERS for every attempted insertion in an except + * for a MERGE or INSERT ... ON DUPLICATE KEY UPDATE statement. + */ + if (state->operation != CMD_MERGE && + result_rel_info->ri_TrigDesc && result_rel_info->ri_TrigDesc->trig_insert_before_row) { slot = ExecBRInsertTriggers(estate, result_rel_info, slot); if (slot == NULL) /* "do nothing" */ return NULL; @@ -318,9 +592,12 @@ TupleTableSlot* ExecInsertT(ModifyTableState* state, TupleTableSlot* slot, Tuple tuple = ExecMaterializeSlot(slot); } - /* INSTEAD OF ROW INSERT Triggers */ - if (state->operation != CMD_MERGE && result_rel_info->ri_TrigDesc && - result_rel_info->ri_TrigDesc->trig_insert_instead_row) { + /* INSTEAD OF ROW INSERT Triggers + * Note: We fire INSREAD OF ROW TRIGGERS for every attempted insertion except + * for a MERGE or INSERT ... ON DUPLICATE KEY UPDATE statement. + */ + if (state->operation != CMD_MERGE && + result_rel_info->ri_TrigDesc && result_rel_info->ri_TrigDesc->trig_insert_instead_row) { slot = ExecIRInsertTriggers(estate, result_rel_info, slot); if (slot == NULL) /* "do nothing" */ return NULL; @@ -449,6 +726,14 @@ TupleTableSlot* ExecInsertT(ModifyTableState* state, TupleTableSlot* slot, Tuple } ExecDropSingleTupleTableSlot(tmp_slot); + } else if (state->mt_upsert->us_action != UPSERT_NONE && result_rel_info->ri_NumIndices > 0) { + TupleTableSlot* returning = NULL; + bool updated = false; + new_id = InvalidOid; + new_id = ExecUpsert(state, slot, planSlot, estate, canSetTag, tuple, &returning, &updated); + if (updated) { + return returning; + } } else { /* * insert the tuple @@ -524,7 +809,7 @@ TupleTableSlot* ExecInsertT(ModifyTableState* state, TupleTableSlot* slot, Tuple estate, RELATION_IS_PARTITIONED(result_relation_desc) ? heap_rel : NULL, RELATION_IS_PARTITIONED(result_relation_desc) ? partition : NULL, - bucket_id); + bucket_id, NULL); } } @@ -539,7 +824,10 @@ TupleTableSlot* ExecInsertT(ModifyTableState* state, TupleTableSlot* slot, Tuple setLastTid(&(tuple->t_self)); } - /* AFTER ROW INSERT Triggers */ + /* AFTER ROW INSERT Triggers + * Note: We fire AFTER ROW TRIGGERS for every attempted insertion except + * for a MERGE or INSERT ... ON DUPLICATE KEY UPDATE statement. + */ if (state->operation != CMD_MERGE && !useHeapMultiInsert) ExecARInsertTriggers(estate, result_rel_info, partition_id, bucket_id, tuple, recheck_indexes); @@ -938,6 +1226,8 @@ TupleTableSlot* ExecUpdate(ItemPointer tupleid, #ifdef PGXC RemoteQueryState* result_remote_rel = NULL; #endif + bool allow_update_self = (node->mt_upsert != NULL && + node->mt_upsert->us_action != UPSERT_NONE) ? true : false; /* * abort the operation if not running transactions @@ -1049,7 +1339,6 @@ TupleTableSlot* ExecUpdate(ItemPointer tupleid, return NULL; } - /* FDW might have changed tuple */ tuple = ExecMaterializeSlot(slot); } else { @@ -1102,7 +1391,8 @@ TupleTableSlot* ExecUpdate(ItemPointer tupleid, &update_xmax, estate->es_output_cid, estate->es_crosscheck_snapshot, - true /* wait for commit */); + true /* wait for commit */, + allow_update_self); switch (result) { case HeapTupleSelfUpdated: /* can not update one row more than once for merge into */ @@ -1212,7 +1502,8 @@ TupleTableSlot* ExecUpdate(ItemPointer tupleid, * If it's a HOT update, we mustn't insert new index entries. */ if (result_rel_info->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple)) - recheck_indexes = ExecInsertIndexTuples(slot, &(tuple->t_self), estate, NULL, NULL, bucketid); + recheck_indexes = ExecInsertIndexTuples(slot, &(tuple->t_self), estate, + NULL, NULL, bucketid, NULL); } else { /* for partitioned table */ bool row_movement = false; @@ -1300,7 +1591,8 @@ TupleTableSlot* ExecUpdate(ItemPointer tupleid, &update_xmax, estate->es_output_cid, estate->es_crosscheck_snapshot, - true /* wait for commit */); + true /* wait for commit */, + allow_update_self); switch (result) { case HeapTupleSelfUpdated: /* can not update one row more than once for merge into */ @@ -1403,7 +1695,7 @@ TupleTableSlot* ExecUpdate(ItemPointer tupleid, * delete index entries for tuple */ recheck_indexes = ExecInsertIndexTuples(slot, &(tuple->t_self), estate, - fake_part_rel, partition, bucketid); + fake_part_rel, partition, bucketid, NULL); } } else { /* row movement */ @@ -1432,7 +1724,8 @@ TupleTableSlot* ExecUpdate(ItemPointer tupleid, &update_xmax, estate->es_output_cid, estate->es_crosscheck_snapshot, - true /* wait for commit */); + true /* wait for commit */, + allow_update_self); switch (result) { case HeapTupleSelfUpdated: /* can not update one row more than once for merge into */ @@ -1541,7 +1834,7 @@ TupleTableSlot* ExecUpdate(ItemPointer tupleid, if (result_rel_info->ri_NumIndices > 0) { recheck_indexes = ExecInsertIndexTuples( - slot, &(tuple->t_self), estate, fake_part_rel, insert_partition, bucketid); + slot, &(tuple->t_self), estate, fake_part_rel, insert_partition, bucketid, NULL); } } } @@ -1602,6 +1895,9 @@ static void fireBSTriggers(ModifyTableState* node) switch (node->operation) { case CMD_INSERT: ExecBSInsertTriggers(node->ps.state, node->resultRelInfo); + if (node->mt_upsert->us_action == UPSERT_UPDATE) { + ExecBSUpdateTriggers(node->ps.state, node->resultRelInfo); + } break; case CMD_UPDATE: ExecBSUpdateTriggers(node->ps.state, node->resultRelInfo); @@ -1629,6 +1925,9 @@ static void fireASTriggers(ModifyTableState* node) switch (node->operation) { case CMD_INSERT: ExecASInsertTriggers(node->ps.state, node->resultRelInfo); + if (node->mt_upsert->us_action == UPSERT_UPDATE) { + ExecASUpdateTriggers(node->ps.state, node->resultRelInfo); + } break; case CMD_UPDATE: ExecASUpdateTriggers(node->ps.state, node->resultRelInfo); @@ -1755,7 +2054,7 @@ TupleTableSlot* ExecModifyTable(ModifyTableState* node) #endif if (operation == CMD_INSERT) { - if (node->ps.type == T_ModifyTableState || + if (node->ps.type == T_ModifyTableState || node->mt_upsert->us_action != UPSERT_NONE || (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; @@ -2048,6 +2347,7 @@ ModifyTableState* ExecInitModifyTable(ModifyTable* node, EState* estate, int efl ResultRelInfo* result_rel_info = NULL; TupleDesc tup_desc = NULL; Plan* sub_plan = NULL; + UpsertState* upsertState = NULL; ListCell* l = NULL; int i; #ifdef PGXC @@ -2110,6 +2410,13 @@ ModifyTableState* ExecInitModifyTable(ModifyTable* node, EState* estate, int efl mt_state->mt_arowmarks = (List**)palloc0(sizeof(List*) * nplans); mt_state->mt_nplans = nplans; + upsertState = (UpsertState*)palloc0(sizeof(UpsertState)); + upsertState->us_action = node->upsertAction; + upsertState->us_existing = NULL; + upsertState->us_excludedtlist = NIL; + upsertState->us_updateproj = NULL; + mt_state->mt_upsert = upsertState; + /* set up epqstate with dummy sub_plan data for the moment */ EvalPlanQualInit(&mt_state->mt_epqstate, estate, NULL, NIL, node->epqParam); mt_state->fireBSTriggers = true; @@ -2160,7 +2467,7 @@ ModifyTableState* ExecInitModifyTable(ModifyTable* node, EState* estate, int efl result_rel_info->ri_IndexRelationDescs == NULL) { if (result_rel_info->ri_FdwRoutine == NULL || result_rel_info->ri_FdwRoutine->GetFdwType == NULL || result_rel_info->ri_FdwRoutine->GetFdwType() != MOT_ORC) - ExecOpenIndices(result_rel_info); + ExecOpenIndices(result_rel_info, node->upsertAction != UPSERT_NONE); } init_gtt_storage(operation, result_rel_info); /* Now init the plan for this result rel */ @@ -2281,6 +2588,43 @@ ModifyTableState* ExecInitModifyTable(ModifyTable* node, EState* estate, int efl mt_state->ps.ps_ExprContext = NULL; } + /* + * If needed, Initialize target list, projection and qual for DUPLICATE KEY UPDATE + */ + result_rel_info = mt_state->resultRelInfo; + if (node->upsertAction == UPSERT_UPDATE) { + ExprContext* econtext; + ExprState* setexpr; + TupleDesc tupDesc; + + /* insert may only have one plan, inheritance is not expanded */ + Assert(nplans = 1); + + /* already exists if created by RETURNING processing above */ + if (mt_state->ps.ps_ExprContext == NULL) { + ExecAssignExprContext(estate, &mt_state->ps); + } + + econtext = mt_state->ps.ps_ExprContext; + + /* initialize slot for the existing tuple */ + upsertState->us_existing = ExecInitExtraTupleSlot(mt_state->ps.state); + ExecSetSlotDescriptor(upsertState->us_existing, result_rel_info->ri_RelationDesc->rd_att); + + upsertState->us_excludedtlist = node->exclRelTlist; + + /* create target slot for UPDATE SET projection */ + tupDesc = ExecTypeFromTL((List*)node->updateTlist, result_rel_info->ri_RelationDesc->rd_rel->relhasoids); + upsertState->us_updateproj = ExecInitExtraTupleSlot(mt_state->ps.state); + ExecSetSlotDescriptor(upsertState->us_updateproj, tupDesc); + + /* build UPDATE SET expression and projection state */ + setexpr = ExecInitExpr((Expr*)node->updateTlist, &mt_state->ps); + result_rel_info->ri_updateProj = + ExecBuildProjectionInfo((List*)setexpr, econtext, + upsertState->us_updateproj, result_rel_info->ri_RelationDesc->rd_att); + } + /* * If we have any secondary relations in an UPDATE or DELETE, they need to * be treated like non-locked relations in SELECT FOR UPDATE, ie, the diff --git a/src/gausskernel/runtime/executor/opfusion.cpp b/src/gausskernel/runtime/executor/opfusion.cpp index 944cf8c3d..a89314f99 100644 --- a/src/gausskernel/runtime/executor/opfusion.cpp +++ b/src/gausskernel/runtime/executor/opfusion.cpp @@ -1078,7 +1078,7 @@ bool InsertFusion::execute(long max_rows, char* completionTag) m_estate->es_result_relation_info = result_rel_info; if (result_rel_info->ri_RelationDesc->rd_rel->relhasindex) { - ExecOpenIndices(result_rel_info); + ExecOpenIndices(result_rel_info, false); } CommandId mycid = GetCurrentCommandId(true); @@ -1112,7 +1112,7 @@ bool InsertFusion::execute(long max_rows, char* completionTag) /* insert index entries for tuple */ List* recheck_indexes = NIL; if (result_rel_info->ri_NumIndices > 0) { - recheck_indexes = ExecInsertIndexTuples(m_reslot, &(tuple->t_self), m_estate, NULL, NULL, bucketid); + recheck_indexes = ExecInsertIndexTuples(m_reslot, &(tuple->t_self), m_estate, NULL, NULL, bucketid, NULL); } list_free_ext(recheck_indexes); @@ -1391,7 +1391,7 @@ bool UpdateFusion::execute(long max_rows, char* completionTag) m_estate->es_output_cid = GetCurrentCommandId(true); if (result_rel_info->ri_RelationDesc->rd_rel->relhasindex) { - ExecOpenIndices(result_rel_info); + ExecOpenIndices(result_rel_info, false); } /********************************* @@ -1446,7 +1446,8 @@ bool UpdateFusion::execute(long max_rows, char* completionTag) /* done successfully */ nprocessed++; if (result_rel_info->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tup)) { - recheck_indexes = ExecInsertIndexTuples(m_reslot, &(tup->t_self), m_estate, NULL, NULL, bucketid); + recheck_indexes = ExecInsertIndexTuples(m_reslot, &(tup->t_self), m_estate, + NULL, NULL, bucketid, NULL); list_free_ext(recheck_indexes); } break; @@ -1577,7 +1578,7 @@ bool DeleteFusion::execute(long max_rows, char* completionTag) m_estate->es_output_cid = GetCurrentCommandId(true); if (result_rel_info->ri_RelationDesc->rd_rel->relhasindex) { - ExecOpenIndices(result_rel_info); + ExecOpenIndices(result_rel_info, false); } /******************************** @@ -1784,7 +1785,7 @@ bool SelectForUpdateFusion::execute(long max_rows, char* completionTag) m_estate->es_output_cid = GetCurrentCommandId(true); if (result_rel_info->ri_RelationDesc->rd_rel->relhasindex) { - ExecOpenIndices(result_rel_info); + ExecOpenIndices(result_rel_info, false); } /************************************** @@ -1853,6 +1854,10 @@ bool SelectForUpdateFusion::execute(long max_rows, char* completionTag) } switch (result) { + case HeapTupleSelfCreated: + ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("attempted to lock invisible tuple"))); + break; case HeapTupleSelfUpdated: /* already deleted by self; nothing to do */ break; diff --git a/src/gausskernel/runtime/executor/opfusion_util.cpp b/src/gausskernel/runtime/executor/opfusion_util.cpp index 7541bc5f7..f2be66e1a 100755 --- a/src/gausskernel/runtime/executor/opfusion_util.cpp +++ b/src/gausskernel/runtime/executor/opfusion_util.cpp @@ -208,6 +208,11 @@ const char *getBypassReason(FusionType result) break; } + case NOBYPASS_UPSERT_NOT_SUPPORT: { + return "Bypass not support INSERT INTO ... ON DUPLICATE KEY UPDATE statement"; + break; + } + default: { Assert(0); ereport(ERROR, @@ -757,6 +762,9 @@ FusionType getInsertFusionType(List *stmt_list, ParamListInfo params) if (base->plan.lefttree != NULL || base->plan.initPlan != NIL || base->resconstantqual != NULL) { return NOBYPASS_NO_SIMPLE_INSERT; } + if (node->upsertAction != UPSERT_NONE) { + return NOBYPASS_UPSERT_NOT_SUPPORT; + } /* check relation */ Index res_rel_idx = linitial_int(plannedstmt->resultRelations); diff --git a/src/gausskernel/storage/access/heap/heapam.cpp b/src/gausskernel/storage/access/heap/heapam.cpp index 4605999b7..e0d71c3a2 100644 --- a/src/gausskernel/storage/access/heap/heapam.cpp +++ b/src/gausskernel/storage/access/heap/heapam.cpp @@ -2547,7 +2547,7 @@ Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid, int options, Bu } xlrec.offnum = ItemPointerGetOffsetNumber(&heaptup->t_self); - xlrec.flags = all_visible_cleared ? XLOG_HEAP_ALL_VISIBLE_CLEARED : 0; + xlrec.flags = all_visible_cleared ? XLH_INSERT_ALL_VISIBLE_CLEARED : 0; Assert(ItemPointerGetBlockNumber(&heaptup->t_self) == BufferGetBlockNumber(buffer)); /* @@ -2556,7 +2556,7 @@ Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid, int options, Bu * image. (XXX We could alternatively store a pointer into the FPW). */ if (RelationIsLogicallyLogged(relation)) { - xlrec.flags |= XLOG_HEAP_CONTAINS_NEW_TUPLE; + xlrec.flags |= XLH_INSERT_CONTAINS_NEW_TUPLE; bufflags |= REGBUF_KEEP_DATA; } @@ -2605,6 +2605,7 @@ Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid, int options, Bu */ CacheInvalidateHeapTuple(relation, heaptup, NULL); + /* Note: speculative insertions are counted too, even if aborted later */ pgstat_count_heap_insert(relation, 1); /* @@ -2619,7 +2620,145 @@ Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid, int options, Bu return HeapTupleGetOid(tup); } -/** +/* + * heap_abort_speculative - kill a speculatively inserted tuple + * + * Marks a tuple that was speculatively inserted in the same command as dead, + * by setting its xmin as invalid. That makes it immediately appear as dead + * to all transactions, including our own. In particular, it makes + * HeapTupleSatisfiesDirty() regard the tuple as dead, so that another backend + * inserting a duplicate key value won't unnecessarily wait for our whole + * transaction to finish (it'll just wait for our speculative insertion to + * finish). + * + * Killing the tuple prevents "unprincipled deadlocks", which are deadlocks + * that arise due to a mutual dependency that is not user visible. By + * definition, unprincipled deadlocks cannot be prevented by the user + * reordering lock acquisition in client code, because the implementation level + * lock acquisitions are not under the user's direct control. If speculative + * inserters did not take this precaution, then under high concurrency they + * could deadlock with each other, which would not be acceptable. + * + * This is somewhat redundant with heap_delete, but we prefer to have a + * dedicated routine with stripped down requirements. + * + * This routine does not affect logical decoding as it only looks at + * confirmation records. + */ +void heap_abort_speculative(Relation relation, HeapTuple tuple) +{ + TransactionId xid = GetCurrentTransactionId(); + ItemPointer tid = &(tuple->t_self); + ItemId lp; + HeapTupleData tp; + Page page; + BlockNumber block; + Buffer buffer; + + Assert(ItemPointerIsValid(tid)); + + block = ItemPointerGetBlockNumber(tid); + buffer = ReadBuffer(relation, block); + page = BufferGetPage(buffer); + + LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); + + /* + * Page can't be all visible, we just inserted into it, and are still + * running. + */ + Assert(!PageIsAllVisible(page)); + + lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid)); + Assert(ItemIdIsNormal(lp)); + + tp.t_tableOid = RelationGetRelid(relation); + tp.t_data = (HeapTupleHeader) PageGetItem(page, lp); + tp.t_len = ItemIdGetLength(lp); + tp.t_self = *tid; + + /* + * Sanity check that the tuple really is a speculatively inserted tuple, + * inserted by us. + */ + if (HeapTupleHeaderGetXmin(page, tp.t_data) != xid) { + ereport(ERROR, + (errmsg("attempted to kill a tuple inserted by another transaction: %lu, %lu", + HeapTupleGetRawXmin(&tp), xid))); + } + Assert(!HeapTupleHeaderIsHeapOnly(tp.t_data)); + + /* + * No need to check for serializable conflicts here. There is never a + * need for a combocid, either. No need to extract replica identity, or + * do anything special with infomask bits. + */ + START_CRIT_SECTION(); + + /* + * The tuple will become DEAD immediately. Flag that this page + * immediately is a candidate for pruning by setting xmin to + * RecentGlobalXmin. That's not pretty, but it doesn't seem worth + * inventing a nicer API for this. + */ + PageSetPrunable(page, xid); + + /* store transaction information of xact deleting the tuple */ + tp.t_data->t_infomask &= ~(HEAP_XMAX_COMMITTED | HEAP_XMAX_INVALID | HEAP_XMAX_IS_MULTI | + HEAP_IS_LOCKED | HEAP_MOVED); + + /* + * Set the tuple header xmin to InvalidTransactionId. This makes the + * tuple immediately invisible everyone. (In particular, to any + * transactions waiting on the speculative token, woken up later.) + */ + HeapTupleHeaderSetXmin(page, tp.t_data, InvalidTransactionId); + + MarkBufferDirty(buffer); + + /* + * XLOG stuff + * + * The WAL records generated here match heap_delete(). The same recovery + * routines are used. + */ + if (RelationNeedsWAL(relation)) { + xl_heap_delete xlrec; + XLogRecPtr recptr; + + xlrec.flags = XLH_DELETE_IS_SUPER; + xlrec.offnum = ItemPointerGetOffsetNumber(&tp.t_self); + + XLogBeginInsert(); + XLogRegisterData((char *) &xlrec, SizeOfHeapDelete); + XLogRegisterBuffer(0, buffer, REGBUF_STANDARD); + + /* No replica identity & replication origin logged */ + recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_DELETE); + + PageSetLSN(page, recptr); + } + + END_CRIT_SECTION(); + + LockBuffer(buffer, BUFFER_LOCK_UNLOCK); + + if (HeapTupleHasExternal(&tp)) + toast_delete(relation, &tp, HEAP_INSERT_SPECULATIVE); + + /* + * Never need to mark tuple for invalidation, since catalogs don't support + * speculative insertion + */ + + /* Now we can release the buffer */ + ReleaseBuffer(buffer); + + /* count deletion, as we counted the insertion too */ + pgstat_count_heap_delete(relation); +} + +/* * @Description: Find minimum and maximum short transaction ids which occurs in the page. * @in: page, heap page * @in: multi, Whether multixact @@ -3422,7 +3561,7 @@ int heap_multi_insert(Relation relation, Relation parent, HeapTuple* tuples, int /* the rest of the scratch space is used for tuple data */ tuple_data = scratchptr; - xlrec->flags = all_visible_cleared ? XLOG_HEAP_ALL_VISIBLE_CLEARED : 0; + xlrec->flags = all_visible_cleared ? XLH_INSERT_ALL_VISIBLE_CLEARED : 0; xlrec->ntuples = nthispage; /* xlog: write the dictionary between header and tuples */ @@ -3472,7 +3611,7 @@ int heap_multi_insert(Relation relation, Relation parent, HeapTuple* tuples, int Assert((scratchptr - scratch) < BLCKSZ); if (need_tuple_data) { - xlrec->flags |= XLOG_HEAP_CONTAINS_NEW_TUPLE; + xlrec->flags |= XLH_INSERT_CONTAINS_NEW_TUPLE; } /* @@ -3481,7 +3620,7 @@ int heap_multi_insert(Relation relation, Relation parent, HeapTuple* tuples, int * decoding so it knows when to cleanup temporary data. */ if (ndone + nthispage == ntuples) { - xlrec->flags |= XLOG_HEAP_LAST_MULTI_INSERT; + xlrec->flags |= XLH_INSERT_LAST_IN_MULTI; } /* @@ -3620,7 +3759,7 @@ Oid simple_heap_insert(Relation relation, HeapTuple tup) * (t_xmax is needed to verify that the replacement tuple matches.) */ HTSU_Result heap_delete(Relation relation, ItemPointer tid, ItemPointer ctid, TransactionId* update_xmax, CommandId cid, - Snapshot crosscheck, bool wait) + Snapshot crosscheck, bool wait, bool allow_delete_self) { HTSU_Result result; TransactionId xid = GetCurrentTransactionId(); @@ -3694,11 +3833,16 @@ HTSU_Result heap_delete(Relation relation, ItemPointer tid, ItemPointer ctid, Tr HeapTupleCopyBaseFromPage(&tp, page); l1: - result = HeapTupleSatisfiesUpdate(&tp, cid, buffer); + result = HeapTupleSatisfiesUpdate(&tp, cid, buffer, allow_delete_self); if (result == HeapTupleInvisible) { UnlockReleaseBuffer(buffer); ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("attempted to delete invisible tuple"))); + } else if (result == HeapTupleSelfCreated) { + UnlockReleaseBuffer(buffer); + /* if allow self delete, HeapTupleSelfCreated status will never be reached */ + Assert(!allow_delete_self); + ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("attempted to delete self created tuple"))); } else if (result == HeapTupleBeingUpdated && wait) { TransactionId xwait; uint16 infomask; @@ -3866,7 +4010,7 @@ l1: (void)log_heap_new_cid(relation, &tp); } - xlrec.flags = all_visible_cleared ? XLOG_HEAP_ALL_VISIBLE_CLEARED : 0; + xlrec.flags = all_visible_cleared ? XLH_DELETE_ALL_VISIBLE_CLEARED : 0; xlrec.offnum = ItemPointerGetOffsetNumber(&tp.t_self); if (old_key_tuple != NULL) { @@ -3890,9 +4034,9 @@ l1: } if (relreplident == REPLICA_IDENTITY_FULL) { - xlrec.flags |= XLOG_HEAP_CONTAINS_OLD_TUPLE; + xlrec.flags |= XLH_DELETE_CONTAINS_OLD_TUPLE; } else { - xlrec.flags |= XLOG_HEAP_CONTAINS_OLD_KEY; + xlrec.flags |= XLH_DELETE_CONTAINS_OLD_KEY; } } @@ -3940,7 +4084,7 @@ l1: /* toast table entries should never be recursively toasted */ Assert(!HeapTupleHasExternal(&tp)); } else if (HeapTupleHasExternal(&tp)) - toast_delete(relation, &tp); + toast_delete(relation, &tp, allow_delete_self ? HEAP_INSERT_SPECULATIVE : 0); /* * Mark tuple for invalidation from system caches at next command @@ -3976,11 +4120,12 @@ l1: * on the relation associated with the tuple). Any failure is reported * via ereport(). */ -void simple_heap_delete(Relation relation, ItemPointer tid) +void simple_heap_delete(Relation relation, ItemPointer tid, int options) { HTSU_Result result; ItemPointerData update_ctid; TransactionId update_xmax; + bool allow_delete_self = (options & HEAP_INSERT_SPECULATIVE) ? true : false; result = heap_delete(relation, tid, @@ -3988,7 +4133,8 @@ void simple_heap_delete(Relation relation, ItemPointer tid) &update_xmax, GetCurrentCommandId(true), InvalidSnapshot, - true /* wait for commit */); + true /* wait for commit */, + allow_delete_self); switch (result) { case HeapTupleSelfUpdated: /* Tuple was already updated in current command? */ @@ -4042,8 +4188,9 @@ void simple_heap_delete(Relation relation, ItemPointer tid) * tuple was updated, and t_ctid is the location of the replacement tuple. * (t_xmax is needed to verify that the replacement tuple matches.) */ -HTSU_Result heap_update(Relation relation, Relation parentRelation, ItemPointer otid, HeapTuple newtup, - ItemPointer ctid, TransactionId* update_xmax, CommandId cid, Snapshot crosscheck, bool wait) +HTSU_Result heap_update(Relation relation, Relation parentRelation, ItemPointer otid, + HeapTuple newtup, ItemPointer ctid, TransactionId* update_xmax, CommandId cid, + Snapshot crosscheck, bool wait, bool allow_update_self) { HTSU_Result result; TransactionId xid = GetCurrentTransactionId(); @@ -4144,10 +4291,15 @@ HTSU_Result heap_update(Relation relation, Relation parentRelation, ItemPointer l2: HeapTupleCopyBaseFromPage(&oldtup, BufferGetPage(buffer)); - result = HeapTupleSatisfiesUpdate(&oldtup, cid, buffer); + result = HeapTupleSatisfiesUpdate(&oldtup, cid, buffer, allow_update_self); if (result == HeapTupleInvisible) { UnlockReleaseBuffer(buffer); ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("attempted to update invisible tuple"))); + } else if (result == HeapTupleSelfCreated) { + UnlockReleaseBuffer(buffer); + /* if allow self update, HeapTupleSelfCreated status will never be reached */ + Assert(!allow_update_self); + ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("attempted to update self created tuple"))); } else if (result == HeapTupleBeingUpdated && wait) { TransactionId xwait; uint16 infomask; @@ -4386,7 +4538,8 @@ l2: */ if (need_toast) { /* Note we always use WAL and FSM during updates */ - heaptup = toast_insert_or_update(relation, newtup, &oldtup, 0, page); + heaptup = toast_insert_or_update(relation, newtup, &oldtup, + allow_update_self ? HEAP_INSERT_SPECULATIVE : 0, page); new_tup_size = MAXALIGN(heaptup->t_len); } else { heaptup = newtup; @@ -5064,7 +5217,7 @@ void simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup) * conflict for a tuple, we don't incur any extra overhead. */ HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple, Buffer* buffer, ItemPointer ctid, - TransactionId* update_xmax, CommandId cid, LockTupleMode mode, bool nowait) + TransactionId* update_xmax, CommandId cid, LockTupleMode mode, bool nowait, bool allow_lock_self) { HTSU_Result result; ItemPointer tid = &(tuple->t_self); @@ -5122,7 +5275,7 @@ HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple, Buffer* buffer, l3: HeapTupleCopyBaseFromPage(tuple, page); - result = HeapTupleSatisfiesUpdate(tuple, cid, *buffer); + result = HeapTupleSatisfiesUpdate(tuple, cid, *buffer, allow_lock_self); ereport(DEBUG1, (errmsg("heap lock tuple ctid (%u,%d) cur_xid %lu xmin " "%lu xmax %lu infomask %hu result %d", @@ -5137,6 +5290,25 @@ l3: if (result == HeapTupleInvisible) { UnlockReleaseBuffer(*buffer); ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("attempted to lock invisible tuple"))); + } else if (result == HeapTupleSelfCreated) { + /* + * This is possible when the tuple is going to be updated twice in one command, + * which should be considered as invisible (same with HeapTupleInvisible) and + * throw an error. + * + * However, there is a special case: UPSERT multiple VALUES using a STREAM plan + * e.g INSERT values(1,x),(1,x) ON DUPLICATE KEY UPDATE.. + * As we have to allow this case to be done in MYSQL compatibility, + * we return HeapTupleSelfCreated here rather than throwing an error in + * order to give UPSERT case the opportunity to throw a more specific error or + * allow to UPSERT + * + * NOTE: multiple VALUES UPSERT using a PGXC plan is not a problem because + * the optimizer will spilt the query into multiple commands, each of which only + * UPSERT one VALUES(). + */ + LockBuffer(*buffer, BUFFER_LOCK_UNLOCK); + return HeapTupleSelfCreated; } else if (result == HeapTupleBeingUpdated) { TransactionId xwait; uint16 infomask; @@ -6040,18 +6212,18 @@ static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf, const ItemPointe xlrec.new_offnum = ItemPointerGetOffsetNumber(&newtup->t_self); xlrec.flags = 0; if (all_visible_cleared) { - xlrec.flags |= XLOG_HEAP_ALL_VISIBLE_CLEARED; + xlrec.flags |= XLH_UPDATE_OLD_ALL_VISIBLE_CLEARED; } if (new_all_visible_cleared) { - xlrec.flags |= XLOG_HEAP_NEW_ALL_VISIBLE_CLEARED; + xlrec.flags |= XLH_UPDATE_NEW_ALL_VISIBLE_CLEARED; } if (need_tuple_data) { - xlrec.flags |= XLOG_HEAP_CONTAINS_NEW_TUPLE; + xlrec.flags |= XLH_UPDATE_CONTAINS_NEW_TUPLE; if (old_key_tuple) { if (reln->rd_rel->relreplident == REPLICA_IDENTITY_FULL) - xlrec.flags |= XLOG_HEAP_CONTAINS_OLD_TUPLE; + xlrec.flags |= XLH_UPDATE_CONTAINS_OLD_TUPLE; else - xlrec.flags |= XLOG_HEAP_CONTAINS_OLD_KEY; + xlrec.flags |= XLH_UPDATE_CONTAINS_OLD_KEY; } } if (need_tuple_data) { @@ -6717,7 +6889,7 @@ static void heap_xlog_delete(XLogReaderState* record) * The visibility map may need to be fixed even if the heap page is * already up-to-date. */ - if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED) { + if (xlrec->flags & XLH_DELETE_ALL_VISIBLE_CLEARED) { RelFileNode target_node; BlockNumber blkno; @@ -6759,7 +6931,7 @@ static void heap_xlog_insert(XLogReaderState* record) * The visibility map may need to be fixed even if the heap page is * already up-to-date. */ - if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED) { + if (xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED) { heap_xlog_allvisiblecleared(target_node, blkno); } @@ -6832,7 +7004,7 @@ static void heap_xlog_multi_insert(XLogReaderState* record) * The visibility map may need to be fixed even if the heap page is * already up-to-date. */ - if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED) { + if (xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED) { heap_xlog_allvisiblecleared(rnode, blkno); } @@ -6902,12 +7074,11 @@ static void heap_xlog_update(XLogReaderState* record, bool hot_update) oldblk = newblk; } - /* * The visibility map may need to be fixed even if the heap page is * already up-to-date. */ - if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED) { + if (xlrec->flags & XLH_UPDATE_OLD_ALL_VISIBLE_CLEARED) { heap_xlog_allvisiblecleared(rnode, oldblk); } @@ -6949,7 +7120,7 @@ static void heap_xlog_update(XLogReaderState* record, bool hot_update) * The visibility map may need to be fixed even if the heap page is * already up-to-date. */ - if (xlrec->flags & XLOG_HEAP_NEW_ALL_VISIBLE_CLEARED) { + if (xlrec->flags & XLH_UPDATE_NEW_ALL_VISIBLE_CLEARED) { heap_xlog_allvisiblecleared(rnode, newblk); } diff --git a/src/gausskernel/storage/access/heap/tuptoaster.cpp b/src/gausskernel/storage/access/heap/tuptoaster.cpp index c1a5d5a83..a07bac7dd 100644 --- a/src/gausskernel/storage/access/heap/tuptoaster.cpp +++ b/src/gausskernel/storage/access/heap/tuptoaster.cpp @@ -45,7 +45,7 @@ #undef TOAST_DEBUG -static void toast_delete_datum(Relation rel, Datum value); +static void toast_delete_datum(Relation rel, Datum value, int options); static Datum toast_save_datum(Relation rel, Datum value, struct varlena* oldexternal, int options); static bool toastid_valueid_exists(Oid toastrelid, Oid valueid, int2 bucketid); static struct varlena* toast_fetch_datum(struct varlena* attr); @@ -332,7 +332,7 @@ Size toast_datum_size(Datum value) * Cascaded delete toast-entries on DELETE * ---------- */ -void toast_delete(Relation rel, HeapTuple oldtup) +void toast_delete(Relation rel, HeapTuple oldtup, int options) { TupleDesc tuple_desc; Form_pg_attribute* att = NULL; @@ -381,7 +381,7 @@ void toast_delete(Relation rel, HeapTuple oldtup) if (toast_isnull[i]) continue; else if (VARATT_IS_EXTERNAL_ONDISK_B(PointerGetDatum(value))) - toast_delete_datum(rel, value); + toast_delete_datum(rel, value, options); else if (VARATT_IS_EXTERNAL_INDIRECT(PointerGetDatum(value))) ereport(ERROR, (errcode(ERRCODE_FETCH_DATA_FAILED), errmsg("attempt to delete tuple containing indirect datums"))); @@ -931,7 +931,7 @@ HeapTuple toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtu if (need_delold) { for (i = 0; i < num_attrs; i++) { if (toast_delold[i]) { - toast_delete_datum(rel, toast_oldvalues[i]); + toast_delete_datum(rel, toast_oldvalues[i], options); } } } @@ -1461,7 +1461,7 @@ static Datum toast_save_datum(Relation rel, Datum value, struct varlena* oldexte * Delete a single external stored value. * ---------- */ -static void toast_delete_datum(Relation rel, Datum value) +static void toast_delete_datum(Relation rel, Datum value, int options) { struct varlena* attr = (struct varlena*)DatumGetPointer(value); struct varatt_external toast_pointer; @@ -1501,7 +1501,7 @@ static void toast_delete_datum(Relation rel, Datum value) /* * Have a chunk, delete it */ - simple_heap_delete(toastrel, &toasttup->t_self); + simple_heap_delete(toastrel, &toasttup->t_self, options); if (u_sess->attr.attr_storage.enable_debug_vacuum) elogVacuumInfo(toastrel, toasttup, "toast_delete_datum", u_sess->cmd_cxt.OldestXmin); diff --git a/src/gausskernel/storage/access/redo/heapam.cpp b/src/gausskernel/storage/access/redo/heapam.cpp index 42fc6d1bd..0388973cd 100644 --- a/src/gausskernel/storage/access/redo/heapam.cpp +++ b/src/gausskernel/storage/access/redo/heapam.cpp @@ -367,7 +367,7 @@ void heap_xlog_delete_operator_page(RedoBufferInfo* buffer, void* recorddata, Tr /* Mark the page as a candidate for pruning */ PageSetPrunable(page, recordxid); - if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED) + if (xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED) PageClearAllVisible(page); /* Make sure there is no forward chain link in t_ctid */ @@ -445,7 +445,7 @@ void heap_xlog_insert_operator_page(RedoBufferInfo* buffer, void* recorddata, bo PageSetLSN(page, buffer->lsn); - if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED) + if (xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED) PageClearAllVisible(page); } @@ -548,7 +548,7 @@ void heap_xlog_multi_insert_operator_page(RedoBufferInfo* buffer, void* recoredd PageSetLSN(page, buffer->lsn); - if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED) + if (xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED) PageClearAllVisible(page); } @@ -592,7 +592,7 @@ void heap_xlog_update_operator_oldpage(RedoBufferInfo* buffer, void* recoreddata /* Mark the page as a candidate for pruning */ PageSetPrunable(page, recordxid); - if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED) + if (xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED) PageClearAllVisible(page); PageHeader oldPhdr = (PageHeader)page; @@ -708,7 +708,7 @@ void heap_xlog_update_operator_newpage(RedoBufferInfo* buffer, void* recorddata, if (PageAddItem(page, (Item)htup, newlen, xlrec->new_offnum, true, true) == InvalidOffsetNumber) ereport(PANIC, (errmsg("heap_update_redo: failed to add tuple"))); - if (xlrec->flags & XLOG_HEAP_NEW_ALL_VISIBLE_CLEARED) + if (xlrec->flags & XLH_UPDATE_NEW_ALL_VISIBLE_CLEARED) PageClearAllVisible(page); if (freespace != NULL) { *freespace = PageGetHeapFreeSpace(page); @@ -889,7 +889,7 @@ static XLogRecParseState* heap_xlog_insert_parse_block(XLogReaderState* record, } xlrec = (xl_heap_insert*)rec_data; - if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED) { + if (xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED) { (*blocknum)++; XLogParseBufferAllocListFunc(record, &blockstate, recordstatehead); if (blockstate == NULL) { @@ -915,7 +915,7 @@ static XLogRecParseState* heap_xlog_delete_parse_block(XLogReaderState* record, XLogRecSetBlockDataState(record, HEAP_DELETE_ORIG_BLOCK_NUM, recordstatehead); - if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED) { + if (xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED) { (*blocknum)++; XLogParseBufferAllocListFunc(record, &blockstate, recordstatehead); if (blockstate == NULL) { @@ -973,7 +973,7 @@ static XLogRecParseState* heap_xlog_update_parse_block(XLogReaderState* record, XLogRecSetAuxiBlkNumState(&blockstate->blockparse.extra_rec.blockdatarec, newblk, InvalidForkNumber); // OLD BLOCK - if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED) { + if (xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED) { (*blocknum)++; XLogParseBufferAllocListFunc(record, &blockstate, recordstatehead); if (blockstate == NULL) { @@ -985,8 +985,8 @@ static XLogRecParseState* heap_xlog_update_parse_block(XLogReaderState* record, } } - if ((xlrec->flags & XLOG_HEAP_NEW_ALL_VISIBLE_CLEARED) || - ((oldblk == newblk) && (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED))) { + if ((xlrec->flags & XLH_UPDATE_NEW_ALL_VISIBLE_CLEARED) || + ((oldblk == newblk) && (xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED))) { (*blocknum)++; XLogParseBufferAllocListFunc(record, &blockstate, recordstatehead); if (blockstate == NULL) { @@ -1279,7 +1279,7 @@ static XLogRecParseState* heap_xlog_multi_insert_parse_block(XLogReaderState* re } xlrec = (xl_heap_multi_insert*)rec_data; - if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED) { + if (xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED) { (*blocknum)++; XLogParseBufferAllocListFunc(record, &blockstate, recordstatehead); if (blockstate == NULL) { diff --git a/src/gausskernel/storage/access/transam/cbmparsexlog.cpp b/src/gausskernel/storage/access/transam/cbmparsexlog.cpp index 34271c7fd..aae05690e 100755 --- a/src/gausskernel/storage/access/transam/cbmparsexlog.cpp +++ b/src/gausskernel/storage/access/transam/cbmparsexlog.cpp @@ -1214,7 +1214,7 @@ static void TrackVMPageModification(XLogReaderState* record) recData += sizeof(TransactionId); xl_heap_insert* xlrec = (xl_heap_insert*)recData; - if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED) + if (xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED) (void)XLogRecGetBlockTag(record, 0, &rNode, NULL, &heapBlkNo1); break; @@ -1222,7 +1222,7 @@ static void TrackVMPageModification(XLogReaderState* record) case XLOG_HEAP_DELETE: { xl_heap_delete* xlrec = (xl_heap_delete*)recData; - if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED) + if (xlrec->flags & XLH_DELETE_ALL_VISIBLE_CLEARED) (void)XLogRecGetBlockTag(record, 0, &rNode, NULL, &heapBlkNo1); break; @@ -1232,10 +1232,10 @@ static void TrackVMPageModification(XLogReaderState* record) recData += sizeof(TransactionId); xl_heap_update* xlrec = (xl_heap_update*)recData; - if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED) + if (xlrec->flags & XLH_UPDATE_OLD_ALL_VISIBLE_CLEARED) (void)XLogRecGetBlockTag(record, 1, &rNode, NULL, &heapBlkNo1); - if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED) + if (xlrec->flags & XLH_UPDATE_OLD_ALL_VISIBLE_CLEARED) (void)XLogRecGetBlockTag(record, 0, &rNode, NULL, &heapBlkNo2); break; @@ -1249,7 +1249,7 @@ static void TrackVMPageModification(XLogReaderState* record) recData += sizeof(TransactionId); xl_heap_multi_insert* xlrec = (xl_heap_multi_insert*)recData; - if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED) + if (xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED) (void)XLogRecGetBlockTag(record, 0, &rNode, NULL, &heapBlkNo1); } } else diff --git a/src/gausskernel/storage/replication/logical/decode.cpp b/src/gausskernel/storage/replication/logical/decode.cpp index 7f8fceede..fe8889e8d 100644 --- a/src/gausskernel/storage/replication/logical/decode.cpp +++ b/src/gausskernel/storage/replication/logical/decode.cpp @@ -675,7 +675,7 @@ static void DecodeInsert(LogicalDecodingContext* ctx, XLogRecordBuffer* buf) rc = memcpy_s(&change->data.tp.relnode, sizeof(RelFileNode), &target_node, sizeof(RelFileNode)); securec_check(rc, "\0", "\0"); - if (xlrec->flags & XLOG_HEAP_CONTAINS_NEW_TUPLE) { + if (xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE) { change->data.tp.newtuple = ReorderBufferGetTupleBuf(ctx->reorder, tuplelen); DecodeXLogTuple(tupledata, tuplelen, change->data.tp.newtuple); @@ -741,11 +741,11 @@ static void DecodeUpdate(LogicalDecodingContext* ctx, XLogRecordBuffer* buf) change->origin_id = XLogRecGetOrigin(r); rc = memcpy_s(&change->data.tp.relnode, sizeof(RelFileNode), &target_node, sizeof(RelFileNode)); securec_check(rc, "", ""); - if (xlrec->flags & XLOG_HEAP_CONTAINS_NEW_TUPLE) { + if (xlrec->flags & XLH_UPDATE_CONTAINS_NEW_TUPLE) { change->data.tp.newtuple = ReorderBufferGetTupleBuf(ctx->reorder, tuplelen_new); DecodeXLogTuple(data_new, datalen_new, change->data.tp.newtuple); } - if (xlrec->flags & XLOG_HEAP_CONTAINS_OLD) { + if (xlrec->flags & XLH_UPDATE_CONTAINS_OLD) { change->data.tp.oldtuple = ReorderBufferGetTupleBuf(ctx->reorder, tuplelen_old); DecodeXLogTuple(data_old, datalen_old, change->data.tp.oldtuple); @@ -790,7 +790,7 @@ static void DecodeDelete(LogicalDecodingContext* ctx, XLogRecordBuffer* buf) securec_check(rc, "", ""); /* old primary key stored */ - if (xlrec->flags & XLOG_HEAP_CONTAINS_OLD) { + if (xlrec->flags & XLH_DELETE_CONTAINS_OLD) { Assert(XLogRecGetDataLen(r) > (SizeOfHeapDelete + SizeOfHeapHeader)); change->data.tp.oldtuple = ReorderBufferGetTupleBuf(ctx->reorder, datalen); @@ -848,7 +848,7 @@ static void DecodeMultiInsert(LogicalDecodingContext* ctx, XLogRecordBuffer* buf * We decode the tuple in pretty much the same way as DecodeXLogTuple, * but since the layout is slightly different, we can't use it here. */ - if (xlrec->flags & XLOG_HEAP_CONTAINS_NEW_TUPLE) { + if (xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE) { HeapTupleHeader header; xlhdr = (xl_multi_insert_tuple*)data; data = ((char*)xlhdr) + SizeOfMultiInsertTuple; @@ -889,7 +889,7 @@ static void DecodeMultiInsert(LogicalDecodingContext* ctx, XLogRecordBuffer* buf * xl_multi_insert_tuple record emitted by one heap_multi_insert() * call. */ - if ((xlrec->flags & XLOG_HEAP_LAST_MULTI_INSERT) && ((i + 1) == xlrec->ntuples)) { + if ((xlrec->flags & XLH_INSERT_LAST_IN_MULTI) && ((i + 1) == xlrec->ntuples)) { change->data.tp.clear_toast_afterwards = true; } else { change->data.tp.clear_toast_afterwards = false; diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 6f2f765e4..86b9bc49b 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -119,6 +119,7 @@ extern BulkInsertState GetBulkInsertState(void); extern void FreeBulkInsertState(BulkInsertState); extern Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid, int options, BulkInsertState bistate); +extern void heap_abort_speculative(Relation relation, HeapTuple tuple); extern bool heap_page_prepare_for_xid( Relation relation, Buffer buffer, TransactionId xid, bool multi, bool pageReplication = false); extern bool heap_change_xidbase_after_freeze(Relation relation, Buffer buffer); @@ -127,18 +128,19 @@ extern bool rewrite_page_prepare_for_xid(Page page, TransactionId xid, bool mult extern int heap_multi_insert(Relation relation, Relation parent, HeapTuple* tuples, int ntuples, CommandId cid, int options, BulkInsertState bistate, HeapMultiInsertExtraArgs* args); extern HTSU_Result heap_delete(Relation relation, ItemPointer tid, ItemPointer ctid, TransactionId* update_xmax, - CommandId cid, Snapshot crosscheck, bool wait); + CommandId cid, Snapshot crosscheck, bool wait, bool allow_delete_self = false); extern HTSU_Result heap_update(Relation relation, Relation parentRelation, ItemPointer otid, HeapTuple newtup, - ItemPointer ctid, TransactionId* update_xmax, CommandId cid, Snapshot crosscheck, bool wait); + ItemPointer ctid, TransactionId* update_xmax, CommandId cid, Snapshot crosscheck, + bool wait, bool allow_update_self = false); extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple, Buffer* buffer, ItemPointer ctid, - TransactionId* update_xmax, CommandId cid, LockTupleMode mode, bool nowait); + TransactionId* update_xmax, CommandId cid, LockTupleMode mode, bool nowait, bool allow_lock_self = false); extern void heap_inplace_update(Relation relation, HeapTuple tuple); extern bool heap_freeze_tuple(HeapTuple tuple, TransactionId cutoff_xid); extern bool heap_tuple_needs_freeze(HeapTuple tuple, TransactionId cutoff_xid, Buffer buf); extern Oid simple_heap_insert(Relation relation, HeapTuple tup); -extern void simple_heap_delete(Relation relation, ItemPointer tid); +extern void simple_heap_delete(Relation relation, ItemPointer tid, int options = 0); extern void simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup); extern void heap_markpos(HeapScanDesc scan); diff --git a/src/include/access/htup.h b/src/include/access/htup.h index 9083b26a9..fc8bf9a78 100755 --- a/src/include/access/htup.h +++ b/src/include/access/htup.h @@ -691,23 +691,47 @@ static inline TransactionId HeapTupleGetRawXmax(HeapTuple tup) #define XLOG_HEAP3_NEW_CID 0x00 #define XLOG_HEAP3_REWRITE 0x10 +/* we used to put all xl_heap_* together, which made us run out of opcodes (quickly) + * when trying to add a DELETE_IS_SUPER operation. Thus we split the codes carefully + * for INSERT, UPDATE, DELETE individually. each has 8 bits available to use. + */ /* - * xl_heap_* ->flag values, 8 bits are available + * xl_heap_insert/xl_heap_multi_insert flag values, 8 bits are available */ /* PD_ALL_VISIBLE was cleared */ -#define XLOG_HEAP_ALL_VISIBLE_CLEARED (1 << 0) +#define XLH_INSERT_ALL_VISIBLE_CLEARED (1<<0) +#define XLH_INSERT_CONTAINS_NEW_TUPLE (1<<4) +#define XLH_INSERT_LAST_IN_MULTI (1<<7) + +/* + * xl_heap_update flag values, 8 bits are available. +*/ +/* PD_ALL_VISIBLE was cleared */ +#define XLH_UPDATE_OLD_ALL_VISIBLE_CLEARED (1<<0) /* PD_ALL_VISIBLE was cleared in the 2nd page */ -#define XLOG_HEAP_NEW_ALL_VISIBLE_CLEARED (1 << 1) -#define XLOG_HEAP_CONTAINS_OLD_TUPLE (1 << 2) -#define XLOG_HEAP_CONTAINS_OLD_KEY (1 << 3) -#define XLOG_HEAP_CONTAINS_NEW_TUPLE (1 << 4) -#define XLOG_HEAP_PREFIX_FROM_OLD (1 << 5) -#define XLOG_HEAP_SUFFIX_FROM_OLD (1 << 6) -/* last xl_heap_multi_insert record for one heap_multi_insert() call */ -#define XLOG_HEAP_LAST_MULTI_INSERT (1 << 7) +#define XLH_UPDATE_NEW_ALL_VISIBLE_CLEARED (1<<1) +#define XLH_UPDATE_CONTAINS_OLD_TUPLE (1<<2) +#define XLH_UPDATE_CONTAINS_OLD_KEY (1<<3) +#define XLH_UPDATE_CONTAINS_NEW_TUPLE (1<<4) +#define XLH_UPDATE_PREFIX_FROM_OLD (1<<5) +#define XLH_UPDATE_SUFFIX_FROM_OLD (1<<6) /* convenience macro for checking whether any form of old tuple was logged */ -#define XLOG_HEAP_CONTAINS_OLD (XLOG_HEAP_CONTAINS_OLD_TUPLE | XLOG_HEAP_CONTAINS_OLD_KEY) +#define XLH_UPDATE_CONTAINS_OLD \ + (XLH_UPDATE_CONTAINS_OLD_TUPLE | XLH_UPDATE_CONTAINS_OLD_KEY) + +/* +* xl_heap_delete flag values, 8 bits are available. +*/ +/* PD_ALL_VISIBLE was cleared */ +#define XLH_DELETE_ALL_VISIBLE_CLEARED (1<<0) +#define XLH_DELETE_IS_SUPER (1<<1) +#define XLH_DELETE_CONTAINS_OLD_TUPLE (1<<2) +#define XLH_DELETE_CONTAINS_OLD_KEY (1<<3) + +/* convenience macro for checking whether any form of old tuple was logged */ +#define XLH_DELETE_CONTAINS_OLD \ + (XLH_DELETE_CONTAINS_OLD_TUPLE | XLH_DELETE_CONTAINS_OLD_KEY) /* This is what we need to know about delete */ typedef struct xl_heap_delete { diff --git a/src/include/access/tuptoaster.h b/src/include/access/tuptoaster.h index b6c4c745b..9a8edca2c 100644 --- a/src/include/access/tuptoaster.h +++ b/src/include/access/tuptoaster.h @@ -165,7 +165,7 @@ extern HeapTuple toast_insert_or_update( * Called by heap_delete(). * ---------- */ -extern void toast_delete(Relation rel, HeapTuple oldtup); +extern void toast_delete(Relation rel, HeapTuple oldtup, int options); /* ---------- * heap_tuple_fetch_attr() - diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h index c4de4f8b4..1ed6517a0 100755 --- a/src/include/catalog/index.h +++ b/src/include/catalog/index.h @@ -48,11 +48,15 @@ typedef enum #define REINDEX_CGIN_INDEX (1<<5) #define REINDEX_ALL_INDEX (REINDEX_BTREE_INDEX|REINDEX_HASH_INDEX|REINDEX_GIN_INDEX|REINDEX_GIST_INDEX|REINDEX_CGIN_INDEX) +typedef enum CheckWaitMode +{ + CHECK_WAIT, + CHECK_NOWAIT, +} CheckWaitMode; extern void index_check_primary_key(Relation heapRel, IndexInfo *indexInfo, bool is_alter_table); -typedef struct -{ +typedef struct { Oid existingPSortOid; bool isPartitionedIndex; } IndexCreateExtraArgs; @@ -74,6 +78,8 @@ extern void index_drop(Oid indexId, bool concurrent); extern IndexInfo *BuildIndexInfo(Relation index); extern IndexInfo *BuildDummyIndexInfo(Relation index); +extern void BuildSpeculativeIndexInfo(Relation index, IndexInfo* ii); + extern void FormIndexDatum(IndexInfo *indexInfo, TupleTableSlot *slot, EState *estate, Datum *values, bool *isnull); extern void index_build(Relation heapRelation, Partition heapPartition, Relation indexRelation, Partition indexPartition, IndexInfo *indexInfo, bool isprimary, diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index d3d16347c..e329385e0 100755 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -367,13 +367,15 @@ extern void ExecCloseScanRelation(Relation scanrel); extern Partition ExecOpenScanParitition( EState* estate, Relation parent, PartitionIdentifier* partID, LOCKMODE lockmode); -extern void ExecOpenIndices(ResultRelInfo* resultRelInfo); +extern void ExecOpenIndices(ResultRelInfo* resultRelInfo, bool speculative); extern void ExecCloseIndices(ResultRelInfo* resultRelInfo); extern List* ExecInsertIndexTuples( - TupleTableSlot* slot, ItemPointer tupleid, EState* estate, Relation targetPartRel, Partition p, int2 bucketId); + TupleTableSlot* slot, ItemPointer tupleid, EState* estate, Relation targetPartRel, + Partition p, int2 bucketId, bool* conflict); +extern bool ExecCheckIndexConstraints(TupleTableSlot* slot, EState* estate, + Relation targetRel, Partition p, int2 bucketId, ItemPointer conflictTid); extern bool check_exclusion_constraint(Relation heap, Relation index, IndexInfo* indexInfo, ItemPointer tupleid, Datum* values, const bool* isnull, EState* estate, bool newIndex, bool errorOK); - extern void RegisterExprContextCallback(ExprContext* econtext, ExprContextCallbackFunction function, Datum arg); extern void UnregisterExprContextCallback(ExprContext* econtext, ExprContextCallbackFunction function, Datum arg); extern List* GetAccessedVarnoList(List* targetList, List* qual); diff --git a/src/include/knl/knl_guc/knl_session_attr_sql.h b/src/include/knl/knl_guc/knl_session_attr_sql.h index e3c7a2102..6527cc665 100644 --- a/src/include/knl/knl_guc/knl_session_attr_sql.h +++ b/src/include/knl/knl_guc/knl_session_attr_sql.h @@ -53,6 +53,7 @@ typedef struct knl_session_attr_sql { bool enable_sonic_optspill; bool enable_sonic_hashjoin; bool enable_sonic_hashagg; + bool enable_upsert_to_merge; bool enable_csqual_pushdown; bool enable_change_hjcost; bool enable_seqscan; diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 11c8ab5d6..b474775dd 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -61,6 +61,9 @@ typedef struct UtilityDesc { * ExclusionOps Per-column exclusion operators, or NULL if none * ExclusionProcs Underlying function OIDs for ExclusionOps * ExclusionStrats Opclass strategy numbers for ExclusionOps + * UniqueOps Theses are like Exclusion*, but for unique indexes + * UniqueProcs + * UniqueStrats * Unique is it a unique index? * ReadyForInserts is it valid for inserts? * Concurrent are we doing a concurrent index build? @@ -81,6 +84,9 @@ typedef struct IndexInfo { Oid* ii_ExclusionOps; /* array with one entry per column */ Oid* ii_ExclusionProcs; /* array with one entry per column */ uint16* ii_ExclusionStrats; /* array with one entry per column */ + Oid *ii_UniqueOps; /* array with one entry per column */ + Oid *ii_UniqueProcs; /* array with one entry per column */ + uint16 *ii_UniqueStrats; /* array with one entry per column */ bool ii_Unique; bool ii_ReadyForInserts; bool ii_Concurrent; @@ -396,6 +402,7 @@ typedef struct MergeState { * ConstraintExprs array of constraint-checking expr states * junkFilter for removing junk attributes from tuples * projectReturning for computing a RETURNING list + * updateProj for computing a UPSERT update list * ---------------- */ typedef struct ResultRelInfo { @@ -434,6 +441,7 @@ typedef struct ResultRelInfo { * ri_RangeTableIndex elsewhere. */ Index ri_mergeTargetRTI; + ProjectionInfo* ri_updateProj; } ResultRelInfo; /* bloom filter controller */ @@ -1292,6 +1300,18 @@ typedef struct MergeActionState { VectorBatch* scanBatch; /* scan batch for UPDATE */ } MergeActionState; +/* ---------------- + * UpsertState information + * ---------------- + */ +typedef struct UpsertState { + NodeTag type; + UpsertAction us_action; /* Flags showing DUPLICATE UPDATE NOTHING or SOMETHING */ + TupleTableSlot* us_existing; /* slot to store existing target tuple in */ + List* us_excludedtlist; /* the excluded pseudo relation's tlist */ + TupleTableSlot* us_updateproj; /* slot to update */ +} UpsertState; + /* ---------------- * ModifyTableState information * ---------------- @@ -1326,7 +1346,7 @@ typedef struct ModifyTableState { TupleTableSlot* mt_insert_constr_slot; /* slot to store target tuple in for checking constraints */ TupleTableSlot* mt_mergeproj; /* MERGE action projection target */ uint32 mt_merge_subcommands; /* Flags showing which subcommands are present INS/UPD/DEL/DO NOTHING */ - + UpsertState* mt_upsert; /* DUPLICATE KEY UPDATE evaluation state */ instr_time first_tuple_modified; /* record the end time for the first tuple inserted, deleted, or updated */ } ModifyTableState; diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 1f5398918..1c900fe0d 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -210,6 +210,7 @@ typedef enum NodeTag { T_RangeTblRef, T_JoinExpr, T_FromExpr, + T_UpsertExpr, T_IntoClause, T_IndexVar, #ifdef PGXC @@ -304,7 +305,7 @@ typedef enum NodeTag { #endif /* PGXC */ T_StreamPath, T_MergeAction, - + T_UpsertState, /* * TAGS FOR MEMORY NODES (memnodes.h) */ @@ -499,7 +500,7 @@ typedef enum NodeTag { T_PruningResult, T_Position, T_MergeWhenClause, - + T_UpsertClause, /* * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h) */ @@ -894,4 +895,10 @@ typedef enum JoinType { (1 << JOIN_RIGHT_ANTI) | (1 << JOIN_LEFT_ANTI_FULL) | (1 << JOIN_RIGHT_ANTI_FULL))) != \ 0) +typedef enum UpsertAction { + UPSERT_NONE, /* No "DUPLICATE KEY UPDATE" clause */ + UPSERT_NOTHING, /* DUPLICATE KEY UPDATE NOTHING */ + UPSERT_UPDATE /* DUPLICATE KEY UPDATE ... */ +}UpsertAction; + #endif /* NODES_H */ diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 78d9f6db4..dd3dc1c5b 100755 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -234,6 +234,7 @@ typedef struct Query { List* mergeSourceTargetList; 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 isRowTriggerShippable; /* true if all row triggers are shippable. */ bool use_star_targets; /* true if use * for targetlist. */ @@ -942,6 +943,8 @@ typedef struct RangeTblEntry { bool relhasbucket; /* the rel has underlying buckets, get from pg_class */ bool isbucket; /* the sql only want some buckets from the rel */ List* buckets; /* the bucket id wanted */ + + bool isexcluded; /* the rel is the EXCLUDED relation for UPSERT */ } RangeTblEntry; /* @@ -1139,6 +1142,13 @@ typedef struct WithClause { int location; /* token location, or -1 if unknown */ } WithClause; +typedef struct UpsertClause +{ + NodeTag type; + List* targetList; + int location; +} UpsertClause; + /* * CommonTableExpr - * representation of WITH list element @@ -1194,6 +1204,7 @@ typedef struct InsertStmt { Node* selectStmt; /* the source SELECT/VALUES, or NULL */ List* returningList; /* list of expressions to return */ WithClause* withClause; /* WITH clause */ + UpsertClause* upsertClause; /* DUPLICATE KEY UPDATE clause */ } InsertStmt; /* ---------------------- diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 6c6efc089..f62ecc5aa 100755 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -412,7 +412,13 @@ typedef struct ModifyTable { Index mergeTargetRelation; /* RT index of the merge target */ List* mergeSourceTargetList; List* mergeActionList; /* actions for MERGE */ - OpMemInfo mem_info; /* Memory info for modify node */ + + UpsertAction upsertAction; /* DUPLICATE KEY UPDATE action */ + List* updateTlist; /* List of UPDATE target */ + List* exclRelTlist; /* target list of the EXECLUDED pseudo relation */ + Index exclRelRTIndex; /* RTI of the EXCLUDED pseudo relation */ + + OpMemInfo mem_info; /* Memory info for modify node */ } ModifyTable; /* ---------------- diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index c161e16a2..d7000a823 100755 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -1359,4 +1359,14 @@ typedef struct { bool indexpath; } IndexVar; +typedef struct UpsertExpr { + NodeTag type; + UpsertAction upsertAction; /* DO NOTHING or UPDATE? */ + + /* DUPLICATE KEY UPDATE */ + List* updateTlist; /* List of UPDATE TargetEntrys */ + List* exclRelTlist; /* tlist of the 'EXCLUDED' pseudo relation */ + int exclRelIndex; /* RT index of 'EXCLUDED' relation */ +} UpsertExpr; + #endif /* PRIMNODES_H */ diff --git a/src/include/opfusion/opfusion_util.h b/src/include/opfusion/opfusion_util.h index f6aedc13c..97b15e376 100644 --- a/src/include/opfusion/opfusion_util.h +++ b/src/include/opfusion/opfusion_util.h @@ -64,6 +64,7 @@ enum FusionType { NOBYPASS_INVALID_SELECT_FOR_UPDATE, NOBYPASS_INVALID_MODIFYTABLE, NOBYPASS_NO_SIMPLE_INSERT, + NOBYPASS_UPSERT_NOT_SUPPORT, NOBYPASS_NO_INDEXSCAN, NOBYPASS_INDEXSCAN_WITH_ORDERBY, diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index 2b04b226e..2c6cf7133 100755 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -109,21 +109,21 @@ extern int get_plan_actual_total_width(Plan* plan, bool vectorized, OpType type, #ifdef STREAMPLAN extern Plan* make_modifytable(PlannerInfo* root, CmdType operation, bool canSetTag, List* resultRelations, List* subplans, List* returningLists, List* rowMarks, int epqParam, bool partKeyUpdated, Index mergeTargetRelation, - List* mergeSourceTargetList, List* mergeActionList, bool isDfsStore = false); + List* mergeSourceTargetList, List* mergeActionList, UpsertExpr* upsertClause, bool isDfsStore = false); extern Plan* make_modifytables(PlannerInfo* root, CmdType operation, bool canSetTag, List* resultRelations, List* subplans, List* returningLists, List* rowMarks, int epqParam, bool partKeyUpdated, bool isDfsStore, - Index mergeTargetRelation, List* mergeSourceTargetList, List* mergeActionList); + Index mergeTargetRelation, List* mergeSourceTargetList, List *mergeActionList, UpsertExpr *upsertClause); extern Plan* make_redistribute_for_agg(PlannerInfo* root, Plan* lefttree, List* redistribute_keys, double multiple, Distribution* distribution = NULL, bool is_local_redistribute = false); extern Plan* make_stream_plan(PlannerInfo* root, Plan* lefttree, List* redistribute_keys, double multiple, Distribution* target_distribution = NULL); #else -extern ModifyTable* make_modifytable(CmdType operation, bool canSetTag, List* resultRelations, List* subplans, - List* returningLists, List* rowMarks, int epqParam, bool partKeyUpdated, Index mergeTargetRelation, - List* mergeSourceTargetList, List* mergeActionList, bool isDfsStore = false); -extern ModifyTable* make_modifytables(CmdType operation, bool canSetTag, List* resultRelations, List* subplans, - List* returningLists, List* rowMarks, int epqParam, bool partKeyUpdated, bool isDfsStore, Index mergeTargetRelation, - List* mergeSourceTargetList, List* mergeActionList); +extern ModifyTable* make_modifytable(CmdType operation, bool canSetTag, List* resultRelations, + List* subplans, List* returningLists, List* rowMarks, int epqParam, bool partKeyUpdated, Index mergeTargetRelation, + List* mergeSourceTargetList, List* mergeActionList, UpsertExpr* upsertClause, bool isDfsStore = false); +extern ModifyTable* make_modifytables(CmdType operation, bool canSetTag, List* resultRelations, + List* subplans, List* returningLists, List* rowMarks, int epqParam, bool partKeyUpdated, bool isDfsStore, + Index mergeTargetRelation, List* mergeSourceTargetList, List* mergeActionList, UpsertExpr* upsertClause); #endif extern bool is_projection_capable_plan(Plan* plan); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index 5ab906e72..8e3cb4009 100755 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -58,7 +58,7 @@ extern Expr* canonicalize_qual(Expr* qual); * prototypes for preptlist.c */ extern List* preprocess_targetlist(PlannerInfo* root, List* tlist); - +extern List* preprocess_upsert_targetlist(List* tlist, int result_relation, List* range_table); extern PlanRowMark* get_plan_rowmark(List* rowmarks, Index rtindex); /* diff --git a/src/include/parser/parse_merge.h b/src/include/parser/parse_merge.h index ff0c2a603..6074e1dac 100755 --- a/src/include/parser/parse_merge.h +++ b/src/include/parser/parse_merge.h @@ -19,4 +19,5 @@ extern Query* transformMergeStmt(ParseState* pstate, MergeStmt* stmt); extern List* expandTargetTL(List* te_list, Query* parsetree); extern List* expandActionTL(List* te_list, Query* parsetree); extern List* expandQualTL(List* te_list, Query* parsetree); +extern bool check_unique_constraint(List*& index_list); #endif diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h index 24fc85669..2b349b37c 100755 --- a/src/include/parser/parse_node.h +++ b/src/include/parser/parse_node.h @@ -106,7 +106,6 @@ struct ParseState { bool p_hasSubLinks; bool p_hasModifyingCTE; bool p_is_insert; - bool p_is_update; bool p_locked_from_parent; bool p_resolve_unknowns; /* resolve unknown-type SELECT outputs as type text */ bool p_hasSynonyms; diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h index 7db2ffa4e..5f5f8b0bc 100755 --- a/src/include/parser/parse_relation.h +++ b/src/include/parser/parse_relation.h @@ -23,7 +23,7 @@ extern void checkNameSpaceConflicts(ParseState* pstate, List* namespace1, List* extern int RTERangeTablePosn(ParseState* pstate, RangeTblEntry* rte, int* sublevels_up); extern RangeTblEntry* GetRTEByRangeTablePosn(ParseState* pstate, int varno, int sublevels_up); extern CommonTableExpr* GetCTEForRTE(ParseState* pstate, RangeTblEntry* rte, int rtelevelsup); -extern Node* scanRTEForColumn(ParseState* pstate, RangeTblEntry* rte, char* colname, int location); +extern Node* scanRTEForColumn(ParseState* pstate, RangeTblEntry* rte, char* colname, int location, bool omit = false); extern Node* colNameToVar( ParseState* pstate, char* colname, bool localonly, int location, RangeTblEntry** final_rte = NULL); extern void markVarForSelectPriv(ParseState* pstate, Var* var, RangeTblEntry* rte); diff --git a/src/include/utils/snapshot.h b/src/include/utils/snapshot.h index 7c77d74fc..242244d28 100755 --- a/src/include/utils/snapshot.h +++ b/src/include/utils/snapshot.h @@ -120,7 +120,8 @@ typedef enum { HeapTupleInvisible, HeapTupleSelfUpdated, HeapTupleUpdated, - HeapTupleBeingUpdated + HeapTupleBeingUpdated, + HeapTupleSelfCreated } HTSU_Result; #endif /* SNAPSHOT_H */ diff --git a/src/include/utils/tqual.h b/src/include/utils/tqual.h index 54193c60f..94a757865 100755 --- a/src/include/utils/tqual.h +++ b/src/include/utils/tqual.h @@ -70,7 +70,7 @@ extern bool HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot, Buffer bu extern bool HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot, Buffer buffer); extern bool HeapTupleSatisfiesHistoricMVCC(HeapTuple htup, Snapshot snapshot, Buffer buffer); /* Special "satisfies" routines with different APIs */ -extern HTSU_Result HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, Buffer buffer); +extern HTSU_Result HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, Buffer buffer, bool self_visible = false); extern HTSV_Result HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin, Buffer buffer); extern bool HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin); diff --git a/src/test/regress/expected/insert_update_001.out b/src/test/regress/expected/insert_update_001.out index 8c91d3185..21a4e091a 100644 --- a/src/test/regress/expected/insert_update_001.out +++ b/src/test/regress/expected/insert_update_001.out @@ -2,6 +2,9 @@ DROP SCHEMA test_insert_update_001 CASCADE; ERROR: schema "test_insert_update_001" does not exist CREATE SCHEMA test_insert_update_001; SET CURRENT_SCHEMA TO test_insert_update_001; +-- SET enable_upsert_to_merge=ON to test the upsert implemented by merge, +-- real upsert will be tested in specialized case. +SET enable_upsert_to_merge TO ON; -- test description \h INSERT Command: INSERT diff --git a/src/test/regress/expected/insert_update_002.out b/src/test/regress/expected/insert_update_002.out index 004e431f2..dd75b4bdd 100644 --- a/src/test/regress/expected/insert_update_002.out +++ b/src/test/regress/expected/insert_update_002.out @@ -2,6 +2,9 @@ DROP SCHEMA test_insert_update_002 CASCADE; ERROR: schema "test_insert_update_002" does not exist CREATE SCHEMA test_insert_update_002; SET CURRENT_SCHEMA TO test_insert_update_002; +-- SET enable_upsert_to_merge=ON to test the upsert implemented by merge, +-- real upsert will be tested in specialized case. +SET enable_upsert_to_merge TO ON; -- test t1 with no index CREATE TABLE t1 ( col1 INT, diff --git a/src/test/regress/expected/insert_update_003.out b/src/test/regress/expected/insert_update_003.out index 7ecefa6b3..a792add37 100644 --- a/src/test/regress/expected/insert_update_003.out +++ b/src/test/regress/expected/insert_update_003.out @@ -2,6 +2,9 @@ DROP SCHEMA test_insert_update_003 CASCADE; ERROR: schema "test_insert_update_003" does not exist CREATE SCHEMA test_insert_update_003; SET CURRENT_SCHEMA TO test_insert_update_003; +-- SET enable_upsert_to_merge=ON to test the upsert implemented by merge, +-- real upsert will be tested in specialized case. +SET enable_upsert_to_merge TO ON; -- test t4 with one primary key with three columns CREATE TABLE t4 ( col1 INT, diff --git a/src/test/regress/expected/insert_update_008.out b/src/test/regress/expected/insert_update_008.out index bcb736168..3d20af82f 100644 --- a/src/test/regress/expected/insert_update_008.out +++ b/src/test/regress/expected/insert_update_008.out @@ -4,6 +4,9 @@ -- initial CREATE SCHEMA test_insert_update_008; SET current_schema = test_insert_update_008; +-- SET enable_upsert_to_merge=ON to test the upsert implemented by merge, +-- real upsert will be tested in specialized case. +SET enable_upsert_to_merge TO ON; CREATE TABLE products_base ( product_id INTEGER DEFAULT 0, @@ -58,9 +61,9 @@ EXPLAIN (VERBOSE on, COSTS off) INSERT INTO products_row SELECT product_id, product_name, category, total FROM newproducts_row ON DUPLICATE KEY UPDATE - product_name = __unnamed_subquery_source__.product_name, - category = __unnamed_subquery_source__.category, - total = __unnamed_subquery_source__.total; + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Merge on test_insert_update_008.products_row @@ -84,9 +87,9 @@ INSERT INTO products_row FROM newproducts_row, products_row WHERE products_row.total + newproducts_row.total < 1000 ON DUPLICATE KEY UPDATE - product_name = __unnamed_subquery_source__.product_name, - category = __unnamed_subquery_source__.category, - total = __unnamed_subquery_source__.total; + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Merge on test_insert_update_008.products_row @@ -113,9 +116,9 @@ INSERT INTO products_row SELECT product_id, product_name, category, total FROM newproducts_row WHERE product_id IS NOT NULL AND product_name IS NOT NULL ON DUPLICATE KEY UPDATE - product_name = __unnamed_subquery_source__.product_name, - category = __unnamed_subquery_source__.category, - total = __unnamed_subquery_source__.total; + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Merge on test_insert_update_008.products_row @@ -137,9 +140,9 @@ EXPLAIN (ANALYZE on, COSTS off, TIMING off) INSERT INTO products_row SELECT product_id, product_name, category, total FROM newproducts_row ON DUPLICATE KEY UPDATE - product_name = __unnamed_subquery_source__.product_name, - category = __unnamed_subquery_source__.category, - total = __unnamed_subquery_source__.total; + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; QUERY PLAN --------------------------------------------------------------------------- Merge on products_row (actual rows=4 loops=1) @@ -162,9 +165,9 @@ EXPLAIN PERFORMANCE INSERT INTO products_row SELECT product_id, product_name, category, total FROM newproducts_row ON DUPLICATE KEY UPDATE - product_name = __unnamed_subquery_source__.product_name, - category = __unnamed_subquery_source__.category, - total = __unnamed_subquery_source__.total; + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; ROLLBACK; \o -- explain analyze @@ -173,9 +176,9 @@ EXPLAIN (ANALYZE on, COSTS off, TIMING off) INSERT INTO products_row SELECT product_id, product_name, category, total FROM newproducts_row ON DUPLICATE KEY UPDATE - product_name = __unnamed_subquery_source__.product_name, - category = __unnamed_subquery_source__.category, - total = __unnamed_subquery_source__.total; + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; QUERY PLAN --------------------------------------------------------------------------- Merge on products_row (actual rows=4 loops=1) @@ -198,9 +201,9 @@ EXPLAIN (VERBOSE on, COSTS off) INSERT INTO products_row SELECT product_id, product_name, category, total FROM newproducts_row ON DUPLICATE KEY UPDATE - product_name = __unnamed_subquery_source__.product_name, - category = __unnamed_subquery_source__.category, - total = __unnamed_subquery_source__.total; + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Merge on test_insert_update_008.products_row @@ -221,9 +224,9 @@ EXPLAIN (ANALYZE on, COSTS off, TIMING off) INSERT INTO products_row SELECT product_id, product_name, category, total FROM newproducts_row ON DUPLICATE KEY UPDATE - product_name = __unnamed_subquery_source__.product_name, - category = __unnamed_subquery_source__.category, - total = __unnamed_subquery_source__.total; + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; QUERY PLAN --------------------------------------------------------------------------- Merge on products_row (actual rows=4 loops=1) @@ -245,9 +248,9 @@ EXPLAIN (ANALYZE on, COSTS off, TIMING off) INSERT INTO products_row SELECT product_id, product_name, category, total FROM newproducts_row ON DUPLICATE KEY UPDATE - product_name = __unnamed_subquery_source__.product_name, - category = __unnamed_subquery_source__.category, - total = __unnamed_subquery_source__.total; + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; QUERY PLAN --------------------------------------------------------------------------- Merge on products_row (actual rows=4 loops=1) @@ -270,9 +273,9 @@ EXPLAIN PERFORMANCE INSERT INTO products_row SELECT product_id, product_name, category, total FROM newproducts_row ON DUPLICATE KEY UPDATE - product_name = __unnamed_subquery_source__.product_name, - category = __unnamed_subquery_source__.category, - total = __unnamed_subquery_source__.total; + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; ROLLBACK; SET explain_perf_mode = run; BEGIN; @@ -280,9 +283,9 @@ EXPLAIN PERFORMANCE INSERT INTO products_row SELECT product_id, product_name, category, total FROM newproducts_row ON DUPLICATE KEY UPDATE - product_name = __unnamed_subquery_source__.product_name, - category = __unnamed_subquery_source__.category, - total = __unnamed_subquery_source__.total; + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; ROLLBACK; SET explain_perf_mode = summary; BEGIN; @@ -290,9 +293,9 @@ EXPLAIN PERFORMANCE INSERT INTO products_row SELECT product_id, product_name, category, total FROM newproducts_row ON DUPLICATE KEY UPDATE - product_name = __unnamed_subquery_source__.product_name, - category = __unnamed_subquery_source__.category, - total = __unnamed_subquery_source__.total; + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; ROLLBACK; \o CREATE TABLE item diff --git a/src/test/regress/expected/insert_update_009.out b/src/test/regress/expected/insert_update_009.out index 02bf76df1..d36df3747 100644 --- a/src/test/regress/expected/insert_update_009.out +++ b/src/test/regress/expected/insert_update_009.out @@ -3,6 +3,9 @@ ERROR: schema "test_insert_update_009" does not exist CREATE SCHEMA test_insert_update_009; SET CURRENT_SCHEMA TO test_insert_update_009; SET enable_light_proxy=off; +-- SET enable_upsert_to_merge=ON to test the upsert implemented by merge, +-- real upsert will be tested in specialized case. +SET enable_upsert_to_merge TO ON; -- test t1 with no index CREATE TABLE t1 ( col1 INT, diff --git a/src/test/regress/expected/insert_update_010.out b/src/test/regress/expected/insert_update_010.out index 3a749bcb4..a33893b36 100644 --- a/src/test/regress/expected/insert_update_010.out +++ b/src/test/regress/expected/insert_update_010.out @@ -2,6 +2,9 @@ DROP SCHEMA test_insert_update_010 CASCADE; ERROR: schema "test_insert_update_010" does not exist CREATE SCHEMA test_insert_update_010; SET CURRENT_SCHEMA TO test_insert_update_010; +-- SET enable_upsert_to_merge=ON to test the upsert implemented by merge, +-- real upsert will be tested in specialized case. +SET enable_upsert_to_merge TO ON; -- test t4 with one primary key with three columns CREATE TABLE t4 ( col1 INT, diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out index 6e251bdd7..e43285c01 100644 --- a/src/test/regress/expected/rangefuncs.out +++ b/src/test/regress/expected/rangefuncs.out @@ -1,3 +1,6 @@ +-- SET enable_upsert_to_merge=ON to test the upsert implemented by merge, +-- real upsert will be tested in specialized case. +SET enable_upsert_to_merge TO ON; SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%' ORDER BY name; name | setting -----------------------------------+--------- @@ -74,12 +77,13 @@ SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%' ORDER BY name; enable_thread_pool | off enable_tidscan | on enable_upgrade_merge_lock_mode | off + enable_upsert_to_merge | on enable_user_metric_persistent | on enable_valuepartition_pruning | on enable_vector_engine | on enable_wdr_snapshot | off enable_xlog_prune | on -(78 rows) +(79 rows) CREATE TABLE foo2(fooid int, f2 int); INSERT INTO foo2 VALUES(1, 11); diff --git a/src/test/regress/expected/upsert_001.out b/src/test/regress/expected/upsert_001.out new file mode 100644 index 000000000..3b3625c2f --- /dev/null +++ b/src/test/regress/expected/upsert_001.out @@ -0,0 +1,515 @@ +DROP SCHEMA test_upsert_001 CASCADE; +ERROR: schema "test_upsert_001" does not exist +CREATE SCHEMA test_upsert_001; +SET CURRENT_SCHEMA TO test_upsert_001; +-- enable_upsert_to_merge must is off, or upsert will be translated to merge. +SET enable_upsert_to_merge TO OFF; +-- test description +\h INSERT +Command: INSERT +Description: create new rows in a table +Syntax: +[ WITH [ RECURSIVE ] with_query [, ...] ] +INSERT INTO table_name [ ( column_name [, ...] ) ] + { DEFAULT VALUES | VALUES {( { expression | DEFAULT } [, ...] ) }[, ...] | query } + [ ON DUPLICATE KEY UPDATE { column_name = { expression | DEFAULT } } [, ...] ] + [ RETURNING {* | {output_expression [ [ AS ] output_name ] }[, ...]} ]; + +-- test permission +--- test with no sequence column +CREATE TABLE t00 (col1 INT DEFAULT 1 PRIMARY KEY, col2 INT); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t00_pkey" for table "t00" +CREATE USER upsert_tester PASSWORD '123456@cc'; +GRANT ALL PRIVILEGES ON SCHEMA test_upsert_001 TO upsert_tester; +SET SESSION SESSION AUTHORIZATION upsert_tester PASSWORD '123456@cc'; +INSERT INTO test_upsert_001.t00 VALUES(1) ON DUPLICATE KEY UPDATE col2 = 5; +ERROR: permission denied for relation t00 +RESET SESSION AUTHORIZATION; +---- error: only have INSERT permission +GRANT INSERT ON test_upsert_001.t00 TO upsert_tester; +SET SESSION SESSION AUTHORIZATION upsert_tester PASSWORD '123456@cc'; +INSERT INTO test_upsert_001.t00 VALUES(1) ON DUPLICATE KEY UPDATE col2 = 5; +ERROR: permission denied for relation t00 +RESET SESSION AUTHORIZATION; +---- success: have INSERT UPDATE permission +GRANT INSERT, UPDATE ON test_upsert_001.t00 TO upsert_tester; +SET SESSION SESSION AUTHORIZATION upsert_tester PASSWORD '123456@cc'; +INSERT INTO test_upsert_001.t00 VALUES(1) ON DUPLICATE KEY UPDATE col2 = 5; +RESET SESSION AUTHORIZATION; +--- have SELECT INSERT UPDATE permission +GRANT SELECT, INSERT, UPDATE ON test_upsert_001.t00 TO upsert_tester; +SET SESSION SESSION AUTHORIZATION upsert_tester PASSWORD '123456@cc'; +INSERT INTO test_upsert_001.t00 VALUES(1) ON DUPLICATE KEY UPDATE col2 = 5; +INSERT INTO test_upsert_001.t00 VALUES(1) ON DUPLICATE KEY UPDATE col3 = 5; +ERROR: column "col3" of relation "t00" does not exist +LINE 1: ...t_upsert_001.t00 VALUES(1) ON DUPLICATE KEY UPDATE col3 = 5; + ^ +RESET SESSION AUTHORIZATION; +--- test with sequnce column +CREATE TABLE t01 (col1 INT , col2 BIGSERIAL PRIMARY KEY, col3 INT) ; +NOTICE: CREATE TABLE will create implicit sequence "t01_col2_seq" for serial column "t01.col2" +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t01_pkey" for table "t01" +---- error: don't have UPDATE permission on sequence table. +GRANT SELECT, INSERT, UPDATE ON test_upsert_001.t01 TO upsert_tester; +SET SESSION SESSION AUTHORIZATION upsert_tester PASSWORD '123456@cc'; +INSERT INTO test_upsert_001.t01 VALUES(1) ON DUPLICATE KEY UPDATE col3 = 5; +ERROR: permission denied for sequence t01_col2_seq +CONTEXT: referenced column: col2 +RESET SESSION AUTHORIZATION; +---- have SELECT INSERT UPDATE permission on target relation, and UPDATE permission on sequence table. +GRANT UPDATE ON test_upsert_001.t01_col2_seq TO upsert_tester; +SET SESSION SESSION AUTHORIZATION upsert_tester PASSWORD '123456@cc'; +INSERT INTO test_upsert_001.t01 VALUES(1) ON DUPLICATE KEY UPDATE col3 = 5; +INSERT INTO test_upsert_001.t01 VALUES(1) ON DUPLICATE KEY UPDATE col3 = 5; +RESET SESSION AUTHORIZATION; +-- test ommit INSERT target column +CREATE TABLE t02 (col1 INT DEFAULT 1 PRIMARY KEY, col2 INT, col3 INT); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t02_pkey" for table "t02" +INSERT INTO t02 VALUES(1, 2, 3) ON DUPLICATE KEY UPDATE col2 = 20; +SELECT * FROM t02 ORDER BY 1, 2; + col1 | col2 | col3 +------+------+------ + 1 | 2 | 3 +(1 row) + +INSERT INTO t02 VALUES(1, 2, 3) ON DUPLICATE KEY UPDATE col2 = 20; +SELECT * FROM t02 ORDER BY 1, 2; + col1 | col2 | col3 +------+------+------ + 1 | 20 | 3 +(1 row) + +ALTER TABLE t02 DROP COLUMN col2; +ALTER TABLE t02 ADD COLUMN col4 INT; +INSERT INTO t02 VALUES(1, 2, 3) ON DUPLICATE KEY UPDATE col4 = 40; +SELECT * FROM t02 ORDER BY 1, 2; + col1 | col3 | col4 +------+------+------ + 1 | 3 | 40 +(1 row) + +INSERT INTO t02 VALUES(2, 3, 4) ON DUPLICATE KEY UPDATE col4 = 40; +SELECT * FROM t02 ORDER BY 1, 2; + col1 | col3 | col4 +------+------+------ + 1 | 3 | 40 + 2 | 3 | 4 +(2 rows) + +INSERT INTO t02 VALUES(2, 3, 4) ON DUPLICATE KEY UPDATE col4 = 40; +SELECT * FROM t02 ORDER BY 1, 2; + col1 | col3 | col4 +------+------+------ + 1 | 3 | 40 + 2 | 3 | 40 +(2 rows) + +-- test restriction +--- test replication table +CREATE TABLE t03 (col1 int PRIMARY KEY, col2 INT, col3 smallserial) ; +NOTICE: CREATE TABLE will create implicit sequence "t03_col3_seq" for serial column "t03.col3" +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t03_pkey" for table "t03" +--- error: not allowed volatile function as default value +INSERT INTO t03(col2) VALUES(1) ON DUPLICATE KEY UPDATE col2 = 100; +ERROR: null value in column "col1" violates not-null constraint +DETAIL: Failing row contains (null, 1, 1). +ALTER TABLE t03 DROP COLUMN col3; +--- error: primary key are not allowed to update +INSERT INTO t03 VALUES(1) ON DUPLICATE KEY UPDATE col1 = 1; +ERROR: INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key. +--- error: clause other than VALUSES are not allowed to use +INSERT INTO t03 SELECT * FROM t03 ON DUPLICATE KEY UPDATE col2 = 1; +--- success: expression index are supported +CREATE UNIQUE INDEX u_expr_index ON t03 USING btree (abs(col1)); +INSERT INTO t03 VALUES(-10, 10) ON DUPLICATE KEY UPDATE col2 = 20; +DROP INDEX u_expr_index; +-- test with stream operator on +INSERT INTO t03 VALUES(1) ON DUPLICATE KEY UPDATE col2 = 100; +SELECT * FROM t03; + col1 | col2 +------+------ + -10 | 10 + 1 | +(2 rows) + +INSERT INTO t03 VALUES(1) ON DUPLICATE KEY UPDATE col2 = 100; +SELECT * FROM t03; + col1 | col2 +------+------ + -10 | 10 + 1 | 100 +(2 rows) + +SELECT * FROM t03; + col1 | col2 +------+------ + -10 | 10 + 1 | 100 +(2 rows) + +--- test PBE +PREPARE p1 AS INSERT INTO t03 VALUES($1, $2) ON DUPLICATE KEY UPDATE col2 = $1*100; +EXECUTE p1(5, 50); +SELECT * FROM t03 WHERE col1 = 5; + col1 | col2 +------+------ + 5 | 50 +(1 row) + +EXECUTE p1(5, 50); +SELECT * FROM t03 WHERE col1 = 5; + col1 | col2 +------+------ + 5 | 500 +(1 row) + +DELETE t03 WHERE col1 = 5; +---- test with primary key +INSERT INTO t03 VALUES(2) ON DUPLICATE KEY UPDATE col2 = 200; +SELECT * FROM t03; + col1 | col2 +------+------ + -10 | 10 + 1 | 100 + 2 | +(3 rows) + +INSERT INTO t03 VALUES(2) ON DUPLICATE KEY UPDATE col2 = 200; +SELECT * FROM t03; + col1 | col2 +------+------ + -10 | 10 + 1 | 100 + 2 | 200 +(3 rows) + +SELECT * FROM t03; + col1 | col2 +------+------ + -10 | 10 + 1 | 100 + 2 | 200 +(3 rows) + +---- test with unique key without NOT NULL constraint +ALTER TABLE t03 DROP CONSTRAINT t03_pkey; +ALTER TABLE t03 ADD COLUMN col3 INT; +CREATE UNIQUE INDEX ON t03 (col1, col3); +---- unique constraints might contain NULL, depends on the plan +----- for cn light and fqs, it can be done +INSERT INTO t03 VALUES(3) ON DUPLICATE KEY UPDATE col2 = 300; +----- for stream or pgxc it can not be done +INSERT INTO t03 VALUES(3) ON DUPLICATE KEY UPDATE col2 = 300; +---- test with unique key with NOT NULL constraint, should success +CREATE UNIQUE INDEX ON t03 (col1); +ERROR: could not create unique index "t03_col1_idx" +DETAIL: Key (col1)=(3) is duplicated. +INSERT INTO t03 VALUES(3) ON DUPLICATE KEY UPDATE col2 = 300; +SELECT * FROM t03; + col1 | col2 | col3 +------+------+------ + -10 | 10 | + 1 | 100 | + 2 | 200 | + 3 | | + 3 | | + 3 | | +(6 rows) + +INSERT INTO t03 VALUES(3) ON DUPLICATE KEY UPDATE col2 = 300; +SELECT * FROM t03; + col1 | col2 | col3 +------+------+------ + -10 | 10 | + 1 | 100 | + 2 | 200 | + 3 | | + 3 | | + 3 | | + 3 | | +(7 rows) + +SELECT * FROM t03; + col1 | col2 | col3 +------+------+------ + -10 | 10 | + 1 | 100 | + 2 | 200 | + 3 | | + 3 | | + 3 | | + 3 | | +(7 rows) + +---- test PBE +PREPARE p2 AS INSERT INTO t03 VALUES($1, $2) ON DUPLICATE KEY UPDATE col2 = $1*100; +EXECUTE p2(5, 50); +SELECT * FROM t03 WHERE col1 = 5; + col1 | col2 | col3 +------+------+------ + 5 | 50 | +(1 row) + +EXECUTE p2(5, 50); +SELECT * FROM t03 WHERE col1 = 5; + col1 | col2 | col3 +------+------+------ + 5 | 50 | + 5 | 50 | +(2 rows) + +--- error: test with clause +WITH tmp(col1, col2) AS (SELECT * FROM t01) +INSERT INTO t01 SELECT * FROM tmp ON DUPLICATE KEY UPDATE col1 = 1; +ERROR: WITH clause is not yet supported whithin INSERT ON DUPLICATE KEY UPDATE statement. +WITH RECURSIVE rq AS + ( + SELECT col1, col2 FROM t00 WHERE col1 = 1 + UNION ALL + SELECT origin.col1, rq.col2 + FROM rq JOIN t00 AS origin ON origin.col1 = rq.col1 + ) + INSERT INTO t03 SELECT * FROM rq ON DUPLICATE KEY UPDATE col1 = rq.col1; +ERROR: WITH clause is not yet supported whithin INSERT ON DUPLICATE KEY UPDATE statement. +--- error: test returning clause +INSERT INTO t01 VALUES (1) ON DUPLICATE KEY UPDATE col1 = 1 RETURNING NOT(1::bool); +ERROR: RETURNING clause is not yet supported whithin INSERT ON DUPLICATE KEY UPDATE statement. +--- error: distribute key are not allowed to UPDATE +CREATE TABLE t04 (col1 INT, col2 INT) ; +INSERT INTO t04 VALUES (1) ON DUPLICATE KEY UPDATE col1 = 5; +--- error: unique index referenced column are not allowed to UPDATE +CREATE UNIQUE INDEX t04_u_index ON t04(col1, col2); +INSERT INTO t04 VALUES (1) ON DUPLICATE KEY UPDATE col2 = 5; +ERROR: INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key. +DROP INDEX t04_u_index; +--- error: primary key referenced column are not allowed to UPDATE +ALTER TABLE t04 ADD PRIMARY KEY (col1, col2); +ERROR: column "col2" contains null values +INSERT INTO t04 VALUES (1) ON DUPLICATE KEY UPDATE col2 = 5; +--- error: invalid column +INSERT INTO t04 (col2, col3) VALUES (2, 3) ON DUPLICATE KEY UPDATE col2 = 5; +ERROR: column "col3" of relation "t04" does not exist +LINE 1: INSERT INTO t04 (col2, col3) VALUES (2, 3) ON DUPLICATE KEY ... + ^ +--- error: duplicate column +INSERT INTO t04 (col2, col2) VALUES (2, 3) ON DUPLICATE KEY UPDATE col2 = 5; +ERROR: column "col2" specified more than once +LINE 1: INSERT INTO t04 (col2, col2) VALUES (2, 3) ON DUPLICATE KEY ... + ^ +-- error: target column more than insert target +INSERT INTO t04 (col1, col2) VALUES (2, 3, 4) ON DUPLICATE KEY UPDATE col2 = 5; +ERROR: INSERT has more expressions than target columns +LINE 1: INSERT INTO t04 (col1, col2) VALUES (2, 3, 4) ON DUPLICATE K... + ^ +INSERT INTO t04 (col1, col2) VALUES (1) ON DUPLICATE KEY UPDATE col2 = 5; +ERROR: INSERT has more target columns than expressions +LINE 1: INSERT INTO t04 (col1, col2) VALUES (1) ON DUPLICATE KEY UPD... + ^ +INSERT INTO t04 (col1, col2) SELECT col1 FROM t04 ON DUPLICATE KEY UPDATE col2 = 5; +ERROR: INSERT has more target columns than expressions +LINE 1: INSERT INTO t04 (col1, col2) SELECT col1 FROM t04 ON DUPLICA... + ^ +INSERT INTO t04 (col1, col2) SELECT *, col1 FROM t04 ON DUPLICATE KEY UPDATE col2 = 5; +ERROR: INSERT has more expressions than target columns +LINE 1: INSERT INTO t04 (col1, col2) SELECT *, col1 FROM t04 ON DUPL... + ^ +INSERT INTO t04 VALUES (2, 3, 4) ON DUPLICATE KEY UPDATE col2 = 5; +ERROR: INSERT has more expressions than target columns +LINE 1: INSERT INTO t04 VALUES (2, 3, 4) ON DUPLICATE KEY UPDATE col... + ^ +INSERT INTO t04 VALUES (1) ON DUPLICATE KEY UPDATE col2 = 5; +INSERT INTO t04 SELECT col1 FROM t04 ON DUPLICATE KEY UPDATE col2 = 5; +INSERT INTO t04 SELECT *, col1 FROM t04 ON DUPLICATE KEY UPDATE col2 = 5; +ERROR: INSERT has more expressions than target columns +LINE 1: INSERT INTO t04 SELECT *, col1 FROM t04 ON DUPLICATE KEY UPD... + ^ +-- test DEFAULT VALUES +TRUNCATE t00; +TRUNCATE t01; +--- without sequence +----should insert +INSERT INTO t00 DEFAULT VALUES ON DUPLICATE KEY UPDATE col2 = col1; +SELECT * FROM t00 ORDER BY 1, 2; + col1 | col2 +------+------ + 1 | +(1 row) + +---- should update +INSERT INTO t00 DEFAULT VALUES ON DUPLICATE KEY UPDATE col2 = col1; +SELECT * FROM t00 ORDER BY 1, 2; + col1 | col2 +------+------ + 1 | 1 +(1 row) + +--- test drop column +TRUNCATE t00; +ALTER TABLE t00 DROP COLUMN col2; +ALTER TABLE t00 ADD COLUMN col3 INT DEFAULT 100; +----should insert +INSERT INTO t00 DEFAULT VALUES ON DUPLICATE KEY UPDATE col3 = col1; +SELECT * FROM t00 ORDER BY 1, 2; + col1 | col3 +------+------ + 1 | 100 +(1 row) + +---- should update +INSERT INTO t00 DEFAULT VALUES ON DUPLICATE KEY UPDATE col3 = col1; +SELECT * FROM t00 ORDER BY 1, 2; + col1 | col3 +------+------ + 1 | 1 +(1 row) + +--- with sequence +----should insert +INSERT INTO t01 DEFAULT VALUES ON DUPLICATE KEY UPDATE col1 = col2; +INSERT INTO t01 (col2) SELECT col2 + 1 FROM t01 LIMIT 1; +SELECT * FROM t01 ORDER BY 1, 2, 3; + col1 | col2 | col3 +------+------+------ + | 3 | + | 4 | +(2 rows) + +---- should update +INSERT INTO t01 DEFAULT VALUES ON DUPLICATE KEY UPDATE col1 = col2; +SELECT * FROM t01 ORDER BY 1, 2, 3; + col1 | col2 | col3 +------+------+------ + 4 | 4 | + | 3 | +(2 rows) + +-- test VALUES(DEFAULT) +CREATE TABLE t05 (col1 INT , col2 INT DEFAULT 1 PRIMARY KEY, col3 INT DEFAULT 100) ; +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t05_pkey" for table "t05" +--- should insert +INSERT INTO t05 VALUES(DEFAULT) ON DUPLICATE KEY UPDATE col3 = 1000; +SELECT * FROM t05 ORDER BY 1, 2, 3; + col1 | col2 | col3 +------+------+------ + | 1 | 100 +(1 row) + +--- should update +INSERT INTO t05 VALUES(DEFAULT) ON DUPLICATE KEY UPDATE col3 = 1000; +SELECT * FROM t05 ORDER BY 1, 2, 3; + col1 | col2 | col3 +------+------+------ + | 1 | 1000 +(1 row) + +-- test UPDATE DEFAULT +INSERT INTO t05 (col1, col2, col3) VALUES (2, 2, 2) ON DUPLICATE KEY UPDATE col3 = DEFAULT; +SELECT * FROM t05 ORDER BY 1, 2, 3; + col1 | col2 | col3 +------+------+------ + 2 | 2 | 2 + | 1 | 1000 +(2 rows) + +INSERT INTO t05 (col1, col2, col3) VALUES (2, 2, 2) ON DUPLICATE KEY UPDATE col3 = DEFAULT; +SELECT * FROM t05 ORDER BY 1, 2, 3; + col1 | col2 | col3 +------+------+------ + 2 | 2 | 100 + | 1 | 1000 +(2 rows) + +-- test VALUES (DEFAULT, ...) +TRUNCATE t05; +--- should insert +INSERT INTO t05 VALUES(DEFAULT, DEFAULT, 200), (DEFAULT, 200, DEFAULT) +ON DUPLICATE KEY UPDATE col3 = DEFAULT, col1 = col3; +SELECT * FROM t05 ORDER BY 1, 2, 3; + col1 | col2 | col3 +------+------+------ + | 1 | 200 + | 200 | 100 +(2 rows) + +--- should update +INSERT INTO t05 VALUES(DEFAULT, DEFAULT, 200), (DEFAULT, 200, DEFAULT) +ON DUPLICATE KEY UPDATE col3 = DEFAULT, col1 = col3; +SELECT * FROM t05 ORDER BY 1, 2, 3; + col1 | col2 | col3 +------+------+------ + 100 | 200 | 100 + 200 | 1 | 100 +(2 rows) + +--- test drop coulmn +BEGIN; +ALTER TABLE t05 ADD COLUMN col4 INT; +ALTER TABLE t05 ADD COLUMN col5 INT DEFAULT 500; +ALTER TABLE t05 DROP COLUMN col3; +TRUNCATE t05; +INSERT INTO t05 VALUES(DEFAULT, DEFAULT, DEFAULT, 600) ON DUPLICATE KEY UPDATE col5 = DEFAULT; +SELECT * FROM t05 ORDER BY 1, 2, 3; + col1 | col2 | col4 | col5 +------+------+------+------ + | 1 | | 600 +(1 row) + +INSERT INTO t05 VALUES(DEFAULT, DEFAULT, DEFAULT, 600) ON DUPLICATE KEY UPDATE col5 = DEFAULT; +SELECT * FROM t05 ORDER BY 1, 2, 3; + col1 | col2 | col4 | col5 +------+------+------+------ + | 1 | | 500 +(1 row) + +ROLLBACK; +-- test schema +SET current_schema = public; +TRUNCATE test_upsert_001.t05; +--- should insert +INSERT INTO test_upsert_001.t05 VALUES(DEFAULT, DEFAULT, 200), (DEFAULT, 200, DEFAULT) +ON DUPLICATE KEY UPDATE col3 = DEFAULT, col1 = col3; +SELECT * FROM test_upsert_001.t05 ORDER BY 1, 2, 3; + col1 | col2 | col3 +------+------+------ + | 1 | 200 + | 200 | 100 +(2 rows) + +--- should update +INSERT INTO test_upsert_001.t05 VALUES(DEFAULT, DEFAULT, 200), (DEFAULT, 200, DEFAULT) +ON DUPLICATE KEY UPDATE col3 = DEFAULT, col1 = col3; +SELECT * FROM test_upsert_001.t05 ORDER BY 1, 2, 3; + col1 | col2 | col3 +------+------+------ + 100 | 200 | 100 + 200 | 1 | 100 +(2 rows) + +--- test using schema on update +INSERT INTO test_upsert_001.t05 VALUES(DEFAULT, DEFAULT, 200), (DEFAULT, 200, DEFAULT) +ON DUPLICATE KEY UPDATE t05.col3 = DEFAULT, t05.col1 = t05.col3 + 1; +SELECT * FROM test_upsert_001.t05 ORDER BY 1, 2, 3; + col1 | col2 | col3 +------+------+------ + 101 | 1 | 100 + 101 | 200 | 100 +(2 rows) + +--- error: should not append schema +INSERT INTO test_upsert_001.t05 VALUES(DEFAULT, DEFAULT, 200), (DEFAULT, 200, DEFAULT) +ON DUPLICATE KEY UPDATE test_upsert_001.t05.col3 = DEFAULT, t05.col1 = t05.col3 + 1; +ERROR: column "test_upsert_001.t05" of relation "t05" does not exist +LINE 2: ON DUPLICATE KEY UPDATE test_upsert_001.t05.col3 = DEFAULT, ... + ^ +INSERT INTO test_upsert_001.t05 VALUES(DEFAULT, DEFAULT, 200), (DEFAULT, 200, DEFAULT) +ON DUPLICATE KEY UPDATE t05.col3 = DEFAULT, t05.col1 = test_upsert_001.t05.col3 + 1; +SET CURRENT_SCHEMA TO test_upsert_001; +DROP USER upsert_tester CASCADE; +DROP SCHEMA test_upsert_001 CASCADE; +NOTICE: drop cascades to 6 other objects +DETAIL: drop cascades to table t00 +drop cascades to table t01 +drop cascades to table t02 +drop cascades to table t03 +drop cascades to table t04 +drop cascades to table t05 diff --git a/src/test/regress/expected/upsert_002.out b/src/test/regress/expected/upsert_002.out new file mode 100644 index 000000000..7701d4384 --- /dev/null +++ b/src/test/regress/expected/upsert_002.out @@ -0,0 +1,545 @@ +DROP SCHEMA test_upsert_002 CASCADE; +ERROR: schema "test_upsert_002" does not exist +CREATE SCHEMA test_upsert_002; +SET CURRENT_SCHEMA TO test_upsert_002; +-- enable_upsert_to_merge must is off, or upsert will be translated to merge. +SET enable_upsert_to_merge TO OFF; +-- test t1 with no index +CREATE TABLE t1 ( + col1 INT, + col2 INT, + col3 INT DEFAULT 1, +-- col4 TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + col5 BIGSERIAL +) ; +NOTICE: CREATE TABLE will create implicit sequence "t1_col5_seq" for serial column "t1.col5" +INSERT INTO t1 VALUES (1, 2) ON DUPLICATE KEY UPDATE col1 = 3; +--- should always insert +INSERT INTO t1 VALUES (1, 2) ON DUPLICATE KEY UPDATE col2 = 3; +INSERT INTO t1 VALUES (1, 2) ON DUPLICATE KEY UPDATE t1.col2 = 4; +--- appoint column list in insert clause, should always insert +INSERT INTO t1(col1, col3) VALUES (1, 3) ON DUPLICATE KEY UPDATE col2 = 5; +INSERT INTO t1(col1, col3) VALUES (1, 3) ON DUPLICATE KEY UPDATE t1.col2 = 6; +--- multiple rows, should always insert +INSERT INTO t1 VALUES (2, 1), (2, 1) ON DUPLICATE KEY UPDATE col2 = 7, col3 = 7; +SELECT * FROM t1 ORDER BY col5; + col1 | col2 | col3 | col5 +------+------+------+------ + 1 | 2 | 1 | 1 + 1 | 2 | 1 | 2 + 1 | 2 | 1 | 3 + 1 | | 3 | 4 + 1 | | 3 | 5 + 2 | 1 | 1 | 6 + 2 | 1 | 1 | 7 +(7 rows) + +--- test union, should insert +INSERT INTO t1 (col1, col2) + SELECT * FROM + (SELECT col1, col2 FROM t1 + UNION + SELECT col1, col3 FROM t1) AS union_table + ON DUPLICATE KEY UPDATE col3 = (col1 + col2 + col3) * 10; +SELECT col1, col2, col3 FROM t1 WHERE col5 > 6 ORDER BY col1, col2; + col1 | col2 | col3 +------+------+------ + 1 | 1 | 1 + 1 | 2 | 1 + 1 | 3 | 1 + 1 | | 1 + 2 | 1 | 1 + 2 | 1 | 1 +(6 rows) + +--- test subquery, should insert +INSERT INTO t1 + (SELECT col1 || col2 || '00' FROM t1 ORDER BY col5) + ON DUPLICATE KEY UPDATE col3 = col1 * 100; +SELECT col1, col2, col3 FROM t1 WHERE col1 >= 100 ORDER BY col1; + col1 | col2 | col3 +------+------+------ + 100 | | 1 + 100 | | 1 + 100 | | 1 + 1100 | | 1 + 1200 | | 1 + 1200 | | 1 + 1200 | | 1 + 1200 | | 1 + 1300 | | 1 + 2100 | | 1 + 2100 | | 1 + 2100 | | 1 +(12 rows) + +-- test t2 with one primary key +CREATE TABLE t2 ( + col1 INT, + col2 INT PRIMARY KEY, + col3 INT DEFAULT 1, + col4 TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + col5 BIGSERIAL +) ; +NOTICE: CREATE TABLE will create implicit sequence "t2_col5_seq" for serial column "t2.col5" +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t2_pkey" for table "t2" +--- primary key or unique key are not allowed to update +INSERT INTO t2 VALUES (1, 1) ON DUPLICATE KEY UPDATE col2 = 3; +ERROR: INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key. +INSERT INTO t2 VALUES (1, 1) ON DUPLICATE KEY UPDATE t2.col2 = 3; +ERROR: INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key. +INSERT INTO t2 (col2, col3, col4, col5) + VALUES (10, 10, CURRENT_TIMESTAMP(0), 10), + (20, 20, CURRENT_TIMESTAMP(1), 20), + (30, 30, CURRENT_TIMESTAMP(2), 30) + ON DUPLICATE KEY UPDATE + col1 = 100, + col2 = 100, + col3 = 100, + col4 = '2019-08-09'::TIMESTAMP, + col5 = 100; +ERROR: INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key. +--- should insert +INSERT INTO t2 VALUES (1, 1) ON DUPLICATE KEY UPDATE col1 = 30; +INSERT INTO t2 VALUES (2, 2) ON DUPLICATE KEY UPDATE t2.col1 = 40; +INSERT INTO t2 VALUES (3, 3) ON DUPLICATE KEY UPDATE col1 = col1 * 2; +INSERT INTO t2 VALUES (4, 4) ON DUPLICATE KEY UPDATE t2.col1 = t2.col1 * 2 ; +INSERT INTO t2 VALUES (5, 5) ON DUPLICATE KEY UPDATE col1 = col2 + 1; +INSERT INTO t2 VALUES (6, 6) ON DUPLICATE KEY UPDATE t2.col1 = t2.col2 + 1; +INSERT INTO t2 VALUES (7, 7) ON DUPLICATE KEY UPDATE col1 = extract(dow from col4) + 10; +INSERT INTO t2 VALUES (8, 8) ON DUPLICATE KEY UPDATE t2.col1 = extract(century from col4) * 100 + extract(isodow from col4); +SELECT col1, col2, col3, col5 FROM t2 ORDER BY col5; + col1 | col2 | col3 | col5 +------+------+------+------ + 1 | 1 | 1 | 1 + 2 | 2 | 1 | 2 + 3 | 3 | 1 | 3 + 4 | 4 | 1 | 4 + 5 | 5 | 1 | 5 + 6 | 6 | 1 | 6 + 7 | 7 | 1 | 7 + 8 | 8 | 1 | 8 +(8 rows) + +--- should update +INSERT INTO t2 VALUES (3, 1) ON DUPLICATE KEY UPDATE col1 = 30, col3 = col5 + 1; +INSERT INTO t2 VALUES (4, 2) ON DUPLICATE KEY UPDATE t2.col1 = 40, t2.col3 = t2.col5; +INSERT INTO t2 VALUES (3, 3), (4, 4) ON DUPLICATE KEY UPDATE col3 = t2.col5 + 1; +INSERT INTO t2 VALUES (5, 5) ON DUPLICATE KEY UPDATE col1 = extract(dow from col4) + 10; +INSERT INTO t2 VALUES (6, 6) ON DUPLICATE KEY UPDATE t2.col1 = extract(century from col4) * 100 + extract(isodow from col4); +SELECT col1, col2, col3, col5 FROM t2 ORDER BY col5; + col1 | col2 | col3 | col5 +------+------+------+------ + 30 | 1 | 2 | 1 + 40 | 2 | 2 | 2 + 3 | 3 | 4 | 3 + 4 | 4 | 5 | 4 +--?.*| 5 | 1 | 5 +--?.*| 6 | 1 | 6 + 7 | 7 | 1 | 7 + 8 | 8 | 1 | 8 +(8 rows) + +-- primary key are not allowed to be null +INSERT INTO t2 (col1) VALUES (10) ON DUPLICATE KEY UPDATE col1 = 10; +ERROR: null value in column "col2" violates not-null constraint +--? +--- appoint column list in insert clause +---- should insert +INSERT INTO t2 (col2, col3) VALUES (9, 9) ON DUPLICATE KEY UPDATE col1 = 90; +INSERT INTO t2 (col2, col3, col4, col5) + VALUES (10, 10, CURRENT_TIMESTAMP(0), 10), + (20, 20, CURRENT_TIMESTAMP(1), 20), + (30, 30, CURRENT_TIMESTAMP(2), 30) + ON DUPLICATE KEY UPDATE + col1 = 100, + col3 = 100, + col4 = '2019-08-20'::TIMESTAMP, + col5 = 100; +SELECT * FROM t2 ORDER BY col5; + col1 | col2 | col3 | col4 | col5 +------+------+------+---------------------------------+------ +--? 30 | 1 | 2 | .* | 1 +--? 40 | 2 | 2 | .* | 2 +--? 3 | 3 | 4 | .* | 3 +--? 4 | 4 | 5 | .* | 4 +--? .* | 5 | 1 | .* | 5 +--? .* | 6 | 1 | .* | 6 +--? 7 | 7 | 1 | .* | 7 +--? 8 | 8 | 1 | .* | 8 +--? | 10 | 10 | .* | 10 +--? | 9 | 9 | .* | 16 +--? | 20 | 20 | .* | 20 +--? | 30 | 30 | .* | 30 +(12 rows) + +---- should update +INSERT INTO t2 (col2, col3) VALUES (9, 9) ON DUPLICATE KEY UPDATE col1 = 90; +INSERT INTO t2 (col2, col3, col4, col5) + VALUES (10, 10, CURRENT_TIMESTAMP(0), 10), + (20, 20, CURRENT_TIMESTAMP(1), 20), + (30, 30, CURRENT_TIMESTAMP(2), 30) + ON DUPLICATE KEY UPDATE + col1 = 100, + col3 = 100, + col4 = '2019-08-20'::TIMESTAMP, + col5 = 100; +SELECT * FROM t2 ORDER BY col5, col2; + col1 | col2 | col3 | col4 | col5 +------+------+------+---------------------------------+------ +--? 30 | 1 | 2 | .* | 1 +--? 40 | 2 | 2 | .* | 2 +--? 3 | 3 | 4 | .* | 3 +--? 4 | 4 | 5 | .* | 4 +--? .* | 5 | 1 | .* | 5 +--? .* | 6 | 1 | .* | 6 +--? 7 | 7 | 1 | .* | 7 +--? 8 | 8 | 1 | .* | 8 +--? 90 | 9 | 9 | .* | 16 + 100 | 10 | 100 | Tue Aug 20 00:00:00 2019 | 100 + 100 | 20 | 100 | Tue Aug 20 00:00:00 2019 | 100 + 100 | 30 | 100 | Tue Aug 20 00:00:00 2019 | 100 +(12 rows) + +--- test subquery +---- should insert +INSERT INTO t2 + (SELECT col1 * 1000, col2 * 1000 + 1 FROM t2 ORDER BY col5 LIMIT 2) + ON DUPLICATE KEY UPDATE col3 = col2 + 1; +INSERT INTO t2 (col2, col3) + (SELECT col1 * 1000 + 2, col2 * 1000 FROM t2 ORDER BY col5 LIMIT 2 OFFSET 1) + ON DUPLICATE KEY UPDATE col3 = col2 + 1; +SELECT col1, col2, col3, col5 FROM t2 ORDER BY col5, col2; + col1 | col2 | col3 | col5 +-------+-------+------+------ + 30 | 1 | 2 | 1 + 40 | 2 | 2 | 2 + 3 | 3 | 4 | 3 + 4 | 4 | 5 | 4 +--? .* | 5 | 1 | 5 +--? .* | 6 | 1 | 6 + 7 | 7 | 1 | 7 + 8 | 8 | 1 | 8 + 90 | 9 | 9 | 16 +--? 30000 | 1001 | 1 | 11 +--? 40000 | 2001 | 1 | 12 +--? | 40002 | 2000 | 13 +--? | 3002 | 3000 | 14 + 100 | 10 | 100 | 100 + 100 | 20 | 100 | 100 + 100 | 30 | 100 | 100 +(16 rows) + +---- should update +INSERT INTO t2 + (SELECT col1 * 1000, col2 * 1000 + 1 FROM t2 ORDER BY col5 LIMIT 2) + ON DUPLICATE KEY UPDATE col3 = col2 + 1; +INSERT INTO t2 (col2, col3) + (SELECT col1 * 1000 + 2, col2 * 1000 FROM t2 ORDER BY col5 LIMIT 2 OFFSET 1) + ON DUPLICATE KEY UPDATE col3 = col2 + 1; +SELECT col1, col2, col3, col5 FROM t2 ORDER BY col5, col2; + col1 | col2 | col3 | col5 +-------+-------+-------+------ + 30 | 1 | 2 | 1 + 40 | 2 | 2 | 2 + 3 | 3 | 4 | 3 + 4 | 4 | 5 | 4 +--? .* | 5 | 1 | 5 +--? .* | 6 | 1 | 6 + 7 | 7 | 1 | 7 + 8 | 8 | 1 | 8 + 90 | 9 | 9 | 16 +--? 30000 | 1001 | 1002 | 11 +--? 40000 | 2001 | 2002 | 12 +--? | 40002 | 40003 | 13 +--? | 3002 | 3003 | 14 + 100 | 10 | 100 | 100 + 100 | 20 | 100 | 100 + 100 | 30 | 100 | 100 +(16 rows) + +--- test union, some insert some update +SELECT col1, col2 FROM t1 WHERE col2 IS NOT NULL + UNION + SELECT col1, col3 FROM t1 ORDER BY 1, 2; + col1 | col2 +------+------ + 1 | 1 + 1 | 2 + 1 | 3 + 2 | 1 + 100 | 1 + 1100 | 1 + 1200 | 1 + 1300 | 1 + 2100 | 1 +(9 rows) + +INSERT INTO t2 (col1, col2) + SELECT * FROM + (SELECT col1, col2 FROM t1 WHERE col2 IS NOT NULL + UNION + SELECT col1, col3 FROM t1) AS union_table + ON DUPLICATE KEY UPDATE col3 = (col1 + col2 + col3); +SELECT col1, col2, col3, col5 FROM t2 ORDER BY col5, col2; + col1 | col2 | col3 | col5 +-------+-------+-------+------ + 30 | 1 | 219 | 1 + 40 | 2 | 44 | 2 + 3 | 3 | 10 | 3 + 4 | 4 | 5 | 4 +--? .* | 5 | 1 | 5 +--? .* | 6 | 1 | 6 + 7 | 7 | 1 | 7 + 8 | 8 | 1 | 8 + 90 | 9 | 9 | 16 +--? 30000 | 1001 | 1002 | 11 +--? 40000 | 2001 | 2002 | 12 +--? | 40002 | 40003 | 13 +--? | 3002 | 3003 | 14 + 100 | 10 | 100 | 100 + 100 | 20 | 100 | 100 + 100 | 30 | 100 | 100 +(16 rows) + +INSERT INTO t2 (col1, col2) + SELECT col1, col2 FROM t1 WHERE col2 IS NOT NULL + UNION + SELECT col1, col3 FROM t1 + ON DUPLICATE KEY UPDATE col3 = col3 + 1; +SELECT col1, col2, col3, col5 FROM t2 ORDER BY col5, col2; + col1 | col2 | col3 | col5 +-------+-------+-------+------ + 30 | 1 | 226 | 1 + 40 | 2 | 45 | 2 + 3 | 3 | 11 | 3 + 4 | 4 | 5 | 4 +--? 15 | 5 | 1 | 5 +--? 2105 | 6 | 1 | 6 + 7 | 7 | 1 | 7 + 8 | 8 | 1 | 8 + 90 | 9 | 9 | 16 +--? 30000 | 1001 | 1002 | 11 +--? 40000 | 2001 | 2002 | 12 +--? | 40002 | 40003 | 13 +--? | 3002 | 3003 | 14 + 100 | 10 | 100 | 100 + 100 | 20 | 100 | 100 + 100 | 30 | 100 | 100 +(16 rows) + +reset behavior_compat_options; +-- test INTERSECT, should update +(SELECT col1, col1 + col2 FROM t1 WHERE col2 IS NOT NULL + UNION + SELECT 1, 2) + INTERSECT + SELECT col1, col3 FROM t1; + col1 | ?column? +------+---------- + 1 | 3 +(1 row) + +INSERT INTO t2 (col1, col2) + (SELECT col1, col1 + col2 FROM t1 WHERE col2 IS NOT NULL + UNION + SELECT 1, 2) + INTERSECT + SELECT col1, col3 FROM t1 + ON DUPLICATE KEY UPDATE col3 = col3 + 1; +SELECT col1, col2, col3, col5 FROM t2 WHERE col2 = 3 ORDER BY col5, col2; + col1 | col2 | col3 | col5 +------+------+------+------ + 3 | 3 | 12 | 3 +(1 row) + +-- test EXCEPT, should update +SELECT col1, col2 FROM t1 WHERE col2 IS NOT NULL + EXCEPT + (SELECT col1, col3 FROM t1 + UNION + SELECT NULL, NULL); + col1 | col2 +------+------ + 1 | 2 +(1 row) + +INSERT INTO t2 (col1, col2) + SELECT col1, col2 FROM t1 WHERE col2 IS NOT NULL + EXCEPT + (SELECT col1, col3 FROM t1 + UNION + SELECT NULL, NULL) + ON DUPLICATE KEY UPDATE col3 = col3 + 1; +SELECT col1, col2, col3, col5 FROM t2 WHERE col2 = 2 ORDER BY col5, col2; + col1 | col2 | col3 | col5 +------+------+------+------ + 40 | 2 | 46 | 2 +(1 row) + +-- test unique index with not default value +ALTER TABLE t2 DROP CONSTRAINT t2_pkey; +CREATE UNIQUE INDEX t2_u_index ON t2(col2, col5); +SELECT col1, col2 FROM t1 WHERE col2 IS NOT NULL + EXCEPT + (SELECT col1, col3 FROM t1 + UNION + SELECT NULL, NULL); + col1 | col2 +------+------ + 1 | 2 +(1 row) + +INSERT INTO t2 + SELECT col1, col2 FROM t1 WHERE col2 IS NOT NULL + EXCEPT + (SELECT col1, col3 FROM t1 + UNION + SELECT NULL, NULL) + ON DUPLICATE KEY UPDATE col3 = col3 + 1; +SELECT col1, col2, col3, col5 FROM t2 WHERE col2 = 2 ORDER BY col5, col2; + col1 | col2 | col3 | col5 +------+------+------+------ + 40 | 2 | 46 | 2 + 1 | 2 | 1 | 46 +(2 rows) + +-- test t3 with one primary index with two columns +CREATE TABLE t3 ( + col1 INT, + col2 INT, + col3 INT DEFAULT 1, +-- col4 TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + col5 BIGSERIAL, + PRIMARY KEY (col2, col3) +) ; +NOTICE: CREATE TABLE will create implicit sequence "t3_col5_seq" for serial column "t3.col5" +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t3_pkey" for table "t3" +--- column referenced by primary key are not allowed to update +INSERT INTO t3 VALUES (1, 1) ON DUPLICATE KEY UPDATE col3 = 3; +ERROR: INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key. +--- should insert when not contains primary key and not all primary key referred columns have default value. +--- but will fail cause primary key should not be null +INSERT INTO t3 (col1) VALUES (1) ON DUPLICATE KEY UPDATE col1 = 2; +ERROR: null value in column "col2" violates not-null constraint +--?.* +--- should insert +--- (SEQUENCE BUG: the serial column will starts from 2 since the above statement has applied for a sequence) +INSERT INTO t3 VALUES (1, 1) ON DUPLICATE KEY UPDATE col1 = 10; +INSERT INTO t3 VALUES (2, 2, 2) ON DUPLICATE KEY UPDATE col5 = 20; +SELECT * FROM t3 order by col5; + col1 | col2 | col3 | col5 +------+------+------+------ + 1 | 1 | 1 | 2 + 2 | 2 | 2 | 3 +(2 rows) + +--- should update +INSERT INTO t3 VALUES (1, 1) ON DUPLICATE KEY UPDATE col1 = 10; +INSERT INTO t3 VALUES (2, 2, 2) ON DUPLICATE KEY UPDATE col5 = 20; +SELECT * FROM t3 order by col5; + col1 | col2 | col3 | col5 +------+------+------+------ + 10 | 1 | 1 | 2 + 2 | 2 | 2 | 20 +(2 rows) + +--- test subquery +---- should insert +INSERT INTO t3 (col2, col3) (SELECT max(col2) + 1, max(col3) + 1 FROM t3) ON DUPLICATE KEY UPDATE col1 = col2 + col3; +SELECT * FROM t3 order by col5; + col1 | col2 | col3 | col5 +------+------+------+------ + 10 | 1 | 1 | 2 + | 3 | 3 | 6 + 2 | 2 | 2 | 20 +(3 rows) + +---- should update +INSERT INTO t3 (col2, col3) (SELECT max(col2), max(col3) FROM t3) ON DUPLICATE KEY UPDATE col1 = col2 + col3; +SELECT * FROM t3 order by col5; + col1 | col2 | col3 | col5 +------+------+------+------ + 10 | 1 | 1 | 2 + 6 | 3 | 3 | 6 + 2 | 2 | 2 | 20 +(3 rows) + +-- test t3 with one unique index with two columns +TRUNCATE t3; +ALTER TABLE t3 DROP CONSTRAINT t3_pkey; +ALTER TABLE t3 ALTER COLUMN col2 DROP NOT NULL; +CREATE UNIQUE INDEX t3_ukey ON t3 (col2, col3); +--- column referenced by unique key are not allowed to update +INSERT INTO t3 VALUES (1, 1) ON DUPLICATE KEY UPDATE col3 = 3; +ERROR: INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key. +--- should insert cause not contains unique key and not all unique key referred columns have default value. +INSERT INTO t3 (col1) VALUES (1) ON DUPLICATE KEY UPDATE col1 = 2; +INSERT INTO t3 (col1) VALUES (1) ON DUPLICATE KEY UPDATE col1 = 2; +SELECT * FROM t3 order by col5; + col1 | col2 | col3 | col5 +------+------+------+------ + 1 | | 1 | 8 + 1 | | 1 | 9 +(2 rows) + +--- should insert +INSERT INTO t3 VALUES (1, 1) ON DUPLICATE KEY UPDATE col1 = 10; +INSERT INTO t3 VALUES (2, 2, 2) ON DUPLICATE KEY UPDATE t3.col5 = 20; +SELECT * FROM t3 ORDER BY col5; + col1 | col2 | col3 | col5 +------+------+------+------ + 1 | | 1 | 8 + 1 | | 1 | 9 + 1 | 1 | 1 | 10 + 2 | 2 | 2 | 11 +(4 rows) + +--- should update +INSERT INTO t3 VALUES (1, 1) ON DUPLICATE KEY UPDATE t3.col1 = 10; +INSERT INTO t3 VALUES (2, 2, 2) ON DUPLICATE KEY UPDATE col5 = 20; +SELECT * FROM t3 ORDER BY col5; + col1 | col2 | col3 | col5 +------+------+------+------ + 1 | | 1 | 8 + 1 | | 1 | 9 + 10 | 1 | 1 | 10 + 2 | 2 | 2 | 20 +(4 rows) + +--- test subquery +---- should insert +INSERT INTO t3 (SELECT 100, NULL, max(col3) FROM t3) ON DUPLICATE KEY UPDATE col1 = col2 + col3; +INSERT INTO t3 (SELECT 100, NULL, max(col3) FROM t3) ON DUPLICATE KEY UPDATE col1 = col2 + col3; +INSERT INTO t3 (col2, col3) (SELECT max(col2) + 1, max(col3) + 1 FROM t3) ON DUPLICATE KEY UPDATE col1 = col2 + col3; +SELECT * FROM t3 ORDER BY col5; + col1 | col2 | col3 | col5 +------+------+------+------ + 1 | | 1 | 8 + 1 | | 1 | 9 + 10 | 1 | 1 | 10 + 100 | | 2 | 14 + 100 | | 2 | 15 + | 3 | 3 | 16 + 2 | 2 | 2 | 20 +(7 rows) + +---- should update +INSERT INTO t3 (col2, col3) (SELECT max(col2), max(col3) FROM t3) ON DUPLICATE KEY UPDATE col1 = col2 + col3; +SELECT * FROM t3 ORDER BY col5; + col1 | col2 | col3 | col5 +------+------+------+------ + 1 | | 1 | 8 + 1 | | 1 | 9 + 10 | 1 | 1 | 10 + 100 | | 2 | 14 + 100 | | 2 | 15 + 6 | 3 | 3 | 16 + 2 | 2 | 2 | 20 +(7 rows) + +DROP SCHEMA test_upsert_002 CASCADE; +NOTICE: drop cascades to 3 other objects +DETAIL: drop cascades to table t1 +drop cascades to table t2 +drop cascades to table t3 diff --git a/src/test/regress/expected/upsert_003.out b/src/test/regress/expected/upsert_003.out new file mode 100644 index 000000000..6b2e90ad6 --- /dev/null +++ b/src/test/regress/expected/upsert_003.out @@ -0,0 +1,313 @@ +DROP SCHEMA test_insert_update_003 CASCADE; +ERROR: schema "test_insert_update_003" does not exist +CREATE SCHEMA test_insert_update_003; +SET CURRENT_SCHEMA TO test_insert_update_003; +-- enable_upsert_to_merge must is off, or upsert will be translated to merge. +SET enable_upsert_to_merge TO OFF; +-- test t4 with one primary key with three columns +CREATE TABLE t4 ( + col1 INT, + col2 INT DEFAULT 0, + col3 INT DEFAULT 1, + col4 TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + col5 BIGSERIAL, + PRIMARY KEY (col2, col3, col5) +) ; +NOTICE: CREATE TABLE will create implicit sequence "t4_col5_seq" for serial column "t4.col5" +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t4_pkey" for table "t4" +--- should insert +INSERT INTO t4 VALUES (1) ON DUPLICATE KEY UPDATE col1 = 10; +INSERT INTO t4 VALUES (1) ON DUPLICATE KEY UPDATE col1 = 10; +INSERT INTO t4 VALUES (2, 2, 2) ON DUPLICATE KEY UPDATE col1 = 200; +INSERT INTO t4 VALUES (100, 100, 100, CURRENT_TIMESTAMP, 100) ON DUPLICATE KEY UPDATE col1 = 1000; +SELECT col1, col2, col3, col5 FROM t4 ORDER BY col5; + col1 | col2 | col3 | col5 +------+------+------+------ + 1 | 0 | 1 | 1 + 1 | 0 | 1 | 2 + 2 | 2 | 2 | 3 + 100 | 100 | 100 | 100 +(4 rows) + +--- should update +INSERT INTO t4 VALUES (2, 2, 2, CURRENT_TIMESTAMP, 3) ON DUPLICATE KEY UPDATE col1 = 200; +INSERT INTO t4 VALUES (100, 100, 100, CURRENT_TIMESTAMP, 100) ON DUPLICATE KEY UPDATE col1 = 1000; +SELECT col1, col2, col3, col5 FROM t4 ORDER BY col5; + col1 | col2 | col3 | col5 +------+------+------+------ + 1 | 0 | 1 | 1 + 1 | 0 | 1 | 2 + 200 | 2 | 2 | 3 + 1000 | 100 | 100 | 100 +(4 rows) + +--- error: duplicate key update on (x, x, 20) +--- this is because current version is not inplace update but merge, +--- so when the subquery contains multiple same values, it will cause duplicate insert failure. +SELECT col3, sum(col3) * 10 FROM t4 GROUP BY col3 ORDER BY 1, 2; + col3 | ?column? +------+---------- + 1 | 20 + 2 | 20 + 100 | 1000 +(3 rows) + +INSERT INTO t4 (col1, col5) + (SELECT col3, sum(col3) * 10 FROM t4 GROUP BY col3) + ON DUPLICATE KEY UPDATE col1 = 3; +-- test t5 with sequence or default column with volatile function in constaint index +CREATE TABLE t5 ( + col1 INT, + col2 INT DEFAULT 1, + col3 BIGSERIAL, +-- col4 TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + col5 INTEGER(10, 5) DEFAULT RANDOM() + 1 +) ; +NOTICE: CREATE TABLE will create implicit sequence "t5_col3_seq" for serial column "t5.col3" +-- test t5 with sequence column in constaint index +CREATE UNIQUE INDEX u_t5_index1 ON t5(col1, col3); +--- should insert +INSERT INTO t5 VALUES (1), (1), (1) ON DUPLICATE KEY UPDATE col2 = col3; +INSERT INTO t5 DEFAULT VALUES ON DUPLICATE KEY UPDATE col2 = col3; +SELECT * FROM t5 ORDER BY col3; + col1 | col2 | col3 | col5 +------+------+------+--------- +--? 1 | 1 | 1 | 1.58182 +--? 1 | 1 | 2 | 1.00814 +--? 1 | 1 | 3 | 1.50194 +--? | 1 | 4 | 1.12955 +(4 rows) + +--- should update +INSERT INTO t5 (col1, col3) VALUES (1, 1), (1, 2), (1, 3) ON DUPLICATE KEY UPDATE col5 = col2, col2 = col3 * 10; +SELECT * FROM t5 WHERE col1 = 1 ORDER BY col3; + col1 | col2 | col3 | col5 +------+------+------+--------- + 1 | 10 | 1 | 1.00000 + 1 | 20 | 2 | 1.00000 + 1 | 30 | 3 | 1.00000 +(3 rows) + +--- should some insert some update +INSERT INTO t5 (col1, col3) VALUES (2, 5), (2, 6); +SELECT col1, col2, col3 FROM t5 ORDER BY col3; + col1 | col2 | col3 +------+------+------ + 1 | 10 | 1 + 1 | 20 | 2 + 1 | 30 | 3 + | 1 | 4 + 2 | 1 | 5 + 2 | 1 | 6 +(6 rows) + +INSERT INTO t5 (col1) VALUES (2), (2), (2) ON DUPLICATE KEY UPDATE col2 = col3; +SELECT col1, col2, col3 FROM t5 ORDER BY col3; + col1 | col2 | col3 +------+------+------ + 1 | 10 | 1 + 1 | 20 | 2 + 1 | 30 | 3 + | 1 | 4 + 2 | 5 | 5 + 2 | 6 | 6 + 2 | 1 | 7 +(7 rows) + +--- should INSERT and sequence starting from 7 +INSERT INTO t5 VALUES (2), (2); +SELECT col1, col2, col3 FROM t5 ORDER BY col3; + col1 | col2 | col3 +------+------+------ + 1 | 10 | 1 + 1 | 20 | 2 + 1 | 30 | 3 + | 1 | 4 + 2 | 5 | 5 + 2 | 6 | 6 + 2 | 1 | 7 + 2 | 1 | 8 + 2 | 1 | 9 +(9 rows) + +-- test with volatile function as default column in constraint index +TRUNCATE t5; +DROP INDEX u_t5_index1; +CREATE UNIQUE INDEX u_t5_index2 ON t5(col1, col5) WHERE col1 > 2; +--- should insert +INSERT INTO t5 VALUES (3), (3), (3) ON DUPLICATE KEY UPDATE col2 = col3; +INSERT INTO t5 (col1) VALUES (4), (4), (4) ON DUPLICATE KEY UPDATE col2 = col3; +SELECT * FROM t5 ORDER BY col3; + col1 | col2 | col3 | col5 +------+------+------+--------- +--? 3 | 1 | 10 | 1.24521 +--? 3 | 1 | 11 | 1.80598 +--? 3 | 1 | 12 | 1.86845 +--? 4 | 1 | 13 | 1.65797 +--? 4 | 1 | 14 | 1.07881 +--? 4 | 1 | 15 | 1.69493 +(6 rows) + +--- should update +INSERT INTO t5 (col1, col5) SELECT col1, col5 FROM t5 where col1 = 3 ON DUPLICATE KEY UPDATE col2 = col5 * 100; +SELECT * FROM t5 ORDER BY col3; + col1 | col2 | col3 | col5 +------+------+------+--------- +--? 3 | 125 | 10 | 1.24521 +--? 3 | 181 | 11 | 1.80598 +--? 3 | 187 | 12 | 1.86845 +--? 4 | 1 | 13 | 1.65797 +--? 4 | 1 | 14 | 1.07881 +--? 4 | 1 | 15 | 1.69493 +(6 rows) + +--- test subquery +---- should insert +INSERT INTO t5 (col1, col2) SELECT col1, col2 FROM t5 ON DUPLICATE KEY UPDATE col2 = col5 * 100; +SELECT * FROM t5 ORDER BY col3; + col1 | col2 | col3 | col5 +------+------+------+--------- +--? 3 | 167 | 10 | 1.67400 +--? 3 | 132 | 11 | 1.31552 +--? 3 | 129 | 12 | 1.29423 +--? 4 | 1 | 13 | 1.44100 +--? 4 | 1 | 14 | 1.37620 +--? 4 | 1 | 15 | 1.51296 +--? 4 | 1 | 16 | 1.37516 +--? 4 | 1 | 17 | 1.21710 +--? 4 | 1 | 18 | 1.50422 +--? 3 | 132 | 19 | 1.30560 +--? 3 | 167 | 20 | 1.62282 +--? 3 | 129 | 21 | 1.76699 +(12 rows) + +---- should update +INSERT INTO t5 SELECT * FROM t5 ON DUPLICATE KEY UPDATE col2 = col5 * 1000; +SELECT * FROM t5 ORDER BY col3; + col1 | col2 | col3 | col5 +------+------+------+--------- +--? 3 | 1674 | 10 | 1.67400 +--? 3 | 1316 | 11 | 1.31552 +--? 3 | 1294 | 12 | 1.29423 +--? 4 | 1441 | 13 | 1.44100 +--? 4 | 1376 | 14 | 1.37620 +--? 4 | 1513 | 15 | 1.51296 +--? 4 | 1375 | 16 | 1.37516 +--? 4 | 1217 | 17 | 1.21710 +--? 4 | 1504 | 18 | 1.50422 +--? 3 | 1306 | 19 | 1.30560 +--? 3 | 1623 | 20 | 1.62282 +--? 3 | 1767 | 21 | 1.76699 +(12 rows) + +-- test t6 with one more index +CREATE TABLE t6 ( + col1 INT, + col2 INT DEFAULT 1, + col3 BIGSERIAL, +-- col4 TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + col5 INTEGER(10, 5) DEFAULT RANDOM(), + col6 INT, + col7 TEXT +) ; +NOTICE: CREATE TABLE will create implicit sequence "t6_col3_seq" for serial column "t6.col3" +ALTER TABLE t6 ADD PRIMARY KEY (col1, col3); +NOTICE: ALTER TABLE / ADD PRIMARY KEY will create implicit index "t6_pkey" for table "t6" +CREATE UNIQUE INDEX u_t6_index1 ON t6(col1, col5, col6); +INSERT INTO t6 (col1) VALUES (1), (2), (3), (4), (5); +SELECT col1, col2, col3, col6, col7 FROM t6 ORDER BY col3; + col1 | col2 | col3 | col6 | col7 +------+------+------+------+------ + 1 | 1 | 1 | | + 2 | 1 | 2 | | + 3 | 1 | 3 | | + 4 | 1 | 4 | | + 5 | 1 | 5 | | +(5 rows) + +--- should not insert +INSERT INTO t6 (col1) VALUES (1), (2), (3), (4), (5) ON DUPLICATE KEY UPDATE col6 = power(col1, col2); +ERROR: INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key. +SELECT col1, col2, col3, col6, col7 FROM t6 ORDER BY col3; + col1 | col2 | col3 | col6 | col7 +------+------+------+------+------ + 1 | 1 | 1 | | + 2 | 1 | 2 | | + 3 | 1 | 3 | | + 4 | 1 | 4 | | + 5 | 1 | 5 | | +(5 rows) + +--- should update because primary key matches +INSERT INTO t6 (col1, col3) VALUES (6, 11), (6, 12); +SELECT col1, col2, col3, col6, col7 FROM t6 WHERE col1 = 6 ORDER BY col3; + col1 | col2 | col3 | col6 | col7 +------+------+------+------+------ + 6 | 1 | 11 | | + 6 | 1 | 12 | | +(2 rows) + +INSERT INTO t6 (col1) VALUES (6), (6), (6) ON DUPLICATE KEY UPDATE col2 = col1 + col3, col6 = col2 * 10; +ERROR: INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key. +SELECT col1, col2, col3, col6, col7 FROM t6 WHERE col1 = 6 ORDER BY col3; + col1 | col2 | col3 | col6 | col7 +------+------+------+------+------ + 6 | 1 | 11 | | + 6 | 1 | 12 | | +(2 rows) + +--- should update for those col6 is not null bacause they will match the unique index, +--- and insert for those col6 is null because the unique index containing null never matches, +--- also primary key will not match +--- be ware the sequence column of the inserted row will jump n step, where n is the count of the not null rows, +--- because those sequence have to be generated during the unique index join stage. +INSERT INTO t6 (col1, col5, col6) + (SELECT col1, col5, col6 FROM t6 WHERE col1 = 6) + ON DUPLICATE KEY UPDATE + col7 = col2 + 1; +SELECT col1, col2, col3, col6, col7 FROM t6 WHERE col1 = 6 ORDER BY col1, col3, col6, col7; + col1 | col2 | col3 | col6 | col7 +------+------+------+------+------ + 6 | 1 | 6 | | + 6 | 1 | 7 | | + 6 | 1 | 11 | | + 6 | 1 | 12 | | +(4 rows) + +--- should update because unique index and primary key both match +INSERT INTO t6 (col1, col3, col5, col6) + (SELECT col1, col3, col5, col6 FROM t6 WHERE col1 = 6 AND col6 IS NOT NULL) + ON DUPLICATE KEY UPDATE + col7 = col7 * 10; +SELECT col1, col2, col3, col6, col7 FROM t6 WHERE col1 = 6 ORDER BY col1, col3, col6, col7; + col1 | col2 | col3 | col6 | col7 +------+------+------+------+------ + 6 | 1 | 6 | | + 6 | 1 | 7 | | + 6 | 1 | 11 | | + 6 | 1 | 12 | | +(4 rows) + +--- insert when col3 = 17 because constaints does not match, +--- but update when col3 = 18 because 18 has been inserted and will cause a match +INSERT INTO t6 (col1, col3, col5, col6) VALUES (7, 18, 100, 100); +SELECT * FROM t6 WHERE col3 > 16 ORDER BY col1, col3, col6, col7; + col1 | col2 | col3 | col5 | col6 | col7 +------+------+------+-----------+------+------ + 7 | 1 | 18 | 100.00000 | 100 | +(1 row) + +INSERT INTO t6 (col1, col5, col6) VALUES (7, 10, 10), (7, 100, 100) ON DUPLICATE KEY UPDATE + col7 = col3 * 100; +SELECT * FROM t6 WHERE col1 = 7 ORDER BY col1, col3, col6, col7; + col1 | col2 | col3 | col5 | col6 | col7 +------+------+------+-----------+------+------ + 7 | 1 | 8 | 10.00000 | 10 | + 7 | 1 | 18 | 100.00000 | 100 | 1800 +(2 rows) + +DROP SCHEMA test_insert_update_003 CASCADE; +NOTICE: drop cascades to 3 other objects +DETAIL: drop cascades to table t4 +drop cascades to table t5 +drop cascades to table t6 diff --git a/src/test/regress/expected/upsert_008.out b/src/test/regress/expected/upsert_008.out new file mode 100644 index 000000000..1d241be3b --- /dev/null +++ b/src/test/regress/expected/upsert_008.out @@ -0,0 +1,466 @@ +-- +-- INSERT UPDATE, test explain command, comes from merge_explain and merge_explain_pretty +-- +-- initial +CREATE SCHEMA test_insert_update_008; +SET current_schema = test_insert_update_008; +-- enable_upsert_to_merge must is off, or upsert will be translated to merge. +SET enable_upsert_to_merge TO OFF; +CREATE TABLE products_base +( +product_id INTEGER DEFAULT 0, +product_name VARCHAR(60) DEFAULT 'null', +category VARCHAR(60) DEFAULT 'unknown', +total INTEGER DEFAULT '0' +); +INSERT INTO products_base VALUES (1501, 'vivitar 35mm', 'electrncs', 100); +INSERT INTO products_base VALUES (1502, 'olympus is50', 'electrncs', 100); +INSERT INTO products_base VALUES (1600, 'play gym', 'toys', 100); +INSERT INTO products_base VALUES (1601, 'lamaze', 'toys', 100); +INSERT INTO products_base VALUES (1666, 'harry potter', 'dvd', 100); +CREATE TABLE newproducts_base +( +product_id INTEGER DEFAULT 0, +product_name VARCHAR(60) DEFAULT 'null', +category VARCHAR(60) DEFAULT 'unknown', +total INTEGER DEFAULT '0' +); +INSERT INTO newproducts_base VALUES (1502, 'olympus camera', 'electrncs', 200); +INSERT INTO newproducts_base VALUES (1601, 'lamaze', 'toys', 200); +INSERT INTO newproducts_base VALUES (1666, 'harry potter', 'toys', 200); +INSERT INTO newproducts_base VALUES (1700, 'wait interface', 'books', 200); +ANALYZE products_base; +ANALYZE newproducts_base; +-- +-- row table +-- +CREATE TABLE products_row +( +product_id INTEGER DEFAULT 0 PRIMARY KEY, +product_name VARCHAR(60) DEFAULT 'null', +category VARCHAR(60) DEFAULT 'unknown', +total INTEGER DEFAULT '0' +); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "products_row_pkey" for table "products_row" +CREATE TABLE newproducts_row +( +product_id INTEGER DEFAULT 0 PRIMARY KEY, +product_name VARCHAR(60) DEFAULT 'null', +category VARCHAR(60) DEFAULT 'unknown', +total INTEGER DEFAULT '0' +); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "newproducts_row_pkey" for table "newproducts_row" +INSERT INTO products_row SELECT * FROM products_base; +INSERT INTO newproducts_row SELECT * FROM newproducts_base; +ANALYZE products_row; +ANALYZE newproducts_row; +SET explain_perf_mode = normal; +-- explain verbose +EXPLAIN (VERBOSE on, COSTS off) +INSERT INTO products_row + SELECT product_id, product_name, category, total FROM newproducts_row + ON DUPLICATE KEY UPDATE + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------- + Insert on test_insert_update_008.products_row + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: products_row_pkey + -> Seq Scan on test_insert_update_008.newproducts_row + Output: newproducts_row.product_id, newproducts_row.product_name, newproducts_row.category, newproducts_row.total +(5 rows) + +EXPLAIN (VERBOSE on, COSTS off) +INSERT INTO products_row + SELECT newproducts_row.product_id, + newproducts_row.product_name, + newproducts_row.category, + newproducts_row.total + FROM newproducts_row, products_row + WHERE products_row.total + newproducts_row.total < 1000 + ON DUPLICATE KEY UPDATE + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Insert on test_insert_update_008.products_row + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: products_row_pkey + -> Nested Loop + Output: newproducts_row.product_id, newproducts_row.product_name, newproducts_row.category, newproducts_row.total + Join Filter: ((test_insert_update_008.products_row.total + newproducts_row.total) < 1000) + -> Seq Scan on test_insert_update_008.products_row + Output: test_insert_update_008.products_row.product_id, test_insert_update_008.products_row.product_name, test_insert_update_008.products_row.category, test_insert_update_008.products_row.total + -> Materialize + Output: newproducts_row.product_id, newproducts_row.product_name, newproducts_row.category, newproducts_row.total + -> Seq Scan on test_insert_update_008.newproducts_row + Output: newproducts_row.product_id, newproducts_row.product_name, newproducts_row.category, newproducts_row.total +(12 rows) + +EXPLAIN (VERBOSE on, COSTS off) +INSERT INTO products_row + SELECT product_id, product_name, category, total + FROM newproducts_row WHERE product_id IS NOT NULL AND product_name IS NOT NULL + ON DUPLICATE KEY UPDATE + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------- + Insert on test_insert_update_008.products_row + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: products_row_pkey + -> Seq Scan on test_insert_update_008.newproducts_row + Output: newproducts_row.product_id, newproducts_row.product_name, newproducts_row.category, newproducts_row.total + Filter: ((newproducts_row.product_id IS NOT NULL) AND (newproducts_row.product_name IS NOT NULL)) +(6 rows) + +-- explain analyze +BEGIN; +EXPLAIN (ANALYZE on, COSTS off, TIMING off) +INSERT INTO products_row + SELECT product_id, product_name, category, total FROM newproducts_row + ON DUPLICATE KEY UPDATE + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; + QUERY PLAN +----------------------------------------------------------- + Insert on products_row (actual rows=4 loops=1) + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: products_row_pkey + -> Seq Scan on newproducts_row (actual rows=4 loops=1) +--? Total runtime: .* ms +(5 rows) + +ROLLBACK; +-- explain performance +\o insert_update_explain.txt +BEGIN; +EXPLAIN PERFORMANCE +INSERT INTO products_row + SELECT product_id, product_name, category, total FROM newproducts_row + ON DUPLICATE KEY UPDATE + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; +ROLLBACK; +\o +-- explain analyze +BEGIN; +EXPLAIN (ANALYZE on, COSTS off, TIMING off) +INSERT INTO products_row + SELECT product_id, product_name, category, total FROM newproducts_row + ON DUPLICATE KEY UPDATE + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; + QUERY PLAN +----------------------------------------------------------- + Insert on products_row (actual rows=4 loops=1) + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: products_row_pkey + -> Seq Scan on newproducts_row (actual rows=4 loops=1) +--? Total runtime: .* ms +(5 rows) + +ROLLBACK; +-- pretty mode performance +SET explain_perf_mode = pretty; +-- explain verbose +EXPLAIN (VERBOSE on, COSTS off) +INSERT INTO products_row + SELECT product_id, product_name, category, total FROM newproducts_row + ON DUPLICATE KEY UPDATE + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------- + Insert on test_insert_update_008.products_row + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: products_row_pkey + -> Seq Scan on test_insert_update_008.newproducts_row + Output: newproducts_row.product_id, newproducts_row.product_name, newproducts_row.category, newproducts_row.total +(5 rows) + +-- explain analyze +BEGIN; +EXPLAIN (ANALYZE on, COSTS off, TIMING off) +INSERT INTO products_row + SELECT product_id, product_name, category, total FROM newproducts_row + ON DUPLICATE KEY UPDATE + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; + QUERY PLAN +----------------------------------------------------------- + Insert on products_row (actual rows=4 loops=1) + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: products_row_pkey + -> Seq Scan on newproducts_row (actual rows=4 loops=1) +--? Total runtime: .* ms +(5 rows) + +ROLLBACK; +-- explain analyze +BEGIN; +EXPLAIN (ANALYZE on, COSTS off, TIMING off) +INSERT INTO products_row + SELECT product_id, product_name, category, total FROM newproducts_row + ON DUPLICATE KEY UPDATE + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; + QUERY PLAN +----------------------------------------------------------- + Insert on products_row (actual rows=4 loops=1) + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: products_row_pkey + -> Seq Scan on newproducts_row (actual rows=4 loops=1) +--? Total runtime: .* ms +(5 rows) + +ROLLBACK; +-- explain performance +\o insert_update_explain_pretty.txt +BEGIN; +EXPLAIN PERFORMANCE +INSERT INTO products_row + SELECT product_id, product_name, category, total FROM newproducts_row + ON DUPLICATE KEY UPDATE + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; +ROLLBACK; +SET explain_perf_mode = run; +BEGIN; +EXPLAIN PERFORMANCE +INSERT INTO products_row + SELECT product_id, product_name, category, total FROM newproducts_row + ON DUPLICATE KEY UPDATE + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; +ROLLBACK; +SET explain_perf_mode = summary; +BEGIN; +EXPLAIN PERFORMANCE +INSERT INTO products_row + SELECT product_id, product_name, category, total FROM newproducts_row + ON DUPLICATE KEY UPDATE + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; +ROLLBACK; +\o +CREATE TABLE item +( + a INT DEFAULT 3, + item_id NUMERIC(18,10), + item_name VARCHAR(100), + item_level NUMERIC(39,0), + item_desc VARCHAR(250), + item_subclass_cd VARCHAR(50), + item_type_cd VARCHAR(50), + inventory_ind CHAR(300), + vendor_party_id SMALLINT, + commodity_cd VARCHAR(50), + brand_cd VARCHAR(50), + item_available CHAR(100), + CONSTRAINT u_item_index UNIQUE (item_subclass_cd, vendor_party_id) +) +PARTITION BY RANGE (vendor_party_id) +( +PARTITION item_1 VALUES LESS THAN (0), +PARTITION item_2 VALUES LESS THAN (1), +PARTITION item_3 VALUES LESS THAN (2), +PARTITION item_4 VALUES LESS THAN (3), +PARTITION item_5 VALUES LESS THAN (6), +PARTITION item_6 VALUES LESS THAN (8), +PARTITION item_7 VALUES LESS THAN (10), +PARTITION item_8 VALUES LESS THAN (15), +PARTITION item_9 VALUES LESS THAN (MAXVALUE) +) ENABLE ROW MOVEMENT; +NOTICE: CREATE TABLE / UNIQUE will create implicit index "u_item_index" for table "item" +CREATE TABLE region +( + a INT DEFAULT 8, + region_cd VARCHAR(50), + region_name VARCHAR(100), + division_cd VARCHAR(50), + region_mgr_associate_id number(18,9) +); +CREATE TABLE associate_benefit_expense +( + a INT DEFAULT 44, + period_end_dt DATE, + associate_expns_type_cd VARCHAR(50), + associate_party_id INTEGER, + benefit_hours_qty decimal(38,11), + benefit_cost_amt number(38,4) +) +PARTITION BY RANGE (associate_expns_type_cd) +( +PARTITION associate_benefit_expense_1 VALUES LESS THAN ('B'), +PARTITION associate_benefit_expense_2 VALUES LESS THAN ('E'), +PARTITION associate_benefit_expense_3 VALUES LESS THAN ('G'), +PARTITION associate_benefit_expense_4 VALUES LESS THAN ('I'), +PARTITION associate_benefit_expense_5 VALUES LESS THAN ('L'), +PARTITION associate_benefit_expense_6 VALUES LESS THAN ('N'), +PARTITION associate_benefit_expense_7 VALUES LESS THAN ('P'), +PARTITION associate_benefit_expense_8 VALUES LESS THAN ('Q'), +PARTITION associate_benefit_expense_9 VALUES LESS THAN ('R'), +PARTITION associate_benefit_expense_10 VALUES LESS THAN ('T'), +PARTITION associate_benefit_expense_11 VALUES LESS THAN ('U'), +PARTITION associate_benefit_expense_12 VALUES LESS THAN ('V'), +PARTITION associate_benefit_expense_13 VALUES LESS THAN (MAXVALUE) +) ENABLE ROW MOVEMENT; +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (0.12, ' ' , 'A' , NULL, 'TGK' , 'A' , 2, 'A' , 'A' , 'Y'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (1.3, 'B' , NULL, 'B' , 'B' , NULL, 1, 'B' , NULL , 'N'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (2.23, 'C' , 'C' , NULL, 'C' , 'C' , 2, 'C' , 'C' , 'N'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (3.33, 'D' , 'D' , 'PT' , NULL, 'D' , 3, 'D' , 'D' , 'N'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (4.98, ' ' , NULL, 'E' , 'E' , 'E' , 4, 'E' , 'E' , 'Y'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (5.01, NULL, 'F' , ' ' , 'F' , 'F' , 5, 'F' , 'F' , 'N'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (6, 'G' , 'G' , 'G' , '_D' , 'G' , 6, 'G' , NULL ,'N'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (0.7, NULL, NULL, NULL, 'H' , 'H' , 7, NULL, 'G' , 'Y'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (0.08, 'I' , ' ' , ' T ' , NULL, 'I' , 8, 'I' , '' , 'N'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (9.12, ' ' , 'J' , ' PP' , 'J' , 'J' , 9, 'J' , NULL , 'Y'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (10.10, NULL, ' ' , 'A' , 'A' , 'A' , 2, NULL, 'A','Y'); +--INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (11.11, 'B' , 'B' , 'B' , 'BCDAA' , NULL, 1, 'B' , 'B','N'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (12.02, 'D' , NULL, NULL, 'C' , 'C' , 2, 'C' , 'C','N'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (13.99, NULL, ' ' , 'D' , 'D' , 'D' , 3, 'D' , 'D','Y'); +--INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (14, 'G' , 'E' , 'E' , NULL, 'E' , 4, 'E' , 'E','N'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (15, 'F' , ' ' , 'C' , 'CLEANING' , 'F' , 5, 'F' , 'F','Y'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (16, '' , 'Z' , NULL, 'G' , 'G' , 6, 'G' , NULL,'N'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (17, NULL, '' , ' PAPER' , 'H' , '' , 7, NULL, NULL,'Y'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (19, ' ' , 'B' , '' , '' , 'I' , 8, 'I' , NULL,'N'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (20 , 'A' , 'J' , 'J' , 'J' , NULL, 9, 'J' , 'G','Y'); +/*--REGION--*/ +INSERT INTO REGION (REGION_CD, REGION_NAME, DIVISION_CD, REGION_MGR_ASSOCIATE_ID) VALUES ('A', 'A ', 'A', 0.123433); +INSERT INTO REGION (REGION_CD, REGION_NAME, DIVISION_CD, REGION_MGR_ASSOCIATE_ID) VALUES ('B', 'B', 'B', NULL); +INSERT INTO REGION (REGION_CD, REGION_NAME, DIVISION_CD, REGION_MGR_ASSOCIATE_ID) VALUES ('C', 'C', 'C', 2.232008908); +INSERT INTO REGION (REGION_CD, REGION_NAME, DIVISION_CD, REGION_MGR_ASSOCIATE_ID) VALUES ('D', ' DD', 'D', 3.878789); +INSERT INTO REGION (REGION_CD, REGION_NAME, DIVISION_CD, REGION_MGR_ASSOCIATE_ID) VALUES ('E', 'A', 'E', 4.89060603); +INSERT INTO REGION (REGION_CD, REGION_NAME, DIVISION_CD, REGION_MGR_ASSOCIATE_ID) VALUES ('F', 'F', 'F', 5.82703827); +INSERT INTO REGION (REGION_CD, REGION_NAME, DIVISION_CD, REGION_MGR_ASSOCIATE_ID) VALUES ('G', 'G', 'TTT', NULL); +INSERT INTO REGION (REGION_CD, REGION_NAME, DIVISION_CD, REGION_MGR_ASSOCIATE_ID) VALUES ('H', 'H', 'G', 7.3829083); +INSERT INTO REGION (REGION_CD, REGION_NAME, DIVISION_CD, REGION_MGR_ASSOCIATE_ID) VALUES ('I', 'C', 'M', 8.983989); +INSERT INTO REGION (REGION_CD, REGION_NAME, DIVISION_CD, REGION_MGR_ASSOCIATE_ID) VALUES ('J', 'J', 'G', NULL); +INSERT INTO REGION (REGION_CD, REGION_NAME, DIVISION_CD, REGION_MGR_ASSOCIATE_ID) VALUES ('K', ' ', 'C', 2.232008908); +INSERT INTO REGION (REGION_CD, REGION_NAME, DIVISION_CD, REGION_MGR_ASSOCIATE_ID) VALUES ('L', 'D', 'X', 3.878789); +INSERT INTO REGION (REGION_CD, REGION_NAME, DIVISION_CD, REGION_MGR_ASSOCIATE_ID) VALUES ('M', 'TTTTTT ', 'D' , 4.89060603); +INSERT INTO REGION (REGION_CD, REGION_NAME, DIVISION_CD, REGION_MGR_ASSOCIATE_ID) VALUES ('N', 'G' , 'B' , NULL); +INSERT INTO REGION (REGION_CD, REGION_NAME, DIVISION_CD, REGION_MGR_ASSOCIATE_ID) VALUES ('O' , 'G', 'F', 6.6703972); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1970-01-01', 'A', 5, 0.5 , 0.5); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1973-01-01', 'B', 1, NULL, 1.0); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1976-01-01', 'C', 2, 2.0 , NULL); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1979-01-01', 'D', 3, 3.0 , 3.0); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1982-01-01', 'E', 4, 4.0 , 4.0); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1985-01-01', 'F', 5, 5.0 , 5.0); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1988-01-01', 'F', 6, NULL, 6.0); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1991-01-01', 'G', 6, NULL, NULL); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1994-01-01', 'G', 15, 8.0 , 8.0); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1997-01-01', 'G', 16, 9.0 , 9.0); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1983-01-03', 'I', 14, 4.0 , 4.0); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1984-01-01', 'GO', 15, 5.0 , NULL); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1985-05-01', 'I', 16, 6.0 , 6.0); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1990-01-01', 'TTT', 16, NULL, 7.0); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1992-02-01', 'A', 15, 8.0 , 8.0); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1997-02-01', 'G', 17, 9.0 , NULL); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1997-05-01', 'G' , 17, 9.0 , NULL); +ANALYZE item; +ANALYZE region; +ANALYZE associate_benefit_expense; +EXPLAIN (VERBOSE ON, COSTS OFF) +INSERT INTO item (item_level, item_subclass_cd, item_desc, vendor_party_id) + SELECT Table_001.REGION_MGR_ASSOCIATE_ID Column_003, + Table_002.associate_expns_type_cd Column_004, + CAST(Table_001.region_name AS VARCHAR) Column_005, + 10 Column_006 +-- 'o' Column_007, +-- 'F' Column_008, +-- pg_client_encoding() Column_009 + FROM region Table_001, associate_benefit_expense Table_002 +ON DUPLICATE KEY UPDATE item_level = -1000; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Insert on test_insert_update_008.item + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: u_item_index + -> Nested Loop + Output: 3, NULL::numeric, NULL::character varying, table_001.region_mgr_associate_id, (table_001.region_name)::character varying(250), table_002.associate_expns_type_cd, NULL::character varying, NULL::bpchar, 10::smallint, NULL::character varying, NULL::character varying, NULL::bpchar + -> Partition Iterator + Output: table_002.a, table_002.period_end_dt, table_002.associate_expns_type_cd, table_002.associate_party_id, table_002.benefit_hours_qty, table_002.benefit_cost_amt + Iterations: 13 + -> Partitioned Seq Scan on test_insert_update_008.associate_benefit_expense table_002 + Output: table_002.a, table_002.period_end_dt, table_002.associate_expns_type_cd, table_002.associate_party_id, table_002.benefit_hours_qty, table_002.benefit_cost_amt + Selected Partitions: 1..13 + -> Materialize + Output: table_001.region_mgr_associate_id, table_001.region_name + -> Seq Scan on test_insert_update_008.region table_001 + Output: table_001.region_mgr_associate_id, table_001.region_name +(15 rows) + +EXPLAIN (VERBOSE ON, COSTS OFF) +INSERT INTO products_row VALUES(100) + ON DUPLICATE KEY UPDATE total=100; + QUERY PLAN +----------------------------------------------------------------------------------------- + Insert on test_insert_update_008.products_row + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: products_row_pkey + -> Result + Output: 100, 'null'::character varying(60), 'unknown'::character varying(60), 0 +(5 rows) + +SET enable_light_proxy=off; +EXPLAIN (VERBOSE ON, COSTS OFF) +INSERT INTO products_row VALUES(100) + ON DUPLICATE KEY UPDATE total=100; + QUERY PLAN +----------------------------------------------------------------------------------------- + Insert on test_insert_update_008.products_row + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: products_row_pkey + -> Result + Output: 100, 'null'::character varying(60), 'unknown'::character varying(60), 0 +(5 rows) + +EXPLAIN (VERBOSE ON, COSTS OFF) +INSERT INTO products_row VALUES(100) + ON DUPLICATE KEY UPDATE total=100; + QUERY PLAN +----------------------------------------------------------------------------------------- + Insert on test_insert_update_008.products_row + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: products_row_pkey + -> Result + Output: 100, 'null'::character varying(60), 'unknown'::character varying(60), 0 +(5 rows) + +EXPLAIN (VERBOSE ON, COSTS OFF) +INSERT INTO products_row VALUES(100) + ON DUPLICATE KEY UPDATE total=100; + QUERY PLAN +----------------------------------------------------------------------------------------- + Insert on test_insert_update_008.products_row + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: products_row_pkey + -> Result + Output: 100, 'null'::character varying(60), 'unknown'::character varying(60), 0 +(5 rows) + +RESET enable_light_proxy; +DROP SCHEMA test_insert_update_008 CASCADE; +NOTICE: drop cascades to 7 other objects +DETAIL: drop cascades to table products_base +drop cascades to table newproducts_base +drop cascades to table products_row +drop cascades to table newproducts_row +drop cascades to table item +drop cascades to table region +drop cascades to table associate_benefit_expense diff --git a/src/test/regress/expected/upsert_009.out b/src/test/regress/expected/upsert_009.out new file mode 100644 index 000000000..ab5bad7ad --- /dev/null +++ b/src/test/regress/expected/upsert_009.out @@ -0,0 +1,548 @@ +DROP SCHEMA test_insert_update_009 CASCADE; +ERROR: schema "test_insert_update_009" does not exist +CREATE SCHEMA test_insert_update_009; +SET CURRENT_SCHEMA TO test_insert_update_009; +SET enable_light_proxy=off; +-- enable_upsert_to_merge must is off, or upsert will be translated to merge. +SET enable_upsert_to_merge TO OFF; +-- test t1 with no index +CREATE TABLE t1 ( + col1 INT, + col2 INT, + col3 INT DEFAULT 1, +-- col4 TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + col5 BIGSERIAL +) ; +NOTICE: CREATE TABLE will create implicit sequence "t1_col5_seq" for serial column "t1.col5" +--- distribute key are not allowed to update +INSERT INTO t1 VALUES (1, 2) ON DUPLICATE KEY UPDATE col1 = 3; +--- should always insert +INSERT INTO t1 VALUES (1, 2) ON DUPLICATE KEY UPDATE col2 = 3; +INSERT INTO t1 VALUES (1, 2) ON DUPLICATE KEY UPDATE t1.col2 = 4; +--- appoint column list in insert clause, should always insert +INSERT INTO t1(col1, col3) VALUES (1, 3) ON DUPLICATE KEY UPDATE col2 = 5; +INSERT INTO t1(col1, col3) VALUES (1, 3) ON DUPLICATE KEY UPDATE t1.col2 = 6; +--- multiple rows, should always insert +INSERT INTO t1 VALUES (2, 1), (2, 1) ON DUPLICATE KEY UPDATE col2 = 7, col3 = 7; +SELECT * FROM t1 ORDER BY col5; + col1 | col2 | col3 | col5 +------+------+------+------ + 1 | 2 | 1 | 1 + 1 | 2 | 1 | 2 + 1 | 2 | 1 | 3 + 1 | | 3 | 4 + 1 | | 3 | 5 + 2 | 1 | 1 | 6 + 2 | 1 | 1 | 7 +(7 rows) + +--- test union, should insert +INSERT INTO t1 (col1, col2) + SELECT * FROM + (SELECT col1, col2 FROM t1 + UNION + SELECT col1, col3 FROM t1) AS union_table + ON DUPLICATE KEY UPDATE col3 = (col1 + col2 + col3); +SELECT col1, col2, col3 FROM t1 WHERE col5 > 6 ORDER BY col1, col2; + col1 | col2 | col3 +------+------+------ + 1 | 1 | 1 + 1 | 2 | 1 + 1 | 3 | 1 + 1 | | 1 + 2 | 1 | 1 + 2 | 1 | 1 +(6 rows) + +--- test subquery, should insert +INSERT INTO t1 + (SELECT col1 || col2 || '00' FROM t1 ORDER BY col5) + ON DUPLICATE KEY UPDATE col3 = col1 * 100; +SELECT col1, col2, col3 FROM t1 WHERE col1 >= 100 ORDER BY col1; + col1 | col2 | col3 +------+------+------ + 100 | | 1 + 100 | | 1 + 100 | | 1 + 1100 | | 1 + 1200 | | 1 + 1200 | | 1 + 1200 | | 1 + 1200 | | 1 + 1300 | | 1 + 2100 | | 1 + 2100 | | 1 + 2100 | | 1 +(12 rows) + +-- test t2 with one primary key +CREATE TABLE t2 ( + col1 INT, + col2 INT PRIMARY KEY, + col3 INT DEFAULT 1, + col4 TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + col5 BIGSERIAL +) ; +NOTICE: CREATE TABLE will create implicit sequence "t2_col5_seq" for serial column "t2.col5" +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t2_pkey" for table "t2" +--- distribute key are not allowed to update +INSERT INTO t2 VALUES (1, 1) ON DUPLICATE KEY UPDATE col2 = 3; +ERROR: INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key. +INSERT INTO t2 VALUES (1, 1) ON DUPLICATE KEY UPDATE t2.col2 = 3; +ERROR: INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key. +INSERT INTO t2 (col2, col3, col4, col5) + VALUES (10, 10, CURRENT_TIMESTAMP(0), 10), + (20, 20, CURRENT_TIMESTAMP(1), 20), + (30, 30, CURRENT_TIMESTAMP(2), 30) + ON DUPLICATE KEY UPDATE + col1 = 100, + col2 = 100, + col3 = 100, + col4 = '2019-08-09'::TIMESTAMP, + col5 = 100; +ERROR: INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key. +--- should insert +INSERT INTO t2 VALUES (1, 1) ON DUPLICATE KEY UPDATE col1 = 30; +INSERT INTO t2 VALUES (2, 2) ON DUPLICATE KEY UPDATE t2.col1 = 40; +INSERT INTO t2 VALUES (3, 3) ON DUPLICATE KEY UPDATE col1 = col1 * 2; +INSERT INTO t2 VALUES (4, 4) ON DUPLICATE KEY UPDATE t2.col1 = t2.col1 * 2 ; +INSERT INTO t2 VALUES (5, 5) ON DUPLICATE KEY UPDATE col1 = col2 + 1; +INSERT INTO t2 VALUES (6, 6) ON DUPLICATE KEY UPDATE t2.col1 = t2.col2 + 1; +INSERT INTO t2 VALUES (7, 7) ON DUPLICATE KEY UPDATE col1 = extract(dow from col4) + 10; +INSERT INTO t2 VALUES (8, 8) ON DUPLICATE KEY UPDATE t2.col1 = extract(century from col4) * 100 + extract(isodow from col4); +SELECT col1, col2, col3, col5 FROM t2 ORDER BY col5; + col1 | col2 | col3 | col5 +------+------+------+------ + 1 | 1 | 1 | 1 + 2 | 2 | 1 | 2 + 3 | 3 | 1 | 3 + 4 | 4 | 1 | 4 + 5 | 5 | 1 | 5 + 6 | 6 | 1 | 6 + 7 | 7 | 1 | 7 + 8 | 8 | 1 | 8 +(8 rows) + +--- should update +INSERT INTO t2 VALUES (3, 1) ON DUPLICATE KEY UPDATE col1 = 30, col3 = col5 + 1; +INSERT INTO t2 VALUES (4, 2) ON DUPLICATE KEY UPDATE t2.col1 = 40, t2.col3 = t2.col5; +INSERT INTO t2 VALUES (3, 3), (4, 4) ON DUPLICATE KEY UPDATE col3 = t2.col5 + 1; +INSERT INTO t2 VALUES (5, 5) ON DUPLICATE KEY UPDATE col1 = extract(dow from col4) + 10; +INSERT INTO t2 VALUES (6, 6) ON DUPLICATE KEY UPDATE t2.col1 = extract(century from col4) * 100 + extract(isodow from col4); +SELECT col1, col2, col3, col5 FROM t2 ORDER BY col5; + col1 | col2 | col3 | col5 +------+------+------+------ + 30 | 1 | 2 | 1 + 40 | 2 | 2 | 2 + 3 | 3 | 4 | 3 + 4 | 4 | 5 | 4 +--?.*| 5 | 1 | 5 +--?.*| 6 | 1 | 6 + 7 | 7 | 1 | 7 + 8 | 8 | 1 | 8 +(8 rows) + +-- primary key are not allowed to be null +INSERT INTO t2 (col1) VALUES (10) ON DUPLICATE KEY UPDATE col1 = 10; +ERROR: null value in column "col2" violates not-null constraint +--? +--- appoint column list in insert clause +---- should insert +INSERT INTO t2 (col2, col3) VALUES (9, 9) ON DUPLICATE KEY UPDATE col1 = 90; +INSERT INTO t2 (col2, col3, col4, col5) + VALUES (10, 10, CURRENT_TIMESTAMP(0), 10), + (20, 20, CURRENT_TIMESTAMP(1), 20), + (30, 30, CURRENT_TIMESTAMP(2), 30) + ON DUPLICATE KEY UPDATE + col1 = 100, + col3 = 100, + col4 = '2019-08-20'::TIMESTAMP, + col5 = 100; +SELECT * FROM t2 ORDER BY col5; + col1 | col2 | col3 | col4 | col5 +------+------+------+---------------------------------+------ +--? 30 | 1 | 2 | .* | 1 +--? 40 | 2 | 2 | .* | 2 +--? 3 | 3 | 4 | .* | 3 +--? 4 | 4 | 5 | .* | 4 +--? .* | 5 | 1 | .* | 5 +--? .* | 6 | 1 | .* | 6 +--? 7 | 7 | 1 | .* | 7 +--? 8 | 8 | 1 | .* | 8 +--? | 9 | 9 | .* | 10 +--? | 10 | 10 | .* | 10 +--? | 20 | 20 | .* | 20 +--? | 30 | 30 | .* | 30 +(12 rows) + +---- should update +INSERT INTO t2 (col2, col3) VALUES (9, 9) ON DUPLICATE KEY UPDATE col1 = 90; +INSERT INTO t2 (col2, col3, col4, col5) + VALUES (10, 10, CURRENT_TIMESTAMP(0), 10), + (20, 20, CURRENT_TIMESTAMP(1), 20), + (30, 30, CURRENT_TIMESTAMP(2), 30) + ON DUPLICATE KEY UPDATE + col1 = 100, + col3 = 100, + col4 = '2019-08-20'::TIMESTAMP, + col5 = 100; +SELECT * FROM t2 ORDER BY col5, col2; + col1 | col2 | col3 | col4 | col5 +------+------+------+---------------------------------+------ +--? 30 | 1 | 2 | .* | 1 +--? 40 | 2 | 2 | .* | 2 +--? 3 | 3 | 4 | .* | 3 +--? 4 | 4 | 5 | .* | 4 +--? .* | 5 | 1 | .* | 5 +--? .* | 6 | 1 | .* | 6 +--? 7 | 7 | 1 | .* | 7 +--? 8 | 8 | 1 | .* | 8 +--? 90 | 9 | 9 | .* | 10 + 100 | 10 | 100 | Tue Aug 20 00:00:00 2019 | 100 + 100 | 20 | 100 | Tue Aug 20 00:00:00 2019 | 100 + 100 | 30 | 100 | Tue Aug 20 00:00:00 2019 | 100 +(12 rows) + +--- test subquery +---- should insert +INSERT INTO t2 + (SELECT col1 * 1000, col2 * 1000 + 1 FROM t2 ORDER BY col5 LIMIT 2) + ON DUPLICATE KEY UPDATE col3 = col2 + 1; +INSERT INTO t2 (col2, col3) + (SELECT col1 * 1000 + 2, col2 * 1000 FROM t2 ORDER BY col5 LIMIT 2 OFFSET 1) + ON DUPLICATE KEY UPDATE col3 = col2 + 1; +SELECT col1, col2, col3, col5 FROM t2 ORDER BY col5, col2; + col1 | col2 | col3 | col5 +-------+-------+------+------ + 30 | 1 | 2 | 1 + 40 | 2 | 2 | 2 + 3 | 3 | 4 | 3 + 4 | 4 | 5 | 4 +--? .* | 5 | 1 | 5 +--? .* | 6 | 1 | 6 + 7 | 7 | 1 | 7 + 8 | 8 | 1 | 8 + 90 | 9 | 9 | 16 +--? 30000 | 1001 | 1 | 11 +--? 40000 | 2001 | 1 | 12 +--? | 40002 | 2000 | 13 +--? | 3002 | 3000 | 14 + 100 | 10 | 100 | 100 + 100 | 20 | 100 | 100 + 100 | 30 | 100 | 100 +(16 rows) + +---- should update +INSERT INTO t2 + (SELECT col1 * 1000, col2 * 1000 + 1 FROM t2 ORDER BY col5 LIMIT 2) + ON DUPLICATE KEY UPDATE col3 = col2 + 1; +INSERT INTO t2 (col2, col3) + (SELECT col1 * 1000 + 2, col2 * 1000 FROM t2 ORDER BY col5 LIMIT 2 OFFSET 1) + ON DUPLICATE KEY UPDATE col3 = col2 + 1; +SELECT col1, col2, col3, col5 FROM t2 ORDER BY col5, col2; + col1 | col2 | col3 | col5 +-------+-------+-------+------ + 30 | 1 | 2 | 1 + 40 | 2 | 2 | 2 + 3 | 3 | 4 | 3 + 4 | 4 | 5 | 4 +--? 2 | 5 | 1 | 5 +--? 2101 | 6 | 1 | 6 + 7 | 7 | 1 | 7 + 8 | 8 | 1 | 8 + 90 | 9 | 9 | 16 +--? 30000 | 1001 | 1002 | 11 +--? 40000 | 2001 | 2002 | 12 +--? | 40002 | 40003 | 13 +--? | 3002 | 3003 | 14 + 100 | 10 | 100 | 100 + 100 | 20 | 100 | 100 + 100 | 30 | 100 | 100 +(16 rows) + +--- test union, some insert some update +SELECT col1, col2 FROM t1 WHERE col2 IS NOT NULL + UNION + SELECT col1, col3 FROM t1 ORDER BY 1, 2; + col1 | col2 +------+------ + 1 | 1 + 1 | 2 + 1 | 3 + 2 | 1 + 100 | 1 + 1100 | 1 + 1200 | 1 + 1300 | 1 + 2100 | 1 +(9 rows) + +INSERT INTO t2 (col1, col2) + SELECT * FROM + (SELECT col1, col2 FROM t1 WHERE col2 IS NOT NULL + UNION + SELECT col1, col3 FROM t1) AS union_table + ON DUPLICATE KEY UPDATE col3 = (col1 + col2 + col3); +SELECT col1, col2, col3, col5 FROM t2 ORDER BY col5, col2; + col1 | col2 | col3 | col5 +-------+-------+-------+------ + 30 | 1 | 219 | 1 + 40 | 2 | 44 | 2 + 3 | 3 | 10 | 3 + 4 | 4 | 5 | 4 +--? .* | 5 | 1 | 5 +--? .* | 6 | 1 | 6 + 7 | 7 | 1 | 7 + 8 | 8 | 1 | 8 + 90 | 9 | 9 | 16 +--? 30000 | 1001 | 1002 | 11 +--? 40000 | 2001 | 2002 | 12 +--? | 40002 | 40003 | 13 +--? | 3002 | 3003 | 14 + 100 | 10 | 100 | 100 + 100 | 20 | 100 | 100 + 100 | 30 | 100 | 100 +(16 rows) + +INSERT INTO t2 (col1, col2) + SELECT col1, col2 FROM t1 WHERE col2 IS NOT NULL + UNION + SELECT col1, col3 FROM t1 + ON DUPLICATE KEY UPDATE col3 = (col1 + col2 + col3); +SELECT col1, col2, col3, col5 FROM t2 ORDER BY col5, col2; + col1 | col2 | col3 | col5 +-------+-------+-------+------ + 30 | 1 | 436 | 1 + 40 | 2 | 86 | 2 + 3 | 3 | 16 | 3 + 4 | 4 | 5 | 4 +--? 15 | 5 | 1 | 5 +--? 2105 | 6 | 1 | 6 + 7 | 7 | 1 | 7 + 8 | 8 | 1 | 8 + 90 | 9 | 9 | 16 +--? 30000 | 1001 | 1002 | 11 +--? 40000 | 2001 | 2002 | 12 +--? | 40002 | 40003 | 13 +--? | 3002 | 3003 | 14 + 100 | 10 | 100 | 100 + 100 | 20 | 100 | 100 + 100 | 30 | 100 | 100 +(16 rows) + +reset behavior_compat_options; +-- test INTERSECT, should update +(SELECT col1, col1 + col2 FROM t1 WHERE col2 IS NOT NULL + UNION + SELECT 1, 2) + INTERSECT + SELECT col1, col3 FROM t1; + col1 | ?column? +------+---------- + 1 | 3 +(1 row) + +INSERT INTO t2 (col1, col2) + (SELECT col1, col1 + col2 FROM t1 WHERE col2 IS NOT NULL + UNION + SELECT 1, 2) + INTERSECT + SELECT col1, col3 FROM t1 + ON DUPLICATE KEY UPDATE col3 = (col1 + col2 + col3); +SELECT col1, col2, col3, col5 FROM t2 WHERE col2 = 3 ORDER BY col5, col2; + col1 | col2 | col3 | col5 +------+------+------+------ + 3 | 3 | 22 | 3 +(1 row) + +-- test EXCEPT, should update +SELECT col1, col2 FROM t1 WHERE col2 IS NOT NULL + EXCEPT + (SELECT col1, col3 FROM t1 + UNION + SELECT NULL, NULL); + col1 | col2 +------+------ + 1 | 2 +(1 row) + +INSERT INTO t2 (col1, col2) + SELECT col1, col2 FROM t1 WHERE col2 IS NOT NULL + EXCEPT + (SELECT col1, col3 FROM t1 + UNION + SELECT NULL, NULL) + ON DUPLICATE KEY UPDATE col3 = (col1 + col2 + col3); +SELECT col1, col2, col3, col5 FROM t2 WHERE col2 = 2 ORDER BY col5, col2; + col1 | col2 | col3 | col5 +------+------+------+------ + 40 | 2 | 128 | 2 +(1 row) + +-- test unique index with not default value +ALTER TABLE t2 DROP CONSTRAINT t2_pkey; +CREATE UNIQUE INDEX t2_u_index ON t2(col2, col5); +SELECT col1, col2 FROM t1 WHERE col2 IS NOT NULL + EXCEPT + (SELECT col1, col3 FROM t1 + UNION + SELECT NULL, NULL); + col1 | col2 +------+------ + 1 | 2 +(1 row) + +INSERT INTO t2 + SELECT col1, col2 FROM t1 WHERE col2 IS NOT NULL + EXCEPT + (SELECT col1, col3 FROM t1 + UNION + SELECT NULL, NULL) + ON DUPLICATE KEY UPDATE col3 = (col1 + col2 + col3); +SELECT col1, col2, col3, col5 FROM t2 WHERE col2 = 2 ORDER BY col5, col2; + col1 | col2 | col3 | col5 +------+------+------+------ + 40 | 2 | 128 | 2 + 1 | 2 | 1 | 46 +(2 rows) + +-- test t3 with one primary index with two columns +CREATE TABLE t3 ( + col1 INT, + col2 INT, + col3 INT DEFAULT 1, +-- col4 TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + col5 BIGSERIAL, + PRIMARY KEY (col2, col3) +) ; +NOTICE: CREATE TABLE will create implicit sequence "t3_col5_seq" for serial column "t3.col5" +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t3_pkey" for table "t3" +--- column referenced by primary key are not allowed to update +INSERT INTO t3 VALUES (1, 1) ON DUPLICATE KEY UPDATE col3 = 3; +ERROR: INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key. +--- should insert when not contains primary key and not all primary key referred columns have default value. +--- but will fail cause primary key should not be null +INSERT INTO t3 (col1) VALUES (1) ON DUPLICATE KEY UPDATE col1 = 2; +ERROR: null value in column "col2" violates not-null constraint +--?.* +--- should insert +--- (SEQUENCE BUG: the serial column will starts from 2 since the above statement has applied for a sequence) +INSERT INTO t3 VALUES (1, 1) ON DUPLICATE KEY UPDATE col1 = 10; +INSERT INTO t3 VALUES (2, 2, 2) ON DUPLICATE KEY UPDATE col5 = 20; +SELECT * FROM t3 order by col5; + col1 | col2 | col3 | col5 +------+------+------+------ + 1 | 1 | 1 | 2 + 2 | 2 | 2 | 3 +(2 rows) + +--- should update +INSERT INTO t3 VALUES (1, 1) ON DUPLICATE KEY UPDATE col1 = 10; +INSERT INTO t3 VALUES (2, 2, 2) ON DUPLICATE KEY UPDATE col5 = 20; +SELECT * FROM t3 order by col5; + col1 | col2 | col3 | col5 +------+------+------+------ + 10 | 1 | 1 | 2 + 2 | 2 | 2 | 20 +(2 rows) + +--- test subquery +---- should insert +INSERT INTO t3 (col2, col3) (SELECT max(col2) + 1, max(col3) + 1 FROM t3) ON DUPLICATE KEY UPDATE col1 = col2 + col3; +SELECT * FROM t3 order by col5; + col1 | col2 | col3 | col5 +------+------+------+------ + 10 | 1 | 1 | 2 + | 3 | 3 | 6 + 2 | 2 | 2 | 20 +(3 rows) + +---- should update +INSERT INTO t3 (col2, col3) (SELECT max(col2), max(col3) FROM t3) ON DUPLICATE KEY UPDATE col1 = col2 + col3; +SELECT * FROM t3 order by col5; + col1 | col2 | col3 | col5 +------+------+------+------ + 10 | 1 | 1 | 2 + 6 | 3 | 3 | 6 + 2 | 2 | 2 | 20 +(3 rows) + +-- test t3 with one unique index with two columns +TRUNCATE t3; +ALTER TABLE t3 DROP CONSTRAINT t3_pkey; +ALTER TABLE t3 ALTER COLUMN col2 DROP NOT NULL; +CREATE UNIQUE INDEX t3_ukey ON t3 (col2, col3); +--- column referenced by unique key are not allowed to update +INSERT INTO t3 VALUES (1, 1) ON DUPLICATE KEY UPDATE col3 = 3; +ERROR: INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key. +--- should insert cause not contains unique key and not all unique key referred columns have default value. +INSERT INTO t3 (col1) VALUES (1) ON DUPLICATE KEY UPDATE col1 = 2; +INSERT INTO t3 (col1) VALUES (1) ON DUPLICATE KEY UPDATE col1 = 2; +SELECT * FROM t3 order by col5; + col1 | col2 | col3 | col5 +------+------+------+------ + 1 | | 1 | 8 + 1 | | 1 | 9 +(2 rows) + +--- should insert +INSERT INTO t3 VALUES (1, 1) ON DUPLICATE KEY UPDATE col1 = 10; +INSERT INTO t3 VALUES (2, 2, 2) ON DUPLICATE KEY UPDATE t3.col5 = 20; +SELECT * FROM t3 ORDER BY col5; + col1 | col2 | col3 | col5 +------+------+------+------ + 1 | | 1 | 8 + 1 | | 1 | 9 + 1 | 1 | 1 | 10 + 2 | 2 | 2 | 11 +(4 rows) + +--- should update +INSERT INTO t3 VALUES (1, 1) ON DUPLICATE KEY UPDATE t3.col1 = 10; +INSERT INTO t3 VALUES (2, 2, 2) ON DUPLICATE KEY UPDATE col5 = 20; +SELECT * FROM t3 ORDER BY col5; + col1 | col2 | col3 | col5 +------+------+------+------ + 1 | | 1 | 8 + 1 | | 1 | 9 + 10 | 1 | 1 | 10 + 2 | 2 | 2 | 20 +(4 rows) + +--- test subquery +---- should insert +INSERT INTO t3 (SELECT 100, NULL, max(col3) FROM t3) ON DUPLICATE KEY UPDATE col1 = col2 + col3; +INSERT INTO t3 (SELECT 100, NULL, max(col3) FROM t3) ON DUPLICATE KEY UPDATE col1 = col2 + col3; +INSERT INTO t3 (col2, col3) (SELECT max(col2) + 1, max(col3) + 1 FROM t3) ON DUPLICATE KEY UPDATE col1 = col2 + col3; +SELECT * FROM t3 ORDER BY col5; + col1 | col2 | col3 | col5 +------+------+------+------ + 1 | | 1 | 8 + 1 | | 1 | 9 + 10 | 1 | 1 | 10 + 100 | | 2 | 14 + 100 | | 2 | 15 + | 3 | 3 | 16 + 2 | 2 | 2 | 20 +(7 rows) + +---- should update +INSERT INTO t3 (col2, col3) (SELECT max(col2), max(col3) FROM t3) ON DUPLICATE KEY UPDATE col1 = col2 + col3; +SELECT * FROM t3 ORDER BY col5; + col1 | col2 | col3 | col5 +------+------+------+------ + 1 | | 1 | 8 + 1 | | 1 | 9 + 10 | 1 | 1 | 10 + 100 | | 2 | 14 + 100 | | 2 | 15 + 6 | 3 | 3 | 16 + 2 | 2 | 2 | 20 +(7 rows) + +RESET enable_light_proxy; +DROP SCHEMA test_insert_update_009 CASCADE; +NOTICE: drop cascades to 3 other objects +DETAIL: drop cascades to table t1 +drop cascades to table t2 +drop cascades to table t3 diff --git a/src/test/regress/expected/upsert_010.out b/src/test/regress/expected/upsert_010.out new file mode 100644 index 000000000..4c506f245 --- /dev/null +++ b/src/test/regress/expected/upsert_010.out @@ -0,0 +1,313 @@ +DROP SCHEMA test_insert_update_010 CASCADE; +ERROR: schema "test_insert_update_010" does not exist +CREATE SCHEMA test_insert_update_010; +SET CURRENT_SCHEMA TO test_insert_update_010; +-- enable_upsert_to_merge must is off, or upsert will be translated to merge. +SET enable_upsert_to_merge TO OFF; +-- test t4 with one primary key with three columns +CREATE TABLE t4 ( + col1 INT, + col2 INT DEFAULT 0, + col3 INT DEFAULT 1, + col4 TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + col5 BIGSERIAL, + PRIMARY KEY (col2, col3, col5) +) ; +NOTICE: CREATE TABLE will create implicit sequence "t4_col5_seq" for serial column "t4.col5" +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t4_pkey" for table "t4" +--- should insert +INSERT INTO t4 VALUES (1) ON DUPLICATE KEY UPDATE col1 = 10; +INSERT INTO t4 VALUES (1) ON DUPLICATE KEY UPDATE col1 = 10; +INSERT INTO t4 VALUES (2, 2, 2) ON DUPLICATE KEY UPDATE col1 = 200; +INSERT INTO t4 VALUES (100, 100, 100, CURRENT_TIMESTAMP, 100) ON DUPLICATE KEY UPDATE col1 = 1000; +SELECT col1, col2, col3, col5 FROM t4 ORDER BY col5; + col1 | col2 | col3 | col5 +------+------+------+------ + 1 | 0 | 1 | 1 + 1 | 0 | 1 | 2 + 2 | 2 | 2 | 3 + 100 | 100 | 100 | 100 +(4 rows) + +--- should update +INSERT INTO t4 VALUES (2, 2, 2, CURRENT_TIMESTAMP, 3) ON DUPLICATE KEY UPDATE col1 = 200; +INSERT INTO t4 VALUES (100, 100, 100, CURRENT_TIMESTAMP, 100) ON DUPLICATE KEY UPDATE col1 = 1000; +SELECT col1, col2, col3, col5 FROM t4 ORDER BY col5; + col1 | col2 | col3 | col5 +------+------+------+------ + 1 | 0 | 1 | 1 + 1 | 0 | 1 | 2 + 200 | 2 | 2 | 3 + 1000 | 100 | 100 | 100 +(4 rows) + +--- error: duplicate key update on (x, x, 20) +--- this is because current version is not inplace update but merge, +--- so when the subquery contains multiple same values, it will cause duplicate insert failure. +SELECT col3, sum(col3) * 10 FROM t4 GROUP BY col3 ORDER BY 1, 2; + col3 | ?column? +------+---------- + 1 | 20 + 2 | 20 + 100 | 1000 +(3 rows) + +INSERT INTO t4 (col1, col5) + (SELECT col3, sum(col3) * 10 FROM t4 GROUP BY col3) + ON DUPLICATE KEY UPDATE col1 = 3; +-- test t5 with sequence or default column with volatile function in constaint index +CREATE TABLE t5 ( + col1 INT, + col2 INT DEFAULT 1, + col3 BIGSERIAL, +-- col4 TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + col5 INTEGER(10, 5) DEFAULT RANDOM() + 1 +) ; +NOTICE: CREATE TABLE will create implicit sequence "t5_col3_seq" for serial column "t5.col3" +-- test t5 with sequence column in constaint index +CREATE UNIQUE INDEX u_t5_index1 ON t5(col1, col3); +--- should insert +INSERT INTO t5 VALUES (1), (1), (1) ON DUPLICATE KEY UPDATE col2 = col3; +INSERT INTO t5 DEFAULT VALUES ON DUPLICATE KEY UPDATE col2 = col3; +SELECT * FROM t5 ORDER BY col3; + col1 | col2 | col3 | col5 +------+------+------+--------- +--? 1 | 1 | 1 | 1.58182 +--? 1 | 1 | 2 | 1.00814 +--? 1 | 1 | 3 | 1.50194 +--? | 1 | 4 | 1.12955 +(4 rows) + +--- should update +INSERT INTO t5 (col1, col3) VALUES (1, 1), (1, 2), (1, 3) ON DUPLICATE KEY UPDATE col5 = col2, col2 = col3 * 10; +SELECT * FROM t5 WHERE col1 = 1 ORDER BY col3; + col1 | col2 | col3 | col5 +------+------+------+--------- + 1 | 10 | 1 | 1.00000 + 1 | 20 | 2 | 1.00000 + 1 | 30 | 3 | 1.00000 +(3 rows) + +--- should some insert some update +INSERT INTO t5 (col1, col3) VALUES (2, 5), (2, 6); +SELECT col1, col2, col3 FROM t5 ORDER BY col3; + col1 | col2 | col3 +------+------+------ + 1 | 10 | 1 + 1 | 20 | 2 + 1 | 30 | 3 + | 1 | 4 + 2 | 1 | 5 + 2 | 1 | 6 +(6 rows) + +INSERT INTO t5 (col1) VALUES (2), (2), (2) ON DUPLICATE KEY UPDATE col2 = col3; +SELECT col1, col2, col3 FROM t5 ORDER BY col3; + col1 | col2 | col3 +------+------+------ + 1 | 10 | 1 + 1 | 20 | 2 + 1 | 30 | 3 + | 1 | 4 + 2 | 5 | 5 + 2 | 6 | 6 + 2 | 1 | 7 +(7 rows) + +--- should INSERT and sequence starting from 7 +INSERT INTO t5 VALUES (2), (2); +SELECT col1, col2, col3 FROM t5 ORDER BY col3; + col1 | col2 | col3 +------+------+------ + 1 | 10 | 1 + 1 | 20 | 2 + 1 | 30 | 3 + | 1 | 4 + 2 | 5 | 5 + 2 | 6 | 6 + 2 | 1 | 7 + 2 | 1 | 8 + 2 | 1 | 9 +(9 rows) + +-- test with volatile function as default column in constraint index +TRUNCATE t5; +DROP INDEX u_t5_index1; +CREATE UNIQUE INDEX u_t5_index2 ON t5(col1, col5) WHERE col1 > 2; +--- should insert +INSERT INTO t5 VALUES (3), (3), (3) ON DUPLICATE KEY UPDATE col2 = col3; +INSERT INTO t5 (col1) VALUES (4), (4), (4) ON DUPLICATE KEY UPDATE col2 = col3; +SELECT * FROM t5 ORDER BY col3; + col1 | col2 | col3 | col5 +------+------+------+--------- +--? 3 | 1 | 10 | 1.24521 +--? 3 | 1 | 11 | 1.80598 +--? 3 | 1 | 12 | 1.86845 +--? 4 | 1 | 13 | 1.65797 +--? 4 | 1 | 14 | 1.07881 +--? 4 | 1 | 15 | 1.69493 +(6 rows) + +--- should update +INSERT INTO t5 (col1, col5) SELECT col1, col5 FROM t5 where col1 = 3 ON DUPLICATE KEY UPDATE col2 = col5 * 100; +SELECT * FROM t5 ORDER BY col3; + col1 | col2 | col3 | col5 +------+------+------+--------- +--? 3 | 125 | 10 | 1.24521 +--? 3 | 181 | 11 | 1.80598 +--? 3 | 187 | 12 | 1.86845 +--? 4 | 1 | 13 | 1.65797 +--? 4 | 1 | 14 | 1.07881 +--? 4 | 1 | 15 | 1.69493 +(6 rows) + +--- test subquery +---- should insert +INSERT INTO t5 (col1, col2) SELECT col1, col2 FROM t5 ON DUPLICATE KEY UPDATE col2 = col5 * 100; +SELECT * FROM t5 ORDER BY col3; + col1 | col2 | col3 | col5 +------+------+------+--------- +--? 3 | 167 | 10 | 1.67400 +--? 3 | 132 | 11 | 1.31552 +--? 3 | 129 | 12 | 1.29423 +--? 4 | 1 | 13 | 1.44100 +--? 4 | 1 | 14 | 1.37620 +--? 4 | 1 | 15 | 1.51296 +--? 4 | 1 | 16 | 1.37516 +--? 4 | 1 | 17 | 1.21710 +--? 4 | 1 | 18 | 1.50422 +--? 3 | 132 | 19 | 1.30560 +--? 3 | 167 | 20 | 1.62282 +--? 3 | 129 | 21 | 1.76699 +(12 rows) + +---- should update +INSERT INTO t5 SELECT * FROM t5 ON DUPLICATE KEY UPDATE col2 = col5 * 1000; +SELECT * FROM t5 ORDER BY col3; + col1 | col2 | col3 | col5 +------+------+------+--------- +--? 3 | 1674 | 10 | 1.67400 +--? 3 | 1316 | 11 | 1.31552 +--? 3 | 1294 | 12 | 1.29423 +--? 4 | 1441 | 13 | 1.44100 +--? 4 | 1376 | 14 | 1.37620 +--? 4 | 1513 | 15 | 1.51296 +--? 4 | 1375 | 16 | 1.37516 +--? 4 | 1217 | 17 | 1.21710 +--? 4 | 1504 | 18 | 1.50422 +--? 3 | 1306 | 19 | 1.30560 +--? 3 | 1623 | 20 | 1.62282 +--? 3 | 1767 | 21 | 1.76699 +(12 rows) + +-- test t6 with one more index +CREATE TABLE t6 ( + col1 INT, + col2 INT DEFAULT 1, + col3 BIGSERIAL, +-- col4 TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + col5 INTEGER(10, 5) DEFAULT RANDOM(), + col6 INT, + col7 TEXT +) ; +NOTICE: CREATE TABLE will create implicit sequence "t6_col3_seq" for serial column "t6.col3" +ALTER TABLE t6 ADD PRIMARY KEY (col1, col3); +NOTICE: ALTER TABLE / ADD PRIMARY KEY will create implicit index "t6_pkey" for table "t6" +CREATE UNIQUE INDEX u_t6_index1 ON t6(col1, col5, col6); +INSERT INTO t6 (col1) VALUES (1), (2), (3), (4), (5); +SELECT col1, col2, col3, col6, col7 FROM t6 ORDER BY col3; + col1 | col2 | col3 | col6 | col7 +------+------+------+------+------ + 1 | 1 | 1 | | + 2 | 1 | 2 | | + 3 | 1 | 3 | | + 4 | 1 | 4 | | + 5 | 1 | 5 | | +(5 rows) + +--- should insert +INSERT INTO t6 (col1) VALUES (1), (2), (3), (4), (5) ON DUPLICATE KEY UPDATE col6 = power(col1, col2); +ERROR: INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key. +SELECT col1, col2, col3, col6, col7 FROM t6 ORDER BY col3; + col1 | col2 | col3 | col6 | col7 +------+------+------+------+------ + 1 | 1 | 1 | | + 2 | 1 | 2 | | + 3 | 1 | 3 | | + 4 | 1 | 4 | | + 5 | 1 | 5 | | +(5 rows) + +--- should update because primary key matches +INSERT INTO t6 (col1, col3) VALUES (6, 11), (6, 12); +SELECT col1, col2, col3, col6, col7 FROM t6 WHERE col1 = 6 ORDER BY col3; + col1 | col2 | col3 | col6 | col7 +------+------+------+------+------ + 6 | 1 | 11 | | + 6 | 1 | 12 | | +(2 rows) + +INSERT INTO t6 (col1) VALUES (6), (6), (6) ON DUPLICATE KEY UPDATE col2 = col1 + col3, col6 = col2 * 10; +ERROR: INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key. +SELECT col1, col2, col3, col6, col7 FROM t6 WHERE col1 = 6 ORDER BY col3; + col1 | col2 | col3 | col6 | col7 +------+------+------+------+------ + 6 | 1 | 11 | | + 6 | 1 | 12 | | +(2 rows) + +--- should update for those col6 is not null bacause they will match the unique index, +--- and insert for those col6 is null because the unique index containing null never matches, +--- also primary key will not match +--- be ware the sequence column of the inserted row will jump n step, where n is the count of the not null rows, +--- because those sequence have to be generated during the unique index join stage. +INSERT INTO t6 (col1, col5, col6) + (SELECT col1, col5, col6 FROM t6 WHERE col1 = 6) + ON DUPLICATE KEY UPDATE + col7 = col2 + 1; +SELECT col1, col2, col3, col6, col7 FROM t6 WHERE col1 = 6 ORDER BY col1, col3, col6, col7; + col1 | col2 | col3 | col6 | col7 +------+------+------+------+------ + 6 | 1 | 6 | | + 6 | 1 | 7 | | + 6 | 1 | 11 | | + 6 | 1 | 12 | | +(4 rows) + +--- should update because unique index and primary key both match +INSERT INTO t6 (col1, col3, col5, col6) + (SELECT col1, col3, col5, col6 FROM t6 WHERE col1 = 6 AND col6 IS NOT NULL) + ON DUPLICATE KEY UPDATE + col7 = col7 * 10; +SELECT col1, col2, col3, col6, col7 FROM t6 WHERE col1 = 6 ORDER BY col1, col3, col6, col7; + col1 | col2 | col3 | col6 | col7 +------+------+------+------+------ + 6 | 1 | 6 | | + 6 | 1 | 7 | | + 6 | 1 | 11 | | + 6 | 1 | 12 | | +(4 rows) + +--- insert when col3 = 17 because constaints does not match, +--- but update when col3 = 18 because 18 has been inserted and will cause a match +INSERT INTO t6 (col1, col3, col5, col6) VALUES (7, 18, 100, 100); +SELECT * FROM t6 WHERE col3 > 16 ORDER BY col1, col3, col6, col7; + col1 | col2 | col3 | col5 | col6 | col7 +------+------+------+-----------+------+------ + 7 | 1 | 18 | 100.00000 | 100 | +(1 row) + +INSERT INTO t6 (col1, col5, col6) VALUES (7, 10, 10), (7, 100, 100) ON DUPLICATE KEY UPDATE + col7 = col3 * 100; +SELECT * FROM t6 WHERE col1 = 7 ORDER BY col1, col3, col6, col7; + col1 | col2 | col3 | col5 | col6 | col7 +------+------+------+-----------+------+------ + 7 | 1 | 8 | 10.00000 | 10 | + 7 | 1 | 18 | 100.00000 | 100 | 1800 +(2 rows) + +DROP SCHEMA test_insert_update_010 CASCADE; +NOTICE: drop cascades to 3 other objects +DETAIL: drop cascades to table t4 +drop cascades to table t5 +drop cascades to table t6 diff --git a/src/test/regress/expected/upsert_clean.out b/src/test/regress/expected/upsert_clean.out new file mode 100644 index 000000000..e6157a7f1 --- /dev/null +++ b/src/test/regress/expected/upsert_clean.out @@ -0,0 +1,19 @@ +\c upsert +DROP SCHEMA upsert_test CASCADE; +NOTICE: drop cascades to 8 other objects +DETAIL: drop cascades to type upsert_test.atype +drop cascades to type upsert_test.btype +drop cascades to table upsert_test.t_grammer +drop cascades to table upsert_test.t_default +drop cascades to table upsert_test.t_data +drop cascades to table upsert_test.t_trigger +drop cascades to function upsert_test.upsert_before_func() +drop cascades to function upsert_test.upsert_after_func() +DROP SCHEMA upsert_test_unlog CASCADE; +NOTICE: drop cascades to 6 other objects +DETAIL: drop cascades to type upsert_test_unlog.atype +drop cascades to type upsert_test_unlog.btype +drop cascades to table upsert_test_unlog.t_hash_unlog_0 +drop cascades to table upsert_test_unlog.t_hash_unlog_1 +drop cascades to table upsert_test_unlog.t_rep_unlog_0 +drop cascades to table upsert_test_unlog.t_rep_unlog_1 diff --git a/src/test/regress/expected/upsert_clean_etc.out b/src/test/regress/expected/upsert_clean_etc.out new file mode 100644 index 000000000..0ddfbdc7b --- /dev/null +++ b/src/test/regress/expected/upsert_clean_etc.out @@ -0,0 +1 @@ +drop database upsert_etc; diff --git a/src/test/regress/expected/upsert_composite.out b/src/test/regress/expected/upsert_composite.out new file mode 100644 index 000000000..648d22c9e --- /dev/null +++ b/src/test/regress/expected/upsert_composite.out @@ -0,0 +1,404 @@ +-------------------------------------------------------------------------------------------- +/* + * procedure + */ +-------------------------------------------------------------------------------------------- +\c upsert; +SET CURRENT_SCHEMA TO upsert_test_procedure; +-- enable_upsert_to_merge must be off, or upsert will be translated to merge. +SET enable_upsert_to_merge TO OFF; +SHOW enable_upsert_to_merge; + enable_upsert_to_merge +------------------------ + off +(1 row) + +drop table IF EXISTS t_proc; +NOTICE: table "t_proc" does not exist, skipping +create table t_proc(c1 int, c2 int, c3 int unique); +NOTICE: CREATE TABLE / UNIQUE will create implicit index "t_proc_c3_key" for table "t_proc" +insert into t_proc select a,a,a from generate_series(1,20) as a; +select *from t_proc order by c3; + c1 | c2 | c3 +----+----+---- + 1 | 1 | 1 + 2 | 2 | 2 + 3 | 3 | 3 + 4 | 4 | 4 + 5 | 5 | 5 + 6 | 6 | 6 + 7 | 7 | 7 + 8 | 8 | 8 + 9 | 9 | 9 + 10 | 10 | 10 + 11 | 11 | 11 + 12 | 12 | 12 + 13 | 13 | 13 + 14 | 14 | 14 + 15 | 15 | 15 + 16 | 16 | 16 + 17 | 17 | 17 + 18 | 18 | 18 + 19 | 19 | 19 + 20 | 20 | 20 +(20 rows) + +create or replace procedure mul_ups( c in INTEGER) +as +declare +val int; +begin + for val in select c3 from t_proc loop + insert into t_proc values(c,c, val) on duplicate key update c1 = c, c2 =c; + end loop; +end; +/ +call mul_ups(100); + mul_ups +--------- + +(1 row) + +select *from t_proc order by c3; + c1 | c2 | c3 +-----+-----+---- + 100 | 100 | 1 + 100 | 100 | 2 + 100 | 100 | 3 + 100 | 100 | 4 + 100 | 100 | 5 + 100 | 100 | 6 + 100 | 100 | 7 + 100 | 100 | 8 + 100 | 100 | 9 + 100 | 100 | 10 + 100 | 100 | 11 + 100 | 100 | 12 + 100 | 100 | 13 + 100 | 100 | 14 + 100 | 100 | 15 + 100 | 100 | 16 + 100 | 100 | 17 + 100 | 100 | 18 + 100 | 100 | 19 + 100 | 100 | 20 +(20 rows) + +create or replace procedure mul_ups( c in INTEGER) +as +declare +val int; +begin + for val in select c3 from t_proc loop + insert into t_proc values(c,c, val) on duplicate key update c1 = $1, c2 =$1; + end loop; +end; +/ +call mul_ups(200); + mul_ups +--------- + +(1 row) + +select *from t_proc order by c3; + c1 | c2 | c3 +-----+-----+---- + 200 | 200 | 1 + 200 | 200 | 2 + 200 | 200 | 3 + 200 | 200 | 4 + 200 | 200 | 5 + 200 | 200 | 6 + 200 | 200 | 7 + 200 | 200 | 8 + 200 | 200 | 9 + 200 | 200 | 10 + 200 | 200 | 11 + 200 | 200 | 12 + 200 | 200 | 13 + 200 | 200 | 14 + 200 | 200 | 15 + 200 | 200 | 16 + 200 | 200 | 17 + 200 | 200 | 18 + 200 | 200 | 19 + 200 | 200 | 20 +(20 rows) + +create or replace procedure mul_ups_00() +as +declare +val int; +begin + for val in select c3 from t_proc loop + insert into t_proc values(val,val,val) on duplicate key update c1 = 300, c2 =300; + end loop; +end; +/ +call mul_ups_00(); + mul_ups_00 +------------ + +(1 row) + +select *from t_proc order by c3; + c1 | c2 | c3 +-----+-----+---- + 300 | 300 | 1 + 300 | 300 | 2 + 300 | 300 | 3 + 300 | 300 | 4 + 300 | 300 | 5 + 300 | 300 | 6 + 300 | 300 | 7 + 300 | 300 | 8 + 300 | 300 | 9 + 300 | 300 | 10 + 300 | 300 | 11 + 300 | 300 | 12 + 300 | 300 | 13 + 300 | 300 | 14 + 300 | 300 | 15 + 300 | 300 | 16 + 300 | 300 | 17 + 300 | 300 | 18 + 300 | 300 | 19 + 300 | 300 | 20 +(20 rows) + +declare +val int; +c int :=400; +begin + for val in select c3 from t_proc loop + insert into t_proc values(c,c, val) on duplicate key update c1 = c, c2 =c; + end loop; +end; +/ +select *from t_proc order by c3; + c1 | c2 | c3 +-----+-----+---- + 400 | 400 | 1 + 400 | 400 | 2 + 400 | 400 | 3 + 400 | 400 | 4 + 400 | 400 | 5 + 400 | 400 | 6 + 400 | 400 | 7 + 400 | 400 | 8 + 400 | 400 | 9 + 400 | 400 | 10 + 400 | 400 | 11 + 400 | 400 | 12 + 400 | 400 | 13 + 400 | 400 | 14 + 400 | 400 | 15 + 400 | 400 | 16 + 400 | 400 | 17 + 400 | 400 | 18 + 400 | 400 | 19 + 400 | 400 | 20 +(20 rows) + +create or replace procedure mul_ups( c in INTEGER) +as +declare +val int; +begin + for val in select c3 from t_proc loop + insert into t_proc values(c,c, val+100) on duplicate key update c1 = $1, c2 =$1; + end loop; +end; +/ +call mul_ups(500); + mul_ups +--------- + +(1 row) + +select *from t_proc order by c3; + c1 | c2 | c3 +-----+-----+----- + 400 | 400 | 1 + 400 | 400 | 2 + 400 | 400 | 3 + 400 | 400 | 4 + 400 | 400 | 5 + 400 | 400 | 6 + 400 | 400 | 7 + 400 | 400 | 8 + 400 | 400 | 9 + 400 | 400 | 10 + 400 | 400 | 11 + 400 | 400 | 12 + 400 | 400 | 13 + 400 | 400 | 14 + 400 | 400 | 15 + 400 | 400 | 16 + 400 | 400 | 17 + 400 | 400 | 18 + 400 | 400 | 19 + 400 | 400 | 20 + 500 | 500 | 101 + 500 | 500 | 102 + 500 | 500 | 103 + 500 | 500 | 104 + 500 | 500 | 105 + 500 | 500 | 106 + 500 | 500 | 107 + 500 | 500 | 108 + 500 | 500 | 109 + 500 | 500 | 110 + 500 | 500 | 111 + 500 | 500 | 112 + 500 | 500 | 113 + 500 | 500 | 114 + 500 | 500 | 115 + 500 | 500 | 116 + 500 | 500 | 117 + 500 | 500 | 118 + 500 | 500 | 119 + 500 | 500 | 120 +(40 rows) + +/* + * insert + */ +drop table if exists t_default; +NOTICE: table "t_default" does not exist, skipping +CREATE TABLE t_default (c1 INT PRIMARY KEY DEFAULT 10, c2 FLOAT DEFAULT 3.0, c3 TIMESTAMP DEFAULT '20200508'); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t_default_pkey" for table "t_default" +create view v_default as select *from t_default order by c1; +-- insert src +---- insert values +truncate t_default; +insert into t_default values(DEFAULT, DEFAULT, DEFAULT); +insert into t_default values(1, 2, '20200630'); +select *from t_default order by c1; + c1 | c2 | c3 +----+----+-------------------------- + 1 | 2 | Tue Jun 30 00:00:00 2020 + 10 | 3 | Fri May 08 00:00:00 2020 +(2 rows) + +insert into t_default values(DEFAULT, DEFAULT, DEFAULT) on duplicate key update c2 = 1; +select *from t_default order by c1; + c1 | c2 | c3 +----+----+-------------------------- + 1 | 2 | Tue Jun 30 00:00:00 2020 + 10 | 1 | Fri May 08 00:00:00 2020 +(2 rows) + +insert into t_default values(1, 2, '20200630') on duplicate key update c2 = 2; +select *from t_default order by c1; + c1 | c2 | c3 +----+----+-------------------------- + 1 | 2 | Tue Jun 30 00:00:00 2020 + 10 | 1 | Fri May 08 00:00:00 2020 +(2 rows) + +insert into t_default values(DEFAULT, DEFAULT, DEFAULT),(1, 2, '20200630') on duplicate key update c2 = 3; +select *from t_default order by c1; + c1 | c2 | c3 +----+----+-------------------------- + 1 | 3 | Tue Jun 30 00:00:00 2020 + 10 | 3 | Fri May 08 00:00:00 2020 +(2 rows) + +---- insert subquery +insert into t_default select *from t_default order by c1 on duplicate key update c2 = 4; +select *from t_default order by c1; + c1 | c2 | c3 +----+----+-------------------------- + 1 | 4 | Tue Jun 30 00:00:00 2020 + 10 | 4 | Fri May 08 00:00:00 2020 +(2 rows) + +insert into t_default select *from t_default order by c1 limit 1 on duplicate key update c2 = 5; +select *from t_default order by c1; + c1 | c2 | c3 +----+----+-------------------------- + 1 | 5 | Tue Jun 30 00:00:00 2020 + 10 | 4 | Fri May 08 00:00:00 2020 +(2 rows) + +insert into t_default select *from t_default where c1=10 on duplicate key update c2 = 6; +select *from t_default order by c1; + c1 | c2 | c3 +----+----+-------------------------- + 1 | 5 | Tue Jun 30 00:00:00 2020 + 10 | 6 | Fri May 08 00:00:00 2020 +(2 rows) + +insert into t_default select *from t_default order by c1 on duplicate key update c2 = 7; +select *from t_default order by c1; + c1 | c2 | c3 +----+----+-------------------------- + 1 | 7 | Tue Jun 30 00:00:00 2020 + 10 | 7 | Fri May 08 00:00:00 2020 +(2 rows) + +insert into t_default select *from v_default order by c1 on duplicate key update c2 = 8; +select *from t_default order by c1; + c1 | c2 | c3 +----+----+-------------------------- + 1 | 8 | Tue Jun 30 00:00:00 2020 + 10 | 8 | Fri May 08 00:00:00 2020 +(2 rows) + +insert into t_default select *from t_default union select c1+1,c2+1,c3+1 from t_default order by c1 on duplicate key update c2 = 9; +select *from t_default order by c1; + c1 | c2 | c3 +----+----+-------------------------- + 1 | 9 | Tue Jun 30 00:00:00 2020 + 2 | 9 | Wed Jul 01 00:00:00 2020 + 10 | 9 | Fri May 08 00:00:00 2020 + 11 | 9 | Sat May 09 00:00:00 2020 +(4 rows) + +insert into t_default(c1,c2) select max(c1),c2 from t_default group by c2 on duplicate key update c2 = 10; +-- index +---- mul index +truncate t_default; +create unique index t_default_mul on t_default(c1,c3); +insert into t_default values(DEFAULT, DEFAULT, DEFAULT); +insert into t_default values(1, 2, '20200630'); +select *from t_default order by c1; + c1 | c2 | c3 +----+----+-------------------------- + 1 | 2 | Tue Jun 30 00:00:00 2020 + 10 | 3 | Fri May 08 00:00:00 2020 +(2 rows) + +insert into t_default values(DEFAULT, DEFAULT, DEFAULT) on duplicate key update c2 = 1; +select *from t_default order by c1; + c1 | c2 | c3 +----+----+-------------------------- + 1 | 2 | Tue Jun 30 00:00:00 2020 + 10 | 1 | Fri May 08 00:00:00 2020 +(2 rows) + +insert into t_default values(1, 2, '20200630') on duplicate key update c2 = 2; +select *from t_default order by c1; + c1 | c2 | c3 +----+----+-------------------------- + 1 | 2 | Tue Jun 30 00:00:00 2020 + 10 | 1 | Fri May 08 00:00:00 2020 +(2 rows) + +insert into t_default values(DEFAULT, DEFAULT, DEFAULT),(1, 2, '20200630') on duplicate key update c2 = 3; +select *from t_default order by c1; + c1 | c2 | c3 +----+----+-------------------------- + 1 | 3 | Tue Jun 30 00:00:00 2020 + 10 | 3 | Fri May 08 00:00:00 2020 +(2 rows) + +insert into t_default select *from t_default on duplicate key update c2 = 4; +select *from t_default order by c1; + c1 | c2 | c3 +----+----+-------------------------- + 1 | 4 | Tue Jun 30 00:00:00 2020 + 10 | 4 | Fri May 08 00:00:00 2020 +(2 rows) + diff --git a/src/test/regress/expected/upsert_explain.out b/src/test/regress/expected/upsert_explain.out new file mode 100644 index 000000000..7aa99f20c --- /dev/null +++ b/src/test/regress/expected/upsert_explain.out @@ -0,0 +1,298 @@ +-------------------------------------------------------------------------------------------- +/* + * explain upsert + */ +-------------------------------------------------------------------------------------------- +\c upsert; +SET CURRENT_SCHEMA TO upsert_test_explain; +-- enable_upsert_to_merge must be off, or upsert will be translated to merge. +SET enable_upsert_to_merge TO OFF; +SHOW enable_upsert_to_merge; + enable_upsert_to_merge +------------------------ + off +(1 row) + +create temp table up_expl_temp(c1 int, c2 int, c3 int unique) ; +NOTICE: CREATE TABLE / UNIQUE will create implicit index "up_expl_temp_c3_key" for table "up_expl_temp" +insert into up_expl_hash select a,a,a from generate_series(1,20) as a; +insert into up_expl_repl select a,a,a from generate_series(1,20) as a; +insert into up_expl_part select a,a,a from generate_series(1,20) as a; +insert into up_expl_temp select a,a,a from generate_series(1,20) as a; +insert into up_expl_unlog select a,a,a from generate_series(1,20) as a; +insert into up_expl_node select a,a,a from generate_series(1,20) as a; +insert into up_expl_repl2 select a,a,a,a,a from generate_series(1,20) as a; +EXPLAIN (VERBOSE on, COSTS off, TIMING off) insert into up_expl_hash values(1,1,1) on duplicate key update c1 = 2; + QUERY PLAN +------------------------------------------------- + Insert on upsert_test_explain.up_expl_hash + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: up_expl_hash_c3_key + -> Result + Output: 1, 1, 1 +(5 rows) + +EXPLAIN (ANALYZE on, COSTS off, TIMING off) insert into up_expl_hash values(1,1,1) on duplicate key update c1 = 2; + QUERY PLAN +------------------------------------------------- + Insert on up_expl_hash (actual rows=1 loops=1) + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: up_expl_hash_c3_key + -> Result (actual rows=1 loops=1) +--? Total runtime: .* ms +(5 rows) + +EXPLAIN PERFORMANCE insert into up_expl_hash values(1,1,1) on duplicate key update c1 = 2; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------ +--? Insert on upsert_test_explain.up_expl_hash (cost=.* rows=1 width=0) (actual time=.* rows=1 loops=1) + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: up_expl_hash_c3_key + (Buffers: shared hit=4) +--? (CPU: ex c/r=.*, ex row=1, ex cyc=.*, inc cyc=.*) +--? -> Result (cost=.* rows=1 width=0) (actual time=.* rows=1 loops=1) + Output: 1, 1, 1 +--? (CPU: ex c/r=.*, ex row=1, ex cyc=.*, inc cyc=.*) +--? Total runtime: .* ms +(9 rows) + +EXPLAIN (VERBOSE on, COSTS off, TIMING off) insert into up_expl_hash values(1,1,1),(2,2,2)on duplicate key update c1 = 2; + QUERY PLAN +---------------------------------------------------------------------------- + Insert on upsert_test_explain.up_expl_hash + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: up_expl_hash_c3_key + -> Values Scan on "*VALUES*" + Output: "*VALUES*".column1, "*VALUES*".column2, "*VALUES*".column3 +(5 rows) + +EXPLAIN (ANALYZE on, COSTS off, TIMING off) insert into up_expl_hash values(1,1,1),(2,2,2)on duplicate key update c1 = 2; + QUERY PLAN +--------------------------------------------------------- + Insert on up_expl_hash (actual rows=2 loops=1) + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: up_expl_hash_c3_key + -> Values Scan on "*VALUES*" (actual rows=2 loops=1) +--? Total runtime: .* ms +(5 rows) + +EXPLAIN (VERBOSE on, COSTS off, TIMING off) insert into up_expl_repl values(1,1,1) on duplicate key update c1 = 2; + QUERY PLAN +------------------------------------------------- + Insert on upsert_test_explain.up_expl_repl + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: up_expl_repl_c3_key + -> Result + Output: 1, 1, 1 +(5 rows) + +EXPLAIN (ANALYZE on, COSTS off, TIMING off) insert into up_expl_repl values(1,1,1) on duplicate key update c1 = 2; + QUERY PLAN +------------------------------------------------- + Insert on up_expl_repl (actual rows=1 loops=1) + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: up_expl_repl_c3_key + -> Result (actual rows=1 loops=1) +--? Total runtime: .* ms +(5 rows) + +EXPLAIN (VERBOSE on, COSTS off, TIMING off) insert into up_expl_part values(1,1,1) on duplicate key update c1 = 2; + QUERY PLAN +------------------------------------------------- + Insert on upsert_test_explain.up_expl_part + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: up_expl_part_c3_key + -> Result + Output: 1, 1, 1 +(5 rows) + +EXPLAIN (ANALYZE on, COSTS off, TIMING off) insert into up_expl_part values(1,1,1) on duplicate key update c1 = 2; + QUERY PLAN +------------------------------------------------- + Insert on up_expl_part (actual rows=1 loops=1) + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: up_expl_part_c3_key + -> Result (actual rows=1 loops=1) +--? Total runtime: .* ms +(5 rows) + +EXPLAIN (VERBOSE on, COSTS off, TIMING off) insert into up_expl_temp values(1,1,1) on duplicate key update c1 = 2; +--?.*QUERY PLAN.* +--?----.* +--? Insert on pg_temp_datanod.*.up_expl_temp + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: up_expl_temp_c3_key + -> Result + Output: 1, 1, 1 +(5 rows) + +EXPLAIN (ANALYZE on, COSTS off, TIMING off) insert into up_expl_temp values(1,1,1) on duplicate key update c1 = 2; + QUERY PLAN +------------------------------------------------- + Insert on up_expl_temp (actual rows=1 loops=1) + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: up_expl_temp_c3_key + -> Result (actual rows=1 loops=1) +--? Total runtime: .* ms +(5 rows) + +EXPLAIN (VERBOSE on, COSTS off, TIMING off) insert into up_expl_unlog values(1,1,1) on duplicate key update c1 = 2; + QUERY PLAN +-------------------------------------------------- + Insert on upsert_test_explain.up_expl_unlog + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: up_expl_unlog_c3_key + -> Result + Output: 1, 1, 1 +(5 rows) + +EXPLAIN (ANALYZE on, COSTS off, TIMING off) insert into up_expl_unlog values(1,1,1) on duplicate key update c1 = 2; + QUERY PLAN +-------------------------------------------------- + Insert on up_expl_unlog (actual rows=1 loops=1) + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: up_expl_unlog_c3_key + -> Result (actual rows=1 loops=1) +--? Total runtime: .* ms +(5 rows) + +EXPLAIN (VERBOSE on, COSTS off, TIMING off) insert into up_expl_node values(1,1,1) on duplicate key update c1 = 2; + QUERY PLAN +------------------------------------------------- + Insert on upsert_test_explain.up_expl_node + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: up_expl_node_c3_key + -> Result + Output: 1, 1, 1 +(5 rows) + +EXPLAIN (ANALYZE on, COSTS off, TIMING off) insert into up_expl_node values(1,1,1) on duplicate key update c1 = 2; + QUERY PLAN +------------------------------------------------- + Insert on up_expl_node (actual rows=1 loops=1) + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: up_expl_node_c3_key + -> Result (actual rows=1 loops=1) +--? Total runtime: .* ms +(5 rows) + +EXPLAIN (VERBOSE on, COSTS off, TIMING off) insert into up_expl_hash values(1,1,1),(2,2,2)on duplicate key update nothing; + QUERY PLAN +---------------------------------------------------------------------------- + Insert on upsert_test_explain.up_expl_hash + Conflict Resolution: NOTHING + Conflict Arbiter Indexes: up_expl_hash_c3_key + -> Values Scan on "*VALUES*" + Output: "*VALUES*".column1, "*VALUES*".column2, "*VALUES*".column3 +(5 rows) + +EXPLAIN (ANALYZE on, COSTS off, TIMING off) insert into up_expl_hash values(1,1,1),(2,2,2)on duplicate key update nothing; + QUERY PLAN +--------------------------------------------------------- + Insert on up_expl_hash (actual rows=0 loops=1) + Conflict Resolution: NOTHING + Conflict Arbiter Indexes: up_expl_hash_c3_key + -> Values Scan on "*VALUES*" (actual rows=2 loops=1) +--? Total runtime: .* ms +(5 rows) + +EXPLAIN (VERBOSE on, COSTS off, TIMING off) insert into up_expl_repl values(1,1,1) on duplicate key update nothing; + QUERY PLAN +------------------------------------------------- + Insert on upsert_test_explain.up_expl_repl + Conflict Resolution: NOTHING + Conflict Arbiter Indexes: up_expl_repl_c3_key + -> Result + Output: 1, 1, 1 +(5 rows) + +EXPLAIN (ANALYZE on, COSTS off, TIMING off) insert into up_expl_repl values(1,1,1) on duplicate key update nothing; + QUERY PLAN +------------------------------------------------- + Insert on up_expl_repl (actual rows=0 loops=1) + Conflict Resolution: NOTHING + Conflict Arbiter Indexes: up_expl_repl_c3_key + -> Result (actual rows=1 loops=1) +--? Total runtime: .* ms +(5 rows) + +EXPLAIN (VERBOSE on, COSTS off, TIMING off) insert into up_expl_part values(1,1,1) on duplicate key update nothing; + QUERY PLAN +------------------------------------------------- + Insert on upsert_test_explain.up_expl_part + Conflict Resolution: NOTHING + Conflict Arbiter Indexes: up_expl_part_c3_key + -> Result + Output: 1, 1, 1 +(5 rows) + +EXPLAIN (ANALYZE on, COSTS off, TIMING off) insert into up_expl_part values(1,1,1) on duplicate key update nothing; + QUERY PLAN +------------------------------------------------- + Insert on up_expl_part (actual rows=0 loops=1) + Conflict Resolution: NOTHING + Conflict Arbiter Indexes: up_expl_part_c3_key + -> Result (actual rows=1 loops=1) +--? Total runtime: .* ms +(5 rows) + +EXPLAIN (VERBOSE on, COSTS off, TIMING off) insert into up_expl_temp values(1,1,1) on duplicate key update nothing; +--?.*QUERY PLAN.* +--?----.* +--? Insert on pg_temp_datanod.*.up_expl_temp + Conflict Resolution: NOTHING + Conflict Arbiter Indexes: up_expl_temp_c3_key + -> Result + Output: 1, 1, 1 +(5 rows) + +EXPLAIN (ANALYZE on, COSTS off, TIMING off) insert into up_expl_temp values(1,1,1) on duplicate key update nothing; + QUERY PLAN +------------------------------------------------- + Insert on up_expl_temp (actual rows=0 loops=1) + Conflict Resolution: NOTHING + Conflict Arbiter Indexes: up_expl_temp_c3_key + -> Result (actual rows=1 loops=1) +--? Total runtime: .* ms +(5 rows) + +EXPLAIN (VERBOSE on, COSTS off, TIMING off) insert into up_expl_unlog values(1,1,1) on duplicate key update nothing; + QUERY PLAN +-------------------------------------------------- + Insert on upsert_test_explain.up_expl_unlog + Conflict Resolution: NOTHING + Conflict Arbiter Indexes: up_expl_unlog_c3_key + -> Result + Output: 1, 1, 1 +(5 rows) + +EXPLAIN (ANALYZE on, COSTS off, TIMING off) insert into up_expl_unlog values(1,1,1) on duplicate key update nothing; + QUERY PLAN +-------------------------------------------------- + Insert on up_expl_unlog (actual rows=0 loops=1) + Conflict Resolution: NOTHING + Conflict Arbiter Indexes: up_expl_unlog_c3_key + -> Result (actual rows=1 loops=1) +--? Total runtime: .* ms +(5 rows) + +EXPLAIN (VERBOSE on, COSTS off, TIMING off) insert into up_expl_node values(1,1,1) on duplicate key update nothing; + QUERY PLAN +------------------------------------------------- + Insert on upsert_test_explain.up_expl_node + Conflict Resolution: NOTHING + Conflict Arbiter Indexes: up_expl_node_c3_key + -> Result + Output: 1, 1, 1 +(5 rows) + +EXPLAIN (ANALYZE on, COSTS off, TIMING off) insert into up_expl_node values(1,1,1) on duplicate key update nothing; + QUERY PLAN +------------------------------------------------- + Insert on up_expl_node (actual rows=0 loops=1) + Conflict Resolution: NOTHING + Conflict Arbiter Indexes: up_expl_node_c3_key + -> Result (actual rows=1 loops=1) +--? Total runtime: .* ms +(5 rows) + diff --git a/src/test/regress/expected/upsert_grammer_test_01.out b/src/test/regress/expected/upsert_grammer_test_01.out new file mode 100644 index 000000000..378d916f5 --- /dev/null +++ b/src/test/regress/expected/upsert_grammer_test_01.out @@ -0,0 +1,188 @@ +\c upsert +SET CURRENT_SCHEMA TO upsert_test; +-- enable_upsert_to_merge must be off, or upsert will be translated to merge. +SET enable_upsert_to_merge TO OFF; +SHOW enable_upsert_to_merge; + enable_upsert_to_merge +------------------------ + off +(1 row) + +-- support multiple set +INSERT INTO t_grammer VALUES(11, 1) ON DUPLICATE KEY UPDATE c2 = 2, c4 = ROW(10, 20), c3 = '{10, 20, 30}'; +INSERT INTO t_grammer VALUES(12, 2) ON DUPLICATE KEY UPDATE c2 = 2, c3[1] = 3; +-- support const value and expr +INSERT INTO t_grammer VALUES(23) ON DUPLICATE KEY UPDATE c2 = 2; +INSERT INTO t_grammer VALUES(24) ON DUPLICATE KEY UPDATE c2 = length('test') + 100; +-- support without table name +INSERT INTO t_grammer VALUES(31, 1) ON DUPLICATE KEY UPDATE c2 = c1, c4.a = VALUES(c1); +INSERT INTO t_grammer VALUES(32, 2) ON DUPLICATE KEY UPDATE c2 = c1 + 5, c4.a = VALUES(c1); +-- support with table name +INSERT INTO t_grammer VALUES(41, 1) ON DUPLICATE KEY UPDATE c2 = t_grammer.c1, c4.a = VALUES(c1); +INSERT INTO t_grammer VALUES(42, 2) ON DUPLICATE KEY UPDATE c2 = t_grammer.c1 + 5, c4.a = VALUES(c1); +-- support EXCLUDED alone and with expr +INSERT INTO t_grammer VALUES(51, 1) ON DUPLICATE KEY UPDATE c2 = c1 + 5, c4.a = EXCLUDED.c1; +INSERT INTO t_grammer VALUES(52, 2) ON DUPLICATE KEY UPDATE c2 = t_grammer.c1 + 5, c4.a = EXCLUDED.c1 + 5; +INSERT INTO t_grammer VALUES(53, 3) ON DUPLICATE KEY UPDATE c4.a = sqrt(EXCLUDED.c1 - EXCLUDED.c2 - 1) + 1, c2 = t_grammer.c1 + 5; +INSERT INTO t_grammer VALUES(54, 4) ON DUPLICATE KEY UPDATE c4.b = EXCLUDED.c1 + t_grammer.c1; +INSERT INTO t_grammer VALUES(55, 5) ON DUPLICATE KEY UPDATE c4.a = EXCLUDED.c2, c4.b = EXCLUDED.c1 + sqrt(c1 - 6); +-- support ARRAY +INSERT INTO t_grammer VALUES(61, 1) ON DUPLICATE KEY UPDATE c2 = VALUES(c3[1]); +INSERT INTO t_grammer VALUES(62, 2) ON DUPLICATE KEY UPDATE c5 = VALUES(c3[2,3]); +INSERT INTO t_grammer VALUES(63, 3) ON DUPLICATE KEY UPDATE c5 = VALUES(c3[2:3]); +INSERT INTO t_grammer VALUES(64, 4, '{10, 20, 30}') ON DUPLICATE KEY UPDATE c2 = EXCLUDED.c3[1]; +INSERT INTO t_grammer VALUES(65, 5, '{10, 20, 30}') ON DUPLICATE KEY UPDATE c5 = EXCLUDED.c3[2,3]; +INSERT INTO t_grammer VALUES(66, 6, '{10, 20, 30}') ON DUPLICATE KEY UPDATE c5 = EXCLUDED.c3[1:2]; +INSERT INTO t_grammer VALUES(67, 7, '{10, 20, 30}') ON DUPLICATE KEY UPDATE c2 = c3[1]; +INSERT INTO t_grammer VALUES(68, 8, '{10, 20, 30}') ON DUPLICATE KEY UPDATE c5 = c3[2,3]; +INSERT INTO t_grammer VALUES(69, 9, '{10, 20, 30}') ON DUPLICATE KEY UPDATE c5 = c3[1:2]; +-- support user defined type +INSERT INTO t_grammer VALUES(71, 1) ON DUPLICATE KEY UPDATE c4.a = c3[1] + c1; +INSERT INTO t_grammer VALUES(72, 2, '{71, 72, 73}') ON DUPLICATE KEY UPDATE c4.a = c3[1] + c1; +INSERT INTO t_grammer VALUES(73, 3, '{71, 72, 73}') ON DUPLICATE KEY UPDATE c4.a = c3[1] + c1, c4.b = c3[2]; +INSERT INTO t_grammer VALUES(74, 4, '{71, 72, 73}', ROW(74, 75)) ON DUPLICATE KEY UPDATE c4 = ROW(740, 750); +-- appoint INSERT target column +INSERT INTO t_grammer (c5, c1, c2) VALUES('{81,82}', 81, DEFAULT) ON DUPLICATE KEY UPDATE c2 = VALUES(c1); +INSERT INTO t_grammer (c3[1], c3[2], c1) VALUES(881, 882, 82) ON DUPLICATE KEY UPDATE c5 = VALUES(c3), c2 = VALUES(c3[1]); +INSERT INTO t_grammer (c1, c3, c4) VALUES(83, '{81, 82, 83}', ROW(810, 820)) ON DUPLICATE KEY UPDATE c4 = EXCLUDED.c4; +INSERT INTO t_grammer (c1, c3, c4.a) VALUES(84, '{81, 82, 83}', 850) ON DUPLICATE KEY UPDATE c4.b = c3[3]; +-- support UPDATE NOTHING +INSERT INTO t_grammer VALUES(91, 1, '{91, 92, 93}', ROW(94, 95)) ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_grammer (c5, c1, c2, c4) VALUES('{91,92}', 92, DEFAULT, ROW(910, 920)) ON DUPLICATE KEY UPDATE NOTHING; +-- UPDATE target: unsupport with schema but support with tablename +INSERT INTO t_grammer VALUES(0, 0, '{0,0}', ROW(0, 0), '{107, 108}') ON DUPLICATE KEY UPDATE + upsert_test.t_grammer.c2 = c2 * 10, upsert_test.t_grammer.c3[1:2] = c5[1:2], upsert_test.t_grammer.c4.a = c1; +ERROR: column "upsert_test.t_grammer" of relation "t_grammer" does not exist +LINE 2: upsert_test.t_grammer.c2 = c2 * 10, upsert_test.t_grammer.c... + ^ +INSERT INTO t_grammer VALUES(101, 1, '{102, 103, 104}', ROW(105, 106), '{107, 108}') ON DUPLICATE KEY UPDATE + t_grammer.c2 = c2 * 10, t_grammer.c3[1:2] = c5[1:2], t_grammer.c4.a = c1; +-- INSERT target: appoint schema +INSERT INTO upsert_test.t_grammer VALUES(102, 2, '{102, 103, 104}', ROW(105, 106), '{107, 108}') ON DUPLICATE KEY UPDATE + t_grammer.c2 = EXCLUDED.c2 * 10, t_grammer.c3[1:2] = c5[1:2], t_grammer.c4.a = VALUES(c1); +CREATE SCHEMA upsert_test_tmpschema; +SET CURRENT_SCHEMA TO upsert_test_tmpschema; +INSERT INTO upsert_test.t_grammer VALUES(103, 3, '{102, 103, 104}', ROW(105, 106), '{107, 108}') ON DUPLICATE KEY UPDATE + t_grammer.c2 = EXCLUDED.c2 * 10, t_grammer.c3[1:2] = c5[1:2], t_grammer.c4.a = VALUES(c1); +SET CURRENT_SCHEMA TO upsert_test; +DROP SCHEMA upsert_test_tmpschema; +-- support data type +INSERT INTO t_data VALUES(11, 11, 11, 11, 11, '11', '11', '11', '2020-04-23', '2020-04-23 11:00', '1:00') ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_data VALUES(12, 12, 12, 12, 12, '12', '12', '12', '2020-04-24', '2020-04-24 11:00', '2:00') + ON DUPLICATE KEY UPDATE + c_tiny = 13, c_smallint = 13, c_bigint = 13, c_numeric = 13, + c_var = '13', c_text = '13', c_bytea = '13', c_date = '2020-05-24', c_timestamp = '2020-05-24 11:00', c_time = '3:00'; +INSERT INTO t_data VALUES(13, 13, 13, 13, 13, '13', '13', '13', '2020-04-25', '2020-04-25 11:00', '4:00') + ON DUPLICATE KEY UPDATE + c_tiny = EXCLUDED.c_tiny + 1, c_smallint = EXCLUDED.c_smallint + 1, c_bigint = EXCLUDED.c_bigint + 1, c_numeric = EXCLUDED.c_numeric + 1, + c_var = EXCLUDED.c_var || '3', c_text = EXCLUDED.c_text || '3', c_bytea = EXCLUDED.c_bytea || '3', + c_date = EXCLUDED.c_date + '1 day'::interval, c_timestamp = EXCLUDED.c_timestamp + '1 day'::interval, c_time = EXCLUDED.c_time + '1 hour'::interval; +-- support UPDATE to default +INSERT INTO t_default DEFAULT VALUES ON DUPLICATE KEY UPDATE c2 = 100, c3 = '2020-05-17'; +INSERT INTO t_default VALUES(91, 0.91, '2020-05-17') ON DUPLICATE KEY UPDATE c2 = DEFAULT, c3 = DEFAULT; +INSERT INTO t_default VALUES(92, DEFAULT, '2020-05-17') ON DUPLICATE KEY UPDATE c2 = 10, c3 = DEFAULT; +-- unsupport alias +INSERT INTO t_grammer AS alias VALUES (91) ON DUPLICATE KEY UPDATE c2 = VALUES(c1); +ERROR: syntax error at or near "AS" +LINE 1: INSERT INTO t_grammer AS alias VALUES (91) ON DUPLICATE KEY ... + ^ +-- unsupport VALUES with expr +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = sqrt(VALUES(c2)); +ERROR: syntax error at or near "(" +LINE 1: ... VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = sqrt(VALUES(c2)); + ^ +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(c3[1]) + 1; +ERROR: syntax error at or near "+" +LINE 1: ...VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(c3[1]) + 1; + ^ +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(c4.a) + 1; +ERROR: only allow column name within VALUES +LINE 1: ... VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(c4.a) + 1; + ^ +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(t_grammer.c3[1]) + 1; +ERROR: only allow column name within VALUES +LINE 1: ... VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(t_grammer.... + ^ +-- unsupport VALUES with table name +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(t_grammer.c4.a); +ERROR: only allow column name within VALUES +LINE 1: ... VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(t_grammer.... + ^ +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(t_grammer.c2); +ERROR: only allow column name within VALUES +LINE 1: ... VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(t_grammer.... + ^ +-- unsupport DEFAULT with expr +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = DEFAULT + 1; +ERROR: syntax error at or near "+" +LINE 1: ...ammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = DEFAULT + 1; + ^ +-- unsupport user defined typed column's element +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(c4.a); +ERROR: only allow column name within VALUES +LINE 1: ...mmer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(c4.a); + ^ +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = EXCLUDED.c4.a; +ERROR: schema "excluded" does not exist +CONTEXT: referenced column: c2 +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = c4.a; +ERROR: missing FROM-clause entry for table "c4" +LINE 1: ...TO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = c4.a; + ^ +CONTEXT: referenced column: c2 +SELECT * FROM t_data ORDER BY 1; + c_int | c_tiny | c_smallint | c_bigint | c_numeric | c_var | c_text | c_bytea | c_date | c_timestamp | c_time | c_intarray | c_com +-------+--------+------------+----------+-----------+-------+--------+---------+--------------------------+--------------------------+----------+------------+------- + 11 | 11 | 11 | 11 | 11 | 11 | 11 | \x3131 | Thu Apr 23 00:00:00 2020 | Thu Apr 23 11:00:00 2020 | 01:00:00 | | + 12 | 12 | 12 | 12 | 12 | 12 | 12 | \x3132 | Fri Apr 24 00:00:00 2020 | Fri Apr 24 11:00:00 2020 | 02:00:00 | | + 13 | 13 | 13 | 13 | 13 | 13 | 13 | \x3133 | Sat Apr 25 00:00:00 2020 | Sat Apr 25 11:00:00 2020 | 04:00:00 | | +(3 rows) + +SELECT * FROM t_grammer ORDER BY 1; + c1 | c2 | c3 | c4 | c5 +-----+------+---------------+-----------+----------- + 11 | 1 | | | + 12 | 2 | | | + 23 | -100 | | | + 24 | -100 | | | + 31 | 1 | | | + 32 | 2 | | | + 41 | 1 | | | + 42 | 2 | | | + 51 | 1 | | | + 52 | 2 | | | + 53 | 3 | | | + 54 | 4 | | | + 55 | 5 | | | + 61 | 1 | | | + 62 | 2 | | | + 63 | 3 | | | + 64 | 4 | {10,20,30} | | + 65 | 5 | {10,20,30} | | + 66 | 6 | {10,20,30} | | + 67 | 7 | {10,20,30} | | + 68 | 8 | {10,20,30} | | + 69 | 9 | {10,20,30} | | + 71 | 1 | | | + 72 | 2 | {71,72,73} | | + 73 | 3 | {71,72,73} | | + 74 | 4 | {71,72,73} | (74,75) | + 81 | -100 | | | {81,82} + 82 | -100 | {881,882} | | + 83 | -100 | {81,82,83} | (810,820) | + 84 | -100 | {81,82,83} | (850,) | + 91 | 1 | {91,92,93} | (94,95) | + 92 | -100 | | (910,920) | {91,92} + 101 | 1 | {102,103,104} | (105,106) | {107,108} + 102 | 2 | {102,103,104} | (105,106) | {107,108} + 103 | 3 | {102,103,104} | (105,106) | {107,108} +(35 rows) + +SELECT * FROM t_default ORDER BY 1; +--? c1 | c2 | c3 +--? +--? 10 | .396464773453772 | Wed Apr 29 10:25:23.202729 2020 +--? 91 | .91 | Sun May 17 00:00:00 2020 +--? 92 | .840485369320959 | Sun May 17 00:00:00 2020 +(3 rows) + diff --git a/src/test/regress/expected/upsert_grammer_test_02.out b/src/test/regress/expected/upsert_grammer_test_02.out new file mode 100644 index 000000000..0b12c39a4 --- /dev/null +++ b/src/test/regress/expected/upsert_grammer_test_02.out @@ -0,0 +1,188 @@ +\c upsert +SET CURRENT_SCHEMA TO upsert_test; +-- enable_upsert_to_merge must be off, or upsert will be translated to merge. +SET enable_upsert_to_merge TO OFF; +SHOW enable_upsert_to_merge; + enable_upsert_to_merge +------------------------ + off +(1 row) + +-- support multiple set +INSERT INTO t_grammer VALUES(11, 1) ON DUPLICATE KEY UPDATE c2 = 2, c4 = ROW(10, 20), c3 = '{10, 20, 30}'; +INSERT INTO t_grammer VALUES(12, 2) ON DUPLICATE KEY UPDATE c2 = 2, c3[1] = 3; +-- support const value and expr +INSERT INTO t_grammer VALUES(23) ON DUPLICATE KEY UPDATE c2 = 2; +INSERT INTO t_grammer VALUES(24) ON DUPLICATE KEY UPDATE c2 = length('test') + 100; +-- support without table name +INSERT INTO t_grammer VALUES(31, 1) ON DUPLICATE KEY UPDATE c2 = c1, c4.a = VALUES(c1); +INSERT INTO t_grammer VALUES(32, 2) ON DUPLICATE KEY UPDATE c2 = c1 + 5, c4.a = VALUES(c1); +-- support with table name +INSERT INTO t_grammer VALUES(41, 1) ON DUPLICATE KEY UPDATE c2 = t_grammer.c1, c4.a = VALUES(c1); +INSERT INTO t_grammer VALUES(42, 2) ON DUPLICATE KEY UPDATE c2 = t_grammer.c1 + 5, c4.a = VALUES(c1); +-- support EXCLUDED alone and with expr +INSERT INTO t_grammer VALUES(51, 1) ON DUPLICATE KEY UPDATE c2 = c1 + 5, c4.a = EXCLUDED.c1; +INSERT INTO t_grammer VALUES(52, 2) ON DUPLICATE KEY UPDATE c2 = t_grammer.c1 + 5, c4.a = EXCLUDED.c1 + 5; +INSERT INTO t_grammer VALUES(53, 3) ON DUPLICATE KEY UPDATE c4.a = sqrt(EXCLUDED.c1 - EXCLUDED.c2 - 1) + 1, c2 = t_grammer.c1 + 5; +INSERT INTO t_grammer VALUES(54, 4) ON DUPLICATE KEY UPDATE c4.b = EXCLUDED.c1 + t_grammer.c1; +INSERT INTO t_grammer VALUES(55, 5) ON DUPLICATE KEY UPDATE c4.a = EXCLUDED.c2, c4.b = EXCLUDED.c1 + sqrt(c1 - 6); +-- support ARRAY +INSERT INTO t_grammer VALUES(61, 1) ON DUPLICATE KEY UPDATE c2 = VALUES(c3[1]); +INSERT INTO t_grammer VALUES(62, 2) ON DUPLICATE KEY UPDATE c5 = VALUES(c3[2,3]); +INSERT INTO t_grammer VALUES(63, 3) ON DUPLICATE KEY UPDATE c5 = VALUES(c3[2:3]); +INSERT INTO t_grammer VALUES(64, 4, '{10, 20, 30}') ON DUPLICATE KEY UPDATE c2 = EXCLUDED.c3[1]; +INSERT INTO t_grammer VALUES(65, 5, '{10, 20, 30}') ON DUPLICATE KEY UPDATE c5 = EXCLUDED.c3[2,3]; +INSERT INTO t_grammer VALUES(66, 6, '{10, 20, 30}') ON DUPLICATE KEY UPDATE c5 = EXCLUDED.c3[1:2]; +INSERT INTO t_grammer VALUES(67, 7, '{10, 20, 30}') ON DUPLICATE KEY UPDATE c2 = c3[1]; +INSERT INTO t_grammer VALUES(68, 8, '{10, 20, 30}') ON DUPLICATE KEY UPDATE c5 = c3[2,3]; +INSERT INTO t_grammer VALUES(69, 9, '{10, 20, 30}') ON DUPLICATE KEY UPDATE c5 = c3[1:2]; +-- support user defined type +INSERT INTO t_grammer VALUES(71, 1) ON DUPLICATE KEY UPDATE c4.a = c3[1] + c1; +INSERT INTO t_grammer VALUES(72, 2, '{71, 72, 73}') ON DUPLICATE KEY UPDATE c4.a = c3[1] + c1; +INSERT INTO t_grammer VALUES(73, 3, '{71, 72, 73}') ON DUPLICATE KEY UPDATE c4.a = c3[1] + c1, c4.b = c3[2]; +INSERT INTO t_grammer VALUES(74, 4, '{71, 72, 73}', ROW(74, 75)) ON DUPLICATE KEY UPDATE c4 = ROW(740, 750); +-- appoint INSERT target column +INSERT INTO t_grammer (c5, c1, c2) VALUES('{81,82}', 81, DEFAULT) ON DUPLICATE KEY UPDATE c2 = VALUES(c1); +INSERT INTO t_grammer (c3[1], c3[2], c1) VALUES(881, 882, 82) ON DUPLICATE KEY UPDATE c5 = VALUES(c3), c2 = VALUES(c3[1]); +INSERT INTO t_grammer (c1, c3, c4) VALUES(83, '{81, 82, 83}', ROW(810, 820)) ON DUPLICATE KEY UPDATE c4 = EXCLUDED.c4; +INSERT INTO t_grammer (c1, c3, c4.a) VALUES(84, '{81, 82, 83}', 850) ON DUPLICATE KEY UPDATE c4.b = c3[3]; +-- support UPDATE NOTHING +INSERT INTO t_grammer VALUES(91, 1, '{91, 92, 93}', ROW(94, 95)) ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_grammer (c5, c1, c2, c4) VALUES('{91,92}', 92, DEFAULT, ROW(910, 920)) ON DUPLICATE KEY UPDATE NOTHING; +-- UPDATE target: unsupport with schema but support with tablename +INSERT INTO t_grammer VALUES(0, 0, '{0,0}', ROW(0, 0), '{107, 108}') ON DUPLICATE KEY UPDATE + upsert_test.t_grammer.c2 = c2 * 10, upsert_test.t_grammer.c3[1:2] = c5[1:2], upsert_test.t_grammer.c4.a = c1; +ERROR: column "upsert_test.t_grammer" of relation "t_grammer" does not exist +LINE 2: upsert_test.t_grammer.c2 = c2 * 10, upsert_test.t_grammer.c... + ^ +INSERT INTO t_grammer VALUES(101, 1, '{102, 103, 104}', ROW(105, 106), '{107, 108}') ON DUPLICATE KEY UPDATE + t_grammer.c2 = c2 * 10, t_grammer.c3[1:2] = c5[1:2], t_grammer.c4.a = c1; +-- INSERT target: appoint schema +INSERT INTO upsert_test.t_grammer VALUES(102, 2, '{102, 103, 104}', ROW(105, 106), '{107, 108}') ON DUPLICATE KEY UPDATE + t_grammer.c2 = EXCLUDED.c2 * 10, t_grammer.c3[1:2] = c5[1:2], t_grammer.c4.a = VALUES(c1); +CREATE SCHEMA upsert_test_tmp; +SET CURRENT_SCHEMA TO upsert_test_tmp; +INSERT INTO upsert_test.t_grammer VALUES(103, 3, '{102, 103, 104}', ROW(105, 106), '{107, 108}') ON DUPLICATE KEY UPDATE + t_grammer.c2 = EXCLUDED.c2 * 10, t_grammer.c3[1:2] = c5[1:2], t_grammer.c4.a = VALUES(c1); +SET CURRENT_SCHEMA TO upsert_test; +DROP SCHEMA upsert_test_tmp; +-- support data type +INSERT INTO t_data VALUES(11, 11, 11, 11, 11, '11', '11', '11', '2020-04-23', '2020-04-23 11:00', '1:00') ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_data VALUES(12, 12, 12, 12, 12, '12', '12', '12', '2020-04-24', '2020-04-24 11:00', '2:00') + ON DUPLICATE KEY UPDATE + c_tiny = 13, c_smallint = 13, c_bigint = 13, c_numeric = 13, + c_var = '13', c_text = '13', c_bytea = '13', c_date = '2020-05-24', c_timestamp = '2020-05-24 11:00', c_time = '3:00'; +INSERT INTO t_data VALUES(13, 13, 13, 13, 13, '13', '13', '13', '2020-04-25', '2020-04-25 11:00', '4:00') + ON DUPLICATE KEY UPDATE + c_tiny = EXCLUDED.c_tiny + 1, c_smallint = EXCLUDED.c_smallint + 1, c_bigint = EXCLUDED.c_bigint + 1, c_numeric = EXCLUDED.c_numeric + 1, + c_var = EXCLUDED.c_var || '3', c_text = EXCLUDED.c_text || '3', c_bytea = EXCLUDED.c_bytea || '3', + c_date = EXCLUDED.c_date + '1 day'::interval, c_timestamp = EXCLUDED.c_timestamp + '1 day'::interval, c_time = EXCLUDED.c_time + '1 hour'::interval; +-- support UPDATE to default +INSERT INTO t_default DEFAULT VALUES ON DUPLICATE KEY UPDATE c2 = 100, c3 = '2020-05-17'; +INSERT INTO t_default VALUES(91, 0.91, '2020-05-17') ON DUPLICATE KEY UPDATE c2 = DEFAULT, c3 = DEFAULT; +INSERT INTO t_default VALUES(92, DEFAULT, '2020-05-17') ON DUPLICATE KEY UPDATE c2 = 10, c3 = DEFAULT; +-- unsupport alias +INSERT INTO t_grammer AS alias VALUES (91) ON DUPLICATE KEY UPDATE c2 = VALUES(c1); +ERROR: syntax error at or near "AS" +LINE 1: INSERT INTO t_grammer AS alias VALUES (91) ON DUPLICATE KEY ... + ^ +-- unsupport VALUES with expr +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = sqrt(VALUES(c2)); +ERROR: syntax error at or near "(" +LINE 1: ... VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = sqrt(VALUES(c2)); + ^ +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(c3[1]) + 1; +ERROR: syntax error at or near "+" +LINE 1: ...VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(c3[1]) + 1; + ^ +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(c4.a) + 1; +ERROR: only allow column name within VALUES +LINE 1: ... VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(c4.a) + 1; + ^ +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(t_grammer.c3[1]) + 1; +ERROR: only allow column name within VALUES +LINE 1: ... VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(t_grammer.... + ^ +-- unsupport VALUES with table name +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(t_grammer.c4.a); +ERROR: only allow column name within VALUES +LINE 1: ... VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(t_grammer.... + ^ +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(t_grammer.c2); +ERROR: only allow column name within VALUES +LINE 1: ... VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(t_grammer.... + ^ +-- unsupport DEFAULT with expr +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = DEFAULT + 1; +ERROR: syntax error at or near "+" +LINE 1: ...ammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = DEFAULT + 1; + ^ +-- unsupport user defined typed column's element +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(c4.a); +ERROR: only allow column name within VALUES +LINE 1: ...mmer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(c4.a); + ^ +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = EXCLUDED.c4.a; +ERROR: schema "excluded" does not exist +CONTEXT: referenced column: c2 +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = c4.a; +ERROR: missing FROM-clause entry for table "c4" +LINE 1: ...TO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = c4.a; + ^ +CONTEXT: referenced column: c2 +SELECT * FROM t_data ORDER BY 1; + c_int | c_tiny | c_smallint | c_bigint | c_numeric | c_var | c_text | c_bytea | c_date | c_timestamp | c_time | c_intarray | c_com +-------+--------+------------+----------+-----------+-------+--------+----------+--------------------------+--------------------------+----------+------------+------- + 11 | 11 | 11 | 11 | 11 | 11 | 11 | \x3131 | Thu Apr 23 00:00:00 2020 | Thu Apr 23 11:00:00 2020 | 01:00:00 | | + 12 | 13 | 13 | 13 | 13 | 13 | 13 | \x3133 | Sun May 24 00:00:00 2020 | Sun May 24 11:00:00 2020 | 03:00:00 | | + 13 | 14 | 14 | 14 | 14 | 133 | 133 | \x313333 | Sun Apr 26 00:00:00 2020 | Sun Apr 26 11:00:00 2020 | 05:00:00 | | +(3 rows) + +SELECT * FROM t_grammer ORDER BY 1; + c1 | c2 | c3 | c4 | c5 +-----+------+---------------+-----------+----------- + 11 | 2 | {10,20,30} | (10,20) | + 12 | 2 | {3} | | + 23 | 2 | | | + 24 | 104 | | | + 31 | 31 | | (31,) | + 32 | 37 | | (32,) | + 41 | 41 | | (41,) | + 42 | 47 | | (42,) | + 51 | 56 | | (51,) | + 52 | 57 | | (57,) | + 53 | 58 | | (8,) | + 54 | 4 | | (,108) | + 55 | 5 | | (5,62) | + 61 | | | | + 62 | 2 | | | + 63 | 3 | | | + 64 | 10 | {10,20,30} | | + 65 | 5 | {10,20,30} | | {20,30} + 66 | 6 | {10,20,30} | | {10,20} + 67 | 10 | {10,20,30} | | + 68 | 8 | {10,20,30} | | {20,30} + 69 | 9 | {10,20,30} | | {10,20} + 71 | 1 | | (,) | + 72 | 2 | {71,72,73} | (143,) | + 73 | 3 | {71,72,73} | (144,72) | + 74 | 4 | {71,72,73} | (740,750) | + 81 | 81 | | | {81,82} + 82 | 881 | {881,882} | | {881,882} + 83 | -100 | {81,82,83} | (810,820) | + 84 | -100 | {81,82,83} | (850,83) | + 91 | 1 | {91,92,93} | (94,95) | + 92 | -100 | | (910,920) | {91,92} + 101 | 10 | {107,108,104} | (101,106) | {107,108} + 102 | 20 | {107,108,104} | (102,106) | {107,108} + 103 | 30 | {107,108,104} | (103,106) | {107,108} +(35 rows) + +SELECT * FROM t_default ORDER BY 1; +--? c1 | c2 | c3 +--? +--? 10 | 100 | Sun May 17 00:00:00 2020 +--? 91 | .840485369320959 | Wed Apr 29 10:25:28.439693 2020 +--? 92 | 10 | Wed Apr 29 10:25:28.442916 2020 +(3 rows) + diff --git a/src/test/regress/expected/upsert_prepare.out b/src/test/regress/expected/upsert_prepare.out new file mode 100644 index 000000000..57868e0e4 --- /dev/null +++ b/src/test/regress/expected/upsert_prepare.out @@ -0,0 +1,92 @@ +-- Note: about upsert test +-- test point: +-- grammer +-- table, unlogged table, temp table, matview +-- constraint(primary key, index, foreign key, null ...) +-- trigger +-- sequence +CREATE DATABASE upsert WITH TEMPLATE template0 ENCODING 'UTF8'; +\c upsert +-- grammer test +CREATE SCHEMA upsert_test; +SET CURRENT_SCHEMA TO upsert_test; +CREATE TYPE atype AS(a int, b int); +CREATE TYPE btype AS(a int, b atype, c varchar[3]); +CREATE TABLE t_grammer (c1 INT PRIMARY KEY, c2 INT DEFAULT -100, c3 int[3], c4 atype, c5 int[2]); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t_grammer_pkey" for table "t_grammer" +CREATE TABLE t_default (c1 INT PRIMARY KEY DEFAULT 10, c2 FLOAT DEFAULT random(), c3 TIMESTAMP DEFAULT current_timestamp); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t_default_pkey" for table "t_default" +CREATE TABLE t_data (c_int INT PRIMARY KEY, c_tiny TINYINT, c_smallint SMALLINT, c_bigint BIGINT, c_numeric NUMERIC, + c_var VARCHAR, c_text TEXT, c_bytea BYTEA, c_date DATE, c_timestamp TIMESTAMP, c_time TIME, + c_intarray INT[3], c_com atype); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t_data_pkey" for table "t_data" +CREATE TABLE t_trigger (key INT PRIMARY KEY, color TEXT); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t_trigger_pkey" for table "t_trigger" +-- unlogged table +CREATE SCHEMA upsert_test_unlog; +SET CURRENT_SCHEMA TO upsert_test_unlog; +CREATE TYPE atype AS(a int, b int); +CREATE TYPE btype AS(a int, b atype, c varchar[3]); +CREATE UNLOGGED TABLE t_hash_unlog_0 (c1 INT, c2 INT, c3 VARCHAR, c4 INT[3], c5 INT[5], c6 atype, c7 btype); +CREATE UNLOGGED TABLE t_hash_unlog_1 (c1 INT, c2 INT PRIMARY KEY, c3 VARCHAR, c4 INT[3], c5 INT[5], c6 atype, c7 btype); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t_hash_unlog_1_pkey" for table "t_hash_unlog_1" +CREATE UNLOGGED TABLE t_rep_unlog_0 (c1 INT, c2 INT, c3 VARCHAR, c4 INT[3], c5 INT[5], c6 atype, c7 btype) ; +CREATE UNLOGGED TABLE t_rep_unlog_1 (c1 INT, c2 INT PRIMARY KEY, c3 VARCHAR, c4 INT[3], c5 INT[5], c6 atype, c7 btype); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t_rep_unlog_1_pkey" for table "t_rep_unlog_1" +-- temp table +-- temp table can not be created here, we create it in upsert_tmp_test. +-- restriction test +CREATE SCHEMA upsert_test_etc; +SET CURRENT_SCHEMA TO upsert_test_etc; +create table up_neg_01 (c1 int, c2 int, c3 int) with (ORIENTATION = COLUMN); +create table up_neg_02 (c1 int, c2 int, c3 int) with (ORIENTATION = COLUMN); +create table up_neg_03 (c1 int, c2 int UNIQUE DEFERRABLE, c3 int); +NOTICE: CREATE TABLE / UNIQUE will create implicit index "up_neg_03_c2_key" for table "up_neg_03" +create table up_neg_04 (c1 int, c2 int UNIQUE, c3 int); +NOTICE: CREATE TABLE / UNIQUE will create implicit index "up_neg_04_c2_key" for table "up_neg_04" +create table up_neg_05(c1 int, c2 int, c3 int, c4 int unique, c5 int primary key, unique(c2,c3)); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "up_neg_05_pkey" for table "up_neg_05" +NOTICE: CREATE TABLE / UNIQUE will create implicit index "up_neg_05_c4_key" for table "up_neg_05" +NOTICE: CREATE TABLE / UNIQUE will create implicit index "up_neg_05_c2_c3_key" for table "up_neg_05" +create table up_neg_0(c1 int, c2 int, c3 int, c4 int unique, c5 int primary key, unique(c2,c3)) ; +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "up_neg_0_pkey" for table "up_neg_0" +NOTICE: CREATE TABLE / UNIQUE will create implicit index "up_neg_0_c4_key" for table "up_neg_0" +NOTICE: CREATE TABLE / UNIQUE will create implicit index "up_neg_0_c2_c3_key" for table "up_neg_0" +create table up_neg_06(c1 int, c2 int, c3 int unique) ; +NOTICE: CREATE TABLE / UNIQUE will create implicit index "up_neg_06_c3_key" for table "up_neg_06" +create table up_neg_07(c1 int, c2 int, c3 int unique) ; +NOTICE: CREATE TABLE / UNIQUE will create implicit index "up_neg_07_c3_key" for table "up_neg_07" +create table up_neg_08(c1 int, c2 int, c3 int unique) ; +NOTICE: CREATE TABLE / UNIQUE will create implicit index "up_neg_08_c3_key" for table "up_neg_08" +create table up_neg_09(c1 int, c2 int, c3 int, unique(c2,c3)) ; +NOTICE: CREATE TABLE / UNIQUE will create implicit index "up_neg_09_c2_c3_key" for table "up_neg_09" +create table up_neg_10(c1 int, c2 int, c3 int, unique(c2,c3)) ; +NOTICE: CREATE TABLE / UNIQUE will create implicit index "up_neg_10_c2_c3_key" for table "up_neg_10" +create view up_view as select *from up_neg_01; +create materialized view mat_view as select *from up_neg_01; +create table pkt (a int primary key, b int, c int); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "pkt_pkey" for table "pkt" +create table fkt (a int primary key, b int references pkt, c int); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "fkt_pkey" for table "fkt" +-- procedure test +CREATE SCHEMA upsert_test_procedure; +-- explain test +CREATE SCHEMA upsert_test_explain; +SET CURRENT_SCHEMA TO upsert_test_explain; +create table up_expl_hash(c1 int, c2 int, c3 int unique) ; +NOTICE: CREATE TABLE / UNIQUE will create implicit index "up_expl_hash_c3_key" for table "up_expl_hash" +create table up_expl_repl(c1 int, c2 int, c3 int unique) ; +NOTICE: CREATE TABLE / UNIQUE will create implicit index "up_expl_repl_c3_key" for table "up_expl_repl" +create table up_expl_part(c1 int, c2 int, c3 int unique) ; +NOTICE: CREATE TABLE / UNIQUE will create implicit index "up_expl_part_c3_key" for table "up_expl_part" +create unlogged table up_expl_unlog(c1 int, c2 int, c3 int unique) ; +NOTICE: CREATE TABLE / UNIQUE will create implicit index "up_expl_unlog_c3_key" for table "up_expl_unlog" +create table up_expl_node(c1 int, c2 int, c3 int unique) ; +NOTICE: CREATE TABLE / UNIQUE will create implicit index "up_expl_node_c3_key" for table "up_expl_node" +create table up_expl_repl(c1 int, c2 int, c3 int unique) ; +ERROR: relation "up_expl_repl" already exists +create table up_expl_repl2(c1 int, c2 int, c3 int, c4 int unique, c5 int primary key, unique(c2,c3)); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "up_expl_repl2_pkey" for table "up_expl_repl2" +NOTICE: CREATE TABLE / UNIQUE will create implicit index "up_expl_repl2_c4_key" for table "up_expl_repl2" +NOTICE: CREATE TABLE / UNIQUE will create implicit index "up_expl_repl2_c2_c3_key" for table "up_expl_repl2" +-- create temp table up_expl_temp(c1 int, c2 int, c3 int unique); diff --git a/src/test/regress/expected/upsert_prepare_etc.out b/src/test/regress/expected/upsert_prepare_etc.out new file mode 100644 index 000000000..3b4fc888a --- /dev/null +++ b/src/test/regress/expected/upsert_prepare_etc.out @@ -0,0 +1,4 @@ +CREATE DATABASE upsert_etc; +\c upsert_etc; +CREATE SCHEMA upsert_test_etc; +SET CURRENT_SCHEMA TO upsert_test_etc; diff --git a/src/test/regress/expected/upsert_restriction.out b/src/test/regress/expected/upsert_restriction.out new file mode 100644 index 000000000..7180de575 --- /dev/null +++ b/src/test/regress/expected/upsert_restriction.out @@ -0,0 +1,229 @@ +/* + * upsert negative test cases + * + table cases + col table + ORC table + VIEW + MAT view + index + DEFERABLE index + insert stmt + returning + with + with recur + update stmt + VALUES with expr + update distribute key + update primary key + update unique key + update partition key + where clause + with clause + sub query + */ +-------------------------------------------------------------------------------------------- +\c upsert +SET CURRENT_SCHEMA TO upsert_test_etc; +-- enable_upsert_to_merge must be off, or upsert will be translated to merge. +SET enable_upsert_to_merge TO OFF; +SHOW enable_upsert_to_merge; + enable_upsert_to_merge +------------------------ + off +(1 row) + +insert into up_neg_04 select a,a,a from generate_series(1,20) as a; +insert into up_neg_05 select a,a,a,a,a from generate_series(1,20) as a; +insert into up_neg_06 select a,a,a from generate_series(1,20) as a; +insert into up_neg_07 select a,a,a from generate_series(1,20) as a; +insert into up_neg_08 select a,a,a from generate_series(1,20) as a; +insert into up_neg_09 select a,a,a from generate_series(1,20) as a; +insert into up_neg_10 select a,a,a from generate_series(1,20) as a; +-- table cases +---- col table +insert into up_neg_01 values(1,2,3) on duplicate key update c3 = 1; +ERROR: INSERT ON DUPLICATE KEY UPDATE is not supported on column orientated table. +---- ORC table +insert into up_neg_02 values(1,2,3) on duplicate key update c3 = 1; +ERROR: INSERT ON DUPLICATE KEY UPDATE is not supported on column orientated table. +---- VIEW +insert into up_view values(1) on duplicate key update c3 = 1; +ERROR: INSERT ON DUPLICATE KEY UPDATE is not supported on VIEW. +-- matview +refresh materialized view mat_view; +insert into mat_view values(1) on duplicate key update c3 = 1; +ERROR: cannot change materialized view "mat_view" +-- index +---- DEFERABLE index +insert into up_neg_03 values(1,2,3) on duplicate key update c1 = 1; +ERROR: INSERT ON DUPLICATE KEY UPDATE does not support deferrable unique constraints/exclusion constraints. +insert into up_neg_03 values(1) on duplicate key update c1 = 1; +ERROR: INSERT ON DUPLICATE KEY UPDATE does not support deferrable unique constraints/exclusion constraints. +-- insert stmt +----returning +insert into up_neg_04 values(1,1,1) on duplicate key update c1 = 1 returning c1; +ERROR: RETURNING clause is not yet supported whithin INSERT ON DUPLICATE KEY UPDATE statement. +----with +with sub as (select *from up_neg_04) +insert into up_neg_04 select *from sub on duplicate key update c1 =1; +ERROR: WITH clause is not yet supported whithin INSERT ON DUPLICATE KEY UPDATE statement. +----with recur +with RECURSIVE sub as (select *from up_neg_04) +insert into up_neg_04 select *from sub on duplicate key update c1 =1; +ERROR: WITH clause is not yet supported whithin INSERT ON DUPLICATE KEY UPDATE statement. +with sub as (select *from up_neg_04) +insert into up_neg_04 select *from sub on duplicate key update c1 =1 returning c1; +ERROR: RETURNING clause is not yet supported whithin INSERT ON DUPLICATE KEY UPDATE statement. +-- update stmt +---- VALUES with expr +insert into up_neg_05 values(1,1,1,1) on duplicate key update c3 = values(1+100); +ERROR: syntax error at or near "1" +LINE 1: ... values(1,1,1,1) on duplicate key update c3 = values(1+100); + ^ +insert into up_neg_05 values(1,1,1,1) on duplicate key update c3 = values(100); +ERROR: syntax error at or near "100" +LINE 1: ...05 values(1,1,1,1) on duplicate key update c3 = values(100); + ^ +insert into up_neg_05 values(1,1,1,1,1) on duplicate key update c1 = 1; +---- update primary key +insert into up_neg_05 values(1,1,1,1,1) on duplicate key update c5 = 1; +ERROR: INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key. +---- update unique key +insert into up_neg_05 values(1,1,1,1,1) on duplicate key update c4 = 1; +ERROR: INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key. +insert into up_neg_05 values(1,1,1,1,1) on duplicate key update c2 = 1, c3=2; +ERROR: INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key. +insert into up_neg_05 values(1,1,1,1,1) on duplicate key update c2 = 1; +ERROR: INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key. +insert into up_neg_05 values(1,1,1,1,1) on duplicate key update c3 = 2; +ERROR: INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key. +insert into up_neg_05 values(1,1,1,1,1) on duplicate key update c1 =1, c2 = 1, c3=2, c4=1,c5=1; +ERROR: INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key. +---- where clause +insert into up_neg_05 values(1,1,1,1,1) on duplicate key update c4 = 1 where c1=1; +ERROR: syntax error at or near "where" +LINE 1: ... values(1,1,1,1,1) on duplicate key update c4 = 1 where c1=1... + ^ +---- from clause +insert into up_neg_05 values(1,1,1,1,1) on duplicate key update c4 = 1 from up_neg_04 where c1=1; +ERROR: syntax error at or near "from" +LINE 1: ... values(1,1,1,1,1) on duplicate key update c4 = 1 from up_ne... + ^ +---- sub query +insert into up_neg_05 values(1,1,1,1,1) on duplicate key update (c2) = (select c2 from up_neg_05); +ERROR: Update with subquery is not yet supported whithin INSERT ON DUPLICATE KEY UPDATE statement. +---- update distribute key +insert into up_neg_06 values(101, 1, 1) on duplicate key update c3=101; +ERROR: INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key. +---- update partition key +insert into up_neg_07 values(101, 1, 300) on duplicate key update c3=101; +ERROR: INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key. +insert into up_neg_08 values(101, 1, 300) on duplicate key update c3=101; +ERROR: INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key. +insert into up_neg_09 values(101, 1, 1) on duplicate key update c3=101; +ERROR: INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key. +insert into up_neg_09 values(101, 1, 1) on duplicate key update c2=101; +ERROR: INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key. +insert into up_neg_09 values(101, 1, 1) on duplicate key update c2 =1,c3=101; +ERROR: INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key. +--update unique key mul type plans +EXPLAIN (VERBOSE on, COSTS off) insert into up_neg_10 values(101, 1, 300) on duplicate key update c1=101; + QUERY PLAN +------------------------------------------------- + Insert on upsert_test_etc.up_neg_10 + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: up_neg_10_c2_c3_key + -> Result + Output: 101, 1, 300 +(5 rows) + +insert into up_neg_10 values(101, 1, 300) on duplicate key update c3=101; +ERROR: INSERT ON DUPLICATE KEY UPDATE don't allow update on primary key or unique key. +--trigger +create table upsert_base +(c1 int primary key, + c2 varchar(100), --record the type + cold varchar(1024), + cnew varchar(1024) ); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "upsert_base_pkey" for table "upsert_base" +create table upsert_tri +(c1 int, + c2 varchar(100), + c3 int, + unique(c1,c3) +); +NOTICE: CREATE TABLE / UNIQUE will create implicit index "upsert_tri_c1_c3_key" for table "upsert_tri" +--S1 after trigger +CREATE OR REPLACE FUNCTION trig_after() RETURNS TRIGGER AS $emp_audit$ + BEGIN + IF (TG_OP = 'INSERT') THEN + insert into upsert_base(c2,cnew) values('after insert',new.c2); + RETURN NEW; + ELSIF (TG_OP = 'UPDATE') THEN + insert into upsert_base(c2,cold,cnew) values('after update',old.c2,new.c2); + RETURN NEW; + END IF; + RETURN NULL; + END; +$emp_audit$ LANGUAGE plpgsql; +CREATE TRIGGER tri_after +AFTER UPDATE OR INSERT ON upsert_tri + FOR EACH ROW EXECUTE PROCEDURE trig_after(); +--S2 before trigger +CREATE OR REPLACE FUNCTION trig_before() RETURNS TRIGGER AS $emp_audit$ + BEGIN + IF (TG_OP = 'INSERT') THEN + insert into upsert_base(c2,cnew) values('before insert',new.c2); + RETURN NEW; + ELSIF (TG_OP = 'UPDATE') THEN + insert into upsert_base(c2,cold,cnew) values('before update',old.c2,new.c2); + RETURN NEW; + END IF; + RETURN NULL; + END; +$emp_audit$ LANGUAGE plpgsql; +CREATE TRIGGER tri_before +BEFORE UPDATE OR INSERT ON upsert_tri + FOR EACH ROW EXECUTE PROCEDURE trig_before(); +---- upsert-insert +insert into upsert_tri(c1,c2,c3) values(1000,'abc',1) on duplicate key update nothing; +ERROR: null value in column "c1" violates not-null constraint +DETAIL: Failing row contains (null, before insert, null, abc). +CONTEXT: SQL statement "insert into upsert_base(c2,cnew) values('before insert',new.c2)" +PL/pgSQL function trig_before() line 4 at SQL statement +insert into upsert_tri(c1,c2,c3) values(2000,'abc',1) on duplicate key update c2='bcd'; +ERROR: null value in column "c1" violates not-null constraint +DETAIL: Failing row contains (null, before insert, null, abc). +CONTEXT: SQL statement "insert into upsert_base(c2,cnew) values('before insert',new.c2)" +PL/pgSQL function trig_before() line 4 at SQL statement +---- upsert-nothing +insert into upsert_tri(c1,c2,c3) values(1000,'abc',1) on duplicate key update nothing; +ERROR: null value in column "c1" violates not-null constraint +DETAIL: Failing row contains (null, before insert, null, abc). +CONTEXT: SQL statement "insert into upsert_base(c2,cnew) values('before insert',new.c2)" +PL/pgSQL function trig_before() line 4 at SQL statement +---- upsert-update +insert into upsert_tri(c1,c2,c3) values(2000,'abc',1) on duplicate key update c2='bcd'; +ERROR: null value in column "c1" violates not-null constraint +DETAIL: Failing row contains (null, before insert, null, abc). +CONTEXT: SQL statement "insert into upsert_base(c2,cnew) values('before insert',new.c2)" +PL/pgSQL function trig_before() line 4 at SQL statement +---- check results +SELECT * from upsert_tri; + c1 | c2 | c3 +----+----+---- +(0 rows) + +SELECT * from upsert_base; + c1 | c2 | cold | cnew +----+----+------+------ +(0 rows) + +-- foreign key +insert into pkt values(1,1,1),(2,2,2),(3,3,3),(4,4,4),(5,5,5); +insert into fkt values(1,1,1),(2,2,2),(3,3,3); +insert into fkt values(1,1,1),(2,2,2),(3,3,3) on duplicate key update b=excluded.b+1, c=0; +insert into fkt values(1,1,1),(2,2,2),(3,3,3) on duplicate key update b=excluded.b+3, c=-1; +ERROR: insert or update on table "fkt" violates foreign key constraint "fkt_b_fkey" +DETAIL: Key (b)=(6) is not present in table "pkt". diff --git a/src/test/regress/expected/upsert_tmp_test.out b/src/test/regress/expected/upsert_tmp_test.out new file mode 100644 index 000000000..c78457643 --- /dev/null +++ b/src/test/regress/expected/upsert_tmp_test.out @@ -0,0 +1,173 @@ +\c upsert +CREATE SCHEMA upsert_test_tmp; +SET CURRENT_SCHEMA TO upsert_test_tmp; +CREATE TYPE atype AS(a int, b int); +CREATE TYPE btype AS(a int, b atype, c varchar[3]); +CREATE TEMP TABLE t_hash_tmp_0 (c1 INT, c2 INT, c3 VARCHAR, c4 INT[3], c5 INT[5], c6 atype, c7 btype); +CREATE TEMP TABLE t_hash_tmp_1 (c1 INT, c2 INT PRIMARY KEY, c3 VARCHAR, c4 INT[3], c5 INT[5], c6 atype, c7 btype); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t_hash_tmp_1_pkey" for table "t_hash_tmp_1" +CREATE TEMP TABLE t_rep_tmp_0 (c1 INT, c2 INT, c3 VARCHAR, c4 INT[3], c5 INT[5], c6 atype, c7 btype) ; +CREATE TEMP TABLE t_rep_tmp_1 (c1 INT, c2 INT PRIMARY KEY, c3 VARCHAR, c4 INT[3], c5 INT[5], c6 atype, c7 btype) ; +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t_rep_tmp_1_pkey" for table "t_rep_tmp_1" +-- enable_upsert_to_merge must be off, or upsert will be translated to merge. +SET enable_upsert_to_merge TO OFF; +SHOW enable_upsert_to_merge; + enable_upsert_to_merge +------------------------ + off +(1 row) + +-- hash table +-- tmp table without primary key +INSERT INTO t_hash_tmp_0 VALUES(1, 1, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (1, 2, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_hash_tmp_0 VALUES(2, 3, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (2, 4, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE c2 = 100; +-- tmp table with primary key +-- multi insert +INSERT INTO t_hash_tmp_1 VALUES(1, 1, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (1, 2, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_hash_tmp_1 VALUES(2, 3, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (2, 4, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE c1 = EXCLUDED.c1, c3 = EXCLUDED.c3, c4 = EXCLUDED.c4, c5 = EXCLUDED.c5, c6 = EXCLUDED.c6, c7 = EXCLUDED.c7; +SELECT * FROM t_hash_tmp_1 ORDER BY c2; + c1 | c2 | c3 | c4 | c5 | c6 | c7 +----+----+----+---------+------------------+---------+----------------------------- + 1 | 1 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 1 | 2 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 2 | 3 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 2 | 4 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") +(4 rows) + +INSERT INTO t_hash_tmp_1 VALUES(10, 1, 'C30', '{10,20,30}', '{100,200,300,400,500}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (11, 2, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_hash_tmp_1 VALUES(20, 3, 'C30', '{10,20,30}', '{10,20,30,40,50}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (21, 4, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE c1 = EXCLUDED.c1, c3 = EXCLUDED.c3, c4 = EXCLUDED.c4, c5 = EXCLUDED.c5, c6 = EXCLUDED.c6, c7 = EXCLUDED.c7; +SELECT * FROM t_hash_tmp_1 ORDER BY c2; + c1 | c2 | c3 | c4 | c5 | c6 | c7 +----+----+-----+------------+-----------------------+-----------+---------------------------------- + 1 | 1 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 1 | 2 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 20 | 3 | C30 | {10,20,30} | {10,20,30,40,50} | (100,200) | (1000,"(100,200)","{1000,2000}") + 21 | 4 | C31 | {11,21,31} | {101,201,301,401,501} | (101,201) | (1001,"(101,201)","{1001,2001}") +(4 rows) + +-- insert and update same tuple, and update twice for another same tuple +INSERT INTO t_hash_tmp_1 VALUES(0, 5, 'C30', '{10,20,30}', '{10,20,30,40,50}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (1, 5, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')), + (10, 1, 'C30', '{10,20,30}', '{100,200,300,400,500}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (11, 1, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE NOTHING; +SELECT * FROM t_hash_tmp_1 ORDER BY c2; + c1 | c2 | c3 | c4 | c5 | c6 | c7 +----+----+-----+------------+-----------------------+-----------+---------------------------------- + 1 | 1 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 1 | 2 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 20 | 3 | C30 | {10,20,30} | {10,20,30,40,50} | (100,200) | (1000,"(100,200)","{1000,2000}") + 21 | 4 | C31 | {11,21,31} | {101,201,301,401,501} | (101,201) | (1001,"(101,201)","{1001,2001}") + 0 | 5 | C30 | {10,20,30} | {10,20,30,40,50} | (100,200) | (1000,"(100,200)","{1000,2000}") +(5 rows) + +INSERT INTO t_hash_tmp_1 VALUES(0, 5, 'C30', '{10,20,30}', '{10,20,30,40,50}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (1, 5, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')), + (10, 1, 'C30', '{10,20,30}', '{100,200,300,400,500}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (11, 1, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE c1 = EXCLUDED.c1, c3 = EXCLUDED.c3, c4 = EXCLUDED.c4, c5 = EXCLUDED.c5, c6 = EXCLUDED.c6, c7 = EXCLUDED.c7; +SELECT * FROM t_hash_tmp_1 ORDER BY c2; + c1 | c2 | c3 | c4 | c5 | c6 | c7 +----+----+-----+------------+-----------------------+-----------+---------------------------------- + 11 | 1 | C31 | {11,21,31} | {101,201,301,401,501} | (101,201) | (1001,"(101,201)","{1001,2001}") + 1 | 2 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 20 | 3 | C30 | {10,20,30} | {10,20,30,40,50} | (100,200) | (1000,"(100,200)","{1000,2000}") + 21 | 4 | C31 | {11,21,31} | {101,201,301,401,501} | (101,201) | (1001,"(101,201)","{1001,2001}") + 1 | 5 | C31 | {11,21,31} | {101,201,301,401,501} | (101,201) | (1001,"(101,201)","{1001,2001}") +(5 rows) + +-- replication table +-- tmp table without primary key +INSERT INTO t_rep_tmp_0 VALUES(1, 1, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (1, 2, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_rep_tmp_0 VALUES(2, 3, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (2, 4, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE c2 = 100; +-- tmp table with primary key +-- multi insert +INSERT INTO t_rep_tmp_1 VALUES(1, 1, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (1, 2, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_rep_tmp_1 VALUES(2, 3, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (2, 4, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE c1 = EXCLUDED.c1, c3 = EXCLUDED.c3, c4 = EXCLUDED.c4, c5 = EXCLUDED.c5, c6 = EXCLUDED.c6, c7 = EXCLUDED.c7; +SELECT * FROM t_rep_tmp_1 ORDER BY c2; + c1 | c2 | c3 | c4 | c5 | c6 | c7 +----+----+----+---------+------------------+---------+----------------------------- + 1 | 1 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 1 | 2 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 2 | 3 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 2 | 4 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") +(4 rows) + +INSERT INTO t_rep_tmp_1 VALUES(10, 1, 'C30', '{10,20,30}', '{100,200,300,400,500}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (11, 2, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_rep_tmp_1 VALUES(20, 3, 'C30', '{10,20,30}', '{10,20,30,40,50}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (21, 4, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE c1 = EXCLUDED.c1, c3 = EXCLUDED.c3, c4 = EXCLUDED.c4, c5 = EXCLUDED.c5, c6 = EXCLUDED.c6, c7 = EXCLUDED.c7; +SELECT * FROM t_rep_tmp_1 ORDER BY c2; + c1 | c2 | c3 | c4 | c5 | c6 | c7 +----+----+-----+------------+-----------------------+-----------+---------------------------------- + 1 | 1 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 1 | 2 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 20 | 3 | C30 | {10,20,30} | {10,20,30,40,50} | (100,200) | (1000,"(100,200)","{1000,2000}") + 21 | 4 | C31 | {11,21,31} | {101,201,301,401,501} | (101,201) | (1001,"(101,201)","{1001,2001}") +(4 rows) + +-- insert and update same tuple, and update twice for another same tuple +INSERT INTO t_rep_tmp_1 VALUES(0, 5, 'C30', '{10,20,30}', '{10,20,30,40,50}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (1, 5, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')), + (10, 1, 'C30', '{10,20,30}', '{100,200,300,400,500}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (11, 1, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE NOTHING; +SELECT * FROM t_rep_tmp_1 ORDER BY c2; + c1 | c2 | c3 | c4 | c5 | c6 | c7 +----+----+-----+------------+-----------------------+-----------+---------------------------------- + 1 | 1 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 1 | 2 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 20 | 3 | C30 | {10,20,30} | {10,20,30,40,50} | (100,200) | (1000,"(100,200)","{1000,2000}") + 21 | 4 | C31 | {11,21,31} | {101,201,301,401,501} | (101,201) | (1001,"(101,201)","{1001,2001}") + 0 | 5 | C30 | {10,20,30} | {10,20,30,40,50} | (100,200) | (1000,"(100,200)","{1000,2000}") +(5 rows) + +INSERT INTO t_rep_tmp_1 VALUES(0, 5, 'C30', '{10,20,30}', '{10,20,30,40,50}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (1, 5, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')), + (10, 1, 'C30', '{10,20,30}', '{100,200,300,400,500}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (11, 1, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE c1 = EXCLUDED.c1, c3 = EXCLUDED.c3, c4 = EXCLUDED.c4, c5 = EXCLUDED.c5, c6 = EXCLUDED.c6, c7 = EXCLUDED.c7; +SELECT * FROM t_rep_tmp_1 ORDER BY c2; + c1 | c2 | c3 | c4 | c5 | c6 | c7 +----+----+-----+------------+-----------------------+-----------+---------------------------------- + 11 | 1 | C31 | {11,21,31} | {101,201,301,401,501} | (101,201) | (1001,"(101,201)","{1001,2001}") + 1 | 2 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 20 | 3 | C30 | {10,20,30} | {10,20,30,40,50} | (100,200) | (1000,"(100,200)","{1000,2000}") + 21 | 4 | C31 | {11,21,31} | {101,201,301,401,501} | (101,201) | (1001,"(101,201)","{1001,2001}") + 1 | 5 | C31 | {11,21,31} | {101,201,301,401,501} | (101,201) | (1001,"(101,201)","{1001,2001}") +(5 rows) + +DROP SCHEMA upsert_test_tmp CASCADE; +NOTICE: drop cascades to 10 other objects +DETAIL: drop cascades to type atype +drop cascades to table t_hash_tmp_0 column c6 +drop cascades to table t_hash_tmp_1 column c6 +drop cascades to table t_rep_tmp_0 column c6 +drop cascades to table t_rep_tmp_1 column c6 +drop cascades to type btype +drop cascades to table t_hash_tmp_0 column c7 +drop cascades to table t_hash_tmp_1 column c7 +drop cascades to table t_rep_tmp_0 column c7 +drop cascades to table t_rep_tmp_1 column c7 diff --git a/src/test/regress/expected/upsert_trigger_test.out b/src/test/regress/expected/upsert_trigger_test.out new file mode 100644 index 000000000..b0c3acaa6 --- /dev/null +++ b/src/test/regress/expected/upsert_trigger_test.out @@ -0,0 +1,148 @@ +\c upsert +SET CURRENT_SCHEMA TO upsert_test; +-- enable_upsert_to_merge must be off, or upsert will be translated to merge. +SET enable_upsert_to_merge TO OFF; +SHOW enable_upsert_to_merge; + enable_upsert_to_merge +------------------------ + off +(1 row) + +CREATE FUNCTION upsert_before_func() + RETURNS TRIGGER language plpgsql AS +$$ +BEGIN + IF (TG_OP = 'UPDATE') THEN + RAISE warning 'before update (old): %', old.*::TEXT; + RAISE warning 'before update (new): %', new.*::TEXT; + elsIF (TG_OP = 'INSERT') THEN + RAISE warning 'before insert (new): %', new.*::TEXT; + IF NEW.key % 2 = 0 THEN + NEW.color := NEW.color || ' trig modified'; + RAISE warning 'before insert (new, modified): %', new.*::TEXT; + END IF; + END IF; + RETURN new; +END; +$$; +CREATE TRIGGER upsert_before_trig BEFORE INSERT OR UPDATE ON t_trigger + FOR EACH ROW EXECUTE procedure upsert_before_func(); +CREATE FUNCTION upsert_after_func() + RETURNS TRIGGER language plpgsql AS +$$ +BEGIN + IF (TG_OP = 'UPDATE') THEN + RAISE warning 'after update (old): %', old.*::TEXT; + RAISE warning 'after update (new): %', new.*::TEXT; + elsIF (TG_OP = 'INSERT') THEN + RAISE warning 'after insert (new): %', new.*::TEXT; + END IF; + RETURN null; +END; +$$; +CREATE TRIGGER upsert_after_trig AFTER INSERT OR UPDATE ON t_trigger + FOR EACH ROW EXECUTE procedure upsert_after_func(); +INSERT INTO t_trigger values(1, 'black') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; +WARNING: before insert (new): (1,black) +WARNING: after insert (new): (1,black) +INSERT INTO t_trigger values(2, 'red') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; +WARNING: before insert (new): (2,red) +WARNING: before insert (new, modified): (2,"red trig modified") +WARNING: after insert (new): (2,"red trig modified") +INSERT INTO t_trigger values(3, 'orange') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; +WARNING: before insert (new): (3,orange) +WARNING: after insert (new): (3,orange) +INSERT INTO t_trigger values(4, 'green') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; +WARNING: before insert (new): (4,green) +WARNING: before insert (new, modified): (4,"green trig modified") +WARNING: after insert (new): (4,"green trig modified") +INSERT INTO t_trigger values(5, 'purple') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; +WARNING: before insert (new): (5,purple) +WARNING: after insert (new): (5,purple) +INSERT INTO t_trigger values(6, 'white') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; +WARNING: before insert (new): (6,white) +WARNING: before insert (new, modified): (6,"white trig modified") +WARNING: after insert (new): (6,"white trig modified") +INSERT INTO t_trigger values(7, 'pink') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; +WARNING: before insert (new): (7,pink) +WARNING: after insert (new): (7,pink) +INSERT INTO t_trigger values(8, 'yellow') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; +WARNING: before insert (new): (8,yellow) +WARNING: before insert (new, modified): (8,"yellow trig modified") +WARNING: after insert (new): (8,"yellow trig modified") +SELECT * FROM t_trigger ORDER BY key; + key | color +-----+---------------------- + 1 | black + 2 | red trig modified + 3 | orange + 4 | green trig modified + 5 | purple + 6 | white trig modified + 7 | pink + 8 | yellow trig modified +(8 rows) + +INSERT INTO t_trigger values(2, 'black') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; +WARNING: before insert (new): (2,black) +WARNING: before insert (new, modified): (2,"black trig modified") +WARNING: before update (old): (2,"red trig modified") +WARNING: before update (new): (2,"updated red trig modified") +WARNING: after update (old): (2,"red trig modified") +WARNING: after update (new): (2,"updated red trig modified") +INSERT INTO t_trigger values(3, 'red') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; +WARNING: before insert (new): (3,red) +WARNING: before update (old): (3,orange) +WARNING: before update (new): (3,"updated orange") +WARNING: after update (old): (3,orange) +WARNING: after update (new): (3,"updated orange") +INSERT INTO t_trigger values(4, 'orange') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; +WARNING: before insert (new): (4,orange) +WARNING: before insert (new, modified): (4,"orange trig modified") +WARNING: before update (old): (4,"green trig modified") +WARNING: before update (new): (4,"updated green trig modified") +WARNING: after update (old): (4,"green trig modified") +WARNING: after update (new): (4,"updated green trig modified") +INSERT INTO t_trigger values(5, 'green') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; +WARNING: before insert (new): (5,green) +WARNING: before update (old): (5,purple) +WARNING: before update (new): (5,"updated purple") +WARNING: after update (old): (5,purple) +WARNING: after update (new): (5,"updated purple") +INSERT INTO t_trigger values(6, 'purple') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; +WARNING: before insert (new): (6,purple) +WARNING: before insert (new, modified): (6,"purple trig modified") +WARNING: before update (old): (6,"white trig modified") +WARNING: before update (new): (6,"updated white trig modified") +WARNING: after update (old): (6,"white trig modified") +WARNING: after update (new): (6,"updated white trig modified") +INSERT INTO t_trigger values(7, 'white') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; +WARNING: before insert (new): (7,white) +WARNING: before update (old): (7,pink) +WARNING: before update (new): (7,"updated pink") +WARNING: after update (old): (7,pink) +WARNING: after update (new): (7,"updated pink") +INSERT INTO t_trigger values(8, 'pink') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; +WARNING: before insert (new): (8,pink) +WARNING: before insert (new, modified): (8,"pink trig modified") +WARNING: before update (old): (8,"yellow trig modified") +WARNING: before update (new): (8,"updated yellow trig modified") +WARNING: after update (old): (8,"yellow trig modified") +WARNING: after update (new): (8,"updated yellow trig modified") +INSERT INTO t_trigger values(9, 'yellow') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; +WARNING: before insert (new): (9,yellow) +WARNING: after insert (new): (9,yellow) +SELECT * FROM t_trigger ORDER BY key; + key | color +-----+------------------------------ + 1 | black + 2 | updated red trig modified + 3 | updated orange + 4 | updated green trig modified + 5 | updated purple + 6 | updated white trig modified + 7 | updated pink + 8 | updated yellow trig modified + 9 | yellow +(9 rows) + diff --git a/src/test/regress/expected/upsert_unlog_test.out b/src/test/regress/expected/upsert_unlog_test.out new file mode 100644 index 000000000..3d6c69ea9 --- /dev/null +++ b/src/test/regress/expected/upsert_unlog_test.out @@ -0,0 +1,152 @@ +\c upsert +SET CURRENT_SCHEMA TO upsert_test_unlog; +-- enable_upsert_to_merge must be off, or upsert will be translated to merge. +SET enable_upsert_to_merge TO OFF; +SHOW enable_upsert_to_merge; + enable_upsert_to_merge +------------------------ + off +(1 row) + +-- hash table +-- unlogged table without primary key +INSERT INTO t_hash_unlog_0 VALUES(1, 1, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (1, 2, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_hash_unlog_0 VALUES(2, 3, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (2, 4, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE c2 = 100; +-- unlogged table with primary key +-- multi insert +INSERT INTO t_hash_unlog_1 VALUES(1, 1, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (1, 2, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_hash_unlog_1 VALUES(2, 3, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (2, 4, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE c1 = EXCLUDED.c1, c3 = EXCLUDED.c3, c4 = EXCLUDED.c4, c5 = EXCLUDED.c5, c6 = EXCLUDED.c6, c7 = EXCLUDED.c7; +SELECT * FROM t_hash_unlog_1 ORDER BY c2; + c1 | c2 | c3 | c4 | c5 | c6 | c7 +----+----+----+---------+------------------+---------+----------------------------- + 1 | 1 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 1 | 2 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 2 | 3 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 2 | 4 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") +(4 rows) + +INSERT INTO t_hash_unlog_1 VALUES(10, 1, 'C30', '{10,20,30}', '{100,200,300,400,500}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (11, 2, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_hash_unlog_1 VALUES(20, 3, 'C30', '{10,20,30}', '{10,20,30,40,50}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (21, 4, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE c1 = EXCLUDED.c1, c3 = EXCLUDED.c3, c4 = EXCLUDED.c4, c5 = EXCLUDED.c5, c6 = EXCLUDED.c6, c7 = EXCLUDED.c7; +SELECT * FROM t_hash_unlog_1 ORDER BY c2; + c1 | c2 | c3 | c4 | c5 | c6 | c7 +----+----+-----+------------+-----------------------+-----------+---------------------------------- + 1 | 1 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 1 | 2 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 20 | 3 | C30 | {10,20,30} | {10,20,30,40,50} | (100,200) | (1000,"(100,200)","{1000,2000}") + 21 | 4 | C31 | {11,21,31} | {101,201,301,401,501} | (101,201) | (1001,"(101,201)","{1001,2001}") +(4 rows) + +-- insert and update same tuple, and update twice for another same tuple +INSERT INTO t_hash_unlog_1 VALUES(0, 5, 'C30', '{10,20,30}', '{10,20,30,40,50}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (1, 5, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')), + (10, 1, 'C30', '{10,20,30}', '{100,200,300,400,500}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (11, 1, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE NOTHING; +SELECT * FROM t_hash_unlog_1 ORDER BY c2; + c1 | c2 | c3 | c4 | c5 | c6 | c7 +----+----+-----+------------+-----------------------+-----------+---------------------------------- + 1 | 1 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 1 | 2 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 20 | 3 | C30 | {10,20,30} | {10,20,30,40,50} | (100,200) | (1000,"(100,200)","{1000,2000}") + 21 | 4 | C31 | {11,21,31} | {101,201,301,401,501} | (101,201) | (1001,"(101,201)","{1001,2001}") + 0 | 5 | C30 | {10,20,30} | {10,20,30,40,50} | (100,200) | (1000,"(100,200)","{1000,2000}") +(5 rows) + +INSERT INTO t_hash_unlog_1 VALUES(0, 5, 'C30', '{10,20,30}', '{10,20,30,40,50}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (1, 5, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')), + (10, 1, 'C30', '{10,20,30}', '{100,200,300,400,500}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (11, 1, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE c1 = EXCLUDED.c1, c3 = EXCLUDED.c3, c4 = EXCLUDED.c4, c5 = EXCLUDED.c5, c6 = EXCLUDED.c6, c7 = EXCLUDED.c7; +SELECT * FROM t_hash_unlog_1 ORDER BY c2; + c1 | c2 | c3 | c4 | c5 | c6 | c7 +----+----+-----+------------+-----------------------+-----------+---------------------------------- + 11 | 1 | C31 | {11,21,31} | {101,201,301,401,501} | (101,201) | (1001,"(101,201)","{1001,2001}") + 1 | 2 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 20 | 3 | C30 | {10,20,30} | {10,20,30,40,50} | (100,200) | (1000,"(100,200)","{1000,2000}") + 21 | 4 | C31 | {11,21,31} | {101,201,301,401,501} | (101,201) | (1001,"(101,201)","{1001,2001}") + 1 | 5 | C31 | {11,21,31} | {101,201,301,401,501} | (101,201) | (1001,"(101,201)","{1001,2001}") +(5 rows) + +-- replication table +-- unlogged table without primary key +INSERT INTO t_rep_unlog_0 VALUES(1, 1, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (1, 2, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_rep_unlog_0 VALUES(2, 3, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (2, 4, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE c2 = 100; +-- unlogged table with primary key +-- multi insert +INSERT INTO t_rep_unlog_1 VALUES(1, 1, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (1, 2, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_rep_unlog_1 VALUES(2, 3, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (2, 4, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE c1 = EXCLUDED.c1, c3 = EXCLUDED.c3, c4 = EXCLUDED.c4, c5 = EXCLUDED.c5, c6 = EXCLUDED.c6, c7 = EXCLUDED.c7; +SELECT * FROM t_rep_unlog_1 ORDER BY c2; + c1 | c2 | c3 | c4 | c5 | c6 | c7 +----+----+----+---------+------------------+---------+----------------------------- + 1 | 1 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 1 | 2 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 2 | 3 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 2 | 4 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") +(4 rows) + +INSERT INTO t_rep_unlog_1 VALUES(10, 1, 'C30', '{10,20,30}', '{100,200,300,400,500}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (11, 2, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_rep_unlog_1 VALUES(20, 3, 'C30', '{10,20,30}', '{10,20,30,40,50}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (21, 4, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE c1 = EXCLUDED.c1, c3 = EXCLUDED.c3, c4 = EXCLUDED.c4, c5 = EXCLUDED.c5, c6 = EXCLUDED.c6, c7 = EXCLUDED.c7; +SELECT * FROM t_rep_unlog_1 ORDER BY c2; + c1 | c2 | c3 | c4 | c5 | c6 | c7 +----+----+-----+------------+-----------------------+-----------+---------------------------------- + 1 | 1 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 1 | 2 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 20 | 3 | C30 | {10,20,30} | {10,20,30,40,50} | (100,200) | (1000,"(100,200)","{1000,2000}") + 21 | 4 | C31 | {11,21,31} | {101,201,301,401,501} | (101,201) | (1001,"(101,201)","{1001,2001}") +(4 rows) + +-- insert and update same tuple, and update twice for another same tuple +INSERT INTO t_rep_unlog_1 VALUES(0, 5, 'C30', '{10,20,30}', '{10,20,30,40,50}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (1, 5, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')), + (10, 1, 'C30', '{10,20,30}', '{100,200,300,400,500}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (11, 1, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE NOTHING; +SELECT * FROM t_rep_unlog_1 ORDER BY c2; + c1 | c2 | c3 | c4 | c5 | c6 | c7 +----+----+-----+------------+-----------------------+-----------+---------------------------------- + 1 | 1 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 1 | 2 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 20 | 3 | C30 | {10,20,30} | {10,20,30,40,50} | (100,200) | (1000,"(100,200)","{1000,2000}") + 21 | 4 | C31 | {11,21,31} | {101,201,301,401,501} | (101,201) | (1001,"(101,201)","{1001,2001}") + 0 | 5 | C30 | {10,20,30} | {10,20,30,40,50} | (100,200) | (1000,"(100,200)","{1000,2000}") +(5 rows) + +INSERT INTO t_rep_unlog_1 VALUES(0, 5, 'C30', '{10,20,30}', '{10,20,30,40,50}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (1, 5, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')), + (10, 1, 'C30', '{10,20,30}', '{100,200,300,400,500}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (11, 1, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE c1 = EXCLUDED.c1, c3 = EXCLUDED.c3, c4 = EXCLUDED.c4, c5 = EXCLUDED.c5, c6 = EXCLUDED.c6, c7 = EXCLUDED.c7; +SELECT * FROM t_rep_unlog_1 ORDER BY c2; + c1 | c2 | c3 | c4 | c5 | c6 | c7 +----+----+-----+------------+-----------------------+-----------+---------------------------------- + 11 | 1 | C31 | {11,21,31} | {101,201,301,401,501} | (101,201) | (1001,"(101,201)","{1001,2001}") + 1 | 2 | C3 | {1,2,3} | {10,20,30,40,50} | (10,20) | (100,"(10,20)","{100,200}") + 20 | 3 | C30 | {10,20,30} | {10,20,30,40,50} | (100,200) | (1000,"(100,200)","{1000,2000}") + 21 | 4 | C31 | {11,21,31} | {101,201,301,401,501} | (101,201) | (1001,"(101,201)","{1001,2001}") + 1 | 5 | C31 | {11,21,31} | {101,201,301,401,501} | (101,201) | (1001,"(101,201)","{1001,2001}") +(5 rows) + diff --git a/src/test/regress/output/recovery_2pc_tools.source b/src/test/regress/output/recovery_2pc_tools.source index cbec567e8..078f72220 100644 --- a/src/test/regress/output/recovery_2pc_tools.source +++ b/src/test/regress/output/recovery_2pc_tools.source @@ -227,6 +227,7 @@ select name,vartype,unit,min_val,max_val from pg_settings where name <> 'qunit_c enable_tsdb | bool | | | enable_twophase_commit | bool | | | enable_upgrade_merge_lock_mode | bool | | | + enable_upsert_to_merge | bool | | | enable_user_metric_persistent | bool | | | enable_valuepartition_pruning | bool | | | enable_vector_engine | bool | | | diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 0b39cb601..ba166aad8 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -44,12 +44,18 @@ test: hw_smp # test MERGE INTO -# test INSERT UPDATE +# test INSERT UPDATE UPSERT test: insert_update_001 insert_update_002 insert_update_003 insert_update_008 insert_update_009 insert_update_010 test: delete update namespace case select_having select_implicit test: hw_test_operate_user test: hw_createtbl_llt gsqlerr test: hw_sql_llt sqlLLT +test: upsert_prepare +test: upsert_001 upsert_002 upsert_003 upsert_008 upsert_009 upsert_010 +test: upsert_grammer_test_01 upsert_unlog_test upsert_tmp_test +test: upsert_grammer_test_02 upsert_restriction upsert_composite +test: upsert_trigger_test upsert_explain +test: upsert_clean # all pass # run tablespace by itself, and first, because it forces a checkpoint; diff --git a/src/test/regress/parallel_schedule3 b/src/test/regress/parallel_schedule3 index 1f8d55795..644da549f 100644 --- a/src/test/regress/parallel_schedule3 +++ b/src/test/regress/parallel_schedule3 @@ -17,3 +17,11 @@ test: delete update namespace case select_having select_implicit test: hw_test_operate_user test: hw_createtbl_llt gsqlerr test: hw_sql_llt sqlLLT + +# test UPSERT +test: upsert_prepare +test: upsert_001 upsert_002 upsert_003 upsert_008 upsert_009 upsert_010 +test: upsert_grammer_test_01 upsert_unlog_test upsert_tmp_test +test: upsert_grammer_test_02 upsert_restriction upsert_composite +test: upsert_trigger_test upsert_explain +test: upsert_clean diff --git a/src/test/regress/sql/insert_update_001.sql b/src/test/regress/sql/insert_update_001.sql index e230ed0ae..abe03deb4 100644 --- a/src/test/regress/sql/insert_update_001.sql +++ b/src/test/regress/sql/insert_update_001.sql @@ -2,6 +2,10 @@ DROP SCHEMA test_insert_update_001 CASCADE; CREATE SCHEMA test_insert_update_001; SET CURRENT_SCHEMA TO test_insert_update_001; +-- SET enable_upsert_to_merge=ON to test the upsert implemented by merge, +-- real upsert will be tested in specialized case. +SET enable_upsert_to_merge TO ON; + -- test description \h INSERT diff --git a/src/test/regress/sql/insert_update_002.sql b/src/test/regress/sql/insert_update_002.sql index e1bbe1720..84238b80e 100644 --- a/src/test/regress/sql/insert_update_002.sql +++ b/src/test/regress/sql/insert_update_002.sql @@ -2,6 +2,10 @@ DROP SCHEMA test_insert_update_002 CASCADE; CREATE SCHEMA test_insert_update_002; SET CURRENT_SCHEMA TO test_insert_update_002; +-- SET enable_upsert_to_merge=ON to test the upsert implemented by merge, +-- real upsert will be tested in specialized case. +SET enable_upsert_to_merge TO ON; + -- test t1 with no index CREATE TABLE t1 ( col1 INT, diff --git a/src/test/regress/sql/insert_update_003.sql b/src/test/regress/sql/insert_update_003.sql index fc82b796d..5d687f084 100644 --- a/src/test/regress/sql/insert_update_003.sql +++ b/src/test/regress/sql/insert_update_003.sql @@ -2,6 +2,10 @@ DROP SCHEMA test_insert_update_003 CASCADE; CREATE SCHEMA test_insert_update_003; SET CURRENT_SCHEMA TO test_insert_update_003; +-- SET enable_upsert_to_merge=ON to test the upsert implemented by merge, +-- real upsert will be tested in specialized case. +SET enable_upsert_to_merge TO ON; + -- test t4 with one primary key with three columns CREATE TABLE t4 ( col1 INT, diff --git a/src/test/regress/sql/insert_update_008.sql b/src/test/regress/sql/insert_update_008.sql index 6cb674b68..7caa71495 100644 --- a/src/test/regress/sql/insert_update_008.sql +++ b/src/test/regress/sql/insert_update_008.sql @@ -6,6 +6,10 @@ CREATE SCHEMA test_insert_update_008; SET current_schema = test_insert_update_008; +-- SET enable_upsert_to_merge=ON to test the upsert implemented by merge, +-- real upsert will be tested in specialized case. +SET enable_upsert_to_merge TO ON; + CREATE TABLE products_base ( product_id INTEGER DEFAULT 0, @@ -66,9 +70,9 @@ EXPLAIN (VERBOSE on, COSTS off) INSERT INTO products_row SELECT product_id, product_name, category, total FROM newproducts_row ON DUPLICATE KEY UPDATE - product_name = __unnamed_subquery_source__.product_name, - category = __unnamed_subquery_source__.category, - total = __unnamed_subquery_source__.total; + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; EXPLAIN (VERBOSE on, COSTS off) INSERT INTO products_row @@ -79,18 +83,18 @@ INSERT INTO products_row FROM newproducts_row, products_row WHERE products_row.total + newproducts_row.total < 1000 ON DUPLICATE KEY UPDATE - product_name = __unnamed_subquery_source__.product_name, - category = __unnamed_subquery_source__.category, - total = __unnamed_subquery_source__.total; + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; EXPLAIN (VERBOSE on, COSTS off) INSERT INTO products_row SELECT product_id, product_name, category, total FROM newproducts_row WHERE product_id IS NOT NULL AND product_name IS NOT NULL ON DUPLICATE KEY UPDATE - product_name = __unnamed_subquery_source__.product_name, - category = __unnamed_subquery_source__.category, - total = __unnamed_subquery_source__.total; + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; -- explain analyze BEGIN; @@ -98,9 +102,9 @@ EXPLAIN (ANALYZE on, COSTS off, TIMING off) INSERT INTO products_row SELECT product_id, product_name, category, total FROM newproducts_row ON DUPLICATE KEY UPDATE - product_name = __unnamed_subquery_source__.product_name, - category = __unnamed_subquery_source__.category, - total = __unnamed_subquery_source__.total; + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; ROLLBACK; -- explain performance @@ -110,9 +114,9 @@ EXPLAIN PERFORMANCE INSERT INTO products_row SELECT product_id, product_name, category, total FROM newproducts_row ON DUPLICATE KEY UPDATE - product_name = __unnamed_subquery_source__.product_name, - category = __unnamed_subquery_source__.category, - total = __unnamed_subquery_source__.total; + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; ROLLBACK; \o @@ -122,9 +126,9 @@ EXPLAIN (ANALYZE on, COSTS off, TIMING off) INSERT INTO products_row SELECT product_id, product_name, category, total FROM newproducts_row ON DUPLICATE KEY UPDATE - product_name = __unnamed_subquery_source__.product_name, - category = __unnamed_subquery_source__.category, - total = __unnamed_subquery_source__.total; + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; ROLLBACK; -- pretty mode performance @@ -135,9 +139,9 @@ EXPLAIN (VERBOSE on, COSTS off) INSERT INTO products_row SELECT product_id, product_name, category, total FROM newproducts_row ON DUPLICATE KEY UPDATE - product_name = __unnamed_subquery_source__.product_name, - category = __unnamed_subquery_source__.category, - total = __unnamed_subquery_source__.total; + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; -- explain analyze BEGIN; @@ -145,9 +149,9 @@ EXPLAIN (ANALYZE on, COSTS off, TIMING off) INSERT INTO products_row SELECT product_id, product_name, category, total FROM newproducts_row ON DUPLICATE KEY UPDATE - product_name = __unnamed_subquery_source__.product_name, - category = __unnamed_subquery_source__.category, - total = __unnamed_subquery_source__.total; + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; ROLLBACK; -- explain analyze @@ -156,9 +160,9 @@ EXPLAIN (ANALYZE on, COSTS off, TIMING off) INSERT INTO products_row SELECT product_id, product_name, category, total FROM newproducts_row ON DUPLICATE KEY UPDATE - product_name = __unnamed_subquery_source__.product_name, - category = __unnamed_subquery_source__.category, - total = __unnamed_subquery_source__.total; + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; ROLLBACK; -- explain performance @@ -168,9 +172,9 @@ EXPLAIN PERFORMANCE INSERT INTO products_row SELECT product_id, product_name, category, total FROM newproducts_row ON DUPLICATE KEY UPDATE - product_name = __unnamed_subquery_source__.product_name, - category = __unnamed_subquery_source__.category, - total = __unnamed_subquery_source__.total; + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; ROLLBACK; SET explain_perf_mode = run; @@ -180,9 +184,9 @@ EXPLAIN PERFORMANCE INSERT INTO products_row SELECT product_id, product_name, category, total FROM newproducts_row ON DUPLICATE KEY UPDATE - product_name = __unnamed_subquery_source__.product_name, - category = __unnamed_subquery_source__.category, - total = __unnamed_subquery_source__.total; + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; ROLLBACK; SET explain_perf_mode = summary; @@ -192,9 +196,9 @@ EXPLAIN PERFORMANCE INSERT INTO products_row SELECT product_id, product_name, category, total FROM newproducts_row ON DUPLICATE KEY UPDATE - product_name = __unnamed_subquery_source__.product_name, - category = __unnamed_subquery_source__.category, - total = __unnamed_subquery_source__.total; + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; ROLLBACK; \o diff --git a/src/test/regress/sql/insert_update_009.sql b/src/test/regress/sql/insert_update_009.sql index 8ba6b0799..25632efce 100644 --- a/src/test/regress/sql/insert_update_009.sql +++ b/src/test/regress/sql/insert_update_009.sql @@ -2,6 +2,11 @@ DROP SCHEMA test_insert_update_009 CASCADE; CREATE SCHEMA test_insert_update_009; SET CURRENT_SCHEMA TO test_insert_update_009; SET enable_light_proxy=off; + +-- SET enable_upsert_to_merge=ON to test the upsert implemented by merge, +-- real upsert will be tested in specialized case. +SET enable_upsert_to_merge TO ON; + -- test t1 with no index CREATE TABLE t1 ( col1 INT, diff --git a/src/test/regress/sql/insert_update_010.sql b/src/test/regress/sql/insert_update_010.sql index 1a7929570..7fa9995a3 100644 --- a/src/test/regress/sql/insert_update_010.sql +++ b/src/test/regress/sql/insert_update_010.sql @@ -1,6 +1,11 @@ DROP SCHEMA test_insert_update_010 CASCADE; CREATE SCHEMA test_insert_update_010; SET CURRENT_SCHEMA TO test_insert_update_010; + +-- SET enable_upsert_to_merge=ON to test the upsert implemented by merge, +-- real upsert will be tested in specialized case. +SET enable_upsert_to_merge TO ON; + -- test t4 with one primary key with three columns CREATE TABLE t4 ( col1 INT, diff --git a/src/test/regress/sql/rangefuncs.sql b/src/test/regress/sql/rangefuncs.sql index 171fc7903..37b6f8c77 100644 --- a/src/test/regress/sql/rangefuncs.sql +++ b/src/test/regress/sql/rangefuncs.sql @@ -1,3 +1,6 @@ +-- SET enable_upsert_to_merge=ON to test the upsert implemented by merge, +-- real upsert will be tested in specialized case. +SET enable_upsert_to_merge TO ON; SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%' ORDER BY name; diff --git a/src/test/regress/sql/upsert_001.sql b/src/test/regress/sql/upsert_001.sql new file mode 100644 index 000000000..dc39b4c39 --- /dev/null +++ b/src/test/regress/sql/upsert_001.sql @@ -0,0 +1,284 @@ +DROP SCHEMA test_upsert_001 CASCADE; +CREATE SCHEMA test_upsert_001; +SET CURRENT_SCHEMA TO test_upsert_001; + +-- enable_upsert_to_merge must is off, or upsert will be translated to merge. +SET enable_upsert_to_merge TO OFF; + +-- test description +\h INSERT + +-- test permission +--- test with no sequence column +CREATE TABLE t00 (col1 INT DEFAULT 1 PRIMARY KEY, col2 INT); +CREATE USER upsert_tester PASSWORD '123456@cc'; +GRANT ALL PRIVILEGES ON SCHEMA test_upsert_001 TO upsert_tester; + +SET SESSION SESSION AUTHORIZATION upsert_tester PASSWORD '123456@cc'; +INSERT INTO test_upsert_001.t00 VALUES(1) ON DUPLICATE KEY UPDATE col2 = 5; +RESET SESSION AUTHORIZATION; + +---- error: only have INSERT permission +GRANT INSERT ON test_upsert_001.t00 TO upsert_tester; +SET SESSION SESSION AUTHORIZATION upsert_tester PASSWORD '123456@cc'; +INSERT INTO test_upsert_001.t00 VALUES(1) ON DUPLICATE KEY UPDATE col2 = 5; +RESET SESSION AUTHORIZATION; + +---- success: have INSERT UPDATE permission +GRANT INSERT, UPDATE ON test_upsert_001.t00 TO upsert_tester; +SET SESSION SESSION AUTHORIZATION upsert_tester PASSWORD '123456@cc'; +INSERT INTO test_upsert_001.t00 VALUES(1) ON DUPLICATE KEY UPDATE col2 = 5; +RESET SESSION AUTHORIZATION; + +--- have SELECT INSERT UPDATE permission +GRANT SELECT, INSERT, UPDATE ON test_upsert_001.t00 TO upsert_tester; +SET SESSION SESSION AUTHORIZATION upsert_tester PASSWORD '123456@cc'; +INSERT INTO test_upsert_001.t00 VALUES(1) ON DUPLICATE KEY UPDATE col2 = 5; +INSERT INTO test_upsert_001.t00 VALUES(1) ON DUPLICATE KEY UPDATE col3 = 5; +RESET SESSION AUTHORIZATION; + +--- test with sequnce column +CREATE TABLE t01 (col1 INT , col2 BIGSERIAL PRIMARY KEY, col3 INT) ; + +---- error: don't have UPDATE permission on sequence table. +GRANT SELECT, INSERT, UPDATE ON test_upsert_001.t01 TO upsert_tester; +SET SESSION SESSION AUTHORIZATION upsert_tester PASSWORD '123456@cc'; +INSERT INTO test_upsert_001.t01 VALUES(1) ON DUPLICATE KEY UPDATE col3 = 5; +RESET SESSION AUTHORIZATION; + +---- have SELECT INSERT UPDATE permission on target relation, and UPDATE permission on sequence table. +GRANT UPDATE ON test_upsert_001.t01_col2_seq TO upsert_tester; +SET SESSION SESSION AUTHORIZATION upsert_tester PASSWORD '123456@cc'; +INSERT INTO test_upsert_001.t01 VALUES(1) ON DUPLICATE KEY UPDATE col3 = 5; +INSERT INTO test_upsert_001.t01 VALUES(1) ON DUPLICATE KEY UPDATE col3 = 5; +RESET SESSION AUTHORIZATION; + +-- test ommit INSERT target column +CREATE TABLE t02 (col1 INT DEFAULT 1 PRIMARY KEY, col2 INT, col3 INT); +INSERT INTO t02 VALUES(1, 2, 3) ON DUPLICATE KEY UPDATE col2 = 20; +SELECT * FROM t02 ORDER BY 1, 2; +INSERT INTO t02 VALUES(1, 2, 3) ON DUPLICATE KEY UPDATE col2 = 20; +SELECT * FROM t02 ORDER BY 1, 2; + +ALTER TABLE t02 DROP COLUMN col2; +ALTER TABLE t02 ADD COLUMN col4 INT; +INSERT INTO t02 VALUES(1, 2, 3) ON DUPLICATE KEY UPDATE col4 = 40; +SELECT * FROM t02 ORDER BY 1, 2; +INSERT INTO t02 VALUES(2, 3, 4) ON DUPLICATE KEY UPDATE col4 = 40; +SELECT * FROM t02 ORDER BY 1, 2; +INSERT INTO t02 VALUES(2, 3, 4) ON DUPLICATE KEY UPDATE col4 = 40; +SELECT * FROM t02 ORDER BY 1, 2; + +-- test restriction +--- test replication table +CREATE TABLE t03 (col1 int PRIMARY KEY, col2 INT, col3 smallserial) ; +--- error: not allowed volatile function as default value +INSERT INTO t03(col2) VALUES(1) ON DUPLICATE KEY UPDATE col2 = 100; + +ALTER TABLE t03 DROP COLUMN col3; +--- error: primary key are not allowed to update +INSERT INTO t03 VALUES(1) ON DUPLICATE KEY UPDATE col1 = 1; +--- error: clause other than VALUSES are not allowed to use +INSERT INTO t03 SELECT * FROM t03 ON DUPLICATE KEY UPDATE col2 = 1; +--- success: expression index are supported +CREATE UNIQUE INDEX u_expr_index ON t03 USING btree (abs(col1)); +INSERT INTO t03 VALUES(-10, 10) ON DUPLICATE KEY UPDATE col2 = 20; +DROP INDEX u_expr_index; + +-- test with stream operator on +INSERT INTO t03 VALUES(1) ON DUPLICATE KEY UPDATE col2 = 100; +SELECT * FROM t03; + +INSERT INTO t03 VALUES(1) ON DUPLICATE KEY UPDATE col2 = 100; +SELECT * FROM t03; +SELECT * FROM t03; + +--- test PBE +PREPARE p1 AS INSERT INTO t03 VALUES($1, $2) ON DUPLICATE KEY UPDATE col2 = $1*100; +EXECUTE p1(5, 50); +SELECT * FROM t03 WHERE col1 = 5; +EXECUTE p1(5, 50); +SELECT * FROM t03 WHERE col1 = 5; +DELETE t03 WHERE col1 = 5; + +---- test with primary key +INSERT INTO t03 VALUES(2) ON DUPLICATE KEY UPDATE col2 = 200; +SELECT * FROM t03; + +INSERT INTO t03 VALUES(2) ON DUPLICATE KEY UPDATE col2 = 200; +SELECT * FROM t03; +SELECT * FROM t03; + +---- test with unique key without NOT NULL constraint +ALTER TABLE t03 DROP CONSTRAINT t03_pkey; +ALTER TABLE t03 ADD COLUMN col3 INT; +CREATE UNIQUE INDEX ON t03 (col1, col3); +---- unique constraints might contain NULL, depends on the plan +----- for cn light and fqs, it can be done +INSERT INTO t03 VALUES(3) ON DUPLICATE KEY UPDATE col2 = 300; +----- for stream or pgxc it can not be done +INSERT INTO t03 VALUES(3) ON DUPLICATE KEY UPDATE col2 = 300; + +---- test with unique key with NOT NULL constraint, should success +CREATE UNIQUE INDEX ON t03 (col1); +INSERT INTO t03 VALUES(3) ON DUPLICATE KEY UPDATE col2 = 300; +SELECT * FROM t03; + +INSERT INTO t03 VALUES(3) ON DUPLICATE KEY UPDATE col2 = 300; +SELECT * FROM t03; +SELECT * FROM t03; + +---- test PBE +PREPARE p2 AS INSERT INTO t03 VALUES($1, $2) ON DUPLICATE KEY UPDATE col2 = $1*100; +EXECUTE p2(5, 50); +SELECT * FROM t03 WHERE col1 = 5; +EXECUTE p2(5, 50); +SELECT * FROM t03 WHERE col1 = 5; + +--- error: test with clause +WITH tmp(col1, col2) AS (SELECT * FROM t01) +INSERT INTO t01 SELECT * FROM tmp ON DUPLICATE KEY UPDATE col1 = 1; + +WITH RECURSIVE rq AS + ( + SELECT col1, col2 FROM t00 WHERE col1 = 1 + UNION ALL + SELECT origin.col1, rq.col2 + FROM rq JOIN t00 AS origin ON origin.col1 = rq.col1 + ) + INSERT INTO t03 SELECT * FROM rq ON DUPLICATE KEY UPDATE col1 = rq.col1; + +--- error: test returning clause +INSERT INTO t01 VALUES (1) ON DUPLICATE KEY UPDATE col1 = 1 RETURNING NOT(1::bool); + +--- error: distribute key are not allowed to UPDATE +CREATE TABLE t04 (col1 INT, col2 INT) ; +INSERT INTO t04 VALUES (1) ON DUPLICATE KEY UPDATE col1 = 5; + +--- error: unique index referenced column are not allowed to UPDATE +CREATE UNIQUE INDEX t04_u_index ON t04(col1, col2); +INSERT INTO t04 VALUES (1) ON DUPLICATE KEY UPDATE col2 = 5; +DROP INDEX t04_u_index; + +--- error: primary key referenced column are not allowed to UPDATE +ALTER TABLE t04 ADD PRIMARY KEY (col1, col2); +INSERT INTO t04 VALUES (1) ON DUPLICATE KEY UPDATE col2 = 5; + +--- error: invalid column +INSERT INTO t04 (col2, col3) VALUES (2, 3) ON DUPLICATE KEY UPDATE col2 = 5; + +--- error: duplicate column +INSERT INTO t04 (col2, col2) VALUES (2, 3) ON DUPLICATE KEY UPDATE col2 = 5; + +-- error: target column more than insert target +INSERT INTO t04 (col1, col2) VALUES (2, 3, 4) ON DUPLICATE KEY UPDATE col2 = 5; +INSERT INTO t04 (col1, col2) VALUES (1) ON DUPLICATE KEY UPDATE col2 = 5; +INSERT INTO t04 (col1, col2) SELECT col1 FROM t04 ON DUPLICATE KEY UPDATE col2 = 5; +INSERT INTO t04 (col1, col2) SELECT *, col1 FROM t04 ON DUPLICATE KEY UPDATE col2 = 5; +INSERT INTO t04 VALUES (2, 3, 4) ON DUPLICATE KEY UPDATE col2 = 5; +INSERT INTO t04 VALUES (1) ON DUPLICATE KEY UPDATE col2 = 5; +INSERT INTO t04 SELECT col1 FROM t04 ON DUPLICATE KEY UPDATE col2 = 5; +INSERT INTO t04 SELECT *, col1 FROM t04 ON DUPLICATE KEY UPDATE col2 = 5; + +-- test DEFAULT VALUES +TRUNCATE t00; +TRUNCATE t01; + +--- without sequence +----should insert +INSERT INTO t00 DEFAULT VALUES ON DUPLICATE KEY UPDATE col2 = col1; +SELECT * FROM t00 ORDER BY 1, 2; +---- should update +INSERT INTO t00 DEFAULT VALUES ON DUPLICATE KEY UPDATE col2 = col1; +SELECT * FROM t00 ORDER BY 1, 2; + +--- test drop column +TRUNCATE t00; +ALTER TABLE t00 DROP COLUMN col2; +ALTER TABLE t00 ADD COLUMN col3 INT DEFAULT 100; +----should insert +INSERT INTO t00 DEFAULT VALUES ON DUPLICATE KEY UPDATE col3 = col1; +SELECT * FROM t00 ORDER BY 1, 2; +---- should update +INSERT INTO t00 DEFAULT VALUES ON DUPLICATE KEY UPDATE col3 = col1; +SELECT * FROM t00 ORDER BY 1, 2; + +--- with sequence +----should insert +INSERT INTO t01 DEFAULT VALUES ON DUPLICATE KEY UPDATE col1 = col2; +INSERT INTO t01 (col2) SELECT col2 + 1 FROM t01 LIMIT 1; +SELECT * FROM t01 ORDER BY 1, 2, 3; + +---- should update +INSERT INTO t01 DEFAULT VALUES ON DUPLICATE KEY UPDATE col1 = col2; +SELECT * FROM t01 ORDER BY 1, 2, 3; + +-- test VALUES(DEFAULT) +CREATE TABLE t05 (col1 INT , col2 INT DEFAULT 1 PRIMARY KEY, col3 INT DEFAULT 100) ; +--- should insert +INSERT INTO t05 VALUES(DEFAULT) ON DUPLICATE KEY UPDATE col3 = 1000; +SELECT * FROM t05 ORDER BY 1, 2, 3; + +--- should update +INSERT INTO t05 VALUES(DEFAULT) ON DUPLICATE KEY UPDATE col3 = 1000; +SELECT * FROM t05 ORDER BY 1, 2, 3; + +-- test UPDATE DEFAULT +INSERT INTO t05 (col1, col2, col3) VALUES (2, 2, 2) ON DUPLICATE KEY UPDATE col3 = DEFAULT; +SELECT * FROM t05 ORDER BY 1, 2, 3; +INSERT INTO t05 (col1, col2, col3) VALUES (2, 2, 2) ON DUPLICATE KEY UPDATE col3 = DEFAULT; +SELECT * FROM t05 ORDER BY 1, 2, 3; + +-- test VALUES (DEFAULT, ...) +TRUNCATE t05; +--- should insert +INSERT INTO t05 VALUES(DEFAULT, DEFAULT, 200), (DEFAULT, 200, DEFAULT) +ON DUPLICATE KEY UPDATE col3 = DEFAULT, col1 = col3; +SELECT * FROM t05 ORDER BY 1, 2, 3; + +--- should update +INSERT INTO t05 VALUES(DEFAULT, DEFAULT, 200), (DEFAULT, 200, DEFAULT) +ON DUPLICATE KEY UPDATE col3 = DEFAULT, col1 = col3; +SELECT * FROM t05 ORDER BY 1, 2, 3; + +--- test drop coulmn +BEGIN; +ALTER TABLE t05 ADD COLUMN col4 INT; +ALTER TABLE t05 ADD COLUMN col5 INT DEFAULT 500; +ALTER TABLE t05 DROP COLUMN col3; +TRUNCATE t05; +INSERT INTO t05 VALUES(DEFAULT, DEFAULT, DEFAULT, 600) ON DUPLICATE KEY UPDATE col5 = DEFAULT; +SELECT * FROM t05 ORDER BY 1, 2, 3; +INSERT INTO t05 VALUES(DEFAULT, DEFAULT, DEFAULT, 600) ON DUPLICATE KEY UPDATE col5 = DEFAULT; +SELECT * FROM t05 ORDER BY 1, 2, 3; +ROLLBACK; + +-- test schema +SET current_schema = public; +TRUNCATE test_upsert_001.t05; +--- should insert +INSERT INTO test_upsert_001.t05 VALUES(DEFAULT, DEFAULT, 200), (DEFAULT, 200, DEFAULT) +ON DUPLICATE KEY UPDATE col3 = DEFAULT, col1 = col3; +SELECT * FROM test_upsert_001.t05 ORDER BY 1, 2, 3; + +--- should update +INSERT INTO test_upsert_001.t05 VALUES(DEFAULT, DEFAULT, 200), (DEFAULT, 200, DEFAULT) +ON DUPLICATE KEY UPDATE col3 = DEFAULT, col1 = col3; +SELECT * FROM test_upsert_001.t05 ORDER BY 1, 2, 3; + +--- test using schema on update +INSERT INTO test_upsert_001.t05 VALUES(DEFAULT, DEFAULT, 200), (DEFAULT, 200, DEFAULT) +ON DUPLICATE KEY UPDATE t05.col3 = DEFAULT, t05.col1 = t05.col3 + 1; +SELECT * FROM test_upsert_001.t05 ORDER BY 1, 2, 3; + +--- error: should not append schema +INSERT INTO test_upsert_001.t05 VALUES(DEFAULT, DEFAULT, 200), (DEFAULT, 200, DEFAULT) +ON DUPLICATE KEY UPDATE test_upsert_001.t05.col3 = DEFAULT, t05.col1 = t05.col3 + 1; + +INSERT INTO test_upsert_001.t05 VALUES(DEFAULT, DEFAULT, 200), (DEFAULT, 200, DEFAULT) +ON DUPLICATE KEY UPDATE t05.col3 = DEFAULT, t05.col1 = test_upsert_001.t05.col3 + 1; + +SET CURRENT_SCHEMA TO test_upsert_001; + +DROP USER upsert_tester CASCADE; +DROP SCHEMA test_upsert_001 CASCADE; diff --git a/src/test/regress/sql/upsert_002.sql b/src/test/regress/sql/upsert_002.sql new file mode 100644 index 000000000..66cd71825 --- /dev/null +++ b/src/test/regress/sql/upsert_002.sql @@ -0,0 +1,279 @@ +DROP SCHEMA test_upsert_002 CASCADE; +CREATE SCHEMA test_upsert_002; +SET CURRENT_SCHEMA TO test_upsert_002; + +-- enable_upsert_to_merge must is off, or upsert will be translated to merge. +SET enable_upsert_to_merge TO OFF; + +-- test t1 with no index +CREATE TABLE t1 ( + col1 INT, + col2 INT, + col3 INT DEFAULT 1, +-- col4 TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + col5 BIGSERIAL +) ; + +INSERT INTO t1 VALUES (1, 2) ON DUPLICATE KEY UPDATE col1 = 3; + +--- should always insert +INSERT INTO t1 VALUES (1, 2) ON DUPLICATE KEY UPDATE col2 = 3; +INSERT INTO t1 VALUES (1, 2) ON DUPLICATE KEY UPDATE t1.col2 = 4; + +--- appoint column list in insert clause, should always insert +INSERT INTO t1(col1, col3) VALUES (1, 3) ON DUPLICATE KEY UPDATE col2 = 5; +INSERT INTO t1(col1, col3) VALUES (1, 3) ON DUPLICATE KEY UPDATE t1.col2 = 6; + +--- multiple rows, should always insert +INSERT INTO t1 VALUES (2, 1), (2, 1) ON DUPLICATE KEY UPDATE col2 = 7, col3 = 7; +SELECT * FROM t1 ORDER BY col5; + +--- test union, should insert +INSERT INTO t1 (col1, col2) + SELECT * FROM + (SELECT col1, col2 FROM t1 + UNION + SELECT col1, col3 FROM t1) AS union_table + ON DUPLICATE KEY UPDATE col3 = (col1 + col2 + col3) * 10; +SELECT col1, col2, col3 FROM t1 WHERE col5 > 6 ORDER BY col1, col2; + +--- test subquery, should insert +INSERT INTO t1 + (SELECT col1 || col2 || '00' FROM t1 ORDER BY col5) + ON DUPLICATE KEY UPDATE col3 = col1 * 100; +SELECT col1, col2, col3 FROM t1 WHERE col1 >= 100 ORDER BY col1; + +-- test t2 with one primary key +CREATE TABLE t2 ( + col1 INT, + col2 INT PRIMARY KEY, + col3 INT DEFAULT 1, + col4 TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + col5 BIGSERIAL +) ; + +--- primary key or unique key are not allowed to update +INSERT INTO t2 VALUES (1, 1) ON DUPLICATE KEY UPDATE col2 = 3; +INSERT INTO t2 VALUES (1, 1) ON DUPLICATE KEY UPDATE t2.col2 = 3; +INSERT INTO t2 (col2, col3, col4, col5) + VALUES (10, 10, CURRENT_TIMESTAMP(0), 10), + (20, 20, CURRENT_TIMESTAMP(1), 20), + (30, 30, CURRENT_TIMESTAMP(2), 30) + ON DUPLICATE KEY UPDATE + col1 = 100, + col2 = 100, + col3 = 100, + col4 = '2019-08-09'::TIMESTAMP, + col5 = 100; + +--- should insert +INSERT INTO t2 VALUES (1, 1) ON DUPLICATE KEY UPDATE col1 = 30; +INSERT INTO t2 VALUES (2, 2) ON DUPLICATE KEY UPDATE t2.col1 = 40; +INSERT INTO t2 VALUES (3, 3) ON DUPLICATE KEY UPDATE col1 = col1 * 2; +INSERT INTO t2 VALUES (4, 4) ON DUPLICATE KEY UPDATE t2.col1 = t2.col1 * 2 ; +INSERT INTO t2 VALUES (5, 5) ON DUPLICATE KEY UPDATE col1 = col2 + 1; +INSERT INTO t2 VALUES (6, 6) ON DUPLICATE KEY UPDATE t2.col1 = t2.col2 + 1; +INSERT INTO t2 VALUES (7, 7) ON DUPLICATE KEY UPDATE col1 = extract(dow from col4) + 10; +INSERT INTO t2 VALUES (8, 8) ON DUPLICATE KEY UPDATE t2.col1 = extract(century from col4) * 100 + extract(isodow from col4); +SELECT col1, col2, col3, col5 FROM t2 ORDER BY col5; + +--- should update +INSERT INTO t2 VALUES (3, 1) ON DUPLICATE KEY UPDATE col1 = 30, col3 = col5 + 1; +INSERT INTO t2 VALUES (4, 2) ON DUPLICATE KEY UPDATE t2.col1 = 40, t2.col3 = t2.col5; +INSERT INTO t2 VALUES (3, 3), (4, 4) ON DUPLICATE KEY UPDATE col3 = t2.col5 + 1; +INSERT INTO t2 VALUES (5, 5) ON DUPLICATE KEY UPDATE col1 = extract(dow from col4) + 10; +INSERT INTO t2 VALUES (6, 6) ON DUPLICATE KEY UPDATE t2.col1 = extract(century from col4) * 100 + extract(isodow from col4); +SELECT col1, col2, col3, col5 FROM t2 ORDER BY col5; + +-- primary key are not allowed to be null +INSERT INTO t2 (col1) VALUES (10) ON DUPLICATE KEY UPDATE col1 = 10; + +--- appoint column list in insert clause +---- should insert +INSERT INTO t2 (col2, col3) VALUES (9, 9) ON DUPLICATE KEY UPDATE col1 = 90; +INSERT INTO t2 (col2, col3, col4, col5) + VALUES (10, 10, CURRENT_TIMESTAMP(0), 10), + (20, 20, CURRENT_TIMESTAMP(1), 20), + (30, 30, CURRENT_TIMESTAMP(2), 30) + ON DUPLICATE KEY UPDATE + col1 = 100, + col3 = 100, + col4 = '2019-08-20'::TIMESTAMP, + col5 = 100; +SELECT * FROM t2 ORDER BY col5; + +---- should update +INSERT INTO t2 (col2, col3) VALUES (9, 9) ON DUPLICATE KEY UPDATE col1 = 90; +INSERT INTO t2 (col2, col3, col4, col5) + VALUES (10, 10, CURRENT_TIMESTAMP(0), 10), + (20, 20, CURRENT_TIMESTAMP(1), 20), + (30, 30, CURRENT_TIMESTAMP(2), 30) + ON DUPLICATE KEY UPDATE + col1 = 100, + col3 = 100, + col4 = '2019-08-20'::TIMESTAMP, + col5 = 100; +SELECT * FROM t2 ORDER BY col5, col2; + +--- test subquery +---- should insert +INSERT INTO t2 + (SELECT col1 * 1000, col2 * 1000 + 1 FROM t2 ORDER BY col5 LIMIT 2) + ON DUPLICATE KEY UPDATE col3 = col2 + 1; +INSERT INTO t2 (col2, col3) + (SELECT col1 * 1000 + 2, col2 * 1000 FROM t2 ORDER BY col5 LIMIT 2 OFFSET 1) + ON DUPLICATE KEY UPDATE col3 = col2 + 1; +SELECT col1, col2, col3, col5 FROM t2 ORDER BY col5, col2; + +---- should update +INSERT INTO t2 + (SELECT col1 * 1000, col2 * 1000 + 1 FROM t2 ORDER BY col5 LIMIT 2) + ON DUPLICATE KEY UPDATE col3 = col2 + 1; +INSERT INTO t2 (col2, col3) + (SELECT col1 * 1000 + 2, col2 * 1000 FROM t2 ORDER BY col5 LIMIT 2 OFFSET 1) + ON DUPLICATE KEY UPDATE col3 = col2 + 1; +SELECT col1, col2, col3, col5 FROM t2 ORDER BY col5, col2; + +--- test union, some insert some update +SELECT col1, col2 FROM t1 WHERE col2 IS NOT NULL + UNION + SELECT col1, col3 FROM t1 ORDER BY 1, 2; +INSERT INTO t2 (col1, col2) + SELECT * FROM + (SELECT col1, col2 FROM t1 WHERE col2 IS NOT NULL + UNION + SELECT col1, col3 FROM t1) AS union_table + ON DUPLICATE KEY UPDATE col3 = (col1 + col2 + col3); + +SELECT col1, col2, col3, col5 FROM t2 ORDER BY col5, col2; + +INSERT INTO t2 (col1, col2) + SELECT col1, col2 FROM t1 WHERE col2 IS NOT NULL + UNION + SELECT col1, col3 FROM t1 + ON DUPLICATE KEY UPDATE col3 = col3 + 1; +SELECT col1, col2, col3, col5 FROM t2 ORDER BY col5, col2; +reset behavior_compat_options; + +-- test INTERSECT, should update +(SELECT col1, col1 + col2 FROM t1 WHERE col2 IS NOT NULL + UNION + SELECT 1, 2) + INTERSECT + SELECT col1, col3 FROM t1; + +INSERT INTO t2 (col1, col2) + (SELECT col1, col1 + col2 FROM t1 WHERE col2 IS NOT NULL + UNION + SELECT 1, 2) + INTERSECT + SELECT col1, col3 FROM t1 + ON DUPLICATE KEY UPDATE col3 = col3 + 1; +SELECT col1, col2, col3, col5 FROM t2 WHERE col2 = 3 ORDER BY col5, col2; + +-- test EXCEPT, should update +SELECT col1, col2 FROM t1 WHERE col2 IS NOT NULL + EXCEPT + (SELECT col1, col3 FROM t1 + UNION + SELECT NULL, NULL); + +INSERT INTO t2 (col1, col2) + SELECT col1, col2 FROM t1 WHERE col2 IS NOT NULL + EXCEPT + (SELECT col1, col3 FROM t1 + UNION + SELECT NULL, NULL) + ON DUPLICATE KEY UPDATE col3 = col3 + 1; +SELECT col1, col2, col3, col5 FROM t2 WHERE col2 = 2 ORDER BY col5, col2; + +-- test unique index with not default value +ALTER TABLE t2 DROP CONSTRAINT t2_pkey; +CREATE UNIQUE INDEX t2_u_index ON t2(col2, col5); +SELECT col1, col2 FROM t1 WHERE col2 IS NOT NULL + EXCEPT + (SELECT col1, col3 FROM t1 + UNION + SELECT NULL, NULL); +INSERT INTO t2 + SELECT col1, col2 FROM t1 WHERE col2 IS NOT NULL + EXCEPT + (SELECT col1, col3 FROM t1 + UNION + SELECT NULL, NULL) + ON DUPLICATE KEY UPDATE col3 = col3 + 1; +SELECT col1, col2, col3, col5 FROM t2 WHERE col2 = 2 ORDER BY col5, col2; + +-- test t3 with one primary index with two columns +CREATE TABLE t3 ( + col1 INT, + col2 INT, + col3 INT DEFAULT 1, +-- col4 TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + col5 BIGSERIAL, + PRIMARY KEY (col2, col3) +) ; + +--- column referenced by primary key are not allowed to update +INSERT INTO t3 VALUES (1, 1) ON DUPLICATE KEY UPDATE col3 = 3; + +--- should insert when not contains primary key and not all primary key referred columns have default value. +--- but will fail cause primary key should not be null +INSERT INTO t3 (col1) VALUES (1) ON DUPLICATE KEY UPDATE col1 = 2; + +--- should insert +--- (SEQUENCE BUG: the serial column will starts from 2 since the above statement has applied for a sequence) +INSERT INTO t3 VALUES (1, 1) ON DUPLICATE KEY UPDATE col1 = 10; +INSERT INTO t3 VALUES (2, 2, 2) ON DUPLICATE KEY UPDATE col5 = 20; +SELECT * FROM t3 order by col5; + +--- should update +INSERT INTO t3 VALUES (1, 1) ON DUPLICATE KEY UPDATE col1 = 10; +INSERT INTO t3 VALUES (2, 2, 2) ON DUPLICATE KEY UPDATE col5 = 20; +SELECT * FROM t3 order by col5; + +--- test subquery +---- should insert +INSERT INTO t3 (col2, col3) (SELECT max(col2) + 1, max(col3) + 1 FROM t3) ON DUPLICATE KEY UPDATE col1 = col2 + col3; +SELECT * FROM t3 order by col5; + +---- should update +INSERT INTO t3 (col2, col3) (SELECT max(col2), max(col3) FROM t3) ON DUPLICATE KEY UPDATE col1 = col2 + col3; +SELECT * FROM t3 order by col5; + +-- test t3 with one unique index with two columns +TRUNCATE t3; +ALTER TABLE t3 DROP CONSTRAINT t3_pkey; +ALTER TABLE t3 ALTER COLUMN col2 DROP NOT NULL; +CREATE UNIQUE INDEX t3_ukey ON t3 (col2, col3); + +--- column referenced by unique key are not allowed to update +INSERT INTO t3 VALUES (1, 1) ON DUPLICATE KEY UPDATE col3 = 3; + +--- should insert cause not contains unique key and not all unique key referred columns have default value. +INSERT INTO t3 (col1) VALUES (1) ON DUPLICATE KEY UPDATE col1 = 2; +INSERT INTO t3 (col1) VALUES (1) ON DUPLICATE KEY UPDATE col1 = 2; +SELECT * FROM t3 order by col5; + +--- should insert +INSERT INTO t3 VALUES (1, 1) ON DUPLICATE KEY UPDATE col1 = 10; +INSERT INTO t3 VALUES (2, 2, 2) ON DUPLICATE KEY UPDATE t3.col5 = 20; +SELECT * FROM t3 ORDER BY col5; + +--- should update +INSERT INTO t3 VALUES (1, 1) ON DUPLICATE KEY UPDATE t3.col1 = 10; +INSERT INTO t3 VALUES (2, 2, 2) ON DUPLICATE KEY UPDATE col5 = 20; +SELECT * FROM t3 ORDER BY col5; + +--- test subquery +---- should insert +INSERT INTO t3 (SELECT 100, NULL, max(col3) FROM t3) ON DUPLICATE KEY UPDATE col1 = col2 + col3; +INSERT INTO t3 (SELECT 100, NULL, max(col3) FROM t3) ON DUPLICATE KEY UPDATE col1 = col2 + col3; +INSERT INTO t3 (col2, col3) (SELECT max(col2) + 1, max(col3) + 1 FROM t3) ON DUPLICATE KEY UPDATE col1 = col2 + col3; +SELECT * FROM t3 ORDER BY col5; + +---- should update +INSERT INTO t3 (col2, col3) (SELECT max(col2), max(col3) FROM t3) ON DUPLICATE KEY UPDATE col1 = col2 + col3; +SELECT * FROM t3 ORDER BY col5; + +DROP SCHEMA test_upsert_002 CASCADE; diff --git a/src/test/regress/sql/upsert_003.sql b/src/test/regress/sql/upsert_003.sql new file mode 100644 index 000000000..9e11df7c5 --- /dev/null +++ b/src/test/regress/sql/upsert_003.sql @@ -0,0 +1,144 @@ +DROP SCHEMA test_insert_update_003 CASCADE; +CREATE SCHEMA test_insert_update_003; +SET CURRENT_SCHEMA TO test_insert_update_003; + +-- enable_upsert_to_merge must is off, or upsert will be translated to merge. +SET enable_upsert_to_merge TO OFF; + +-- test t4 with one primary key with three columns +CREATE TABLE t4 ( + col1 INT, + col2 INT DEFAULT 0, + col3 INT DEFAULT 1, + col4 TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + col5 BIGSERIAL, + PRIMARY KEY (col2, col3, col5) +) ; + +--- should insert +INSERT INTO t4 VALUES (1) ON DUPLICATE KEY UPDATE col1 = 10; +INSERT INTO t4 VALUES (1) ON DUPLICATE KEY UPDATE col1 = 10; +INSERT INTO t4 VALUES (2, 2, 2) ON DUPLICATE KEY UPDATE col1 = 200; +INSERT INTO t4 VALUES (100, 100, 100, CURRENT_TIMESTAMP, 100) ON DUPLICATE KEY UPDATE col1 = 1000; +SELECT col1, col2, col3, col5 FROM t4 ORDER BY col5; + +--- should update +INSERT INTO t4 VALUES (2, 2, 2, CURRENT_TIMESTAMP, 3) ON DUPLICATE KEY UPDATE col1 = 200; +INSERT INTO t4 VALUES (100, 100, 100, CURRENT_TIMESTAMP, 100) ON DUPLICATE KEY UPDATE col1 = 1000; +SELECT col1, col2, col3, col5 FROM t4 ORDER BY col5; + +--- error: duplicate key update on (x, x, 20) +--- this is because current version is not inplace update but merge, +--- so when the subquery contains multiple same values, it will cause duplicate insert failure. +SELECT col3, sum(col3) * 10 FROM t4 GROUP BY col3 ORDER BY 1, 2; +INSERT INTO t4 (col1, col5) + (SELECT col3, sum(col3) * 10 FROM t4 GROUP BY col3) + ON DUPLICATE KEY UPDATE col1 = 3; + +-- test t5 with sequence or default column with volatile function in constaint index +CREATE TABLE t5 ( + col1 INT, + col2 INT DEFAULT 1, + col3 BIGSERIAL, +-- col4 TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + col5 INTEGER(10, 5) DEFAULT RANDOM() + 1 +) ; + +-- test t5 with sequence column in constaint index +CREATE UNIQUE INDEX u_t5_index1 ON t5(col1, col3); +--- should insert +INSERT INTO t5 VALUES (1), (1), (1) ON DUPLICATE KEY UPDATE col2 = col3; +INSERT INTO t5 DEFAULT VALUES ON DUPLICATE KEY UPDATE col2 = col3; +SELECT * FROM t5 ORDER BY col3; + +--- should update +INSERT INTO t5 (col1, col3) VALUES (1, 1), (1, 2), (1, 3) ON DUPLICATE KEY UPDATE col5 = col2, col2 = col3 * 10; +SELECT * FROM t5 WHERE col1 = 1 ORDER BY col3; + +--- should some insert some update +INSERT INTO t5 (col1, col3) VALUES (2, 5), (2, 6); +SELECT col1, col2, col3 FROM t5 ORDER BY col3; +INSERT INTO t5 (col1) VALUES (2), (2), (2) ON DUPLICATE KEY UPDATE col2 = col3; +SELECT col1, col2, col3 FROM t5 ORDER BY col3; + +--- should INSERT and sequence starting from 7 +INSERT INTO t5 VALUES (2), (2); +SELECT col1, col2, col3 FROM t5 ORDER BY col3; + +-- test with volatile function as default column in constraint index +TRUNCATE t5; +DROP INDEX u_t5_index1; +CREATE UNIQUE INDEX u_t5_index2 ON t5(col1, col5) WHERE col1 > 2; + +--- should insert +INSERT INTO t5 VALUES (3), (3), (3) ON DUPLICATE KEY UPDATE col2 = col3; +INSERT INTO t5 (col1) VALUES (4), (4), (4) ON DUPLICATE KEY UPDATE col2 = col3; +SELECT * FROM t5 ORDER BY col3; + +--- should update +INSERT INTO t5 (col1, col5) SELECT col1, col5 FROM t5 where col1 = 3 ON DUPLICATE KEY UPDATE col2 = col5 * 100; +SELECT * FROM t5 ORDER BY col3; + +--- test subquery +---- should insert +INSERT INTO t5 (col1, col2) SELECT col1, col2 FROM t5 ON DUPLICATE KEY UPDATE col2 = col5 * 100; +SELECT * FROM t5 ORDER BY col3; + +---- should update +INSERT INTO t5 SELECT * FROM t5 ON DUPLICATE KEY UPDATE col2 = col5 * 1000; +SELECT * FROM t5 ORDER BY col3; + +-- test t6 with one more index +CREATE TABLE t6 ( + col1 INT, + col2 INT DEFAULT 1, + col3 BIGSERIAL, +-- col4 TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + col5 INTEGER(10, 5) DEFAULT RANDOM(), + col6 INT, + col7 TEXT +) ; + +ALTER TABLE t6 ADD PRIMARY KEY (col1, col3); +CREATE UNIQUE INDEX u_t6_index1 ON t6(col1, col5, col6); + +INSERT INTO t6 (col1) VALUES (1), (2), (3), (4), (5); +SELECT col1, col2, col3, col6, col7 FROM t6 ORDER BY col3; + +--- should not insert +INSERT INTO t6 (col1) VALUES (1), (2), (3), (4), (5) ON DUPLICATE KEY UPDATE col6 = power(col1, col2); +SELECT col1, col2, col3, col6, col7 FROM t6 ORDER BY col3; + +--- should update because primary key matches +INSERT INTO t6 (col1, col3) VALUES (6, 11), (6, 12); +SELECT col1, col2, col3, col6, col7 FROM t6 WHERE col1 = 6 ORDER BY col3; +INSERT INTO t6 (col1) VALUES (6), (6), (6) ON DUPLICATE KEY UPDATE col2 = col1 + col3, col6 = col2 * 10; +SELECT col1, col2, col3, col6, col7 FROM t6 WHERE col1 = 6 ORDER BY col3; + +--- should update for those col6 is not null bacause they will match the unique index, +--- and insert for those col6 is null because the unique index containing null never matches, +--- also primary key will not match +--- be ware the sequence column of the inserted row will jump n step, where n is the count of the not null rows, +--- because those sequence have to be generated during the unique index join stage. +INSERT INTO t6 (col1, col5, col6) + (SELECT col1, col5, col6 FROM t6 WHERE col1 = 6) + ON DUPLICATE KEY UPDATE + col7 = col2 + 1; +SELECT col1, col2, col3, col6, col7 FROM t6 WHERE col1 = 6 ORDER BY col1, col3, col6, col7; + +--- should update because unique index and primary key both match +INSERT INTO t6 (col1, col3, col5, col6) + (SELECT col1, col3, col5, col6 FROM t6 WHERE col1 = 6 AND col6 IS NOT NULL) + ON DUPLICATE KEY UPDATE + col7 = col7 * 10; +SELECT col1, col2, col3, col6, col7 FROM t6 WHERE col1 = 6 ORDER BY col1, col3, col6, col7; + +--- insert when col3 = 17 because constaints does not match, +--- but update when col3 = 18 because 18 has been inserted and will cause a match +INSERT INTO t6 (col1, col3, col5, col6) VALUES (7, 18, 100, 100); +SELECT * FROM t6 WHERE col3 > 16 ORDER BY col1, col3, col6, col7; +INSERT INTO t6 (col1, col5, col6) VALUES (7, 10, 10), (7, 100, 100) ON DUPLICATE KEY UPDATE + col7 = col3 * 100; +SELECT * FROM t6 WHERE col1 = 7 ORDER BY col1, col3, col6, col7; + +DROP SCHEMA test_insert_update_003 CASCADE; diff --git a/src/test/regress/sql/upsert_008.sql b/src/test/regress/sql/upsert_008.sql new file mode 100644 index 000000000..79b3563ef --- /dev/null +++ b/src/test/regress/sql/upsert_008.sql @@ -0,0 +1,359 @@ +-- +-- INSERT UPDATE, test explain command, comes from merge_explain and merge_explain_pretty +-- + +-- initial +CREATE SCHEMA test_insert_update_008; +SET current_schema = test_insert_update_008; + +-- enable_upsert_to_merge must is off, or upsert will be translated to merge. +SET enable_upsert_to_merge TO OFF; + +CREATE TABLE products_base +( +product_id INTEGER DEFAULT 0, +product_name VARCHAR(60) DEFAULT 'null', +category VARCHAR(60) DEFAULT 'unknown', +total INTEGER DEFAULT '0' +); + +INSERT INTO products_base VALUES (1501, 'vivitar 35mm', 'electrncs', 100); +INSERT INTO products_base VALUES (1502, 'olympus is50', 'electrncs', 100); +INSERT INTO products_base VALUES (1600, 'play gym', 'toys', 100); +INSERT INTO products_base VALUES (1601, 'lamaze', 'toys', 100); +INSERT INTO products_base VALUES (1666, 'harry potter', 'dvd', 100); + +CREATE TABLE newproducts_base +( +product_id INTEGER DEFAULT 0, +product_name VARCHAR(60) DEFAULT 'null', +category VARCHAR(60) DEFAULT 'unknown', +total INTEGER DEFAULT '0' +); + +INSERT INTO newproducts_base VALUES (1502, 'olympus camera', 'electrncs', 200); +INSERT INTO newproducts_base VALUES (1601, 'lamaze', 'toys', 200); +INSERT INTO newproducts_base VALUES (1666, 'harry potter', 'toys', 200); +INSERT INTO newproducts_base VALUES (1700, 'wait interface', 'books', 200); + +ANALYZE products_base; +ANALYZE newproducts_base; + +-- +-- row table +-- +CREATE TABLE products_row +( +product_id INTEGER DEFAULT 0 PRIMARY KEY, +product_name VARCHAR(60) DEFAULT 'null', +category VARCHAR(60) DEFAULT 'unknown', +total INTEGER DEFAULT '0' +); + +CREATE TABLE newproducts_row +( +product_id INTEGER DEFAULT 0 PRIMARY KEY, +product_name VARCHAR(60) DEFAULT 'null', +category VARCHAR(60) DEFAULT 'unknown', +total INTEGER DEFAULT '0' +); + +INSERT INTO products_row SELECT * FROM products_base; +INSERT INTO newproducts_row SELECT * FROM newproducts_base; +ANALYZE products_row; +ANALYZE newproducts_row; + +SET explain_perf_mode = normal; +-- explain verbose +EXPLAIN (VERBOSE on, COSTS off) +INSERT INTO products_row + SELECT product_id, product_name, category, total FROM newproducts_row + ON DUPLICATE KEY UPDATE + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; + +EXPLAIN (VERBOSE on, COSTS off) +INSERT INTO products_row + SELECT newproducts_row.product_id, + newproducts_row.product_name, + newproducts_row.category, + newproducts_row.total + FROM newproducts_row, products_row + WHERE products_row.total + newproducts_row.total < 1000 + ON DUPLICATE KEY UPDATE + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; + +EXPLAIN (VERBOSE on, COSTS off) +INSERT INTO products_row + SELECT product_id, product_name, category, total + FROM newproducts_row WHERE product_id IS NOT NULL AND product_name IS NOT NULL + ON DUPLICATE KEY UPDATE + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; + +-- explain analyze +BEGIN; +EXPLAIN (ANALYZE on, COSTS off, TIMING off) +INSERT INTO products_row + SELECT product_id, product_name, category, total FROM newproducts_row + ON DUPLICATE KEY UPDATE + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; +ROLLBACK; + +-- explain performance +\o insert_update_explain.txt +BEGIN; +EXPLAIN PERFORMANCE +INSERT INTO products_row + SELECT product_id, product_name, category, total FROM newproducts_row + ON DUPLICATE KEY UPDATE + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; +ROLLBACK; +\o + +-- explain analyze +BEGIN; +EXPLAIN (ANALYZE on, COSTS off, TIMING off) +INSERT INTO products_row + SELECT product_id, product_name, category, total FROM newproducts_row + ON DUPLICATE KEY UPDATE + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; +ROLLBACK; + +-- pretty mode performance +SET explain_perf_mode = pretty; + +-- explain verbose +EXPLAIN (VERBOSE on, COSTS off) +INSERT INTO products_row + SELECT product_id, product_name, category, total FROM newproducts_row + ON DUPLICATE KEY UPDATE + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; + +-- explain analyze +BEGIN; +EXPLAIN (ANALYZE on, COSTS off, TIMING off) +INSERT INTO products_row + SELECT product_id, product_name, category, total FROM newproducts_row + ON DUPLICATE KEY UPDATE + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; +ROLLBACK; + +-- explain analyze +BEGIN; +EXPLAIN (ANALYZE on, COSTS off, TIMING off) +INSERT INTO products_row + SELECT product_id, product_name, category, total FROM newproducts_row + ON DUPLICATE KEY UPDATE + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; +ROLLBACK; + +-- explain performance +\o insert_update_explain_pretty.txt +BEGIN; +EXPLAIN PERFORMANCE +INSERT INTO products_row + SELECT product_id, product_name, category, total FROM newproducts_row + ON DUPLICATE KEY UPDATE + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; +ROLLBACK; + +SET explain_perf_mode = run; + +BEGIN; +EXPLAIN PERFORMANCE +INSERT INTO products_row + SELECT product_id, product_name, category, total FROM newproducts_row + ON DUPLICATE KEY UPDATE + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; +ROLLBACK; + +SET explain_perf_mode = summary; + +BEGIN; +EXPLAIN PERFORMANCE +INSERT INTO products_row + SELECT product_id, product_name, category, total FROM newproducts_row + ON DUPLICATE KEY UPDATE + product_name = excluded.product_name, + category = excluded.category, + total = excluded.total; +ROLLBACK; +\o + +CREATE TABLE item +( + a INT DEFAULT 3, + item_id NUMERIC(18,10), + item_name VARCHAR(100), + item_level NUMERIC(39,0), + item_desc VARCHAR(250), + item_subclass_cd VARCHAR(50), + item_type_cd VARCHAR(50), + inventory_ind CHAR(300), + vendor_party_id SMALLINT, + commodity_cd VARCHAR(50), + brand_cd VARCHAR(50), + item_available CHAR(100), + CONSTRAINT u_item_index UNIQUE (item_subclass_cd, vendor_party_id) +) +PARTITION BY RANGE (vendor_party_id) +( +PARTITION item_1 VALUES LESS THAN (0), +PARTITION item_2 VALUES LESS THAN (1), +PARTITION item_3 VALUES LESS THAN (2), +PARTITION item_4 VALUES LESS THAN (3), +PARTITION item_5 VALUES LESS THAN (6), +PARTITION item_6 VALUES LESS THAN (8), +PARTITION item_7 VALUES LESS THAN (10), +PARTITION item_8 VALUES LESS THAN (15), +PARTITION item_9 VALUES LESS THAN (MAXVALUE) +) ENABLE ROW MOVEMENT; + +CREATE TABLE region +( + a INT DEFAULT 8, + region_cd VARCHAR(50), + region_name VARCHAR(100), + division_cd VARCHAR(50), + region_mgr_associate_id number(18,9) +); + +CREATE TABLE associate_benefit_expense +( + a INT DEFAULT 44, + period_end_dt DATE, + associate_expns_type_cd VARCHAR(50), + associate_party_id INTEGER, + benefit_hours_qty decimal(38,11), + benefit_cost_amt number(38,4) +) +PARTITION BY RANGE (associate_expns_type_cd) +( +PARTITION associate_benefit_expense_1 VALUES LESS THAN ('B'), +PARTITION associate_benefit_expense_2 VALUES LESS THAN ('E'), +PARTITION associate_benefit_expense_3 VALUES LESS THAN ('G'), +PARTITION associate_benefit_expense_4 VALUES LESS THAN ('I'), +PARTITION associate_benefit_expense_5 VALUES LESS THAN ('L'), +PARTITION associate_benefit_expense_6 VALUES LESS THAN ('N'), +PARTITION associate_benefit_expense_7 VALUES LESS THAN ('P'), +PARTITION associate_benefit_expense_8 VALUES LESS THAN ('Q'), +PARTITION associate_benefit_expense_9 VALUES LESS THAN ('R'), +PARTITION associate_benefit_expense_10 VALUES LESS THAN ('T'), +PARTITION associate_benefit_expense_11 VALUES LESS THAN ('U'), +PARTITION associate_benefit_expense_12 VALUES LESS THAN ('V'), +PARTITION associate_benefit_expense_13 VALUES LESS THAN (MAXVALUE) +) ENABLE ROW MOVEMENT; + +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (0.12, ' ' , 'A' , NULL, 'TGK' , 'A' , 2, 'A' , 'A' , 'Y'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (1.3, 'B' , NULL, 'B' , 'B' , NULL, 1, 'B' , NULL , 'N'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (2.23, 'C' , 'C' , NULL, 'C' , 'C' , 2, 'C' , 'C' , 'N'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (3.33, 'D' , 'D' , 'PT' , NULL, 'D' , 3, 'D' , 'D' , 'N'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (4.98, ' ' , NULL, 'E' , 'E' , 'E' , 4, 'E' , 'E' , 'Y'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (5.01, NULL, 'F' , ' ' , 'F' , 'F' , 5, 'F' , 'F' , 'N'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (6, 'G' , 'G' , 'G' , '_D' , 'G' , 6, 'G' , NULL ,'N'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (0.7, NULL, NULL, NULL, 'H' , 'H' , 7, NULL, 'G' , 'Y'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (0.08, 'I' , ' ' , ' T ' , NULL, 'I' , 8, 'I' , '' , 'N'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (9.12, ' ' , 'J' , ' PP' , 'J' , 'J' , 9, 'J' , NULL , 'Y'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (10.10, NULL, ' ' , 'A' , 'A' , 'A' , 2, NULL, 'A','Y'); +--INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (11.11, 'B' , 'B' , 'B' , 'BCDAA' , NULL, 1, 'B' , 'B','N'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (12.02, 'D' , NULL, NULL, 'C' , 'C' , 2, 'C' , 'C','N'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (13.99, NULL, ' ' , 'D' , 'D' , 'D' , 3, 'D' , 'D','Y'); +--INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (14, 'G' , 'E' , 'E' , NULL, 'E' , 4, 'E' , 'E','N'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (15, 'F' , ' ' , 'C' , 'CLEANING' , 'F' , 5, 'F' , 'F','Y'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (16, '' , 'Z' , NULL, 'G' , 'G' , 6, 'G' , NULL,'N'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (17, NULL, '' , ' PAPER' , 'H' , '' , 7, NULL, NULL,'Y'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (19, ' ' , 'B' , '' , '' , 'I' , 8, 'I' , NULL,'N'); +INSERT INTO item (ITEM_ID, ITEM_NAME, ITEM_DESC, ITEM_SUBCLASS_CD, ITEM_TYPE_CD, INVENTORY_IND, VENDOR_PARTY_ID, COMMODITY_CD, BRAND_CD,ITEM_AVAILABLE) VALUES (20 , 'A' , 'J' , 'J' , 'J' , NULL, 9, 'J' , 'G','Y'); + +/*--REGION--*/ +INSERT INTO REGION (REGION_CD, REGION_NAME, DIVISION_CD, REGION_MGR_ASSOCIATE_ID) VALUES ('A', 'A ', 'A', 0.123433); +INSERT INTO REGION (REGION_CD, REGION_NAME, DIVISION_CD, REGION_MGR_ASSOCIATE_ID) VALUES ('B', 'B', 'B', NULL); +INSERT INTO REGION (REGION_CD, REGION_NAME, DIVISION_CD, REGION_MGR_ASSOCIATE_ID) VALUES ('C', 'C', 'C', 2.232008908); +INSERT INTO REGION (REGION_CD, REGION_NAME, DIVISION_CD, REGION_MGR_ASSOCIATE_ID) VALUES ('D', ' DD', 'D', 3.878789); +INSERT INTO REGION (REGION_CD, REGION_NAME, DIVISION_CD, REGION_MGR_ASSOCIATE_ID) VALUES ('E', 'A', 'E', 4.89060603); +INSERT INTO REGION (REGION_CD, REGION_NAME, DIVISION_CD, REGION_MGR_ASSOCIATE_ID) VALUES ('F', 'F', 'F', 5.82703827); +INSERT INTO REGION (REGION_CD, REGION_NAME, DIVISION_CD, REGION_MGR_ASSOCIATE_ID) VALUES ('G', 'G', 'TTT', NULL); +INSERT INTO REGION (REGION_CD, REGION_NAME, DIVISION_CD, REGION_MGR_ASSOCIATE_ID) VALUES ('H', 'H', 'G', 7.3829083); +INSERT INTO REGION (REGION_CD, REGION_NAME, DIVISION_CD, REGION_MGR_ASSOCIATE_ID) VALUES ('I', 'C', 'M', 8.983989); +INSERT INTO REGION (REGION_CD, REGION_NAME, DIVISION_CD, REGION_MGR_ASSOCIATE_ID) VALUES ('J', 'J', 'G', NULL); +INSERT INTO REGION (REGION_CD, REGION_NAME, DIVISION_CD, REGION_MGR_ASSOCIATE_ID) VALUES ('K', ' ', 'C', 2.232008908); +INSERT INTO REGION (REGION_CD, REGION_NAME, DIVISION_CD, REGION_MGR_ASSOCIATE_ID) VALUES ('L', 'D', 'X', 3.878789); +INSERT INTO REGION (REGION_CD, REGION_NAME, DIVISION_CD, REGION_MGR_ASSOCIATE_ID) VALUES ('M', 'TTTTTT ', 'D' , 4.89060603); +INSERT INTO REGION (REGION_CD, REGION_NAME, DIVISION_CD, REGION_MGR_ASSOCIATE_ID) VALUES ('N', 'G' , 'B' , NULL); +INSERT INTO REGION (REGION_CD, REGION_NAME, DIVISION_CD, REGION_MGR_ASSOCIATE_ID) VALUES ('O' , 'G', 'F', 6.6703972); + +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1970-01-01', 'A', 5, 0.5 , 0.5); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1973-01-01', 'B', 1, NULL, 1.0); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1976-01-01', 'C', 2, 2.0 , NULL); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1979-01-01', 'D', 3, 3.0 , 3.0); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1982-01-01', 'E', 4, 4.0 , 4.0); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1985-01-01', 'F', 5, 5.0 , 5.0); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1988-01-01', 'F', 6, NULL, 6.0); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1991-01-01', 'G', 6, NULL, NULL); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1994-01-01', 'G', 15, 8.0 , 8.0); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1997-01-01', 'G', 16, 9.0 , 9.0); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1983-01-03', 'I', 14, 4.0 , 4.0); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1984-01-01', 'GO', 15, 5.0 , NULL); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1985-05-01', 'I', 16, 6.0 , 6.0); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1990-01-01', 'TTT', 16, NULL, 7.0); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1992-02-01', 'A', 15, 8.0 , 8.0); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1997-02-01', 'G', 17, 9.0 , NULL); +INSERT INTO ASSOCIATE_BENEFIT_EXPENSE (PERIOD_END_DT, ASSOCIATE_EXPNS_TYPE_CD, ASSOCIATE_PARTY_ID, BENEFIT_HOURS_QTY, BENEFIT_COST_AMT) VALUES (DATE '1997-05-01', 'G' , 17, 9.0 , NULL); + +ANALYZE item; +ANALYZE region; +ANALYZE associate_benefit_expense; + +EXPLAIN (VERBOSE ON, COSTS OFF) +INSERT INTO item (item_level, item_subclass_cd, item_desc, vendor_party_id) + SELECT Table_001.REGION_MGR_ASSOCIATE_ID Column_003, + Table_002.associate_expns_type_cd Column_004, + CAST(Table_001.region_name AS VARCHAR) Column_005, + 10 Column_006 +-- 'o' Column_007, +-- 'F' Column_008, +-- pg_client_encoding() Column_009 + FROM region Table_001, associate_benefit_expense Table_002 +ON DUPLICATE KEY UPDATE item_level = -1000; + +EXPLAIN (VERBOSE ON, COSTS OFF) +INSERT INTO products_row VALUES(100) + ON DUPLICATE KEY UPDATE total=100; + +SET enable_light_proxy=off; +EXPLAIN (VERBOSE ON, COSTS OFF) +INSERT INTO products_row VALUES(100) + ON DUPLICATE KEY UPDATE total=100; + +EXPLAIN (VERBOSE ON, COSTS OFF) +INSERT INTO products_row VALUES(100) + ON DUPLICATE KEY UPDATE total=100; + +EXPLAIN (VERBOSE ON, COSTS OFF) +INSERT INTO products_row VALUES(100) + ON DUPLICATE KEY UPDATE total=100; + +RESET enable_light_proxy; + +DROP SCHEMA test_insert_update_008 CASCADE; diff --git a/src/test/regress/sql/upsert_009.sql b/src/test/regress/sql/upsert_009.sql new file mode 100644 index 000000000..f6b68d3c7 --- /dev/null +++ b/src/test/regress/sql/upsert_009.sql @@ -0,0 +1,281 @@ +DROP SCHEMA test_insert_update_009 CASCADE; +CREATE SCHEMA test_insert_update_009; +SET CURRENT_SCHEMA TO test_insert_update_009; +SET enable_light_proxy=off; + +-- enable_upsert_to_merge must is off, or upsert will be translated to merge. +SET enable_upsert_to_merge TO OFF; + +-- test t1 with no index +CREATE TABLE t1 ( + col1 INT, + col2 INT, + col3 INT DEFAULT 1, +-- col4 TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + col5 BIGSERIAL +) ; + +--- distribute key are not allowed to update +INSERT INTO t1 VALUES (1, 2) ON DUPLICATE KEY UPDATE col1 = 3; + +--- should always insert +INSERT INTO t1 VALUES (1, 2) ON DUPLICATE KEY UPDATE col2 = 3; +INSERT INTO t1 VALUES (1, 2) ON DUPLICATE KEY UPDATE t1.col2 = 4; + +--- appoint column list in insert clause, should always insert +INSERT INTO t1(col1, col3) VALUES (1, 3) ON DUPLICATE KEY UPDATE col2 = 5; +INSERT INTO t1(col1, col3) VALUES (1, 3) ON DUPLICATE KEY UPDATE t1.col2 = 6; + +--- multiple rows, should always insert +INSERT INTO t1 VALUES (2, 1), (2, 1) ON DUPLICATE KEY UPDATE col2 = 7, col3 = 7; +SELECT * FROM t1 ORDER BY col5; + +--- test union, should insert +INSERT INTO t1 (col1, col2) + SELECT * FROM + (SELECT col1, col2 FROM t1 + UNION + SELECT col1, col3 FROM t1) AS union_table + ON DUPLICATE KEY UPDATE col3 = (col1 + col2 + col3); +SELECT col1, col2, col3 FROM t1 WHERE col5 > 6 ORDER BY col1, col2; + +--- test subquery, should insert +INSERT INTO t1 + (SELECT col1 || col2 || '00' FROM t1 ORDER BY col5) + ON DUPLICATE KEY UPDATE col3 = col1 * 100; +SELECT col1, col2, col3 FROM t1 WHERE col1 >= 100 ORDER BY col1; + +-- test t2 with one primary key +CREATE TABLE t2 ( + col1 INT, + col2 INT PRIMARY KEY, + col3 INT DEFAULT 1, + col4 TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + col5 BIGSERIAL +) ; + +--- distribute key are not allowed to update +INSERT INTO t2 VALUES (1, 1) ON DUPLICATE KEY UPDATE col2 = 3; +INSERT INTO t2 VALUES (1, 1) ON DUPLICATE KEY UPDATE t2.col2 = 3; +INSERT INTO t2 (col2, col3, col4, col5) + VALUES (10, 10, CURRENT_TIMESTAMP(0), 10), + (20, 20, CURRENT_TIMESTAMP(1), 20), + (30, 30, CURRENT_TIMESTAMP(2), 30) + ON DUPLICATE KEY UPDATE + col1 = 100, + col2 = 100, + col3 = 100, + col4 = '2019-08-09'::TIMESTAMP, + col5 = 100; + +--- should insert +INSERT INTO t2 VALUES (1, 1) ON DUPLICATE KEY UPDATE col1 = 30; +INSERT INTO t2 VALUES (2, 2) ON DUPLICATE KEY UPDATE t2.col1 = 40; +INSERT INTO t2 VALUES (3, 3) ON DUPLICATE KEY UPDATE col1 = col1 * 2; +INSERT INTO t2 VALUES (4, 4) ON DUPLICATE KEY UPDATE t2.col1 = t2.col1 * 2 ; +INSERT INTO t2 VALUES (5, 5) ON DUPLICATE KEY UPDATE col1 = col2 + 1; +INSERT INTO t2 VALUES (6, 6) ON DUPLICATE KEY UPDATE t2.col1 = t2.col2 + 1; +INSERT INTO t2 VALUES (7, 7) ON DUPLICATE KEY UPDATE col1 = extract(dow from col4) + 10; +INSERT INTO t2 VALUES (8, 8) ON DUPLICATE KEY UPDATE t2.col1 = extract(century from col4) * 100 + extract(isodow from col4); +SELECT col1, col2, col3, col5 FROM t2 ORDER BY col5; + +--- should update +INSERT INTO t2 VALUES (3, 1) ON DUPLICATE KEY UPDATE col1 = 30, col3 = col5 + 1; +INSERT INTO t2 VALUES (4, 2) ON DUPLICATE KEY UPDATE t2.col1 = 40, t2.col3 = t2.col5; +INSERT INTO t2 VALUES (3, 3), (4, 4) ON DUPLICATE KEY UPDATE col3 = t2.col5 + 1; +INSERT INTO t2 VALUES (5, 5) ON DUPLICATE KEY UPDATE col1 = extract(dow from col4) + 10; +INSERT INTO t2 VALUES (6, 6) ON DUPLICATE KEY UPDATE t2.col1 = extract(century from col4) * 100 + extract(isodow from col4); +SELECT col1, col2, col3, col5 FROM t2 ORDER BY col5; + +-- primary key are not allowed to be null +INSERT INTO t2 (col1) VALUES (10) ON DUPLICATE KEY UPDATE col1 = 10; + +--- appoint column list in insert clause +---- should insert +INSERT INTO t2 (col2, col3) VALUES (9, 9) ON DUPLICATE KEY UPDATE col1 = 90; +INSERT INTO t2 (col2, col3, col4, col5) + VALUES (10, 10, CURRENT_TIMESTAMP(0), 10), + (20, 20, CURRENT_TIMESTAMP(1), 20), + (30, 30, CURRENT_TIMESTAMP(2), 30) + ON DUPLICATE KEY UPDATE + col1 = 100, + col3 = 100, + col4 = '2019-08-20'::TIMESTAMP, + col5 = 100; +SELECT * FROM t2 ORDER BY col5; + +---- should update +INSERT INTO t2 (col2, col3) VALUES (9, 9) ON DUPLICATE KEY UPDATE col1 = 90; +INSERT INTO t2 (col2, col3, col4, col5) + VALUES (10, 10, CURRENT_TIMESTAMP(0), 10), + (20, 20, CURRENT_TIMESTAMP(1), 20), + (30, 30, CURRENT_TIMESTAMP(2), 30) + ON DUPLICATE KEY UPDATE + col1 = 100, + col3 = 100, + col4 = '2019-08-20'::TIMESTAMP, + col5 = 100; +SELECT * FROM t2 ORDER BY col5, col2; + +--- test subquery +---- should insert +INSERT INTO t2 + (SELECT col1 * 1000, col2 * 1000 + 1 FROM t2 ORDER BY col5 LIMIT 2) + ON DUPLICATE KEY UPDATE col3 = col2 + 1; +INSERT INTO t2 (col2, col3) + (SELECT col1 * 1000 + 2, col2 * 1000 FROM t2 ORDER BY col5 LIMIT 2 OFFSET 1) + ON DUPLICATE KEY UPDATE col3 = col2 + 1; +SELECT col1, col2, col3, col5 FROM t2 ORDER BY col5, col2; + +---- should update +INSERT INTO t2 + (SELECT col1 * 1000, col2 * 1000 + 1 FROM t2 ORDER BY col5 LIMIT 2) + ON DUPLICATE KEY UPDATE col3 = col2 + 1; +INSERT INTO t2 (col2, col3) + (SELECT col1 * 1000 + 2, col2 * 1000 FROM t2 ORDER BY col5 LIMIT 2 OFFSET 1) + ON DUPLICATE KEY UPDATE col3 = col2 + 1; +SELECT col1, col2, col3, col5 FROM t2 ORDER BY col5, col2; + +--- test union, some insert some update +SELECT col1, col2 FROM t1 WHERE col2 IS NOT NULL + UNION + SELECT col1, col3 FROM t1 ORDER BY 1, 2; +INSERT INTO t2 (col1, col2) + SELECT * FROM + (SELECT col1, col2 FROM t1 WHERE col2 IS NOT NULL + UNION + SELECT col1, col3 FROM t1) AS union_table + ON DUPLICATE KEY UPDATE col3 = (col1 + col2 + col3); +SELECT col1, col2, col3, col5 FROM t2 ORDER BY col5, col2; + +INSERT INTO t2 (col1, col2) + SELECT col1, col2 FROM t1 WHERE col2 IS NOT NULL + UNION + SELECT col1, col3 FROM t1 + ON DUPLICATE KEY UPDATE col3 = (col1 + col2 + col3); +SELECT col1, col2, col3, col5 FROM t2 ORDER BY col5, col2; +reset behavior_compat_options; + +-- test INTERSECT, should update +(SELECT col1, col1 + col2 FROM t1 WHERE col2 IS NOT NULL + UNION + SELECT 1, 2) + INTERSECT + SELECT col1, col3 FROM t1; + +INSERT INTO t2 (col1, col2) + (SELECT col1, col1 + col2 FROM t1 WHERE col2 IS NOT NULL + UNION + SELECT 1, 2) + INTERSECT + SELECT col1, col3 FROM t1 + ON DUPLICATE KEY UPDATE col3 = (col1 + col2 + col3); +SELECT col1, col2, col3, col5 FROM t2 WHERE col2 = 3 ORDER BY col5, col2; + +-- test EXCEPT, should update +SELECT col1, col2 FROM t1 WHERE col2 IS NOT NULL + EXCEPT + (SELECT col1, col3 FROM t1 + UNION + SELECT NULL, NULL); + +INSERT INTO t2 (col1, col2) + SELECT col1, col2 FROM t1 WHERE col2 IS NOT NULL + EXCEPT + (SELECT col1, col3 FROM t1 + UNION + SELECT NULL, NULL) + ON DUPLICATE KEY UPDATE col3 = (col1 + col2 + col3); +SELECT col1, col2, col3, col5 FROM t2 WHERE col2 = 2 ORDER BY col5, col2; + +-- test unique index with not default value +ALTER TABLE t2 DROP CONSTRAINT t2_pkey; +CREATE UNIQUE INDEX t2_u_index ON t2(col2, col5); +SELECT col1, col2 FROM t1 WHERE col2 IS NOT NULL + EXCEPT + (SELECT col1, col3 FROM t1 + UNION + SELECT NULL, NULL); +INSERT INTO t2 + SELECT col1, col2 FROM t1 WHERE col2 IS NOT NULL + EXCEPT + (SELECT col1, col3 FROM t1 + UNION + SELECT NULL, NULL) + ON DUPLICATE KEY UPDATE col3 = (col1 + col2 + col3); +SELECT col1, col2, col3, col5 FROM t2 WHERE col2 = 2 ORDER BY col5, col2; + +-- test t3 with one primary index with two columns +CREATE TABLE t3 ( + col1 INT, + col2 INT, + col3 INT DEFAULT 1, +-- col4 TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + col5 BIGSERIAL, + PRIMARY KEY (col2, col3) +) ; + +--- column referenced by primary key are not allowed to update +INSERT INTO t3 VALUES (1, 1) ON DUPLICATE KEY UPDATE col3 = 3; + +--- should insert when not contains primary key and not all primary key referred columns have default value. +--- but will fail cause primary key should not be null +INSERT INTO t3 (col1) VALUES (1) ON DUPLICATE KEY UPDATE col1 = 2; + +--- should insert +--- (SEQUENCE BUG: the serial column will starts from 2 since the above statement has applied for a sequence) +INSERT INTO t3 VALUES (1, 1) ON DUPLICATE KEY UPDATE col1 = 10; +INSERT INTO t3 VALUES (2, 2, 2) ON DUPLICATE KEY UPDATE col5 = 20; +SELECT * FROM t3 order by col5; + +--- should update +INSERT INTO t3 VALUES (1, 1) ON DUPLICATE KEY UPDATE col1 = 10; +INSERT INTO t3 VALUES (2, 2, 2) ON DUPLICATE KEY UPDATE col5 = 20; +SELECT * FROM t3 order by col5; + +--- test subquery +---- should insert +INSERT INTO t3 (col2, col3) (SELECT max(col2) + 1, max(col3) + 1 FROM t3) ON DUPLICATE KEY UPDATE col1 = col2 + col3; +SELECT * FROM t3 order by col5; + +---- should update +INSERT INTO t3 (col2, col3) (SELECT max(col2), max(col3) FROM t3) ON DUPLICATE KEY UPDATE col1 = col2 + col3; +SELECT * FROM t3 order by col5; + +-- test t3 with one unique index with two columns +TRUNCATE t3; +ALTER TABLE t3 DROP CONSTRAINT t3_pkey; +ALTER TABLE t3 ALTER COLUMN col2 DROP NOT NULL; +CREATE UNIQUE INDEX t3_ukey ON t3 (col2, col3); + +--- column referenced by unique key are not allowed to update +INSERT INTO t3 VALUES (1, 1) ON DUPLICATE KEY UPDATE col3 = 3; + +--- should insert cause not contains unique key and not all unique key referred columns have default value. +INSERT INTO t3 (col1) VALUES (1) ON DUPLICATE KEY UPDATE col1 = 2; +INSERT INTO t3 (col1) VALUES (1) ON DUPLICATE KEY UPDATE col1 = 2; +SELECT * FROM t3 order by col5; + +--- should insert +INSERT INTO t3 VALUES (1, 1) ON DUPLICATE KEY UPDATE col1 = 10; +INSERT INTO t3 VALUES (2, 2, 2) ON DUPLICATE KEY UPDATE t3.col5 = 20; +SELECT * FROM t3 ORDER BY col5; + +--- should update +INSERT INTO t3 VALUES (1, 1) ON DUPLICATE KEY UPDATE t3.col1 = 10; +INSERT INTO t3 VALUES (2, 2, 2) ON DUPLICATE KEY UPDATE col5 = 20; +SELECT * FROM t3 ORDER BY col5; + +--- test subquery +---- should insert +INSERT INTO t3 (SELECT 100, NULL, max(col3) FROM t3) ON DUPLICATE KEY UPDATE col1 = col2 + col3; +INSERT INTO t3 (SELECT 100, NULL, max(col3) FROM t3) ON DUPLICATE KEY UPDATE col1 = col2 + col3; +INSERT INTO t3 (col2, col3) (SELECT max(col2) + 1, max(col3) + 1 FROM t3) ON DUPLICATE KEY UPDATE col1 = col2 + col3; +SELECT * FROM t3 ORDER BY col5; + +---- should update +INSERT INTO t3 (col2, col3) (SELECT max(col2), max(col3) FROM t3) ON DUPLICATE KEY UPDATE col1 = col2 + col3; +SELECT * FROM t3 ORDER BY col5; + +RESET enable_light_proxy; +DROP SCHEMA test_insert_update_009 CASCADE; diff --git a/src/test/regress/sql/upsert_010.sql b/src/test/regress/sql/upsert_010.sql new file mode 100644 index 000000000..0838b836c --- /dev/null +++ b/src/test/regress/sql/upsert_010.sql @@ -0,0 +1,144 @@ +DROP SCHEMA test_insert_update_010 CASCADE; +CREATE SCHEMA test_insert_update_010; +SET CURRENT_SCHEMA TO test_insert_update_010; + +-- enable_upsert_to_merge must is off, or upsert will be translated to merge. +SET enable_upsert_to_merge TO OFF; + +-- test t4 with one primary key with three columns +CREATE TABLE t4 ( + col1 INT, + col2 INT DEFAULT 0, + col3 INT DEFAULT 1, + col4 TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + col5 BIGSERIAL, + PRIMARY KEY (col2, col3, col5) +) ; + +--- should insert +INSERT INTO t4 VALUES (1) ON DUPLICATE KEY UPDATE col1 = 10; +INSERT INTO t4 VALUES (1) ON DUPLICATE KEY UPDATE col1 = 10; +INSERT INTO t4 VALUES (2, 2, 2) ON DUPLICATE KEY UPDATE col1 = 200; +INSERT INTO t4 VALUES (100, 100, 100, CURRENT_TIMESTAMP, 100) ON DUPLICATE KEY UPDATE col1 = 1000; +SELECT col1, col2, col3, col5 FROM t4 ORDER BY col5; + +--- should update +INSERT INTO t4 VALUES (2, 2, 2, CURRENT_TIMESTAMP, 3) ON DUPLICATE KEY UPDATE col1 = 200; +INSERT INTO t4 VALUES (100, 100, 100, CURRENT_TIMESTAMP, 100) ON DUPLICATE KEY UPDATE col1 = 1000; +SELECT col1, col2, col3, col5 FROM t4 ORDER BY col5; + +--- error: duplicate key update on (x, x, 20) +--- this is because current version is not inplace update but merge, +--- so when the subquery contains multiple same values, it will cause duplicate insert failure. +SELECT col3, sum(col3) * 10 FROM t4 GROUP BY col3 ORDER BY 1, 2; +INSERT INTO t4 (col1, col5) + (SELECT col3, sum(col3) * 10 FROM t4 GROUP BY col3) + ON DUPLICATE KEY UPDATE col1 = 3; + +-- test t5 with sequence or default column with volatile function in constaint index +CREATE TABLE t5 ( + col1 INT, + col2 INT DEFAULT 1, + col3 BIGSERIAL, +-- col4 TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + col5 INTEGER(10, 5) DEFAULT RANDOM() + 1 +) ; + +-- test t5 with sequence column in constaint index +CREATE UNIQUE INDEX u_t5_index1 ON t5(col1, col3); +--- should insert +INSERT INTO t5 VALUES (1), (1), (1) ON DUPLICATE KEY UPDATE col2 = col3; +INSERT INTO t5 DEFAULT VALUES ON DUPLICATE KEY UPDATE col2 = col3; +SELECT * FROM t5 ORDER BY col3; + +--- should update +INSERT INTO t5 (col1, col3) VALUES (1, 1), (1, 2), (1, 3) ON DUPLICATE KEY UPDATE col5 = col2, col2 = col3 * 10; +SELECT * FROM t5 WHERE col1 = 1 ORDER BY col3; + +--- should some insert some update +INSERT INTO t5 (col1, col3) VALUES (2, 5), (2, 6); +SELECT col1, col2, col3 FROM t5 ORDER BY col3; +INSERT INTO t5 (col1) VALUES (2), (2), (2) ON DUPLICATE KEY UPDATE col2 = col3; +SELECT col1, col2, col3 FROM t5 ORDER BY col3; + +--- should INSERT and sequence starting from 7 +INSERT INTO t5 VALUES (2), (2); +SELECT col1, col2, col3 FROM t5 ORDER BY col3; + +-- test with volatile function as default column in constraint index +TRUNCATE t5; +DROP INDEX u_t5_index1; +CREATE UNIQUE INDEX u_t5_index2 ON t5(col1, col5) WHERE col1 > 2; + +--- should insert +INSERT INTO t5 VALUES (3), (3), (3) ON DUPLICATE KEY UPDATE col2 = col3; +INSERT INTO t5 (col1) VALUES (4), (4), (4) ON DUPLICATE KEY UPDATE col2 = col3; +SELECT * FROM t5 ORDER BY col3; + +--- should update +INSERT INTO t5 (col1, col5) SELECT col1, col5 FROM t5 where col1 = 3 ON DUPLICATE KEY UPDATE col2 = col5 * 100; +SELECT * FROM t5 ORDER BY col3; + +--- test subquery +---- should insert +INSERT INTO t5 (col1, col2) SELECT col1, col2 FROM t5 ON DUPLICATE KEY UPDATE col2 = col5 * 100; +SELECT * FROM t5 ORDER BY col3; + +---- should update +INSERT INTO t5 SELECT * FROM t5 ON DUPLICATE KEY UPDATE col2 = col5 * 1000; +SELECT * FROM t5 ORDER BY col3; + +-- test t6 with one more index +CREATE TABLE t6 ( + col1 INT, + col2 INT DEFAULT 1, + col3 BIGSERIAL, +-- col4 TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + col5 INTEGER(10, 5) DEFAULT RANDOM(), + col6 INT, + col7 TEXT +) ; + +ALTER TABLE t6 ADD PRIMARY KEY (col1, col3); +CREATE UNIQUE INDEX u_t6_index1 ON t6(col1, col5, col6); + +INSERT INTO t6 (col1) VALUES (1), (2), (3), (4), (5); +SELECT col1, col2, col3, col6, col7 FROM t6 ORDER BY col3; + +--- should insert +INSERT INTO t6 (col1) VALUES (1), (2), (3), (4), (5) ON DUPLICATE KEY UPDATE col6 = power(col1, col2); +SELECT col1, col2, col3, col6, col7 FROM t6 ORDER BY col3; + +--- should update because primary key matches +INSERT INTO t6 (col1, col3) VALUES (6, 11), (6, 12); +SELECT col1, col2, col3, col6, col7 FROM t6 WHERE col1 = 6 ORDER BY col3; +INSERT INTO t6 (col1) VALUES (6), (6), (6) ON DUPLICATE KEY UPDATE col2 = col1 + col3, col6 = col2 * 10; +SELECT col1, col2, col3, col6, col7 FROM t6 WHERE col1 = 6 ORDER BY col3; + +--- should update for those col6 is not null bacause they will match the unique index, +--- and insert for those col6 is null because the unique index containing null never matches, +--- also primary key will not match +--- be ware the sequence column of the inserted row will jump n step, where n is the count of the not null rows, +--- because those sequence have to be generated during the unique index join stage. +INSERT INTO t6 (col1, col5, col6) + (SELECT col1, col5, col6 FROM t6 WHERE col1 = 6) + ON DUPLICATE KEY UPDATE + col7 = col2 + 1; +SELECT col1, col2, col3, col6, col7 FROM t6 WHERE col1 = 6 ORDER BY col1, col3, col6, col7; + +--- should update because unique index and primary key both match +INSERT INTO t6 (col1, col3, col5, col6) + (SELECT col1, col3, col5, col6 FROM t6 WHERE col1 = 6 AND col6 IS NOT NULL) + ON DUPLICATE KEY UPDATE + col7 = col7 * 10; +SELECT col1, col2, col3, col6, col7 FROM t6 WHERE col1 = 6 ORDER BY col1, col3, col6, col7; + +--- insert when col3 = 17 because constaints does not match, +--- but update when col3 = 18 because 18 has been inserted and will cause a match +INSERT INTO t6 (col1, col3, col5, col6) VALUES (7, 18, 100, 100); +SELECT * FROM t6 WHERE col3 > 16 ORDER BY col1, col3, col6, col7; +INSERT INTO t6 (col1, col5, col6) VALUES (7, 10, 10), (7, 100, 100) ON DUPLICATE KEY UPDATE + col7 = col3 * 100; +SELECT * FROM t6 WHERE col1 = 7 ORDER BY col1, col3, col6, col7; + +DROP SCHEMA test_insert_update_010 CASCADE; diff --git a/src/test/regress/sql/upsert_clean.sql b/src/test/regress/sql/upsert_clean.sql new file mode 100644 index 000000000..442be9ffe --- /dev/null +++ b/src/test/regress/sql/upsert_clean.sql @@ -0,0 +1,3 @@ +\c upsert +DROP SCHEMA upsert_test CASCADE; +DROP SCHEMA upsert_test_unlog CASCADE; diff --git a/src/test/regress/sql/upsert_composite.sql b/src/test/regress/sql/upsert_composite.sql new file mode 100644 index 000000000..57ce939df --- /dev/null +++ b/src/test/regress/sql/upsert_composite.sql @@ -0,0 +1,146 @@ +-------------------------------------------------------------------------------------------- +/* + * procedure + */ +-------------------------------------------------------------------------------------------- +\c upsert; +SET CURRENT_SCHEMA TO upsert_test_procedure; +-- enable_upsert_to_merge must be off, or upsert will be translated to merge. +SET enable_upsert_to_merge TO OFF; +SHOW enable_upsert_to_merge; + +drop table IF EXISTS t_proc; +create table t_proc(c1 int, c2 int, c3 int unique); +insert into t_proc select a,a,a from generate_series(1,20) as a; +select *from t_proc order by c3; + +create or replace procedure mul_ups( c in INTEGER) +as +declare +val int; +begin + for val in select c3 from t_proc loop + insert into t_proc values(c,c, val) on duplicate key update c1 = c, c2 =c; + end loop; +end; +/ + +call mul_ups(100); +select *from t_proc order by c3; + +create or replace procedure mul_ups( c in INTEGER) +as +declare +val int; +begin + for val in select c3 from t_proc loop + insert into t_proc values(c,c, val) on duplicate key update c1 = $1, c2 =$1; + end loop; +end; +/ + +call mul_ups(200); +select *from t_proc order by c3; + +create or replace procedure mul_ups_00() +as +declare +val int; +begin + for val in select c3 from t_proc loop + insert into t_proc values(val,val,val) on duplicate key update c1 = 300, c2 =300; + end loop; +end; +/ + +call mul_ups_00(); +select *from t_proc order by c3; + +declare +val int; +c int :=400; +begin + for val in select c3 from t_proc loop + insert into t_proc values(c,c, val) on duplicate key update c1 = c, c2 =c; + end loop; +end; +/ +select *from t_proc order by c3; + +create or replace procedure mul_ups( c in INTEGER) +as +declare +val int; +begin + for val in select c3 from t_proc loop + insert into t_proc values(c,c, val+100) on duplicate key update c1 = $1, c2 =$1; + end loop; +end; +/ + +call mul_ups(500); +select *from t_proc order by c3; + +/* + * insert + */ +drop table if exists t_default; +CREATE TABLE t_default (c1 INT PRIMARY KEY DEFAULT 10, c2 FLOAT DEFAULT 3.0, c3 TIMESTAMP DEFAULT '20200508'); +create view v_default as select *from t_default order by c1; + +-- insert src +---- insert values +truncate t_default; +insert into t_default values(DEFAULT, DEFAULT, DEFAULT); +insert into t_default values(1, 2, '20200630'); +select *from t_default order by c1; + +insert into t_default values(DEFAULT, DEFAULT, DEFAULT) on duplicate key update c2 = 1; +select *from t_default order by c1; + +insert into t_default values(1, 2, '20200630') on duplicate key update c2 = 2; +select *from t_default order by c1; + +insert into t_default values(DEFAULT, DEFAULT, DEFAULT),(1, 2, '20200630') on duplicate key update c2 = 3; +select *from t_default order by c1; + +---- insert subquery +insert into t_default select *from t_default order by c1 on duplicate key update c2 = 4; +select *from t_default order by c1; + +insert into t_default select *from t_default order by c1 limit 1 on duplicate key update c2 = 5; +select *from t_default order by c1; + +insert into t_default select *from t_default where c1=10 on duplicate key update c2 = 6; +select *from t_default order by c1; + +insert into t_default select *from t_default order by c1 on duplicate key update c2 = 7; +select *from t_default order by c1; + +insert into t_default select *from v_default order by c1 on duplicate key update c2 = 8; +select *from t_default order by c1; + +insert into t_default select *from t_default union select c1+1,c2+1,c3+1 from t_default order by c1 on duplicate key update c2 = 9; +select *from t_default order by c1; + +insert into t_default(c1,c2) select max(c1),c2 from t_default group by c2 on duplicate key update c2 = 10; + +-- index +---- mul index +truncate t_default; +create unique index t_default_mul on t_default(c1,c3); +insert into t_default values(DEFAULT, DEFAULT, DEFAULT); +insert into t_default values(1, 2, '20200630'); +select *from t_default order by c1; + +insert into t_default values(DEFAULT, DEFAULT, DEFAULT) on duplicate key update c2 = 1; +select *from t_default order by c1; + +insert into t_default values(1, 2, '20200630') on duplicate key update c2 = 2; +select *from t_default order by c1; + +insert into t_default values(DEFAULT, DEFAULT, DEFAULT),(1, 2, '20200630') on duplicate key update c2 = 3; +select *from t_default order by c1; + +insert into t_default select *from t_default on duplicate key update c2 = 4; +select *from t_default order by c1; diff --git a/src/test/regress/sql/upsert_explain.sql b/src/test/regress/sql/upsert_explain.sql new file mode 100644 index 000000000..6408507c9 --- /dev/null +++ b/src/test/regress/sql/upsert_explain.sql @@ -0,0 +1,63 @@ +-------------------------------------------------------------------------------------------- +/* + * explain upsert + */ +-------------------------------------------------------------------------------------------- +\c upsert; +SET CURRENT_SCHEMA TO upsert_test_explain; +-- enable_upsert_to_merge must be off, or upsert will be translated to merge. +SET enable_upsert_to_merge TO OFF; +SHOW enable_upsert_to_merge; + +create temp table up_expl_temp(c1 int, c2 int, c3 int unique) ; + + +insert into up_expl_hash select a,a,a from generate_series(1,20) as a; +insert into up_expl_repl select a,a,a from generate_series(1,20) as a; +insert into up_expl_part select a,a,a from generate_series(1,20) as a; +insert into up_expl_temp select a,a,a from generate_series(1,20) as a; +insert into up_expl_unlog select a,a,a from generate_series(1,20) as a; +insert into up_expl_node select a,a,a from generate_series(1,20) as a; +insert into up_expl_repl2 select a,a,a,a,a from generate_series(1,20) as a; + +EXPLAIN (VERBOSE on, COSTS off, TIMING off) insert into up_expl_hash values(1,1,1) on duplicate key update c1 = 2; +EXPLAIN (ANALYZE on, COSTS off, TIMING off) insert into up_expl_hash values(1,1,1) on duplicate key update c1 = 2; +EXPLAIN PERFORMANCE insert into up_expl_hash values(1,1,1) on duplicate key update c1 = 2; + +EXPLAIN (VERBOSE on, COSTS off, TIMING off) insert into up_expl_hash values(1,1,1),(2,2,2)on duplicate key update c1 = 2; +EXPLAIN (ANALYZE on, COSTS off, TIMING off) insert into up_expl_hash values(1,1,1),(2,2,2)on duplicate key update c1 = 2; + +EXPLAIN (VERBOSE on, COSTS off, TIMING off) insert into up_expl_repl values(1,1,1) on duplicate key update c1 = 2; +EXPLAIN (ANALYZE on, COSTS off, TIMING off) insert into up_expl_repl values(1,1,1) on duplicate key update c1 = 2; + +EXPLAIN (VERBOSE on, COSTS off, TIMING off) insert into up_expl_part values(1,1,1) on duplicate key update c1 = 2; +EXPLAIN (ANALYZE on, COSTS off, TIMING off) insert into up_expl_part values(1,1,1) on duplicate key update c1 = 2; + +EXPLAIN (VERBOSE on, COSTS off, TIMING off) insert into up_expl_temp values(1,1,1) on duplicate key update c1 = 2; +EXPLAIN (ANALYZE on, COSTS off, TIMING off) insert into up_expl_temp values(1,1,1) on duplicate key update c1 = 2; + +EXPLAIN (VERBOSE on, COSTS off, TIMING off) insert into up_expl_unlog values(1,1,1) on duplicate key update c1 = 2; +EXPLAIN (ANALYZE on, COSTS off, TIMING off) insert into up_expl_unlog values(1,1,1) on duplicate key update c1 = 2; + +EXPLAIN (VERBOSE on, COSTS off, TIMING off) insert into up_expl_node values(1,1,1) on duplicate key update c1 = 2; +EXPLAIN (ANALYZE on, COSTS off, TIMING off) insert into up_expl_node values(1,1,1) on duplicate key update c1 = 2; + + +EXPLAIN (VERBOSE on, COSTS off, TIMING off) insert into up_expl_hash values(1,1,1),(2,2,2)on duplicate key update nothing; +EXPLAIN (ANALYZE on, COSTS off, TIMING off) insert into up_expl_hash values(1,1,1),(2,2,2)on duplicate key update nothing; + +EXPLAIN (VERBOSE on, COSTS off, TIMING off) insert into up_expl_repl values(1,1,1) on duplicate key update nothing; +EXPLAIN (ANALYZE on, COSTS off, TIMING off) insert into up_expl_repl values(1,1,1) on duplicate key update nothing; + +EXPLAIN (VERBOSE on, COSTS off, TIMING off) insert into up_expl_part values(1,1,1) on duplicate key update nothing; +EXPLAIN (ANALYZE on, COSTS off, TIMING off) insert into up_expl_part values(1,1,1) on duplicate key update nothing; + +EXPLAIN (VERBOSE on, COSTS off, TIMING off) insert into up_expl_temp values(1,1,1) on duplicate key update nothing; +EXPLAIN (ANALYZE on, COSTS off, TIMING off) insert into up_expl_temp values(1,1,1) on duplicate key update nothing; + +EXPLAIN (VERBOSE on, COSTS off, TIMING off) insert into up_expl_unlog values(1,1,1) on duplicate key update nothing; +EXPLAIN (ANALYZE on, COSTS off, TIMING off) insert into up_expl_unlog values(1,1,1) on duplicate key update nothing; + +EXPLAIN (VERBOSE on, COSTS off, TIMING off) insert into up_expl_node values(1,1,1) on duplicate key update nothing; +EXPLAIN (ANALYZE on, COSTS off, TIMING off) insert into up_expl_node values(1,1,1) on duplicate key update nothing; + diff --git a/src/test/regress/sql/upsert_grammer_test_01.sql b/src/test/regress/sql/upsert_grammer_test_01.sql new file mode 100644 index 000000000..7124bed4c --- /dev/null +++ b/src/test/regress/sql/upsert_grammer_test_01.sql @@ -0,0 +1,113 @@ +\c upsert +SET CURRENT_SCHEMA TO upsert_test; +-- enable_upsert_to_merge must be off, or upsert will be translated to merge. +SET enable_upsert_to_merge TO OFF; +SHOW enable_upsert_to_merge; + +-- support multiple set +INSERT INTO t_grammer VALUES(11, 1) ON DUPLICATE KEY UPDATE c2 = 2, c4 = ROW(10, 20), c3 = '{10, 20, 30}'; +INSERT INTO t_grammer VALUES(12, 2) ON DUPLICATE KEY UPDATE c2 = 2, c3[1] = 3; + +-- support const value and expr +INSERT INTO t_grammer VALUES(23) ON DUPLICATE KEY UPDATE c2 = 2; +INSERT INTO t_grammer VALUES(24) ON DUPLICATE KEY UPDATE c2 = length('test') + 100; + +-- support without table name +INSERT INTO t_grammer VALUES(31, 1) ON DUPLICATE KEY UPDATE c2 = c1, c4.a = VALUES(c1); +INSERT INTO t_grammer VALUES(32, 2) ON DUPLICATE KEY UPDATE c2 = c1 + 5, c4.a = VALUES(c1); + +-- support with table name +INSERT INTO t_grammer VALUES(41, 1) ON DUPLICATE KEY UPDATE c2 = t_grammer.c1, c4.a = VALUES(c1); +INSERT INTO t_grammer VALUES(42, 2) ON DUPLICATE KEY UPDATE c2 = t_grammer.c1 + 5, c4.a = VALUES(c1); + +-- support EXCLUDED alone and with expr +INSERT INTO t_grammer VALUES(51, 1) ON DUPLICATE KEY UPDATE c2 = c1 + 5, c4.a = EXCLUDED.c1; +INSERT INTO t_grammer VALUES(52, 2) ON DUPLICATE KEY UPDATE c2 = t_grammer.c1 + 5, c4.a = EXCLUDED.c1 + 5; +INSERT INTO t_grammer VALUES(53, 3) ON DUPLICATE KEY UPDATE c4.a = sqrt(EXCLUDED.c1 - EXCLUDED.c2 - 1) + 1, c2 = t_grammer.c1 + 5; +INSERT INTO t_grammer VALUES(54, 4) ON DUPLICATE KEY UPDATE c4.b = EXCLUDED.c1 + t_grammer.c1; +INSERT INTO t_grammer VALUES(55, 5) ON DUPLICATE KEY UPDATE c4.a = EXCLUDED.c2, c4.b = EXCLUDED.c1 + sqrt(c1 - 6); + +-- support ARRAY +INSERT INTO t_grammer VALUES(61, 1) ON DUPLICATE KEY UPDATE c2 = VALUES(c3[1]); +INSERT INTO t_grammer VALUES(62, 2) ON DUPLICATE KEY UPDATE c5 = VALUES(c3[2,3]); +INSERT INTO t_grammer VALUES(63, 3) ON DUPLICATE KEY UPDATE c5 = VALUES(c3[2:3]); +INSERT INTO t_grammer VALUES(64, 4, '{10, 20, 30}') ON DUPLICATE KEY UPDATE c2 = EXCLUDED.c3[1]; +INSERT INTO t_grammer VALUES(65, 5, '{10, 20, 30}') ON DUPLICATE KEY UPDATE c5 = EXCLUDED.c3[2,3]; +INSERT INTO t_grammer VALUES(66, 6, '{10, 20, 30}') ON DUPLICATE KEY UPDATE c5 = EXCLUDED.c3[1:2]; +INSERT INTO t_grammer VALUES(67, 7, '{10, 20, 30}') ON DUPLICATE KEY UPDATE c2 = c3[1]; +INSERT INTO t_grammer VALUES(68, 8, '{10, 20, 30}') ON DUPLICATE KEY UPDATE c5 = c3[2,3]; +INSERT INTO t_grammer VALUES(69, 9, '{10, 20, 30}') ON DUPLICATE KEY UPDATE c5 = c3[1:2]; + +-- support user defined type +INSERT INTO t_grammer VALUES(71, 1) ON DUPLICATE KEY UPDATE c4.a = c3[1] + c1; +INSERT INTO t_grammer VALUES(72, 2, '{71, 72, 73}') ON DUPLICATE KEY UPDATE c4.a = c3[1] + c1; +INSERT INTO t_grammer VALUES(73, 3, '{71, 72, 73}') ON DUPLICATE KEY UPDATE c4.a = c3[1] + c1, c4.b = c3[2]; +INSERT INTO t_grammer VALUES(74, 4, '{71, 72, 73}', ROW(74, 75)) ON DUPLICATE KEY UPDATE c4 = ROW(740, 750); + +-- appoint INSERT target column +INSERT INTO t_grammer (c5, c1, c2) VALUES('{81,82}', 81, DEFAULT) ON DUPLICATE KEY UPDATE c2 = VALUES(c1); +INSERT INTO t_grammer (c3[1], c3[2], c1) VALUES(881, 882, 82) ON DUPLICATE KEY UPDATE c5 = VALUES(c3), c2 = VALUES(c3[1]); +INSERT INTO t_grammer (c1, c3, c4) VALUES(83, '{81, 82, 83}', ROW(810, 820)) ON DUPLICATE KEY UPDATE c4 = EXCLUDED.c4; +INSERT INTO t_grammer (c1, c3, c4.a) VALUES(84, '{81, 82, 83}', 850) ON DUPLICATE KEY UPDATE c4.b = c3[3]; + +-- support UPDATE NOTHING +INSERT INTO t_grammer VALUES(91, 1, '{91, 92, 93}', ROW(94, 95)) ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_grammer (c5, c1, c2, c4) VALUES('{91,92}', 92, DEFAULT, ROW(910, 920)) ON DUPLICATE KEY UPDATE NOTHING; + +-- UPDATE target: unsupport with schema but support with tablename +INSERT INTO t_grammer VALUES(0, 0, '{0,0}', ROW(0, 0), '{107, 108}') ON DUPLICATE KEY UPDATE + upsert_test.t_grammer.c2 = c2 * 10, upsert_test.t_grammer.c3[1:2] = c5[1:2], upsert_test.t_grammer.c4.a = c1; +INSERT INTO t_grammer VALUES(101, 1, '{102, 103, 104}', ROW(105, 106), '{107, 108}') ON DUPLICATE KEY UPDATE + t_grammer.c2 = c2 * 10, t_grammer.c3[1:2] = c5[1:2], t_grammer.c4.a = c1; + +-- INSERT target: appoint schema +INSERT INTO upsert_test.t_grammer VALUES(102, 2, '{102, 103, 104}', ROW(105, 106), '{107, 108}') ON DUPLICATE KEY UPDATE + t_grammer.c2 = EXCLUDED.c2 * 10, t_grammer.c3[1:2] = c5[1:2], t_grammer.c4.a = VALUES(c1); +CREATE SCHEMA upsert_test_tmpschema; +SET CURRENT_SCHEMA TO upsert_test_tmpschema; +INSERT INTO upsert_test.t_grammer VALUES(103, 3, '{102, 103, 104}', ROW(105, 106), '{107, 108}') ON DUPLICATE KEY UPDATE + t_grammer.c2 = EXCLUDED.c2 * 10, t_grammer.c3[1:2] = c5[1:2], t_grammer.c4.a = VALUES(c1); +SET CURRENT_SCHEMA TO upsert_test; +DROP SCHEMA upsert_test_tmpschema; + +-- support data type +INSERT INTO t_data VALUES(11, 11, 11, 11, 11, '11', '11', '11', '2020-04-23', '2020-04-23 11:00', '1:00') ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_data VALUES(12, 12, 12, 12, 12, '12', '12', '12', '2020-04-24', '2020-04-24 11:00', '2:00') + ON DUPLICATE KEY UPDATE + c_tiny = 13, c_smallint = 13, c_bigint = 13, c_numeric = 13, + c_var = '13', c_text = '13', c_bytea = '13', c_date = '2020-05-24', c_timestamp = '2020-05-24 11:00', c_time = '3:00'; +INSERT INTO t_data VALUES(13, 13, 13, 13, 13, '13', '13', '13', '2020-04-25', '2020-04-25 11:00', '4:00') + ON DUPLICATE KEY UPDATE + c_tiny = EXCLUDED.c_tiny + 1, c_smallint = EXCLUDED.c_smallint + 1, c_bigint = EXCLUDED.c_bigint + 1, c_numeric = EXCLUDED.c_numeric + 1, + c_var = EXCLUDED.c_var || '3', c_text = EXCLUDED.c_text || '3', c_bytea = EXCLUDED.c_bytea || '3', + c_date = EXCLUDED.c_date + '1 day'::interval, c_timestamp = EXCLUDED.c_timestamp + '1 day'::interval, c_time = EXCLUDED.c_time + '1 hour'::interval; + +-- support UPDATE to default +INSERT INTO t_default DEFAULT VALUES ON DUPLICATE KEY UPDATE c2 = 100, c3 = '2020-05-17'; +INSERT INTO t_default VALUES(91, 0.91, '2020-05-17') ON DUPLICATE KEY UPDATE c2 = DEFAULT, c3 = DEFAULT; +INSERT INTO t_default VALUES(92, DEFAULT, '2020-05-17') ON DUPLICATE KEY UPDATE c2 = 10, c3 = DEFAULT; + +-- unsupport alias +INSERT INTO t_grammer AS alias VALUES (91) ON DUPLICATE KEY UPDATE c2 = VALUES(c1); + +-- unsupport VALUES with expr +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = sqrt(VALUES(c2)); +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(c3[1]) + 1; +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(c4.a) + 1; +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(t_grammer.c3[1]) + 1; + +-- unsupport VALUES with table name +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(t_grammer.c4.a); +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(t_grammer.c2); + +-- unsupport DEFAULT with expr +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = DEFAULT + 1; + +-- unsupport user defined typed column's element +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(c4.a); +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = EXCLUDED.c4.a; +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = c4.a; + +SELECT * FROM t_data ORDER BY 1; +SELECT * FROM t_grammer ORDER BY 1; +SELECT * FROM t_default ORDER BY 1; diff --git a/src/test/regress/sql/upsert_grammer_test_02.sql b/src/test/regress/sql/upsert_grammer_test_02.sql new file mode 100644 index 000000000..ecdaf5d28 --- /dev/null +++ b/src/test/regress/sql/upsert_grammer_test_02.sql @@ -0,0 +1,113 @@ +\c upsert +SET CURRENT_SCHEMA TO upsert_test; +-- enable_upsert_to_merge must be off, or upsert will be translated to merge. +SET enable_upsert_to_merge TO OFF; +SHOW enable_upsert_to_merge; + +-- support multiple set +INSERT INTO t_grammer VALUES(11, 1) ON DUPLICATE KEY UPDATE c2 = 2, c4 = ROW(10, 20), c3 = '{10, 20, 30}'; +INSERT INTO t_grammer VALUES(12, 2) ON DUPLICATE KEY UPDATE c2 = 2, c3[1] = 3; + +-- support const value and expr +INSERT INTO t_grammer VALUES(23) ON DUPLICATE KEY UPDATE c2 = 2; +INSERT INTO t_grammer VALUES(24) ON DUPLICATE KEY UPDATE c2 = length('test') + 100; + +-- support without table name +INSERT INTO t_grammer VALUES(31, 1) ON DUPLICATE KEY UPDATE c2 = c1, c4.a = VALUES(c1); +INSERT INTO t_grammer VALUES(32, 2) ON DUPLICATE KEY UPDATE c2 = c1 + 5, c4.a = VALUES(c1); + +-- support with table name +INSERT INTO t_grammer VALUES(41, 1) ON DUPLICATE KEY UPDATE c2 = t_grammer.c1, c4.a = VALUES(c1); +INSERT INTO t_grammer VALUES(42, 2) ON DUPLICATE KEY UPDATE c2 = t_grammer.c1 + 5, c4.a = VALUES(c1); + +-- support EXCLUDED alone and with expr +INSERT INTO t_grammer VALUES(51, 1) ON DUPLICATE KEY UPDATE c2 = c1 + 5, c4.a = EXCLUDED.c1; +INSERT INTO t_grammer VALUES(52, 2) ON DUPLICATE KEY UPDATE c2 = t_grammer.c1 + 5, c4.a = EXCLUDED.c1 + 5; +INSERT INTO t_grammer VALUES(53, 3) ON DUPLICATE KEY UPDATE c4.a = sqrt(EXCLUDED.c1 - EXCLUDED.c2 - 1) + 1, c2 = t_grammer.c1 + 5; +INSERT INTO t_grammer VALUES(54, 4) ON DUPLICATE KEY UPDATE c4.b = EXCLUDED.c1 + t_grammer.c1; +INSERT INTO t_grammer VALUES(55, 5) ON DUPLICATE KEY UPDATE c4.a = EXCLUDED.c2, c4.b = EXCLUDED.c1 + sqrt(c1 - 6); + +-- support ARRAY +INSERT INTO t_grammer VALUES(61, 1) ON DUPLICATE KEY UPDATE c2 = VALUES(c3[1]); +INSERT INTO t_grammer VALUES(62, 2) ON DUPLICATE KEY UPDATE c5 = VALUES(c3[2,3]); +INSERT INTO t_grammer VALUES(63, 3) ON DUPLICATE KEY UPDATE c5 = VALUES(c3[2:3]); +INSERT INTO t_grammer VALUES(64, 4, '{10, 20, 30}') ON DUPLICATE KEY UPDATE c2 = EXCLUDED.c3[1]; +INSERT INTO t_grammer VALUES(65, 5, '{10, 20, 30}') ON DUPLICATE KEY UPDATE c5 = EXCLUDED.c3[2,3]; +INSERT INTO t_grammer VALUES(66, 6, '{10, 20, 30}') ON DUPLICATE KEY UPDATE c5 = EXCLUDED.c3[1:2]; +INSERT INTO t_grammer VALUES(67, 7, '{10, 20, 30}') ON DUPLICATE KEY UPDATE c2 = c3[1]; +INSERT INTO t_grammer VALUES(68, 8, '{10, 20, 30}') ON DUPLICATE KEY UPDATE c5 = c3[2,3]; +INSERT INTO t_grammer VALUES(69, 9, '{10, 20, 30}') ON DUPLICATE KEY UPDATE c5 = c3[1:2]; + +-- support user defined type +INSERT INTO t_grammer VALUES(71, 1) ON DUPLICATE KEY UPDATE c4.a = c3[1] + c1; +INSERT INTO t_grammer VALUES(72, 2, '{71, 72, 73}') ON DUPLICATE KEY UPDATE c4.a = c3[1] + c1; +INSERT INTO t_grammer VALUES(73, 3, '{71, 72, 73}') ON DUPLICATE KEY UPDATE c4.a = c3[1] + c1, c4.b = c3[2]; +INSERT INTO t_grammer VALUES(74, 4, '{71, 72, 73}', ROW(74, 75)) ON DUPLICATE KEY UPDATE c4 = ROW(740, 750); + +-- appoint INSERT target column +INSERT INTO t_grammer (c5, c1, c2) VALUES('{81,82}', 81, DEFAULT) ON DUPLICATE KEY UPDATE c2 = VALUES(c1); +INSERT INTO t_grammer (c3[1], c3[2], c1) VALUES(881, 882, 82) ON DUPLICATE KEY UPDATE c5 = VALUES(c3), c2 = VALUES(c3[1]); +INSERT INTO t_grammer (c1, c3, c4) VALUES(83, '{81, 82, 83}', ROW(810, 820)) ON DUPLICATE KEY UPDATE c4 = EXCLUDED.c4; +INSERT INTO t_grammer (c1, c3, c4.a) VALUES(84, '{81, 82, 83}', 850) ON DUPLICATE KEY UPDATE c4.b = c3[3]; + +-- support UPDATE NOTHING +INSERT INTO t_grammer VALUES(91, 1, '{91, 92, 93}', ROW(94, 95)) ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_grammer (c5, c1, c2, c4) VALUES('{91,92}', 92, DEFAULT, ROW(910, 920)) ON DUPLICATE KEY UPDATE NOTHING; + +-- UPDATE target: unsupport with schema but support with tablename +INSERT INTO t_grammer VALUES(0, 0, '{0,0}', ROW(0, 0), '{107, 108}') ON DUPLICATE KEY UPDATE + upsert_test.t_grammer.c2 = c2 * 10, upsert_test.t_grammer.c3[1:2] = c5[1:2], upsert_test.t_grammer.c4.a = c1; +INSERT INTO t_grammer VALUES(101, 1, '{102, 103, 104}', ROW(105, 106), '{107, 108}') ON DUPLICATE KEY UPDATE + t_grammer.c2 = c2 * 10, t_grammer.c3[1:2] = c5[1:2], t_grammer.c4.a = c1; + +-- INSERT target: appoint schema +INSERT INTO upsert_test.t_grammer VALUES(102, 2, '{102, 103, 104}', ROW(105, 106), '{107, 108}') ON DUPLICATE KEY UPDATE + t_grammer.c2 = EXCLUDED.c2 * 10, t_grammer.c3[1:2] = c5[1:2], t_grammer.c4.a = VALUES(c1); +CREATE SCHEMA upsert_test_tmp; +SET CURRENT_SCHEMA TO upsert_test_tmp; +INSERT INTO upsert_test.t_grammer VALUES(103, 3, '{102, 103, 104}', ROW(105, 106), '{107, 108}') ON DUPLICATE KEY UPDATE + t_grammer.c2 = EXCLUDED.c2 * 10, t_grammer.c3[1:2] = c5[1:2], t_grammer.c4.a = VALUES(c1); +SET CURRENT_SCHEMA TO upsert_test; +DROP SCHEMA upsert_test_tmp; + +-- support data type +INSERT INTO t_data VALUES(11, 11, 11, 11, 11, '11', '11', '11', '2020-04-23', '2020-04-23 11:00', '1:00') ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_data VALUES(12, 12, 12, 12, 12, '12', '12', '12', '2020-04-24', '2020-04-24 11:00', '2:00') + ON DUPLICATE KEY UPDATE + c_tiny = 13, c_smallint = 13, c_bigint = 13, c_numeric = 13, + c_var = '13', c_text = '13', c_bytea = '13', c_date = '2020-05-24', c_timestamp = '2020-05-24 11:00', c_time = '3:00'; +INSERT INTO t_data VALUES(13, 13, 13, 13, 13, '13', '13', '13', '2020-04-25', '2020-04-25 11:00', '4:00') + ON DUPLICATE KEY UPDATE + c_tiny = EXCLUDED.c_tiny + 1, c_smallint = EXCLUDED.c_smallint + 1, c_bigint = EXCLUDED.c_bigint + 1, c_numeric = EXCLUDED.c_numeric + 1, + c_var = EXCLUDED.c_var || '3', c_text = EXCLUDED.c_text || '3', c_bytea = EXCLUDED.c_bytea || '3', + c_date = EXCLUDED.c_date + '1 day'::interval, c_timestamp = EXCLUDED.c_timestamp + '1 day'::interval, c_time = EXCLUDED.c_time + '1 hour'::interval; + +-- support UPDATE to default +INSERT INTO t_default DEFAULT VALUES ON DUPLICATE KEY UPDATE c2 = 100, c3 = '2020-05-17'; +INSERT INTO t_default VALUES(91, 0.91, '2020-05-17') ON DUPLICATE KEY UPDATE c2 = DEFAULT, c3 = DEFAULT; +INSERT INTO t_default VALUES(92, DEFAULT, '2020-05-17') ON DUPLICATE KEY UPDATE c2 = 10, c3 = DEFAULT; + +-- unsupport alias +INSERT INTO t_grammer AS alias VALUES (91) ON DUPLICATE KEY UPDATE c2 = VALUES(c1); + +-- unsupport VALUES with expr +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = sqrt(VALUES(c2)); +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(c3[1]) + 1; +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(c4.a) + 1; +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(t_grammer.c3[1]) + 1; + +-- unsupport VALUES with table name +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(t_grammer.c4.a); +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(t_grammer.c2); + +-- unsupport DEFAULT with expr +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = DEFAULT + 1; + +-- unsupport user defined typed column's element +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = VALUES(c4.a); +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = EXCLUDED.c4.a; +INSERT INTO t_grammer VALUES(0, 0) ON DUPLICATE KEY UPDATE c2 = c4.a; + +SELECT * FROM t_data ORDER BY 1; +SELECT * FROM t_grammer ORDER BY 1; +SELECT * FROM t_default ORDER BY 1; diff --git a/src/test/regress/sql/upsert_prepare.sql b/src/test/regress/sql/upsert_prepare.sql new file mode 100644 index 000000000..4ff4a7807 --- /dev/null +++ b/src/test/regress/sql/upsert_prepare.sql @@ -0,0 +1,76 @@ +-- Note: about upsert test +-- test point: +-- grammer +-- table, unlogged table, temp table, matview +-- constraint(primary key, index, foreign key, null ...) +-- trigger +-- sequence + +CREATE DATABASE upsert WITH TEMPLATE template0 ENCODING 'UTF8'; +\c upsert + +-- grammer test +CREATE SCHEMA upsert_test; +SET CURRENT_SCHEMA TO upsert_test; +CREATE TYPE atype AS(a int, b int); +CREATE TYPE btype AS(a int, b atype, c varchar[3]); +CREATE TABLE t_grammer (c1 INT PRIMARY KEY, c2 INT DEFAULT -100, c3 int[3], c4 atype, c5 int[2]); +CREATE TABLE t_default (c1 INT PRIMARY KEY DEFAULT 10, c2 FLOAT DEFAULT random(), c3 TIMESTAMP DEFAULT current_timestamp); +CREATE TABLE t_data (c_int INT PRIMARY KEY, c_tiny TINYINT, c_smallint SMALLINT, c_bigint BIGINT, c_numeric NUMERIC, + c_var VARCHAR, c_text TEXT, c_bytea BYTEA, c_date DATE, c_timestamp TIMESTAMP, c_time TIME, + c_intarray INT[3], c_com atype); +CREATE TABLE t_trigger (key INT PRIMARY KEY, color TEXT); + +-- unlogged table +CREATE SCHEMA upsert_test_unlog; +SET CURRENT_SCHEMA TO upsert_test_unlog; +CREATE TYPE atype AS(a int, b int); +CREATE TYPE btype AS(a int, b atype, c varchar[3]); +CREATE UNLOGGED TABLE t_hash_unlog_0 (c1 INT, c2 INT, c3 VARCHAR, c4 INT[3], c5 INT[5], c6 atype, c7 btype); +CREATE UNLOGGED TABLE t_hash_unlog_1 (c1 INT, c2 INT PRIMARY KEY, c3 VARCHAR, c4 INT[3], c5 INT[5], c6 atype, c7 btype); +CREATE UNLOGGED TABLE t_rep_unlog_0 (c1 INT, c2 INT, c3 VARCHAR, c4 INT[3], c5 INT[5], c6 atype, c7 btype) ; +CREATE UNLOGGED TABLE t_rep_unlog_1 (c1 INT, c2 INT PRIMARY KEY, c3 VARCHAR, c4 INT[3], c5 INT[5], c6 atype, c7 btype); + +-- temp table +-- temp table can not be created here, we create it in upsert_tmp_test. + + +-- restriction test +CREATE SCHEMA upsert_test_etc; +SET CURRENT_SCHEMA TO upsert_test_etc; +create table up_neg_01 (c1 int, c2 int, c3 int) with (ORIENTATION = COLUMN); +create table up_neg_02 (c1 int, c2 int, c3 int) with (ORIENTATION = COLUMN); +create table up_neg_03 (c1 int, c2 int UNIQUE DEFERRABLE, c3 int); +create table up_neg_04 (c1 int, c2 int UNIQUE, c3 int); + +create table up_neg_05(c1 int, c2 int, c3 int, c4 int unique, c5 int primary key, unique(c2,c3)); + +create table up_neg_0(c1 int, c2 int, c3 int, c4 int unique, c5 int primary key, unique(c2,c3)) ; + +create table up_neg_06(c1 int, c2 int, c3 int unique) ; +create table up_neg_07(c1 int, c2 int, c3 int unique) ; +create table up_neg_08(c1 int, c2 int, c3 int unique) ; +create table up_neg_09(c1 int, c2 int, c3 int, unique(c2,c3)) ; +create table up_neg_10(c1 int, c2 int, c3 int, unique(c2,c3)) ; +create view up_view as select *from up_neg_01; +create materialized view mat_view as select *from up_neg_01; +create table pkt (a int primary key, b int, c int); +create table fkt (a int primary key, b int references pkt, c int); + + +-- procedure test +CREATE SCHEMA upsert_test_procedure; + + +-- explain test +CREATE SCHEMA upsert_test_explain; +SET CURRENT_SCHEMA TO upsert_test_explain; +create table up_expl_hash(c1 int, c2 int, c3 int unique) ; +create table up_expl_repl(c1 int, c2 int, c3 int unique) ; +create table up_expl_part(c1 int, c2 int, c3 int unique) ; +create unlogged table up_expl_unlog(c1 int, c2 int, c3 int unique) ; +create table up_expl_node(c1 int, c2 int, c3 int unique) ; +create table up_expl_repl(c1 int, c2 int, c3 int unique) ; +create table up_expl_repl2(c1 int, c2 int, c3 int, c4 int unique, c5 int primary key, unique(c2,c3)); +-- create temp table up_expl_temp(c1 int, c2 int, c3 int unique); + diff --git a/src/test/regress/sql/upsert_restriction.sql b/src/test/regress/sql/upsert_restriction.sql new file mode 100644 index 000000000..65e714d41 --- /dev/null +++ b/src/test/regress/sql/upsert_restriction.sql @@ -0,0 +1,170 @@ +/* + * upsert negative test cases + * + table cases + col table + ORC table + VIEW + MAT view + index + DEFERABLE index + insert stmt + returning + with + with recur + update stmt + VALUES with expr + update distribute key + update primary key + update unique key + update partition key + where clause + with clause + sub query + */ +-------------------------------------------------------------------------------------------- +\c upsert +SET CURRENT_SCHEMA TO upsert_test_etc; +-- enable_upsert_to_merge must be off, or upsert will be translated to merge. +SET enable_upsert_to_merge TO OFF; +SHOW enable_upsert_to_merge; + +insert into up_neg_04 select a,a,a from generate_series(1,20) as a; +insert into up_neg_05 select a,a,a,a,a from generate_series(1,20) as a; +insert into up_neg_06 select a,a,a from generate_series(1,20) as a; +insert into up_neg_07 select a,a,a from generate_series(1,20) as a; +insert into up_neg_08 select a,a,a from generate_series(1,20) as a; +insert into up_neg_09 select a,a,a from generate_series(1,20) as a; +insert into up_neg_10 select a,a,a from generate_series(1,20) as a; + +-- table cases +---- col table +insert into up_neg_01 values(1,2,3) on duplicate key update c3 = 1; + +---- ORC table +insert into up_neg_02 values(1,2,3) on duplicate key update c3 = 1; + +---- VIEW +insert into up_view values(1) on duplicate key update c3 = 1; + +-- matview +refresh materialized view mat_view; +insert into mat_view values(1) on duplicate key update c3 = 1; + +-- index +---- DEFERABLE index +insert into up_neg_03 values(1,2,3) on duplicate key update c1 = 1; +insert into up_neg_03 values(1) on duplicate key update c1 = 1; + +-- insert stmt +----returning +insert into up_neg_04 values(1,1,1) on duplicate key update c1 = 1 returning c1; +----with +with sub as (select *from up_neg_04) +insert into up_neg_04 select *from sub on duplicate key update c1 =1; + +----with recur +with RECURSIVE sub as (select *from up_neg_04) +insert into up_neg_04 select *from sub on duplicate key update c1 =1; + +with sub as (select *from up_neg_04) +insert into up_neg_04 select *from sub on duplicate key update c1 =1 returning c1; + +-- update stmt +---- VALUES with expr +insert into up_neg_05 values(1,1,1,1) on duplicate key update c3 = values(1+100); +insert into up_neg_05 values(1,1,1,1) on duplicate key update c3 = values(100); + +insert into up_neg_05 values(1,1,1,1,1) on duplicate key update c1 = 1; +---- update primary key +insert into up_neg_05 values(1,1,1,1,1) on duplicate key update c5 = 1; +---- update unique key +insert into up_neg_05 values(1,1,1,1,1) on duplicate key update c4 = 1; +insert into up_neg_05 values(1,1,1,1,1) on duplicate key update c2 = 1, c3=2; +insert into up_neg_05 values(1,1,1,1,1) on duplicate key update c2 = 1; +insert into up_neg_05 values(1,1,1,1,1) on duplicate key update c3 = 2; +insert into up_neg_05 values(1,1,1,1,1) on duplicate key update c1 =1, c2 = 1, c3=2, c4=1,c5=1; + +---- where clause +insert into up_neg_05 values(1,1,1,1,1) on duplicate key update c4 = 1 where c1=1; +---- from clause +insert into up_neg_05 values(1,1,1,1,1) on duplicate key update c4 = 1 from up_neg_04 where c1=1; +---- sub query +insert into up_neg_05 values(1,1,1,1,1) on duplicate key update (c2) = (select c2 from up_neg_05); + +---- update distribute key +insert into up_neg_06 values(101, 1, 1) on duplicate key update c3=101; +---- update partition key +insert into up_neg_07 values(101, 1, 300) on duplicate key update c3=101; +insert into up_neg_08 values(101, 1, 300) on duplicate key update c3=101; +insert into up_neg_09 values(101, 1, 1) on duplicate key update c3=101; +insert into up_neg_09 values(101, 1, 1) on duplicate key update c2=101; +insert into up_neg_09 values(101, 1, 1) on duplicate key update c2 =1,c3=101; + +--update unique key mul type plans +EXPLAIN (VERBOSE on, COSTS off) insert into up_neg_10 values(101, 1, 300) on duplicate key update c1=101; +insert into up_neg_10 values(101, 1, 300) on duplicate key update c3=101; + +--trigger +create table upsert_base +(c1 int primary key, + c2 varchar(100), --record the type + cold varchar(1024), + cnew varchar(1024) ); +create table upsert_tri +(c1 int, + c2 varchar(100), + c3 int, + unique(c1,c3) +); + +--S1 after trigger +CREATE OR REPLACE FUNCTION trig_after() RETURNS TRIGGER AS $emp_audit$ + BEGIN + IF (TG_OP = 'INSERT') THEN + insert into upsert_base(c2,cnew) values('after insert',new.c2); + RETURN NEW; + ELSIF (TG_OP = 'UPDATE') THEN + insert into upsert_base(c2,cold,cnew) values('after update',old.c2,new.c2); + RETURN NEW; + END IF; + RETURN NULL; + END; +$emp_audit$ LANGUAGE plpgsql; +CREATE TRIGGER tri_after +AFTER UPDATE OR INSERT ON upsert_tri + FOR EACH ROW EXECUTE PROCEDURE trig_after(); +--S2 before trigger +CREATE OR REPLACE FUNCTION trig_before() RETURNS TRIGGER AS $emp_audit$ + BEGIN + IF (TG_OP = 'INSERT') THEN + insert into upsert_base(c2,cnew) values('before insert',new.c2); + RETURN NEW; + ELSIF (TG_OP = 'UPDATE') THEN + insert into upsert_base(c2,cold,cnew) values('before update',old.c2,new.c2); + RETURN NEW; + END IF; + RETURN NULL; + END; +$emp_audit$ LANGUAGE plpgsql; +CREATE TRIGGER tri_before +BEFORE UPDATE OR INSERT ON upsert_tri + FOR EACH ROW EXECUTE PROCEDURE trig_before(); +---- upsert-insert +insert into upsert_tri(c1,c2,c3) values(1000,'abc',1) on duplicate key update nothing; +insert into upsert_tri(c1,c2,c3) values(2000,'abc',1) on duplicate key update c2='bcd'; +---- upsert-nothing +insert into upsert_tri(c1,c2,c3) values(1000,'abc',1) on duplicate key update nothing; +---- upsert-update +insert into upsert_tri(c1,c2,c3) values(2000,'abc',1) on duplicate key update c2='bcd'; +---- check results +SELECT * from upsert_tri; +SELECT * from upsert_base; + +-- foreign key +insert into pkt values(1,1,1),(2,2,2),(3,3,3),(4,4,4),(5,5,5); +insert into fkt values(1,1,1),(2,2,2),(3,3,3); + +insert into fkt values(1,1,1),(2,2,2),(3,3,3) on duplicate key update b=excluded.b+1, c=0; +insert into fkt values(1,1,1),(2,2,2),(3,3,3) on duplicate key update b=excluded.b+3, c=-1; + diff --git a/src/test/regress/sql/upsert_tmp_test.sql b/src/test/regress/sql/upsert_tmp_test.sql new file mode 100644 index 000000000..62795fc47 --- /dev/null +++ b/src/test/regress/sql/upsert_tmp_test.sql @@ -0,0 +1,99 @@ +\c upsert +CREATE SCHEMA upsert_test_tmp; +SET CURRENT_SCHEMA TO upsert_test_tmp; +CREATE TYPE atype AS(a int, b int); +CREATE TYPE btype AS(a int, b atype, c varchar[3]); +CREATE TEMP TABLE t_hash_tmp_0 (c1 INT, c2 INT, c3 VARCHAR, c4 INT[3], c5 INT[5], c6 atype, c7 btype); +CREATE TEMP TABLE t_hash_tmp_1 (c1 INT, c2 INT PRIMARY KEY, c3 VARCHAR, c4 INT[3], c5 INT[5], c6 atype, c7 btype); +CREATE TEMP TABLE t_rep_tmp_0 (c1 INT, c2 INT, c3 VARCHAR, c4 INT[3], c5 INT[5], c6 atype, c7 btype) ; +CREATE TEMP TABLE t_rep_tmp_1 (c1 INT, c2 INT PRIMARY KEY, c3 VARCHAR, c4 INT[3], c5 INT[5], c6 atype, c7 btype) ; + +-- enable_upsert_to_merge must be off, or upsert will be translated to merge. +SET enable_upsert_to_merge TO OFF; +SHOW enable_upsert_to_merge; + +-- hash table +-- tmp table without primary key +INSERT INTO t_hash_tmp_0 VALUES(1, 1, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (1, 2, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_hash_tmp_0 VALUES(2, 3, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (2, 4, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE c2 = 100; + +-- tmp table with primary key +-- multi insert +INSERT INTO t_hash_tmp_1 VALUES(1, 1, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (1, 2, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_hash_tmp_1 VALUES(2, 3, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (2, 4, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE c1 = EXCLUDED.c1, c3 = EXCLUDED.c3, c4 = EXCLUDED.c4, c5 = EXCLUDED.c5, c6 = EXCLUDED.c6, c7 = EXCLUDED.c7; +SELECT * FROM t_hash_tmp_1 ORDER BY c2; + +INSERT INTO t_hash_tmp_1 VALUES(10, 1, 'C30', '{10,20,30}', '{100,200,300,400,500}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (11, 2, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_hash_tmp_1 VALUES(20, 3, 'C30', '{10,20,30}', '{10,20,30,40,50}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (21, 4, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE c1 = EXCLUDED.c1, c3 = EXCLUDED.c3, c4 = EXCLUDED.c4, c5 = EXCLUDED.c5, c6 = EXCLUDED.c6, c7 = EXCLUDED.c7; +SELECT * FROM t_hash_tmp_1 ORDER BY c2; + +-- insert and update same tuple, and update twice for another same tuple +INSERT INTO t_hash_tmp_1 VALUES(0, 5, 'C30', '{10,20,30}', '{10,20,30,40,50}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (1, 5, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')), + (10, 1, 'C30', '{10,20,30}', '{100,200,300,400,500}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (11, 1, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE NOTHING; +SELECT * FROM t_hash_tmp_1 ORDER BY c2; + +INSERT INTO t_hash_tmp_1 VALUES(0, 5, 'C30', '{10,20,30}', '{10,20,30,40,50}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (1, 5, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')), + (10, 1, 'C30', '{10,20,30}', '{100,200,300,400,500}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (11, 1, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE c1 = EXCLUDED.c1, c3 = EXCLUDED.c3, c4 = EXCLUDED.c4, c5 = EXCLUDED.c5, c6 = EXCLUDED.c6, c7 = EXCLUDED.c7; +SELECT * FROM t_hash_tmp_1 ORDER BY c2; + +-- replication table +-- tmp table without primary key +INSERT INTO t_rep_tmp_0 VALUES(1, 1, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (1, 2, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_rep_tmp_0 VALUES(2, 3, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (2, 4, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE c2 = 100; + +-- tmp table with primary key +-- multi insert +INSERT INTO t_rep_tmp_1 VALUES(1, 1, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (1, 2, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_rep_tmp_1 VALUES(2, 3, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (2, 4, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE c1 = EXCLUDED.c1, c3 = EXCLUDED.c3, c4 = EXCLUDED.c4, c5 = EXCLUDED.c5, c6 = EXCLUDED.c6, c7 = EXCLUDED.c7; +SELECT * FROM t_rep_tmp_1 ORDER BY c2; + +INSERT INTO t_rep_tmp_1 VALUES(10, 1, 'C30', '{10,20,30}', '{100,200,300,400,500}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (11, 2, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_rep_tmp_1 VALUES(20, 3, 'C30', '{10,20,30}', '{10,20,30,40,50}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (21, 4, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE c1 = EXCLUDED.c1, c3 = EXCLUDED.c3, c4 = EXCLUDED.c4, c5 = EXCLUDED.c5, c6 = EXCLUDED.c6, c7 = EXCLUDED.c7; +SELECT * FROM t_rep_tmp_1 ORDER BY c2; + +-- insert and update same tuple, and update twice for another same tuple +INSERT INTO t_rep_tmp_1 VALUES(0, 5, 'C30', '{10,20,30}', '{10,20,30,40,50}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (1, 5, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')), + (10, 1, 'C30', '{10,20,30}', '{100,200,300,400,500}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (11, 1, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE NOTHING; +SELECT * FROM t_rep_tmp_1 ORDER BY c2; + +INSERT INTO t_rep_tmp_1 VALUES(0, 5, 'C30', '{10,20,30}', '{10,20,30,40,50}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (1, 5, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')), + (10, 1, 'C30', '{10,20,30}', '{100,200,300,400,500}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (11, 1, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE c1 = EXCLUDED.c1, c3 = EXCLUDED.c3, c4 = EXCLUDED.c4, c5 = EXCLUDED.c5, c6 = EXCLUDED.c6, c7 = EXCLUDED.c7; +SELECT * FROM t_rep_tmp_1 ORDER BY c2; + +DROP SCHEMA upsert_test_tmp CASCADE; diff --git a/src/test/regress/sql/upsert_trigger_test.sql b/src/test/regress/sql/upsert_trigger_test.sql new file mode 100644 index 000000000..b731ad50e --- /dev/null +++ b/src/test/regress/sql/upsert_trigger_test.sql @@ -0,0 +1,63 @@ +\c upsert +SET CURRENT_SCHEMA TO upsert_test; +-- enable_upsert_to_merge must be off, or upsert will be translated to merge. +SET enable_upsert_to_merge TO OFF; +SHOW enable_upsert_to_merge; + +CREATE FUNCTION upsert_before_func() + RETURNS TRIGGER language plpgsql AS +$$ +BEGIN + IF (TG_OP = 'UPDATE') THEN + RAISE warning 'before update (old): %', old.*::TEXT; + RAISE warning 'before update (new): %', new.*::TEXT; + elsIF (TG_OP = 'INSERT') THEN + RAISE warning 'before insert (new): %', new.*::TEXT; + IF NEW.key % 2 = 0 THEN + NEW.color := NEW.color || ' trig modified'; + RAISE warning 'before insert (new, modified): %', new.*::TEXT; + END IF; + END IF; + RETURN new; +END; +$$; +CREATE TRIGGER upsert_before_trig BEFORE INSERT OR UPDATE ON t_trigger + FOR EACH ROW EXECUTE procedure upsert_before_func(); + +CREATE FUNCTION upsert_after_func() + RETURNS TRIGGER language plpgsql AS +$$ +BEGIN + IF (TG_OP = 'UPDATE') THEN + RAISE warning 'after update (old): %', old.*::TEXT; + RAISE warning 'after update (new): %', new.*::TEXT; + elsIF (TG_OP = 'INSERT') THEN + RAISE warning 'after insert (new): %', new.*::TEXT; + END IF; + RETURN null; +END; +$$; +CREATE TRIGGER upsert_after_trig AFTER INSERT OR UPDATE ON t_trigger + FOR EACH ROW EXECUTE procedure upsert_after_func(); + +INSERT INTO t_trigger values(1, 'black') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; +INSERT INTO t_trigger values(2, 'red') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; +INSERT INTO t_trigger values(3, 'orange') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; +INSERT INTO t_trigger values(4, 'green') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; +INSERT INTO t_trigger values(5, 'purple') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; +INSERT INTO t_trigger values(6, 'white') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; +INSERT INTO t_trigger values(7, 'pink') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; +INSERT INTO t_trigger values(8, 'yellow') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; + +SELECT * FROM t_trigger ORDER BY key; + +INSERT INTO t_trigger values(2, 'black') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; +INSERT INTO t_trigger values(3, 'red') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; +INSERT INTO t_trigger values(4, 'orange') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; +INSERT INTO t_trigger values(5, 'green') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; +INSERT INTO t_trigger values(6, 'purple') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; +INSERT INTO t_trigger values(7, 'white') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; +INSERT INTO t_trigger values(8, 'pink') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; +INSERT INTO t_trigger values(9, 'yellow') ON DUPLICATE KEY UPDATE color = 'updated ' || t_trigger.color; + +SELECT * FROM t_trigger ORDER BY key; diff --git a/src/test/regress/sql/upsert_unlog_test.sql b/src/test/regress/sql/upsert_unlog_test.sql new file mode 100644 index 000000000..22036601f --- /dev/null +++ b/src/test/regress/sql/upsert_unlog_test.sql @@ -0,0 +1,89 @@ +\c upsert +SET CURRENT_SCHEMA TO upsert_test_unlog; +-- enable_upsert_to_merge must be off, or upsert will be translated to merge. +SET enable_upsert_to_merge TO OFF; +SHOW enable_upsert_to_merge; + +-- hash table +-- unlogged table without primary key +INSERT INTO t_hash_unlog_0 VALUES(1, 1, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (1, 2, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_hash_unlog_0 VALUES(2, 3, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (2, 4, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE c2 = 100; + +-- unlogged table with primary key +-- multi insert +INSERT INTO t_hash_unlog_1 VALUES(1, 1, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (1, 2, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_hash_unlog_1 VALUES(2, 3, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (2, 4, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE c1 = EXCLUDED.c1, c3 = EXCLUDED.c3, c4 = EXCLUDED.c4, c5 = EXCLUDED.c5, c6 = EXCLUDED.c6, c7 = EXCLUDED.c7; +SELECT * FROM t_hash_unlog_1 ORDER BY c2; + +INSERT INTO t_hash_unlog_1 VALUES(10, 1, 'C30', '{10,20,30}', '{100,200,300,400,500}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (11, 2, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_hash_unlog_1 VALUES(20, 3, 'C30', '{10,20,30}', '{10,20,30,40,50}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (21, 4, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE c1 = EXCLUDED.c1, c3 = EXCLUDED.c3, c4 = EXCLUDED.c4, c5 = EXCLUDED.c5, c6 = EXCLUDED.c6, c7 = EXCLUDED.c7; +SELECT * FROM t_hash_unlog_1 ORDER BY c2; + +-- insert and update same tuple, and update twice for another same tuple +INSERT INTO t_hash_unlog_1 VALUES(0, 5, 'C30', '{10,20,30}', '{10,20,30,40,50}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (1, 5, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')), + (10, 1, 'C30', '{10,20,30}', '{100,200,300,400,500}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (11, 1, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE NOTHING; +SELECT * FROM t_hash_unlog_1 ORDER BY c2; + +INSERT INTO t_hash_unlog_1 VALUES(0, 5, 'C30', '{10,20,30}', '{10,20,30,40,50}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (1, 5, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')), + (10, 1, 'C30', '{10,20,30}', '{100,200,300,400,500}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (11, 1, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE c1 = EXCLUDED.c1, c3 = EXCLUDED.c3, c4 = EXCLUDED.c4, c5 = EXCLUDED.c5, c6 = EXCLUDED.c6, c7 = EXCLUDED.c7; +SELECT * FROM t_hash_unlog_1 ORDER BY c2; + +-- replication table +-- unlogged table without primary key +INSERT INTO t_rep_unlog_0 VALUES(1, 1, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (1, 2, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_rep_unlog_0 VALUES(2, 3, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (2, 4, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE c2 = 100; + +-- unlogged table with primary key +-- multi insert +INSERT INTO t_rep_unlog_1 VALUES(1, 1, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (1, 2, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_rep_unlog_1 VALUES(2, 3, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')), + (2, 4, 'C3', '{1,2,3}', '{10,20,30,40,50}', ROW(10,20), ROW(100, ROW(10,20), '{100,200}')) + ON DUPLICATE KEY UPDATE c1 = EXCLUDED.c1, c3 = EXCLUDED.c3, c4 = EXCLUDED.c4, c5 = EXCLUDED.c5, c6 = EXCLUDED.c6, c7 = EXCLUDED.c7; +SELECT * FROM t_rep_unlog_1 ORDER BY c2; + +INSERT INTO t_rep_unlog_1 VALUES(10, 1, 'C30', '{10,20,30}', '{100,200,300,400,500}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (11, 2, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE NOTHING; +INSERT INTO t_rep_unlog_1 VALUES(20, 3, 'C30', '{10,20,30}', '{10,20,30,40,50}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (21, 4, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE c1 = EXCLUDED.c1, c3 = EXCLUDED.c3, c4 = EXCLUDED.c4, c5 = EXCLUDED.c5, c6 = EXCLUDED.c6, c7 = EXCLUDED.c7; +SELECT * FROM t_rep_unlog_1 ORDER BY c2; + +-- insert and update same tuple, and update twice for another same tuple +INSERT INTO t_rep_unlog_1 VALUES(0, 5, 'C30', '{10,20,30}', '{10,20,30,40,50}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (1, 5, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')), + (10, 1, 'C30', '{10,20,30}', '{100,200,300,400,500}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (11, 1, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE NOTHING; +SELECT * FROM t_rep_unlog_1 ORDER BY c2; + +INSERT INTO t_rep_unlog_1 VALUES(0, 5, 'C30', '{10,20,30}', '{10,20,30,40,50}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (1, 5, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')), + (10, 1, 'C30', '{10,20,30}', '{100,200,300,400,500}', ROW(100,200), ROW(1000, ROW(100,200), '{1000,2000}')), + (11, 1, 'C31', '{11,21,31}', '{101,201,301,401,501}', ROW(101,201), ROW(1001, ROW(101,201), '{1001,2001}')) + ON DUPLICATE KEY UPDATE c1 = EXCLUDED.c1, c3 = EXCLUDED.c3, c4 = EXCLUDED.c4, c5 = EXCLUDED.c5, c6 = EXCLUDED.c6, c7 = EXCLUDED.c7; +SELECT * FROM t_rep_unlog_1 ORDER BY c2;