diff --git a/src/bin/pg_dump/pg_dump.cpp b/src/bin/pg_dump/pg_dump.cpp index bcf4e4493a97c135ff952ee1f072627148a81090..2d0353fa67643db2fa5ae761560996e9018e6b06 100644 --- a/src/bin/pg_dump/pg_dump.cpp +++ b/src/bin/pg_dump/pg_dump.cpp @@ -9221,6 +9221,7 @@ void getTableAttrs(Archive* fout, TableInfo* tblinfo, int numTables) int numDefaults; ArchiveHandle* AH = (ArchiveHandle*)fout; bool hasGenColFeature = is_column_exists(AH->connection, AttrDefaultRelationId, "adgencol"); + bool hasOnUpdateFeature = is_column_exists(AH->connection, AttrDefaultRelationId, "adbin_on_update"); if (g_verbose) write_msg(NULL, @@ -9228,7 +9229,7 @@ void getTableAttrs(Archive* fout, TableInfo* tblinfo, int numTables) fmtQualifiedId(fout, tbinfo->dobj.nmspace->dobj.name, tbinfo->dobj.name)); resetPQExpBuffer(q); - if (hasGenColFeature) { + if (hasGenColFeature && !hasOnUpdateFeature) { appendPQExpBuffer(q, "SELECT tableoid, oid, adnum, " "pg_catalog.pg_get_expr(adbin, adrelid) AS adsrc " @@ -9236,6 +9237,15 @@ void getTableAttrs(Archive* fout, TableInfo* tblinfo, int numTables) "FROM pg_catalog.pg_attrdef " "WHERE adrelid = '%u'::pg_catalog.oid", tbinfo->dobj.catId.oid); + } else if (hasGenColFeature && hasOnUpdateFeature) { + appendPQExpBuffer(q, + "SELECT tableoid, oid, adnum, " + "pg_catalog.pg_get_expr(adbin, adrelid) AS adsrc " + ", adgencol AS generatedCol, " + "pg_catalog.pg_get_expr(adbin_on_update, adrelid) AS adsrc_on_update " + "FROM pg_catalog.pg_attrdef " + "WHERE adrelid = '%u'::pg_catalog.oid", + tbinfo->dobj.catId.oid); } else if (fout->remoteVersion >= 70300) { appendPQExpBuffer(q, "SELECT tableoid, oid, adnum, " @@ -9305,6 +9315,9 @@ void getTableAttrs(Archive* fout, TableInfo* tblinfo, int numTables) attrdefs[j].adnum = adnum; attrdefs[j].generatedCol = *(PQgetvalue(res, j, 4)); attrdefs[j].adef_expr = gs_strdup(PQgetvalue(res, j, 3)); + if (hasOnUpdateFeature) { + attrdefs[j].adupd_expr = gs_strdup(PQgetvalue(res, j, 5)); + } attrdefs[j].dobj.name = gs_strdup(tbinfo->dobj.name); attrdefs[j].dobj.nmspace = tbinfo->dobj.nmspace; @@ -19008,6 +19021,12 @@ static void dumpTableSchema(Archive* fout, TableInfo* tbinfo) if (has_default) { char *default_value = tbinfo->attrdefs[j]->adef_expr; + char *onUpdate_value = NULL; + ArchiveHandle* AH = (ArchiveHandle*)fout; + bool hasOnUpdateFeature = is_column_exists(AH->connection, AttrDefaultRelationId, "adbin_on_update"); + if (hasOnUpdateFeature) { + onUpdate_value = tbinfo->attrdefs[j]->adupd_expr; + } #ifdef HAVE_CE if (is_clientlogic_datatype(tbinfo->typid[j])) { size_t plainTextSize = 0; @@ -19025,6 +19044,11 @@ static void dumpTableSchema(Archive* fout, TableInfo* tbinfo) default_value); else appendPQExpBuffer(q, " DEFAULT %s", default_value); + if (hasOnUpdateFeature) { + if (pg_strcasecmp(onUpdate_value, "") != 0) { + appendPQExpBuffer(q, " ON UPDATE %s", onUpdate_value); + } + } #ifdef HAVE_CE if (is_clientlogic_datatype(tbinfo->typid[j])) { libpq_free(default_value); diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index a705192ccb9655d7896d7b1d8da2e9801243917d..615df8de30f8fcd469cb7b5936e4cc0d0f76d497 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -289,6 +289,7 @@ typedef struct _attrDefInfo { char* adef_expr; /* decompiled DEFAULT expression */ bool separate; /* TRUE if must dump as separate item */ char generatedCol; + char* adupd_expr; /* on update expression of on update timestamp syntax on Mysql dbcompatibility */ } AttrDefInfo; typedef struct _tableDataInfo { diff --git a/src/bin/psql/describe.cpp b/src/bin/psql/describe.cpp index 5b0ae15f2f726b0c23faf05a71021bb49ccee6c6..1fd925e6ee9e4eed4819766afeb5a08f9567b5e3 100644 --- a/src/bin/psql/describe.cpp +++ b/src/bin/psql/describe.cpp @@ -1301,6 +1301,7 @@ static bool describeOneTableDetails(const char* schemaname, const char* relation bool retval = false; bool hasreplident = is_column_exists(pset.db, RelationRelationId, "relreplident"); bool hasGenColFeature = is_column_exists(pset.db, AttrDefaultRelationId, "adgencol"); + bool hasOnUpdateFeature = is_column_exists(pset.db, AttrDefaultRelationId, "adbin_on_update"); bool has_rlspolicy = false; const char* has_rlspolicy_sql = @@ -1503,12 +1504,24 @@ static bool describeOneTableDetails(const char* schemaname, const char* relation #endif printfPQExpBuffer(&buf, "SELECT a.attname,"); - appendPQExpBuffer(&buf, - "\n pg_catalog.format_type(a.atttypid, a.atttypmod)," - "\n (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid) for %d)" - "\n FROM pg_catalog.pg_attrdef d" - "\n WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef)," - "\n a.attnotnull, a.attnum,", exprForNum); + if (!hasOnUpdateFeature) { + appendPQExpBuffer(&buf, + "\n pg_catalog.format_type(a.atttypid, a.atttypmod)," + "\n (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid) for %d)" + "\n FROM pg_catalog.pg_attrdef d" + "\n WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef)," + "\n a.attnotnull, a.attnum,", exprForNum); + } else { + appendPQExpBuffer(&buf, + "\n pg_catalog.format_type(a.atttypid, a.atttypmod)," + "\n (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid) for %d)" + "\n FROM pg_catalog.pg_attrdef d" + "\n WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef)," + "\n (SELECT substring(pg_catalog.pg_get_expr(d.adbin_on_update, d.adrelid) for %d)" + "\n FROM pg_catalog.pg_attrdef d" + "\n WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef)," + "\n a.attnotnull, a.attnum,", exprForNum, exprForNum); + } if (pset.sversion >= 90100) { appendPQExpBuffer(&buf, "\n (SELECT c.collname FROM pg_catalog.pg_collation c, pg_catalog.pg_type t\n" @@ -1714,17 +1727,35 @@ static bool describeOneTableDetails(const char* schemaname, const char* relation if (show_modifiers) { resetPQExpBuffer(&tmpbuf); + + if (hasOnUpdateFeature) { + if (!PQgetisnull(res, i, 6)) { + if (tmpbuf.len > 0) + appendPQExpBufferStr(&tmpbuf, " "); + appendPQExpBuffer(&tmpbuf, _("collate %s"), PQgetvalue(res, i, 6)); + } + } else { if (!PQgetisnull(res, i, 5)) { if (tmpbuf.len > 0) appendPQExpBufferStr(&tmpbuf, " "); appendPQExpBuffer(&tmpbuf, _("collate %s"), PQgetvalue(res, i, 5)); } + } + + if (hasOnUpdateFeature) { + if (strcmp(PQgetvalue(res, i, 4), "t") == 0) { + if (tmpbuf.len > 0) + appendPQExpBufferStr(&tmpbuf, " "); + appendPQExpBufferStr(&tmpbuf, _("not null")); + } + } else { if (strcmp(PQgetvalue(res, i, 3), "t") == 0) { if (tmpbuf.len > 0) appendPQExpBufferStr(&tmpbuf, " "); appendPQExpBufferStr(&tmpbuf, _("not null")); } + } /* handle "default" here */ /* (note: above we cut off the 'default' string at 128) */ @@ -1754,6 +1785,18 @@ static bool describeOneTableDetails(const char* schemaname, const char* relation appendPQExpBuffer(&tmpbuf, _("default %s"), default_value); } } + + + if (hasOnUpdateFeature) { + char *on_update_value = PQgetvalue(res, i, 3); + if (strlen(on_update_value) > 0) { + if (tmpbuf.len > 0) { + appendPQExpBufferStr(&tmpbuf, " "); + } + /* translator: on_update values of column definitions */ + appendPQExpBuffer(&tmpbuf, _("on update %s"), on_update_value); + } + } #ifdef HAVE_CE if (hasFullEncryptFeature && strlen(PQgetvalue(res, i, PQfnumber(res, "clientlogic_original_type"))) > 0) { @@ -1774,20 +1817,33 @@ static bool describeOneTableDetails(const char* schemaname, const char* relation printTableAddCell(&cont, seq_values[i], false, false); /* Expression for index column */ - if (tableinfo.relkind == 'i' || tableinfo.relkind == 'I') - printTableAddCell(&cont, PQgetvalue(res, i, 6), false, false); + if (tableinfo.relkind == 'i' || tableinfo.relkind == 'I') { + if (hasOnUpdateFeature) { + printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false); + } else { + printTableAddCell(&cont, PQgetvalue(res, i, 6), false, false); + } + } /* FDW options for foreign table column, only for 9.2 or later */ - if ((tableinfo.relkind == 'f' || tableinfo.relkind == 'e') && pset.sversion >= 90200) - printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false); + if ((tableinfo.relkind == 'f' || tableinfo.relkind == 'e') && pset.sversion >= 90200) { + if (hasOnUpdateFeature) { + printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false); + } else { + printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false); + } + } /* Storage and Description */ if (verbose) { #ifdef HAVE_CE - const int firstvcol = (hasFullEncryptFeature ? 10 : 8); + int firstvcol = (hasFullEncryptFeature ? 10 : 8); #else - const int firstvcol = 8; + int firstvcol = 8; #endif + if (hasOnUpdateFeature) { + firstvcol++; + } char* storage = PQgetvalue(res, i, firstvcol); /* these strings are literal in our syntax, so not translated. */ diff --git a/src/common/backend/catalog/heap.cpp b/src/common/backend/catalog/heap.cpp index 97dc81fe72472f202a3c8acd795adf17c87fc1a0..58bed713a6e799fc38ad3504abf74e65cc4ecfbe 100644 --- a/src/common/backend/catalog/heap.cpp +++ b/src/common/backend/catalog/heap.cpp @@ -3626,10 +3626,12 @@ void heap_drop_with_catalog(Oid relid) /* * Store a default expression for column attnum of relation rel. */ -void StoreAttrDefault(Relation rel, AttrNumber attnum, Node* expr, char generatedCol) +void StoreAttrDefault(Relation rel, AttrNumber attnum, Node* expr, char generatedCol, Node* update_expr) { char* adbin = NULL; + char* adbin_on_update = NULL; char* adsrc = NULL; + char* update_src = NULL; Relation adrel; HeapTuple tuple; Datum values[Natts_pg_attrdef]; @@ -3638,11 +3640,39 @@ void StoreAttrDefault(Relation rel, AttrNumber attnum, Node* expr, char generate Form_pg_attribute attStruct; Oid attrdefOid; ObjectAddress colobject, defobject; + ScanKeyData skey[2]; + HeapTuple htup = NULL; + bool isnull = false; + bool on_update_exist = false; + bool adin_on_update_exist = false; /* * Flatten expression to string form for storage. */ - adbin = nodeToString(expr); + if (update_expr != NULL) { + adbin_on_update = nodeToString(update_expr); + update_src = deparse_expression( + update_expr, deparse_context_for(RelationGetRelationName(rel), RelationGetRelid(rel)), false, false); + } else { + Relation adrel_temp = heap_open(AttrDefaultRelationId, RowExclusiveLock); + ScanKeyInit(&skey[0], Anum_pg_attrdef_adrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(rel))); + ScanKeyInit(&skey[1], Anum_pg_attrdef_adnum, BTEqualStrategyNumber, F_INT2EQ, Int16GetDatum(attnum)); + SysScanDesc adscan = systable_beginscan(adrel_temp, AttrDefaultIndexId, true, NULL, 2, skey); + if (HeapTupleIsValid(htup = systable_getnext(adscan))) { + Datum val = heap_getattr(htup, Anum_pg_attrdef_adbin_on_update, adrel_temp->rd_att, &isnull); + if (val && pg_strcasecmp(TextDatumGetCString(val), "") != 0) { + adbin_on_update = TextDatumGetCString(val); + on_update_exist = true; + } + val = heap_getattr(htup, Anum_pg_attrdef_adsrc_on_update, adrel_temp->rd_att, &isnull); + if (val && pg_strcasecmp(TextDatumGetCString(val), "") != 0) { + update_src = TextDatumGetCString(val); + adin_on_update_exist = true; + } + } + systable_endscan(adscan); + heap_close(adrel_temp, RowExclusiveLock); + } /* * Also deparse it to form the mostly-obsolete adsrc field. @@ -3655,8 +3685,16 @@ void StoreAttrDefault(Relation rel, AttrNumber attnum, Node* expr, char generate */ values[Anum_pg_attrdef_adrelid - 1] = RelationGetRelid(rel); values[Anum_pg_attrdef_adnum - 1] = attnum; - values[Anum_pg_attrdef_adbin - 1] = CStringGetTextDatum(adbin); - values[Anum_pg_attrdef_adsrc - 1] = CStringGetTextDatum(adsrc); + if (expr != NULL) { + adbin = nodeToString(expr); + } + values[Anum_pg_attrdef_adbin - 1] = adbin ? CStringGetTextDatum(adbin) : CStringGetTextDatum(""); + values[Anum_pg_attrdef_adsrc - 1] = adsrc ? CStringGetTextDatum(adsrc) : CStringGetTextDatum(""); + values[Anum_pg_attrdef_adbin_on_update - 1] = (update_expr != NULL || (on_update_exist && adin_on_update_exist)) ? + CStringGetTextDatum(adbin_on_update) : CStringGetTextDatum(""); + values[Anum_pg_attrdef_adsrc_on_update - 1] = (update_expr != NULL || (on_update_exist && adin_on_update_exist)) ? + CStringGetTextDatum(update_src) : CStringGetTextDatum(""); + if (t_thrd.proc->workingVersionNum >= GENERATED_COL_VERSION_NUM) { values[Anum_pg_attrdef_adgencol - 1] = CharGetDatum(generatedCol); } @@ -3664,6 +3702,9 @@ void StoreAttrDefault(Relation rel, AttrNumber attnum, Node* expr, char generate adrel = heap_open(AttrDefaultRelationId, RowExclusiveLock); tuple = heap_form_tuple(adrel->rd_att, values, u_sess->catalog_cxt.nulls); + if (on_update_exist) { + RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, false, true); + } attrdefOid = simple_heap_insert(adrel, tuple); CatalogUpdateIndexes(adrel, tuple); @@ -3677,9 +3718,19 @@ void StoreAttrDefault(Relation rel, AttrNumber attnum, Node* expr, char generate /* now can free some of the stuff allocated above */ pfree(DatumGetPointer(values[Anum_pg_attrdef_adbin - 1])); pfree(DatumGetPointer(values[Anum_pg_attrdef_adsrc - 1])); + pfree(DatumGetPointer(values[Anum_pg_attrdef_adbin_on_update - 1])); + pfree(DatumGetPointer(values[Anum_pg_attrdef_adsrc_on_update - 1])); heap_freetuple(tuple); - pfree(adbin); pfree(adsrc); + if (adbin) { + pfree(adbin); + } + if (adbin_on_update) { + pfree(adbin_on_update); + } + if (update_src) { + pfree(update_src); + } /* * Update the pg_attribute entry for the column to show that a default @@ -3855,10 +3906,10 @@ static void StoreConstraints(Relation rel, List* cooked_constraints) switch (con->contype) { case CONSTR_DEFAULT: - StoreAttrDefault(rel, con->attnum, con->expr, 0); + StoreAttrDefault(rel, con->attnum, con->expr, 0, con->update_expr); break; case CONSTR_GENERATED: - StoreAttrDefault(rel, con->attnum, con->expr, ATTRIBUTE_GENERATED_STORED); + StoreAttrDefault(rel, con->attnum, con->expr, ATTRIBUTE_GENERATED_STORED, NULL); break; case CONSTR_CHECK: StoreRelCheck( @@ -3915,6 +3966,7 @@ List* AddRelationNewConstraints( ListCell* cell = NULL; Node* expr = NULL; CookedConstraint* cooked = NULL; + Node* update_expr = NULL; /* * Get info about existing constraints. @@ -3943,8 +3995,14 @@ List* AddRelationNewConstraints( RawColumnDefault* colDef = (RawColumnDefault*)lfirst(cell); Form_pg_attribute atp = rel->rd_att->attrs[colDef->attnum - 1]; - expr = cookDefault(pstate, colDef->raw_default, atp->atttypid, atp->atttypmod, NameStr(atp->attname), - colDef->generatedCol); + if (colDef->raw_default != NULL) { + expr = cookDefault(pstate, colDef->raw_default, atp->atttypid, atp->atttypmod, NameStr(atp->attname), + colDef->generatedCol); + } + if (colDef->update_expr != NULL) { + update_expr = cookDefault(pstate, colDef->update_expr, atp->atttypid, atp->atttypmod, NameStr(atp->attname), + colDef->generatedCol); + } /* * If the expression is just a NULL constant, we do not bother to make @@ -3958,10 +4016,10 @@ List* AddRelationNewConstraints( * critical because the column default needs to be retained to * override any default that the domain might have. */ - if (expr == NULL || (!colDef->generatedCol && IsA(expr, Const) && ((Const *)expr)->constisnull)) + if (expr != NULL && !colDef->generatedCol && IsA(expr, Const) && ((Const *)expr)->constisnull) continue; - StoreAttrDefault(rel, colDef->attnum, expr, colDef->generatedCol); + StoreAttrDefault(rel, colDef->attnum, expr, colDef->generatedCol, update_expr); cooked = (CookedConstraint*)palloc(sizeof(CookedConstraint)); cooked->contype = CONSTR_DEFAULT; @@ -3973,6 +4031,8 @@ List* AddRelationNewConstraints( cooked->inhcount = is_local ? 0 : 1; cooked->is_no_inherit = false; cookedConstraints = lappend(cookedConstraints, cooked); + expr = NULL; + update_expr = NULL; } pstate->p_rawdefaultlist = NIL; diff --git a/src/common/backend/nodes/copyfuncs.cpp b/src/common/backend/nodes/copyfuncs.cpp index dcf3e2b6a30343628e7adb6a8e996227ea90194a..77d609c549f6098fdeff1ddc098408c35f66e449 100644 --- a/src/common/backend/nodes/copyfuncs.cpp +++ b/src/common/backend/nodes/copyfuncs.cpp @@ -4103,7 +4103,7 @@ static ColumnDef* _copyColumnDef(const ColumnDef* from) COPY_NODE_FIELD(columnOptions); COPY_NODE_FIELD(clientLogicColumnRef); COPY_NODE_FIELD(position); - + COPY_NODE_FIELD(update_default); return newnode; } @@ -4149,6 +4149,8 @@ static Constraint* _copyConstraint(const Constraint* from) COPY_SCALAR_FIELD(initially_valid); COPY_NODE_FIELD(inforConstraint); COPY_NODE_FIELD(constraintOptions); + COPY_NODE_FIELD(update_expr); + return newnode; } diff --git a/src/common/backend/nodes/equalfuncs.cpp b/src/common/backend/nodes/equalfuncs.cpp index 1dbd799c131a465bd4468391c4e25685172e3e5a..75bf0904e9b95090bb7a21f7c912501f58488001 100644 --- a/src/common/backend/nodes/equalfuncs.cpp +++ b/src/common/backend/nodes/equalfuncs.cpp @@ -2571,7 +2571,7 @@ static bool _equalColumnDef(const ColumnDef* a, const ColumnDef* b) COMPARE_NODE_FIELD(constraints); COMPARE_NODE_FIELD(fdwoptions); COMPARE_NODE_FIELD(columnOptions); - + COMPARE_NODE_FIELD(update_default); return true; } @@ -2604,6 +2604,7 @@ static bool _equalConstraint(const Constraint* a, const Constraint* b) COMPARE_SCALAR_FIELD(skip_validation); COMPARE_SCALAR_FIELD(initially_valid); COMPARE_NODE_FIELD(constraintOptions); + COMPARE_NODE_FIELD(update_expr); return true; } diff --git a/src/common/backend/nodes/outfuncs.cpp b/src/common/backend/nodes/outfuncs.cpp index 14bde11603546b95e07cc0eb83c1c1dedab61a46..aa8ae02deae7037322cc105039e74ed09ac18578 100755 --- a/src/common/backend/nodes/outfuncs.cpp +++ b/src/common/backend/nodes/outfuncs.cpp @@ -3818,6 +3818,9 @@ static void _outColumnDef(StringInfo str, ColumnDef* node) if (node->generatedCol) WRITE_CHAR_FIELD(generatedCol); } + if (t_thrd.proc->workingVersionNum >= ON_UPDATE_TIMESTAMP_VERSION_NUM) { + WRITE_NODE_FIELD(update_default); + } } static void _outTypeName(StringInfo str, TypeName* node) @@ -4999,6 +5002,9 @@ static void _outConstraint(StringInfo str, Constraint* node) case CONSTR_DEFAULT: appendStringInfo(str, "DEFAULT"); WRITE_NODE_FIELD(raw_expr); + if (t_thrd.proc->workingVersionNum >= ON_UPDATE_TIMESTAMP_VERSION_NUM) { + WRITE_NODE_FIELD(update_expr); + } WRITE_STRING_FIELD(cooked_expr); break; diff --git a/src/common/backend/nodes/readfuncs.cpp b/src/common/backend/nodes/readfuncs.cpp index 9469c0fd6fee87e1b7e135433d92f4251aa50669..fece385c66ea6fb3cfc76c540985159d5515b260 100755 --- a/src/common/backend/nodes/readfuncs.cpp +++ b/src/common/backend/nodes/readfuncs.cpp @@ -5285,7 +5285,9 @@ static ColumnDef* _readColumnDef() IF_EXIST(generatedCol) { READ_CHAR_FIELD(generatedCol); } - + IF_EXIST(update_default) { + READ_NODE_FIELD(update_default); + } READ_DONE(); } @@ -5427,6 +5429,9 @@ static Constraint* _readConstraint() } else if (MATCH_TYPE("DEFAULT")) { local_node->contype = CONSTR_DEFAULT; READ_NODE_FIELD(raw_expr); + if (t_thrd.proc->workingVersionNum >= ON_UPDATE_TIMESTAMP_VERSION_NUM) { + READ_NODE_FIELD(update_expr); + } READ_STRING_FIELD(cooked_expr); } else if (MATCH_TYPE("CHECK")) { local_node->contype = CONSTR_CHECK; diff --git a/src/common/backend/parser/gram.y b/src/common/backend/parser/gram.y index 7be7ca9ff230a5012b19ffa1c23c58af6bb735fa..5f1f5d624771ba04cffdbda2a980858b50fbcb74 100644 --- a/src/common/backend/parser/gram.y +++ b/src/common/backend/parser/gram.y @@ -910,7 +910,7 @@ static int errstate; DROP_SUBPARTITION NOT_ENFORCED VALID_BEGIN - DECLARE_CURSOR + DECLARE_CURSOR ON_UPDATE_TIME START_WITH CONNECT_BY /* Precedence: lowest to highest */ @@ -2799,9 +2799,41 @@ modify_column_cmd: def->typname = $2; def->collClause = NULL; def->raw_default = NULL; + def->update_default = NULL; def->clientLogicColumnRef=NULL; $$ = (Node *)n; } + | ColId Typename ON_UPDATE_TIME UPDATE b_expr + { +#ifndef ENABLE_MULTIPLE_NODES + if (u_sess->attr.attr_sql.sql_compatibility == B_FORMAT) + { + AlterTableCmd *n = makeNode(AlterTableCmd); + ColumnDef *def = makeNode(ColumnDef); + Constraint *cons = makeNode(Constraint); + n->subtype = AT_AlterColumnType; + n->name = $1; + n->def = (Node *) def; + /* We only use these three fields of the ColumnDef node */ + def->typname = $2; + def->constraints = list_make1(cons); + cons->contype = CONSTR_DEFAULT; + cons->location = @3; + cons->update_expr = $5; + cons->cooked_expr = NULL; + $$ = (Node *)n; + } else { + const char* message = "on update syntax be supported dbcompatibility B."; + InsertErrorMessage(message, u_sess->plsql_cxt.plpgsql_yylloc); + ereport(errstate, + (errmodule(MOD_PARSER), + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("on update syntax is supported in dbcompatibility B."), + parser_errposition(@1))); + $$ = NULL; + } +#endif + } | ColId NOT NULL_P opt_enable { AlterTableCmd *n = makeNode(AlterTableCmd); @@ -4392,6 +4424,7 @@ alter_type_cmd: def->clientLogicColumnRef=NULL; def->collClause = (CollateClause *) $7; def->raw_default = NULL; + def->update_default = NULL; $$ = (Node *)n; } ; @@ -5955,6 +5988,7 @@ columnDef: ColId Typename KVType ColCmprsMode create_generic_options ColQualList n->storage = 0; n->cmprs_mode = $4; n->raw_default = NULL; + n->update_default = NULL; n->cooked_default = NULL; n->collOid = InvalidOid; n->fdwoptions = $5; @@ -6358,6 +6392,29 @@ ColConstraintElem: n->cooked_expr = NULL; $$ = (Node *)n; } + | ON_UPDATE_TIME UPDATE b_expr + { +#ifndef ENABLE_MULTIPLE_NODES + if (u_sess->attr.attr_sql.sql_compatibility == B_FORMAT) + { + Constraint *n = makeNode(Constraint); + n->contype = CONSTR_DEFAULT; + n->location = @1; + n->update_expr = $3; + n->cooked_expr = NULL; + $$ = (Node *)n; + } else { + const char* message = "on update syntax be supported dbcompatibility B."; + InsertErrorMessage(message, u_sess->plsql_cxt.plpgsql_yylloc); + ereport(errstate, + (errmodule(MOD_PARSER), + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("on update syntax is supported in dbcompatibility B."), + parser_errposition(@1))); + $$ = NULL; + } +#endif + } | GENERATED ALWAYS AS '(' a_expr ')' STORED { #ifdef ENABLE_MULTIPLE_NODES diff --git a/src/common/backend/parser/parse_utilcmd.cpp b/src/common/backend/parser/parse_utilcmd.cpp index f9753e14cf9c77ee898dfc46180697edadd50a2b..051d316228f900835861eb5c235c50a0679093c9 100644 --- a/src/common/backend/parser/parse_utilcmd.cpp +++ b/src/common/backend/parser/parse_utilcmd.cpp @@ -534,6 +534,7 @@ Oid *namespaceid, bool isFirstNode) col->collOid = InvalidOid; col->constraints = NIL; col->fdwoptions = NIL; + col->update_default = NULL; transformColumnDefinition(&cxt, col, !isFirstNode && preCheck); } @@ -1000,14 +1001,19 @@ static void transformColumnDefinition(CreateStmtContext* cxt, ColumnDef* column, break; case CONSTR_DEFAULT: - if (saw_default) + if (saw_default && constraint->update_expr == NULL) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("multiple default values specified for column \"%s\" of table \"%s\"", column->colname, cxt->relation->relname), parser_errposition(cxt->pstate, constraint->location))); - column->raw_default = constraint->raw_expr; + if (!saw_default) { + column->raw_default = constraint->raw_expr; + } + if (constraint->update_expr != NULL) { + column->update_default = constraint->update_expr; + } AssertEreport(constraint->cooked_expr == NULL, MOD_OPT, ""); saw_default = true; break; @@ -1664,6 +1670,7 @@ static void transformTableLikeClause( } def->raw_default = NULL; def->cooked_default = NULL; + def->update_default = NULL; def->collClause = NULL; def->collOid = attribute->attcollation; def->constraints = NIL; @@ -1682,6 +1689,7 @@ static void transformTableLikeClause( */ if (attribute->atthasdef) { Node* this_default = NULL; + Node* update_default = NULL; AttrDefault* attrdef = NULL; int i; Oid seqId = InvalidOid; @@ -1692,25 +1700,30 @@ static void transformTableLikeClause( for (i = 0; i < constr->num_defval; i++) { if (attrdef[i].adnum == parent_attno) { this_default = (Node*)stringToNode_skip_extern_fields(attrdef[i].adbin); + if (attrdef[i].has_on_update) { + update_default = (Node*)stringToNode_skip_extern_fields(attrdef[i].adbin_on_update); + } break; } } - Assert(this_default != NULL); + Assert(this_default != NULL || update_default != NULL); /* * Whether default expr is serial type and the sequence is owned by the table. */ if (!IsCreatingSeqforAnalyzeTempTable(cxt) && (table_like_clause->options & CREATE_TABLE_LIKE_DEFAULTS_SERIAL)) { - seqId = searchSeqidFromExpr(this_default); - if (OidIsValid(seqId)) { - List* seqs = getOwnedSequences(relation->rd_id); - if (seqs != NULL && list_member_oid(seqs, DatumGetObjectId(seqId))) { - /* is serial type */ - def->is_serial = true; - bool large = (get_rel_relkind(seqId) == RELKIND_LARGE_SEQUENCE); - /* Special actions for SERIAL pseudo-types */ - - createSeqOwnedByTable(cxt, def, preCheck, large); + if (this_default != NULL) { + seqId = searchSeqidFromExpr(this_default); + if (OidIsValid(seqId)) { + List* seqs = getOwnedSequences(relation->rd_id); + if (seqs != NULL && list_member_oid(seqs, DatumGetObjectId(seqId))) { + /* is serial type */ + def->is_serial = true; + bool large = (get_rel_relkind(seqId) == RELKIND_LARGE_SEQUENCE); + /* Special actions for SERIAL pseudo-types */ + + createSeqOwnedByTable(cxt, def, preCheck, large); + } } } } @@ -1722,11 +1735,22 @@ static void transformTableLikeClause( * but it can't; so default is ready to apply to child. */ def->cooked_default = this_default; + def->update_default = update_default; } else if (!def->is_serial && (table_like_clause->options & CREATE_TABLE_LIKE_GENERATED) && GetGeneratedCol(tupleDesc, parent_attno - 1)) { bool found_whole_row = false; - def->cooked_default = - map_variable_attnos(this_default, 1, 0, attmap, tupleDesc->natts, &found_whole_row); + if (this_default != NULL) { + def->cooked_default = + map_variable_attnos(this_default, 1, 0, attmap, tupleDesc->natts, &found_whole_row); + } else { + def->cooked_default = NULL; + } + if (update_default != NULL) { + def->update_default = + map_variable_attnos(update_default, 1, 0, attmap, tupleDesc->natts, &found_whole_row); + } else { + def->update_default = NULL; + } if (found_whole_row) { ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot convert whole-row table reference"), @@ -2353,6 +2377,7 @@ static void transformOfType(CreateStmtContext* cxt, TypeName* ofTypename) /* CREATE TYPE CANNOT provied compression feature, so the default is set. */ n->cmprs_mode = ATT_CMPR_UNDEFINED; n->raw_default = NULL; + n->update_default = NULL; n->cooked_default = NULL; n->collClause = NULL; n->collOid = attr->attcollation; diff --git a/src/common/backend/parser/parser.cpp b/src/common/backend/parser/parser.cpp index d91fb17cc2caa90240d4a22851ca610d723aaaca..1ffa345bb0ada7a21bb33f3161fb6513bdb8bf62 100644 --- a/src/common/backend/parser/parser.cpp +++ b/src/common/backend/parser/parser.cpp @@ -480,6 +480,38 @@ int base_yylex(YYSTYPE* lvalp, YYLTYPE* llocp, core_yyscan_t yyscanner) break; } break; + case ON: + cur_yylval = lvalp->core_yystype; + cur_yylloc = *llocp; + next_token = core_yylex(&(lvalp->core_yystype), llocp, yyscanner); + /* get first token after ON (Normal UPDATE). We don't care what it is */ + yyextra->lookahead_token[1] = next_token; + yyextra->lookahead_yylval[1] = lvalp->core_yystype; + yyextra->lookahead_yylloc[1] = *llocp; + + /* get the second token after ON. */ + next_token = core_yylex(&(lvalp->core_yystype), llocp, yyscanner); + yyextra->lookahead_token[0] = next_token; + yyextra->lookahead_yylval[0] = lvalp->core_yystype; + yyextra->lookahead_yylloc[0] = *llocp; + yyextra->lookahead_num = 2; + switch (next_token) { + case CURRENT_TIMESTAMP: + case CURRENT_TIME: + case CURRENT_DATE: + case LOCALTIME: + case LOCALTIMESTAMP: + cur_token = ON_UPDATE_TIME; + lvalp->core_yystype = cur_yylval; + *llocp = cur_yylloc; + break; + default: + /* and back up the output info to cur_token */ + lvalp->core_yystype = cur_yylval; + *llocp = cur_yylloc; + break; + } + break; default: break; } diff --git a/src/common/backend/utils/adt/ruleutils.cpp b/src/common/backend/utils/adt/ruleutils.cpp index 2fea73042af313df76497750d72efc0046cb5abb..170d9f78adcd16fa5e6573bc6c1557910b11c46a 100644 --- a/src/common/backend/utils/adt/ruleutils.cpp +++ b/src/common/backend/utils/adt/ruleutils.cpp @@ -1583,6 +1583,8 @@ static int get_table_attribute( HeapTuple tup = NULL; bool isnull = false; char generatedCol = '\0'; + bool isDefault = false; + bool isOnUpdate = false; attrdefDesc = heap_open(AttrDefaultRelationId, AccessShareLock); @@ -1597,6 +1599,18 @@ static int get_table_attribute( Datum txt = DirectFunctionCall2(pg_get_expr, val, ObjectIdGetDatum(tableoid)); + if (txt && pg_strcasecmp(TextDatumGetCString(txt), "") == 0) { + isDefault = false; + } else { + isDefault = true; + } + Datum onUpdateExpr = fastgetattr(tup, Anum_pg_attrdef_adsrc_on_update, attrdefDesc->rd_att, &isnull); + if (onUpdateExpr && pg_strcasecmp(TextDatumGetCString(onUpdateExpr), "") == 0) { + isOnUpdate = false; + } else { + isOnUpdate = true; + } + if (attrdef->adnum == att_tup->attnum) { val = fastgetattr(tup, Anum_pg_attrdef_adgencol, attrdefDesc->rd_att, &isnull); if (!isnull) { @@ -1604,9 +1618,12 @@ static int get_table_attribute( } if (generatedCol == ATTRIBUTE_GENERATED_STORED) { appendStringInfo(buf, " GENERATED ALWAYS AS (%s) STORED", TextDatumGetCString(txt)); - } else { + } else if (isDefault) { appendStringInfo(buf, " DEFAULT %s", TextDatumGetCString(txt)); } + if (isOnUpdate) { + appendStringInfo(buf, " ON UPDATE %s", TextDatumGetCString(onUpdateExpr)); + } break; } } diff --git a/src/common/backend/utils/cache/relcache.cpp b/src/common/backend/utils/cache/relcache.cpp index b888de3e04a6bbefb6c26899f831b492bce29e7e..f01a9d915455e0511a0cb9a235838f8c3bf76ed0 100644 --- a/src/common/backend/utils/cache/relcache.cpp +++ b/src/common/backend/utils/cache/relcache.cpp @@ -1717,11 +1717,14 @@ static void RelationBuildTupleDesc(Relation relation, bool onlyLoadInitDefVal) constr->num_defval = ndef; constr->generatedCols = (char *)MemoryContextAllocZero(LocalMyDBCacheMemCxt(), RelationGetNumberOfAttributes(relation) * sizeof(char)); + constr->has_on_update = (bool *)MemoryContextAllocZero(LocalMyDBCacheMemCxt(), + RelationGetNumberOfAttributes(relation) * sizeof(bool)); AttrDefaultFetch(relation); } else { constr->num_defval = 0; constr->defval = NULL; constr->generatedCols = NULL; + constr->has_on_update = NULL; } if (relation->rd_rel->relchecks > 0) /* CHECKs */ @@ -5527,6 +5530,28 @@ static void GeneratedColFetch(TupleConstr *constr, HeapTuple htup, Relation adre } } +/* + * Load updated column attribute value definitions for the relation. + */ +static void UpdatedColFetch(TupleConstr *constr, HeapTuple htup, Relation adrel, int attrdefIndex) +{ + bool *on_update = constr->has_on_update; + AttrDefault *attrdef = constr->defval; + bool updatedCol = false; + if (HeapTupleHeaderGetNatts(htup->t_data, adrel->rd_att) >= Anum_pg_attrdef_adsrc_on_update) { + bool isnull = false; + Datum val; + val = fastgetattr(htup, Anum_pg_attrdef_adsrc_on_update, adrel->rd_att, &isnull); + if (val && pg_strcasecmp(TextDatumGetCString(val), "") == 0) { + updatedCol = false; + } else { + updatedCol = true; + } + } + attrdef[attrdefIndex].has_on_update = updatedCol; + on_update[attrdef[attrdefIndex].adnum - 1] = updatedCol; +} + /* * Load any default attribute value definitions for the relation. */ @@ -5563,12 +5588,18 @@ static void AttrDefaultFetch(Relation relation) GeneratedColFetch(relation->rd_att->constr, htup, adrel, i); } + UpdatedColFetch(relation->rd_att->constr, htup, adrel, i); + val = fastgetattr(htup, Anum_pg_attrdef_adbin, adrel->rd_att, &isnull); if (isnull) ereport(WARNING, (errmsg("null adbin for attr %s of rel %s", NameStr(relation->rd_att->attrs[adform->adnum - 1]->attname), RelationGetRelationName(relation)))); else attrdef[i].adbin = MemoryContextStrdup(LocalMyDBCacheMemCxt(), TextDatumGetCString(val)); + + val = fastgetattr(htup, Anum_pg_attrdef_adbin_on_update, adrel->rd_att, &isnull); + if (!isnull) + attrdef[i].adbin_on_update = MemoryContextStrdup(LocalMyDBCacheMemCxt(), TextDatumGetCString(val)); break; } diff --git a/src/common/backend/utils/init/globals.cpp b/src/common/backend/utils/init/globals.cpp index 05817c5375f05c56c4a89892c645a4848319f936..96a9f7d362ac1b9427b627e86b38420b595dacd4 100644 --- a/src/common/backend/utils/init/globals.cpp +++ b/src/common/backend/utils/init/globals.cpp @@ -59,7 +59,7 @@ bool open_join_children = true; bool will_shutdown = false; /* hard-wired binary version number */ -const uint32 GRAND_VERSION_NUM = 92613; +const uint32 GRAND_VERSION_NUM = 92615; const uint32 PREDPUSH_SAME_LEVEL_VERSION_NUM = 92522; const uint32 UPSERT_WHERE_VERSION_NUM = 92514; @@ -141,6 +141,8 @@ const uint32 PITR_INIT_VERSION_NUM = 92599; const uint32 COMMENT_SUPPORT_VERSION_NUM = 92612; +const uint32 ON_UPDATE_TIMESTAMP_VERSION_NUM = 92614; + #ifdef PGXC bool useLocalXid = false; #endif diff --git a/src/gausskernel/optimizer/commands/createas.cpp b/src/gausskernel/optimizer/commands/createas.cpp index 85cb8305c00df4b74e608298f07ea2f8de49efe3..b978ebd0192f38928462e0db7f5beb27daa8e84a 100644 --- a/src/gausskernel/optimizer/commands/createas.cpp +++ b/src/gausskernel/optimizer/commands/createas.cpp @@ -357,6 +357,7 @@ static void intorel_startup(DestReceiver* self, int operation, TupleDesc typeinf col->kvtype = attribute->attkvtype; col->cmprs_mode = attribute->attcmprmode; col->raw_default = NULL; + col->update_default = NULL; col->cooked_default = NULL; col->collClause = NULL; col->collOid = attribute->attcollation; diff --git a/src/gausskernel/optimizer/commands/foreigncmds.cpp b/src/gausskernel/optimizer/commands/foreigncmds.cpp index 9d0f5ed173dc8d255069caeeed12735f7820eaba..ff39228fe488def71d4ac2e4d8f26dcfea0b4540 100644 --- a/src/gausskernel/optimizer/commands/foreigncmds.cpp +++ b/src/gausskernel/optimizer/commands/foreigncmds.cpp @@ -1390,6 +1390,7 @@ ColumnDef* makeColumnDef(const char* colname, char* coltype) col->collOid = InvalidOid; col->constraints = NIL; col->fdwoptions = NIL; + col->update_default = NULL; return col; } diff --git a/src/gausskernel/optimizer/commands/tablecmds.cpp b/src/gausskernel/optimizer/commands/tablecmds.cpp index ba1607bd6d06db6f135e3098dd05cf312a37ac72..0463912c972f3c1b50dc6a476d988e2765107349 100644 --- a/src/gausskernel/optimizer/commands/tablecmds.cpp +++ b/src/gausskernel/optimizer/commands/tablecmds.cpp @@ -2586,7 +2586,7 @@ Oid DefineRelation(CreateStmt* stmt, char relkind, Oid ownerId, bool isCTAS) } } - if (colDef->raw_default != NULL) { + if ((colDef->raw_default != NULL || colDef->update_default != NULL) && colDef->cooked_default == NULL) { RawColumnDefault* rawEnt = NULL; if (relkind == RELKIND_FOREIGN_TABLE) { @@ -2617,9 +2617,10 @@ Oid DefineRelation(CreateStmt* stmt, char relkind, Oid ownerId, bool isCTAS) rawEnt->attnum = attnum; rawEnt->raw_default = colDef->raw_default; rawEnt->generatedCol = colDef->generatedCol; + rawEnt->update_expr = colDef->update_default; rawDefaults = lappend(rawDefaults, rawEnt); descriptor->attrs[attnum - 1]->atthasdef = true; - } else if (colDef->cooked_default != NULL) { + } else if (colDef->cooked_default != NULL || colDef->update_default != NULL) { CookedConstraint* cooked = NULL; cooked = (CookedConstraint*)palloc(sizeof(CookedConstraint)); @@ -2635,6 +2636,7 @@ Oid DefineRelation(CreateStmt* stmt, char relkind, Oid ownerId, bool isCTAS) cooked->is_local = true; /* not used for defaults */ cooked->inhcount = 0; /* ditto */ cooked->is_no_inherit = false; + cooked->update_expr = colDef->update_default; cookedDefaults = lappend(cookedDefaults, cooked); descriptor->attrs[attnum - 1]->atthasdef = true; } @@ -4717,6 +4719,7 @@ static List* MergeAttributes( coldef->is_not_null = restdef->is_not_null; coldef->raw_default = restdef->raw_default; coldef->cooked_default = restdef->cooked_default; + coldef->update_default = restdef->update_default; coldef->constraints = restdef->constraints; coldef->is_from_type = false; coldef->kvtype = restdef->kvtype; @@ -4912,6 +4915,7 @@ static List* MergeAttributes( def->kvtype = attribute->attkvtype; def->cmprs_mode = attribute->attcmprmode; def->raw_default = NULL; + def->update_default = NULL; def->generatedCol = '\0'; def->cooked_default = NULL; def->collClause = NULL; @@ -4926,6 +4930,7 @@ static List* MergeAttributes( */ if (attribute->atthasdef) { Node* this_default = NULL; + Node* this_update_default = NULL; AttrDefault* attrdef = NULL; int i; @@ -4938,10 +4943,13 @@ static List* MergeAttributes( for (i = 0; i < constr->num_defval; i++) { if (attrdef[i].adnum == parent_attno) { this_default = (Node*)stringToNode_skip_extern_fields(attrdef[i].adbin); + if (attrdef[i].has_on_update) { + this_update_default = (Node*)stringToNode_skip_extern_fields(attrdef[i].adbin_on_update); + } break; } } - Assert(this_default != NULL); + Assert(this_default != NULL || this_update_default != NULL); /* * If default expr could contain any vars, we'd need to fix @@ -4960,6 +4968,8 @@ static List* MergeAttributes( def->cooked_default = &u_sess->cmd_cxt.bogus_marker; have_bogus_defaults = true; } + if (def->update_default == NULL) + def->update_default = this_update_default; } } @@ -5104,6 +5114,9 @@ static List* MergeAttributes( def->raw_default = newdef->raw_default; def->cooked_default = newdef->cooked_default; } + if (newdef->update_default != NULL) { + def->update_default = newdef->update_default; + } } else { /* * No, attach new column to result schema @@ -9530,6 +9543,7 @@ static void ATExecAddColumn(List** wqueue, AlteredTableInfo* tab, Relation rel, rawEnt = (RawColumnDefault*)palloc(sizeof(RawColumnDefault)); rawEnt->attnum = attribute.attnum; rawEnt->raw_default = (Node*)copyObject(colDef->raw_default); + rawEnt->update_expr = (Node*)copyObject(colDef->update_default); rawEnt->generatedCol = colDef->generatedCol; @@ -10032,6 +10046,35 @@ static void ATExecSetNotNull(AlteredTableInfo* tab, Relation rel, const char* co heap_close(attr_rel, RowExclusiveLock); } +bool FetchOnUpdateExpress(Relation rel, const char* colName) +{ + HeapTuple htup = NULL; + bool isnull = false; + bool temp_on_update = FALSE; + htup = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName); + if (!HeapTupleIsValid(htup)) + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", colName, RelationGetRelationName(rel)))); + Form_pg_attribute attTup = (Form_pg_attribute)GETSTRUCT(htup); + AttrNumber temp_attnum = attTup->attnum; + + ScanKeyData skey[2]; + Relation adrel = heap_open(AttrDefaultRelationId, RowExclusiveLock); + ScanKeyInit(&skey[0], Anum_pg_attrdef_adrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(rel))); + ScanKeyInit(&skey[1], Anum_pg_attrdef_adnum, BTEqualStrategyNumber, F_INT2EQ, Int16GetDatum(temp_attnum)); + SysScanDesc adscan = systable_beginscan(adrel, AttrDefaultIndexId, true, NULL, 2, skey); + + if (HeapTupleIsValid(htup = systable_getnext(adscan))) { + Datum val = heap_getattr(htup, Anum_pg_attrdef_adsrc_on_update, adrel->rd_att, &isnull); + if (val && pg_strcasecmp(TextDatumGetCString(val), "") != 0) { + temp_on_update = TRUE; + } + } + systable_endscan(adscan); + heap_close(adrel, RowExclusiveLock); + return temp_on_update; +} + /* * ALTER TABLE ALTER COLUMN SET/DROP DEFAULT */ @@ -10067,6 +10110,8 @@ static void ATExecColumnDefault(Relation rel, const char* colName, Node* newDefa errmsg("column \"%s\" of relation \"%s\" is a generated column", colName, RelationGetRelationName(rel)))); } + bool on_update = FetchOnUpdateExpress(rel, colName); + /* * Remove any old default for the column. We use RESTRICT here for * safety, but at present we do not expect anything to depend on the @@ -10076,9 +10121,11 @@ static void ATExecColumnDefault(Relation rel, const char* colName, Node* newDefa * is preparatory to adding a new default, but as a user-initiated * operation when the user asked for a drop. */ - RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, false, newDefault == NULL ? false : true); + if (!on_update) { + RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, false, newDefault == NULL ? false : true); + } - if (newDefault != NULL) { + if (newDefault != NULL || (on_update && newDefault == NULL)) { /* SET DEFAULT */ RawColumnDefault* rawEnt = NULL; @@ -10086,6 +10133,7 @@ static void ATExecColumnDefault(Relation rel, const char* colName, Node* newDefa rawEnt->attnum = attnum; rawEnt->raw_default = newDefault; rawEnt->generatedCol = '\0'; + rawEnt->update_expr = NULL; /* * This function is intended for CREATE TABLE, so it processes a @@ -12944,6 +12992,9 @@ static void ATExecAlterColumnType(AlteredTableInfo* tab, Relation rel, AlterTabl SysScanDesc scan; HeapTuple depTup; char generatedCol = '\0'; + Node* update_expr = NULL; + bool flagDropOnUpdateTimestamp = false; + bool existOnUpdateTimestamp = false; attrelation = heap_open(AttributeRelationId, RowExclusiveLock); @@ -13018,25 +13069,27 @@ static void ATExecAlterColumnType(AlteredTableInfo* tab, Relation rel, AlterTabl */ if (attTup->atthasdef) { defaultexpr = build_column_default(rel, attnum); - Assert(defaultexpr); - defaultexpr = strip_implicit_coercions(defaultexpr); - defaultexpr = coerce_to_target_type(NULL, /* no UNKNOWN params */ - defaultexpr, - exprType(defaultexpr), - targettype, - targettypmod, - COERCION_ASSIGNMENT, - COERCE_IMPLICIT_CAST, - -1); - if (defaultexpr == NULL) { - if (generatedCol == ATTRIBUTE_GENERATED_STORED) { - ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("generation expression for column \"%s\" cannot be cast automatically to type %s", colName, - format_type_be(targettype)))); - } else { - ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("default for column \"%s\" cannot be cast automatically to type %s", colName, - format_type_be(targettype)))); + /* for column only with on update but no default ,here could be NULL*/ + if (defaultexpr != NULL) { + defaultexpr = strip_implicit_coercions(defaultexpr); + defaultexpr = coerce_to_target_type(NULL, /* no UNKNOWN params */ + defaultexpr, + exprType(defaultexpr), + targettype, + targettypmod, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (defaultexpr == NULL) { + if (generatedCol == ATTRIBUTE_GENERATED_STORED) { + ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("generation expression for column \"%s\" cannot be cast automatically to type %s", colName, + format_type_be(targettype)))); + } else { + ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("default for column \"%s\" cannot be cast automatically to type %s", colName, + format_type_be(targettype)))); + } } } } else @@ -13181,7 +13234,6 @@ static void ATExecAlterColumnType(AlteredTableInfo* tab, Relation rel, AlterTabl * Ignore the column's default expression, since we will fix * it below. */ - Assert(defaultexpr); break; case OCLASS_PROC: @@ -13268,6 +13320,35 @@ static void ATExecAlterColumnType(AlteredTableInfo* tab, Relation rel, AlterTabl RemoveStatistics<'c'>(RelationGetRelid(rel), attnum); } + /* def->constraints maybe is null when execute sql(alter table x alter column type new_type) */ + if (cmd->subtype == AT_AlterColumnType && def->constraints && def->constraints->head) { + Constraint* temp_cons = (Constraint*)lfirst(def->constraints->head); + if (temp_cons->contype == CONSTR_DEFAULT && temp_cons->update_expr != NULL) { + update_expr = temp_cons->update_expr; + } + } + + /* when the default expr is NULL and on update expr exist, the defaultexpr should be NULL. + * because the build_column_default maybe return the on update expr. */ + if (rel->rd_att->constr && rel->rd_att->constr->num_defval > 0) { + int ndef = rel->rd_att->constr->num_defval -1; + while (ndef >= 0 && rel->rd_att->constr->defval[ndef].adnum != attnum) { + /* modify column on update expr when the column don't at the end of table */ + --ndef; + } + if (ndef >= 0) { + if (pg_strcasecmp(rel->rd_att->constr->defval[ndef].adbin, "") == 0 && + rel->rd_att->constr->defval[ndef].has_on_update) { + existOnUpdateTimestamp = true; + if (update_expr == NULL) { + CommandCounterIncrement(); + RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, true, true); + flagDropOnUpdateTimestamp = true; + } + } + } + } + /* * Update the default, if present, by brute force --- remove and re-add * the default. Probably unsafe to take shortcuts, since the new version @@ -13275,7 +13356,7 @@ static void ATExecAlterColumnType(AlteredTableInfo* tab, Relation rel, AlterTabl * rather than after other ALTER TYPE commands, since the default won't * depend on other column types.) */ - if (defaultexpr != NULL) { + if ((defaultexpr != NULL || update_expr != NULL) && !flagDropOnUpdateTimestamp) { /* Must make new row visible since it will be updated again */ CommandCounterIncrement(); @@ -13283,9 +13364,19 @@ static void ATExecAlterColumnType(AlteredTableInfo* tab, Relation rel, AlterTabl * We use RESTRICT here for safety, but at present we do not expect * anything to depend on the default. */ - RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, true, true); + if (defaultexpr != NULL || (update_expr != NULL && existOnUpdateTimestamp)) { + RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, true, true); + } + if (update_expr != NULL) { + ParseState* pstate = make_parsestate(NULL); + RangeTblEntry* rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true); + addRTEtoQuery(pstate, rte, true, true, true); + pstate->p_rawdefaultlist = NULL; + update_expr = cookDefault(pstate, update_expr, attTup->atttypid, attTup->atttypmod, NameStr(attTup->attname), + def->generatedCol); + } - StoreAttrDefault(rel, attnum, defaultexpr, generatedCol); + StoreAttrDefault(rel, attnum, defaultexpr, generatedCol, update_expr); } /* Cleanup */ diff --git a/src/gausskernel/optimizer/commands/trigger.cpp b/src/gausskernel/optimizer/commands/trigger.cpp index a16f211cad3c35b4b40bddfb49120434e7b6faa8..6fed9882799db38b35c64040c4af69b8fcc97c62 100644 --- a/src/gausskernel/optimizer/commands/trigger.cpp +++ b/src/gausskernel/optimizer/commands/trigger.cpp @@ -87,7 +87,7 @@ /* Local function prototypes */ static void ConvertTriggerToFK(CreateTrigStmt* stmt, Oid funcoid); static void SetTriggerFlags(TriggerDesc* trigdesc, const Trigger* trigger); -static HeapTuple GetTupleForTrigger(EState* estate, EPQState* epqstate, ResultRelInfo* relinfo, Oid targetPartitionOid, +HeapTuple GetTupleForTrigger(EState* estate, EPQState* epqstate, ResultRelInfo* relinfo, Oid targetPartitionOid, int2 bucketid, ItemPointer tid, LockTupleMode lockmode, TupleTableSlot** newSlot); static void ReleaseFakeRelation(Relation relation, Partition part, Relation* fakeRelation); static bool TriggerEnabled(EState* estate, ResultRelInfo* relinfo, Trigger* trigger, TriggerEvent event, @@ -2659,7 +2659,7 @@ void ExecASTruncateTriggers(EState* estate, ResultRelInfo* relinfo) NULL); } -static HeapTuple GetTupleForTrigger(EState* estate, EPQState* epqstate, ResultRelInfo* relinfo, Oid targetPartitionOid, +HeapTuple GetTupleForTrigger(EState* estate, EPQState* epqstate, ResultRelInfo* relinfo, Oid targetPartitionOid, int2 bucketid, ItemPointer tid, LockTupleMode lockmode, TupleTableSlot** newSlot) { Relation relation = relinfo->ri_RelationDesc; diff --git a/src/gausskernel/optimizer/commands/view.cpp b/src/gausskernel/optimizer/commands/view.cpp index 6bce00bd0844c74d50d4693c896b26858885dbd9..0f5e90984286cc34f7eeeb8f3609d4b2249b15c4 100644 --- a/src/gausskernel/optimizer/commands/view.cpp +++ b/src/gausskernel/optimizer/commands/view.cpp @@ -114,6 +114,7 @@ static Oid DefineVirtualRelation(RangeVar* relation, List* tlist, bool replace, def->storage = 0; def->cmprs_mode = ATT_CMPR_NOCOMPRESS; /* dont compress */ def->raw_default = NULL; + def->update_default = NULL; def->cooked_default = NULL; def->collClause = NULL; def->collOid = exprCollation((Node*)tle->expr); diff --git a/src/gausskernel/optimizer/rewrite/rewriteHandler.cpp b/src/gausskernel/optimizer/rewrite/rewriteHandler.cpp index 3a8c3b8317fbb394b3a46f6ab807bfb1a6ceaf99..03b96d9100ae6ad5f3fd763fc34a5c611dd8eba3 100644 --- a/src/gausskernel/optimizer/rewrite/rewriteHandler.cpp +++ b/src/gausskernel/optimizer/rewrite/rewriteHandler.cpp @@ -928,7 +928,7 @@ static Node* get_assignment_input(Node* node) * If auto truncation function enabled and it is insert statement then * we use this arg to determin if default should be casted explict. */ -Node* build_column_default(Relation rel, int attrno, bool isInsertCmd) +Node* build_column_default(Relation rel, int attrno, bool isInsertCmd, bool needOnUpdate) { TupleDesc rd_att = rel->rd_att; Form_pg_attribute att_tup = rd_att->attrs[attrno - 1]; @@ -948,8 +948,21 @@ Node* build_column_default(Relation rel, int attrno, bool isInsertCmd) if (attrno == defval[ndef].adnum) { /* * Found it, convert string representation to node tree. + * + * isInsertCmd is false, has_on_update is true and adbin_on_update is not null character string, + * then doing convert adbin_on_update to expression. + * if adbin is not null character string, then doing convert adbin to expression. */ - expr = (Node*)stringToNode_skip_extern_fields(defval[ndef].adbin); + if (needOnUpdate && (!isInsertCmd) && defval[ndef].has_on_update && + pg_strcasecmp(defval[ndef].adbin_on_update, "") != 0) { + expr = (Node*)stringToNode_skip_extern_fields(defval[ndef].adbin_on_update); + break; + } else { + if (pg_strcasecmp(defval[ndef].adbin, "") != 0) { + expr = (Node*)stringToNode_skip_extern_fields(defval[ndef].adbin); + break; + } + } break; } } @@ -2911,6 +2924,7 @@ char* GetCreateTableStmt(Query* parsetree, CreateTableAsStmt* stmt) coldef->cmprs_mode = ATT_CMPR_UNDEFINED; coldef->raw_default = NULL; coldef->cooked_default = NULL; + coldef->update_default = NULL; coldef->constraints = NIL; /* diff --git a/src/gausskernel/runtime/executor/nodeModifyTable.cpp b/src/gausskernel/runtime/executor/nodeModifyTable.cpp index 943b4ff8573119605a4a675be70674e2b65f9e79..f7218ae85984617a0d858af2d541d21178e336da 100644 --- a/src/gausskernel/runtime/executor/nodeModifyTable.cpp +++ b/src/gausskernel/runtime/executor/nodeModifyTable.cpp @@ -42,6 +42,7 @@ #include "access/xact.h" #include "access/tableam.h" #include "catalog/heap.h" +#include "catalog/pg_attrdef.h" #include "catalog/pg_namespace.h" #include "catalog/pg_partition_fn.h" #include "catalog/storage_gtt.h" @@ -408,6 +409,173 @@ void ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo, EState *estate, Tu (void)MemoryContextSwitchTo(oldContext); } +static bool GetUpdateExprCol(TupleDesc tupdesc, int atti) +{ + if (!tupdesc->constr) + return false; + + if (tupdesc->constr->num_defval == 0) + return false; + + return tupdesc->constr->has_on_update[atti]; +} + +static void RecoredUpdateExpr(ResultRelInfo *resultRelInfo, EState *estate, CmdType cmdtype) +{ + TupleDesc tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc); + int natts = tupdesc->natts; + MemoryContext oldContext; + + Assert(tupdesc->constr && tupdesc->constr->has_on_update); + + /* + * If first time through for this result relation, build expression + * nodetrees for rel's stored generation expressions. Keep them in the + * per-query memory context so they'll survive throughout the query. + */ + if (resultRelInfo->ri_UpdatedExprs == NULL) { + oldContext = MemoryContextSwitchTo(estate->es_query_cxt); + + resultRelInfo->ri_UpdatedExprs = (ExprState **)palloc(natts * sizeof(ExprState *)); + resultRelInfo->ri_NumUpdatedNeeded = 0; + + for (int i = 0; i < natts; i++) { + if (GetUpdateExprCol(tupdesc, i)) { + Expr *expr; + + expr = (Expr *)build_column_default(resultRelInfo->ri_RelationDesc, i + 1, false, true); + if (expr == NULL) + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("no update expression found for column number %d of table \"%s\"", i + 1, + RelationGetRelationName(resultRelInfo->ri_RelationDesc)))); + + resultRelInfo->ri_UpdatedExprs[i] = ExecPrepareExpr(expr, estate); + resultRelInfo->ri_NumUpdatedNeeded++; + } + } + + (void)MemoryContextSwitchTo(oldContext); + } +} + +/* + * Compute stored updated columns for a tuple + */ +bool ExecComputeStoredUpdateExpr(ResultRelInfo *resultRelInfo, EState *estate, TupleTableSlot *slot, Tuple tuple, + CmdType cmdtype, ModifyTableState* node, ItemPointer otid, Oid oldPartitionOid, int2 bucketid) +{ + Relation rel = resultRelInfo->ri_RelationDesc; + TupleDesc tupdesc = RelationGetDescr(rel); + uint32 natts = (uint32)tupdesc->natts; + MemoryContext oldContext; + Datum *values; + bool *nulls; + bool *replaces; + Tuple newtuple; + errno_t rc = EOK; + FmgrInfo eqproc; + Oid opfuncoid = InvalidOid; + bool match = false; + bool update_fix_result = true; + int temp_id = -1; + int attnum; + uint32 updated_colnum_resno; + Bitmapset* updatedCols = GetUpdatedColumns(node->resultRelInfo, node->ps.state); + /* + * If no generated columns have been affected by this change, then skip + * the rest. + */ + if (resultRelInfo->ri_NumUpdatedNeeded == 0) + return true; + + HeapTuple oldtup = GetTupleForTrigger(estate, NULL, resultRelInfo, oldPartitionOid, bucketid, otid, LockTupleShared, NULL); + + RecoredUpdateExpr(resultRelInfo, estate, cmdtype); + + /* compare update operator whether the newtuple is equal to the oldtuple, + * if equal, so update don't fix the default column value */ + Datum* oldvalues = (Datum*)palloc(natts * sizeof(Datum)); + bool* oldnulls = (bool*)palloc(natts * sizeof(bool)); + heap_deform_tuple(oldtup, tupdesc, oldvalues, oldnulls); + temp_id = -1; + attnum = bms_next_member(updatedCols, temp_id); + updated_colnum_resno = attnum + FirstLowInvalidHeapAttributeNumber; + temp_id = attnum; + for (int32 i = 0; i < (int32)natts; i++) { + if (updated_colnum_resno == (i + 1)) { + if (slot->tts_isnull[i] && oldnulls[i]) { + match = true; + } else if (slot->tts_isnull[i] && !oldnulls[i]) { + match = false; + } else if (!slot->tts_isnull[i] && oldnulls[i]) { + match = false; + } else { + Form_pg_attribute attr = TupleDescAttr(tupdesc, i); + opfuncoid = OpernameGetOprid(list_make1(makeString("=")), attr->atttypid, attr->atttypid); + RegProcedure oprcode = get_opcode(opfuncoid); + fmgr_info(oprcode, &eqproc); + match = DatumGetBool(FunctionCall2Coll(&eqproc, DEFAULT_COLLATION_OID, slot->tts_values[i], oldvalues[i])); + } + update_fix_result = update_fix_result && match; + attnum = bms_next_member(updatedCols, temp_id); + if (attnum >= 0) { + updated_colnum_resno = attnum + FirstLowInvalidHeapAttributeNumber; + temp_id = attnum; + } + } + } + pfree_ext(oldvalues); + pfree_ext(oldnulls); + + if (update_fix_result) + return true; + + oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate)); + values = (Datum *)palloc(sizeof(*values) * natts); + nulls = (bool *)palloc(sizeof(*nulls) * natts); + replaces = (bool *)palloc0(sizeof(*replaces) * natts); + temp_id = -1; + attnum = bms_next_member(updatedCols, temp_id); + updated_colnum_resno = attnum + FirstLowInvalidHeapAttributeNumber; + temp_id = attnum; + for (uint32 i = 0; i < natts; i++) { + Form_pg_attribute attr = TupleDescAttr(tupdesc, i); + if (updated_colnum_resno == (i + 1)) { + attnum = bms_next_member(updatedCols, temp_id); + if (attnum >= 0) { + updated_colnum_resno = attnum + FirstLowInvalidHeapAttributeNumber; + temp_id = attnum; + } + continue; + } + if (GetUpdateExprCol(tupdesc, i) && resultRelInfo->ri_UpdatedExprs[i]) { + ExprContext *econtext; + Datum val; + bool isnull; + + econtext = GetPerTupleExprContext(estate); + econtext->ecxt_scantuple = slot; + + val = ExecEvalExpr(resultRelInfo->ri_UpdatedExprs[i], econtext, &isnull, NULL); + + /* + * We must make a copy of val as we have no guarantees about where + * memory for a pass-by-reference Datum is located. + */ + if (!isnull) + val = datumCopy(val, attr->attbyval, attr->attlen); + + values[i] = val; + nulls[i] = isnull; + replaces[i] = true; + } + } + + newtuple = tableam_tops_modify_tuple(tuple, tupdesc, values, nulls, replaces); + (void)ExecStoreTuple(newtuple, slot, InvalidBuffer, false); + (void)MemoryContextSwitchTo(oldContext); + return false; +} + static bool ExecConflictUpdate(ModifyTableState* mtstate, ResultRelInfo* resultRelInfo, ConflictInfoData* conflictInfo, TupleTableSlot* planSlot, TupleTableSlot* excludedSlot, EState* estate, Relation targetRel, Oid oldPartitionOid, int2 bucketid, bool canSetTag, TupleTableSlot** returning) @@ -1897,6 +2065,14 @@ TupleTableSlot* ExecUpdate(ItemPointer tupleid, bool update_indexes = false; LockTupleMode lockmode; + /* acquire Form_pg_attrdef ad_on_update */ + if (result_relation_desc->rd_att->constr && result_relation_desc->rd_att->constr->has_on_update) { + bool update_fix_result = ExecComputeStoredUpdateExpr(result_rel_info, estate, slot, tuple, CMD_UPDATE, node, tupleid, oldPartitionOid, bucketid); + if (!update_fix_result) { + tuple = slot->tts_tuple; + } + } + /* * Compute stored generated columns */ diff --git a/src/gausskernel/storage/access/common/tupdesc.cpp b/src/gausskernel/storage/access/common/tupdesc.cpp index 2a61c5c70f10049d91e7f06f8d942c7da03fef8f..e973a6db387a3aa8512428c55b0f94145ece6a06 100644 --- a/src/gausskernel/storage/access/common/tupdesc.cpp +++ b/src/gausskernel/storage/access/common/tupdesc.cpp @@ -218,11 +218,18 @@ TupleConstr *TupleConstrCopy(const TupleDesc tupdesc) if (constr->defval[i].adbin) { cpy->defval[i].adbin = pstrdup(constr->defval[i].adbin); } + if (constr->defval[i].adbin_on_update) { + cpy->defval[i].adbin_on_update = pstrdup(constr->defval[i].adbin_on_update); + } } uint32 genColsLen = (uint32)tupdesc->natts * sizeof(char); cpy->generatedCols = (char *)palloc(genColsLen); rc = memcpy_s(cpy->generatedCols, genColsLen, constr->generatedCols, genColsLen); securec_check(rc, "\0", "\0"); + uint32 updateColsLen = (uint32)tupdesc->natts * sizeof(bool); + cpy->has_on_update = (bool *)palloc(updateColsLen); + rc = memcpy_s(cpy->has_on_update, updateColsLen, constr->has_on_update, updateColsLen); + securec_check(rc, "\0", "\0"); } if ((cpy->num_check = constr->num_check) > 0) { @@ -309,6 +316,7 @@ void FreeTupleDesc(TupleDesc tupdesc) } pfree(attrdef); pfree(tupdesc->constr->generatedCols); + pfree(tupdesc->constr->has_on_update); } if (tupdesc->constr->num_check > 0) { ConstrCheck *check = tupdesc->constr->check; @@ -1072,6 +1080,7 @@ TupleDesc BuildDescForRelation(List *schema, Node *orientedFrom, char relkind) constr->check = NULL; constr->num_check = 0; constr->generatedCols = NULL; + constr->has_on_update = NULL; desc->constr = constr; } else { desc->constr = NULL; diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h index fbbdc8b2a7c5c517e0a589050f085bf3ef81e3e8..e2cde5fe88f297da6cf8ab87b609d255623ba4fe 100644 --- a/src/include/access/tupdesc.h +++ b/src/include/access/tupdesc.h @@ -54,6 +54,8 @@ typedef struct attrDefault { AttrNumber adnum; char* adbin; /* nodeToString representation of expr */ char generatedCol; /* generated column setting */ + bool has_on_update; + char* adbin_on_update; } AttrDefault; typedef struct constrCheck { @@ -74,6 +76,7 @@ typedef struct tupleConstr { bool has_not_null; bool has_generated_stored; char* generatedCols; /* attribute array */ + bool* has_on_update; } TupleConstr; /* This structure contains initdefval of a tuple */ diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h index c5991b132bf2c39dc4d9b46afa11d70adff97c39..bc0e2044d21c37c77370c6cc146805b5bd493f99 100644 --- a/src/include/catalog/heap.h +++ b/src/include/catalog/heap.h @@ -29,6 +29,7 @@ typedef struct RawColumnDefault { AttrNumber attnum; /* attribute to attach default to */ Node *raw_default; /* default value (untransformed parse tree) */ char generatedCol; /* generated column setting */ + Node *update_expr; } RawColumnDefault; typedef struct CookedConstraint { @@ -41,6 +42,7 @@ typedef struct CookedConstraint { int inhcount; /* number of times constraint is inherited */ bool is_no_inherit; /* constraint has local def and cannot be * inherited */ + Node *update_expr; } CookedConstraint; typedef struct CeHeapInfo { @@ -180,7 +182,7 @@ extern void InsertPgClassTuple(Relation pg_class_desc, Relation new_rel_desc, Oi extern List *AddRelationNewConstraints(Relation rel, List *newColDefaults, List *newConstraints, bool allow_merge, bool is_local); extern List *AddRelClusterConstraints(Relation rel, List *clusterKeys); -extern void StoreAttrDefault(Relation rel, AttrNumber attnum, Node *expr, char generatedCol); +extern void StoreAttrDefault(Relation rel, AttrNumber attnum, Node *expr, char generatedCol, Node* update_expr); extern Node *cookDefault(ParseState *pstate, Node *raw_default, Oid atttypid, int32 atttypmod, char *attname, char generatedCol); extern void DeleteRelationTuple(Oid relid); diff --git a/src/include/catalog/pg_attrdef.h b/src/include/catalog/pg_attrdef.h index e800ca6e3f8c17b595073b9d5f68e48c97db257d..4bbaa997d798eccf0d6cd16a62589d6a7f0b1f23 100644 --- a/src/include/catalog/pg_attrdef.h +++ b/src/include/catalog/pg_attrdef.h @@ -39,6 +39,10 @@ CATALOG(pg_attrdef,2604) BKI_SCHEMA_MACRO text adsrc; /* human-readable representation of default */ #endif char adgencol; /* generated column setting */ +#ifdef CATALOG_VARLEN /* variable-length fields start here */ + pg_node_tree adbin_on_update /* binrary format of on update express syntax */ + text adsrc_on_update; /* on update express syntax on Mysql Feature */ +#endif } FormData_pg_attrdef; /* ---------------- @@ -52,11 +56,13 @@ typedef FormData_pg_attrdef *Form_pg_attrdef; * compiler constants for pg_attrdef * ---------------- */ -#define Natts_pg_attrdef 5 +#define Natts_pg_attrdef 7 #define Anum_pg_attrdef_adrelid 1 #define Anum_pg_attrdef_adnum 2 #define Anum_pg_attrdef_adbin 3 #define Anum_pg_attrdef_adsrc 4 #define Anum_pg_attrdef_adgencol 5 +#define Anum_pg_attrdef_adbin_on_update 6 +#define Anum_pg_attrdef_adsrc_on_update 7 #endif /* PG_ATTRDEF_H */ diff --git a/src/include/catalog/upgrade_sql/upgrade_catalog_maindb/upgrade-post_catalog_maindb_92_614.sql b/src/include/catalog/upgrade_sql/upgrade_catalog_maindb/upgrade-post_catalog_maindb_92_614.sql new file mode 100644 index 0000000000000000000000000000000000000000..e9aeceae9fd160d36d24052542cd9f44885e476f --- /dev/null +++ b/src/include/catalog/upgrade_sql/upgrade_catalog_maindb/upgrade-post_catalog_maindb_92_614.sql @@ -0,0 +1,8 @@ +INSERT INTO pg_catalog.pg_attribute(attrelid,attname,atttypid,attstattarget,attlen,attnum,attndims,attcacheoff,atttypmod, +attbyval,attstorage,attalign,attnotnull,atthasdef,attisdropped,attislocal,attcmprmode,attinhcount,attcollation,attkvtype) +VALUES (2604,'adbin_on_update',194,-1,-1,6,0,-1,-1,false,'x','i',false,false,false,true,0,0,100,0), +(2604,'adsrc_on_update',25,-1,-1,7,0,-1,-1,false,'x','i',false,false,false,true,0,0,100,0); + +UPDATE PG_CATALOG.pg_class set relnatts = 7 where relname = 'pg_attrdef' AND relnamespace = 11; + +UPDATE PG_CATALOG.pg_attrdef SET adsrc_on_update=''; diff --git a/src/include/catalog/upgrade_sql/upgrade_catalog_otherdb/upgrade-post_catalog_otherdb_92_614.sql b/src/include/catalog/upgrade_sql/upgrade_catalog_otherdb/upgrade-post_catalog_otherdb_92_614.sql new file mode 100644 index 0000000000000000000000000000000000000000..e9aeceae9fd160d36d24052542cd9f44885e476f --- /dev/null +++ b/src/include/catalog/upgrade_sql/upgrade_catalog_otherdb/upgrade-post_catalog_otherdb_92_614.sql @@ -0,0 +1,8 @@ +INSERT INTO pg_catalog.pg_attribute(attrelid,attname,atttypid,attstattarget,attlen,attnum,attndims,attcacheoff,atttypmod, +attbyval,attstorage,attalign,attnotnull,atthasdef,attisdropped,attislocal,attcmprmode,attinhcount,attcollation,attkvtype) +VALUES (2604,'adbin_on_update',194,-1,-1,6,0,-1,-1,false,'x','i',false,false,false,true,0,0,100,0), +(2604,'adsrc_on_update',25,-1,-1,7,0,-1,-1,false,'x','i',false,false,false,true,0,0,100,0); + +UPDATE PG_CATALOG.pg_class set relnatts = 7 where relname = 'pg_attrdef' AND relnamespace = 11; + +UPDATE PG_CATALOG.pg_attrdef SET adsrc_on_update=''; diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index cd69753883434e355de08949a33564425844e4e2..a75eeef2fabd18087b8a7457267c997f5eaf6907 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -198,4 +198,7 @@ extern void InvalidRelcacheForTriggerFunction(Oid funcoid, Oid returnType); extern void ResetTrigShipFlag(); +extern HeapTuple GetTupleForTrigger(EState* estate, EPQState* epqstate, ResultRelInfo* relinfo, Oid targetPartitionOid, + int2 bucketid, ItemPointer tid, LockTupleMode lockmode, TupleTableSlot** newSlot); + #endif /* TRIGGER_H */ diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index e7e2ce1fc3c56760ba0117faacefca8ccc121863..b7c0a7095b33b93f510c457600f9225ffbff92fd 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -100,6 +100,7 @@ extern const uint32 PUBLICATION_INITIAL_DATA_VERSION_NAME; extern const uint32 CREATE_FUNCTION_DEFINER_VERSION; extern const uint32 KEYWORD_IGNORE_COMPART_VERSION_NUM; extern const uint32 COMMENT_SUPPORT_VERSION_NUM; +extern const uint32 ON_UPDATE_TIMESTAMP_VERSION_NUM; extern void register_backend_version(uint32 backend_version); extern bool contain_backend_version(uint32 version_number); diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 02235b2b9f34d63eaf79fb1b28671312254ef5e9..bc601ad43dd75659fd22a5a8a3cc9111cd70e420 100755 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -506,8 +506,13 @@ typedef struct ResultRelInfo { /* array of stored generated columns expr states */ ExprState **ri_GeneratedExprs; + /* array of stored UpdateExpr columns expr states */ + ExprState **ri_UpdatedExprs; + /* number of stored generated columns we need to compute */ int ri_NumGeneratedNeeded; + /* number of stored UpdateExpr columns we need to compute */ + int ri_NumUpdatedNeeded; } ResultRelInfo; /* bloom filter controller */ diff --git a/src/include/nodes/parsenodes_common.h b/src/include/nodes/parsenodes_common.h index 4769705cfc50e6fbaa6e14682d0cd6e2b76e0a18..9cc53e4217c199f7fa9e1160927c339b6c03c589 100644 --- a/src/include/nodes/parsenodes_common.h +++ b/src/include/nodes/parsenodes_common.h @@ -1015,6 +1015,7 @@ typedef struct ColumnDef { Position *position; Form_pg_attribute dropped_attr; /* strcuture for dropped attribute during create table like OE */ char generatedCol; /* generated column setting */ + Node *update_default; } ColumnDef; /* @@ -1327,6 +1328,7 @@ typedef struct Constraint { InformationalConstraint *inforConstraint; char generated_when; /* ALWAYS or BY DEFAULT */ char generated_kind; /* currently always STORED */ + Node *update_expr; } Constraint; /* diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h index a7880dce2243e1fc29f08c017887384b4f46543d..0150fa74cacc10de208c024b402549e97ec03cfc 100644 --- a/src/include/rewrite/rewriteHandler.h +++ b/src/include/rewrite/rewriteHandler.h @@ -19,7 +19,7 @@ extern List* QueryRewrite(Query* parsetree); extern void AcquireRewriteLocks(Query* parsetree, bool forUpdatePushedDown); -extern Node* build_column_default(Relation rel, int attrno, bool isInsertCmd = false); +extern Node* build_column_default(Relation rel, int attrno, bool isInsertCmd = false, bool needOnUpdate = false); extern List* pull_qual_vars(Node* node, int varno = 0, int flags = 0, bool nonRepeat = false); extern void rewriteTargetListMerge(Query* parsetree, Index result_relation, List* range_table); diff --git a/src/test/regress/expected/function_get_table_def.out b/src/test/regress/expected/function_get_table_def.out index 7359f05a1083fe70d3c8faaa6a3e860aeb981fb0..cdd4882fc22561e346d643056a82993255215b9d 100644 --- a/src/test/regress/expected/function_get_table_def.out +++ b/src/test/regress/expected/function_get_table_def.out @@ -591,3 +591,18 @@ select * from pg_get_tabledef('generated_test'); drop table generated_test; reset current_schema; drop schema test_get_table_def cascade; +create database mysql dbcompatibility 'B'; +\c mysql +create table if not exists test(a int, b timestamp default now() on update current_timestamp); +select * from pg_get_tabledef('test'); + pg_get_tabledef +----------------------------------------------------------------------------- + SET search_path = public; + + CREATE TABLE test ( + + a integer, + + b timestamp without time zone DEFAULT now() ON UPDATE pg_systimestamp()+ + ) + + WITH (orientation=row, compression=no); +(1 row) + +\c regression diff --git a/src/test/regress/expected/single_node_triggers.out b/src/test/regress/expected/single_node_triggers.out index c7589e2e9126a46a15647def0c50687b7dc6f964..3ea503adcc840ea1262f3983c13910f0bb02cc2c 100644 --- a/src/test/regress/expected/single_node_triggers.out +++ b/src/test/regress/expected/single_node_triggers.out @@ -492,7 +492,8 @@ DROP TABLE some_t; -- bogus cases CREATE TRIGGER error_upd_and_col BEFORE UPDATE OR UPDATE OF a ON main_table FOR EACH ROW EXECUTE PROCEDURE trigger_func('error_upd_and_col'); -ERROR: duplicate trigger events specified at or near "ON" +ERROR: duplicate trigger events specified at or near "ON main_table +FOR" LINE 1: ...ER error_upd_and_col BEFORE UPDATE OR UPDATE OF a ON main_ta... ^ CREATE TRIGGER error_upd_a_a BEFORE UPDATE OF a, a ON main_table diff --git a/src/test/regress/expected/single_node_update.out b/src/test/regress/expected/single_node_update.out index 4eb51cd819f43f0d06a270cb3a16e3c61b6a535b..1f8d3563bdc96ef0760aeb3e5f14ceda8fa1526c 100644 --- a/src/test/regress/expected/single_node_update.out +++ b/src/test/regress/expected/single_node_update.out @@ -95,3 +95,470 @@ SELECT a, b, char_length(c) FROM update_test; (2 rows) DROP TABLE update_test; +-- test update, on update current_timestamp +create database mysql dbcompatibility 'B'; +\c mysql +create table update_test_e(a int, b timestamp on update current_timestamp); +insert into update_test_e values(1); +insert into update_test_e values(2); +select * from update_test_e; + a | b +---+--- + 1 | + 2 | +(2 rows) + +update update_test_e set a = 11 where a = 1; +select * from update_test_e; +--?.* +--?.* + 2 | +--?.* +(2 rows) + +update update_test_e set a=1, b = now()-10 where a=11; +select * from update_test_e; +--?.* +--?.* + 2 | +--?.* +(2 rows) + +update update_test_e set a = 1 where a = 1; +select * from update_test_e; +--?.* +--?.* + 2 | +--?.* +(2 rows) + +drop table update_test_e; +create table update_test_f(a int, b timestamp default current_timestamp on update current_timestamp); +insert into update_test_f values(1); +insert into update_test_f values(2); +select * from update_test_f; +--?.* +--?.* +--?.* +--?.* +(2 rows) + +update update_test_f set a = 11 where a = 1; +select * from update_test_f; +--?.* +--?.* +--?.* +--?.* +(2 rows) + +drop table update_test_f; +create table update_test_g(a int, b timestamp default current_timestamp on update current_timestamp, c timestamp on update current_timestamp); +insert into update_test_g values(1); +insert into update_test_g values(2); +select * from update_test_g; +--?.* +--?.* +--?.* +--?.* +(2 rows) + +update update_test_g set a = 1 where a = 1; +select * from update_test_g; +--?.* +--?.* +--?.* +--?.* +(2 rows) + +create incremental materialized view timestamp_test AS select * from update_test_g; +select * from timestamp_test; +--?.* +--?.* +--?.* +--?.* +(2 rows) + +drop materialized view timestamp_test; +drop table update_test_g; +create table update_test_h(a int, b timestamp default current_timestamp on update current_timestamp); +insert into update_test_h values(1); +select * from update_test_h; +--?.* +--?.* +--?.* +(1 row) + +with tab as (update update_test_h set a=10 where a=1 returning 1) select * from tab; + ?column? +---------- + 1 +(1 row) + +select * from update_test_h; +--?.* +--?.* +--?.* +(1 row) + +drop table update_test_h; +CREATE TABLE t2 (a int, b timestamp on update current_timestamp); +INSERT INTO t2 VALUES(1); +select * from t2; + a | b +---+--- + 1 | +(1 row) + +PREPARE insert_reason(integer) AS UPDATE t2 SET a = $1; +EXECUTE insert_reason(52); +select * from t2; +--?.* +--?.* +--?.* +(1 row) + +drop table t2; +create table t1(a int, b timestamp on update current_timestamp); +insert into t1 values(1); +CREATE OR REPLACE FUNCTION TEST1(val integer) +RETURNS VOID AS $$ +declare +val int; +begin +update t1 set a=val; +end; +$$ +LANGUAGE sql; +CREATE OR REPLACE FUNCTION TEST2(val integer) +RETURNS integer AS $$ +update t1 set a=val; +select 1; +$$ +LANGUAGE sql; +select TEST2(11); + test2 +------- + 1 +(1 row) + +select * from t1; +--?.* +--?.* +--?.* +(1 row) + +select TEST1(2); + test1 +------- + +(1 row) + +select * from t1; +--?.* +--?.* +--?.* +(1 row) + +DROP FUNCTION TEST1; +DROP FUNCTION TEST2; +drop table t1; +CREATE TABLE t1(a int, b timestamp DEFAULT current_timestamp on update current_timestamp); +CREATE TABLE t2(a int, b timestamp DEFAULT current_timestamp, c varchar, d timestamp on update current_timestamp); +CREATE TABLE t3(a int, b char, c timestamp on update current_timestamp, d text); +INSERT INTO t1 VALUES(1); +INSERT INTO t2(a,c) VALUES(1,'test'); +INSERT INTO t3(a,b,d) VALUES(1,'T','asfdsaf'); +SELECT * FROM t1; +--?.* +--?.* +--?.* +(1 row) + +SELECT * FROM t2; +--?.* +--?.* +--?.* +(1 row) + +SELECT * FROM t3; + a | b | c | d +---+---+---+--------- + 1 | T | | asfdsaf +(1 row) + +CREATE OR REPLACE FUNCTION test1(src int, dest int) RETURNS void AS $$ + BEGIN + UPDATE t1 SET a = dest WHERE a = src; + UPDATE t2 SET a = dest WHERE a = src; + UPDATE t3 SET a = dest WHERE a = src; + END; +$$ LANGUAGE PLPGSQL; +SELECT test1(1,10); + test1 +------- + +(1 row) + +SELECT * FROM t1; +--?.* +--?.* +--?.* +(1 row) + +SELECT * FROM t2; +--?.* +--?.* +--?.* +(1 row) + +SELECT * FROM t3; +--?.* +--?.* +--?.* +(1 row) + +DROP FUNCTION test1; +DROP TABLE t1; +DROP TABLE t2; +DROP TABLE t3; +CREATE TABLE t1(a int, b timestamp on update current_timestamp); +CREATE TABLE t2(a int, b timestamp on update current_timestamp); +INSERT INTO t1 VALUES(1); +INSERT INTO t2 VALUES(2); +SELECT * FROM t1; + a | b +---+--- + 1 | +(1 row) + +SELECT * FROM t2; + a | b +---+--- + 2 | +(1 row) + +with a as (update t1 set a=a+10 returning 1), b as (update t2 set a=a+10 returning 1) select * from a,b; + ?column? | ?column? +----------+---------- + 1 | 1 +(1 row) + +SELECT * FROM t1; +--?.* +--?.* +--?.* +(1 row) + +SELECT * FROM t2; +--?.* +--?.* +--?.* +(1 row) + +with a as (update t1 set a=a+10), b as (update t2 set a=a+10) select 1; + ?column? +---------- + 1 +(1 row) + +SELECT * FROM t1; +--?.* +--?.* +--?.* +(1 row) + +SELECT * FROM t2; +--?.* +--?.* +--?.* +(1 row) + +DROP TABLE t1; +DROP TABLE t2; +CREATE TABLE t4(a int, b timestamp); +\d t4 + Table "public.t4" + Column | Type | Modifiers +--------+-----------------------------+----------- + a | integer | + b | timestamp without time zone | + +alter table t4 modify b timestamp on update current_timestamp; +\d t4 + Table "public.t4" + Column | Type | Modifiers +--------+-----------------------------+----------------------------- + a | integer | + b | timestamp without time zone | on update pg_systimestamp() + +alter table t4 modify b timestamp; +\d t4 + Table "public.t4" + Column | Type | Modifiers +--------+-----------------------------+----------- + a | integer | + b | timestamp without time zone | + +alter table t4 alter b set default now(); +\d t4 + Table "public.t4" + Column | Type | Modifiers +--------+-----------------------------+--------------- + a | integer | + b | timestamp without time zone | default now() + +alter table t4 alter b drop default; +\d t4 + Table "public.t4" + Column | Type | Modifiers +--------+-----------------------------+----------- + a | integer | + b | timestamp without time zone | + +alter table t4 alter b set default now(); +\d t4 + Table "public.t4" + Column | Type | Modifiers +--------+-----------------------------+--------------- + a | integer | + b | timestamp without time zone | default now() + +alter table t4 modify b timestamp on update current_timestamp; +\d t4 + Table "public.t4" + Column | Type | Modifiers +--------+-----------------------------+------------------------------------------- + a | integer | + b | timestamp without time zone | default now() on update pg_systimestamp() + +alter table t4 alter b drop default; +\d t4 + Table "public.t4" + Column | Type | Modifiers +--------+-----------------------------+----------------------------- + a | integer | + b | timestamp without time zone | on update pg_systimestamp() + +alter table t4 modify b timestamp; +\d t4 + Table "public.t4" + Column | Type | Modifiers +--------+-----------------------------+----------- + a | integer | + b | timestamp without time zone | + +alter table t4 modify b timestamp on update current_timestamp; +\d t4 + Table "public.t4" + Column | Type | Modifiers +--------+-----------------------------+----------------------------- + a | integer | + b | timestamp without time zone | on update pg_systimestamp() + +alter table t4 alter b set default now(); +\d t4; + Table "public.t4" + Column | Type | Modifiers +--------+-----------------------------+------------------------------------------- + a | integer | + b | timestamp without time zone | default now() on update pg_systimestamp() + +alter table t4 modify b timestamp; +\d t4 + Table "public.t4" + Column | Type | Modifiers +--------+-----------------------------+--------------- + a | integer | + b | timestamp without time zone | default now() + +alter table t4 alter b drop default; +\d t4 + Table "public.t4" + Column | Type | Modifiers +--------+-----------------------------+----------- + a | integer | + b | timestamp without time zone | + +alter table t4 modify b not null; +alter table t4 modify b timestamp on update current_timestamp; +\d t4 + Table "public.t4" + Column | Type | Modifiers +--------+-----------------------------+-------------------------------------- + a | integer | + b | timestamp without time zone | not null on update pg_systimestamp() + +alter table t4 modify b null; +alter table t4 modify b timestamp; +\d t4 + Table "public.t4" + Column | Type | Modifiers +--------+-----------------------------+----------- + a | integer | + b | timestamp without time zone | + +alter table t4 modify b timestamp on update current_timestamp; +\d t4 + Table "public.t4" + Column | Type | Modifiers +--------+-----------------------------+----------------------------- + a | integer | + b | timestamp without time zone | on update pg_systimestamp() + +alter table t4 modify b timestamp on update localtimestamp; +\d t4 + Table "public.t4" + Column | Type | Modifiers +--------+-----------------------------+------------------------------------------------------ + a | integer | + b | timestamp without time zone | on update ('now'::text)::timestamp without time zone + +CREATE TABLE t5(id int, a timestamp default now() on update current_timestamp, b timestamp on update current_timestamp, c timestamp default now()); +\d t5 + Table "public.t5" + Column | Type | Modifiers +--------+-----------------------------+------------------------------------------- + id | integer | + a | timestamp without time zone | default now() on update pg_systimestamp() + b | timestamp without time zone | on update pg_systimestamp() + c | timestamp without time zone | default now() + +create table t6 (like t5 including defaults); +\d t6 + Table "public.t6" + Column | Type | Modifiers +--------+-----------------------------+------------------------------------------- + id | integer | + a | timestamp without time zone | default now() on update pg_systimestamp() + b | timestamp without time zone | on update pg_systimestamp() + c | timestamp without time zone | default now() + +alter table t6 modify b timestamp on update localtimestamp; +\d t6 + Table "public.t6" + Column | Type | Modifiers +--------+-----------------------------+------------------------------------------------------ + id | integer | + a | timestamp without time zone | default now() on update pg_systimestamp() + b | timestamp without time zone | on update ('now'::text)::timestamp without time zone + c | timestamp without time zone | default now() + +alter table t6 modify b timestamp; +\d t6 + Table "public.t6" + Column | Type | Modifiers +--------+-----------------------------+------------------------------------------- + id | integer | + a | timestamp without time zone | default now() on update pg_systimestamp() + b | timestamp without time zone | + c | timestamp without time zone | default now() + +-- \! @abs_bindir@/gs_dump mysql -p @portstring@ -f @abs_bindir@/dump_type.sql -F p >/dev/null 2>&1; +-- create table test_feature(a int, b timestamp on update current_timestamp); +-- insert into test_feature values (1); +-- update test_feature set a=2 where a=1; +-- select * from test_feature; +-- \! @abs_bindir@/gsql -d mysql -p @portstring@ -c "update test_feature set a=3;" >/dev/null 2>&1; +-- select * from test_feature; +\c regression +DROP database mysql; \ No newline at end of file diff --git a/src/test/regress/sql/function_get_table_def.sql b/src/test/regress/sql/function_get_table_def.sql index 644b0c787e7c5afc38b959f30ae0093fab1cc09d..a388190fa62b28514db61179f3edc82140f2c5f3 100644 --- a/src/test/regress/sql/function_get_table_def.sql +++ b/src/test/regress/sql/function_get_table_def.sql @@ -216,3 +216,9 @@ drop table generated_test; reset current_schema; drop schema test_get_table_def cascade; + +create database mysql dbcompatibility 'B'; +\c mysql +create table if not exists test(a int, b timestamp default now() on update current_timestamp); +select * from pg_get_tabledef('test'); +\c regression diff --git a/src/test/regress/sql/single_node_update.sql b/src/test/regress/sql/single_node_update.sql index a8a028f7101988eadae702deae190f717f72312c..969d2f577ce48deb4563f66fd3705fcf7cc9128a 100644 --- a/src/test/regress/sql/single_node_update.sql +++ b/src/test/regress/sql/single_node_update.sql @@ -59,3 +59,179 @@ UPDATE update_test SET c = repeat('x', 10000) WHERE c = 'car'; SELECT a, b, char_length(c) FROM update_test; DROP TABLE update_test; + +-- test update, on update current_timestamp +create database mysql dbcompatibility 'B'; +\c mysql +create table update_test_e(a int, b timestamp on update current_timestamp); +insert into update_test_e values(1); +insert into update_test_e values(2); +select * from update_test_e; +update update_test_e set a = 11 where a = 1; +select * from update_test_e; +update update_test_e set a=1, b = now()-10 where a=11; +select * from update_test_e; +update update_test_e set a = 1 where a = 1; +select * from update_test_e; +drop table update_test_e; + +create table update_test_f(a int, b timestamp default current_timestamp on update current_timestamp); +insert into update_test_f values(1); +insert into update_test_f values(2); +select * from update_test_f; +update update_test_f set a = 11 where a = 1; +select * from update_test_f; +drop table update_test_f; + +create table update_test_g(a int, b timestamp default current_timestamp on update current_timestamp, c timestamp on update current_timestamp); +insert into update_test_g values(1); +insert into update_test_g values(2); +select * from update_test_g; +update update_test_g set a = 1 where a = 1; +select * from update_test_g; +create incremental materialized view timestamp_test AS select * from update_test_g; +select * from timestamp_test; +drop materialized view timestamp_test; +drop table update_test_g; + +create table update_test_h(a int, b timestamp default current_timestamp on update current_timestamp); +insert into update_test_h values(1); +select * from update_test_h; +with tab as (update update_test_h set a=10 where a=1 returning 1) select * from tab; +select * from update_test_h; +drop table update_test_h; + +CREATE TABLE t2 (a int, b timestamp on update current_timestamp); +INSERT INTO t2 VALUES(1); +select * from t2; +PREPARE insert_reason(integer) AS UPDATE t2 SET a = $1; +EXECUTE insert_reason(52); +select * from t2; +drop table t2; + +create table t1(a int, b timestamp on update current_timestamp); +insert into t1 values(1); + +CREATE OR REPLACE FUNCTION TEST1(val integer) +RETURNS VOID AS $$ +declare +val int; +begin +update t1 set a=val; +end; +$$ +LANGUAGE sql; +CREATE OR REPLACE FUNCTION TEST2(val integer) +RETURNS integer AS $$ +update t1 set a=val; +select 1; +$$ +LANGUAGE sql; +select TEST2(11); +select * from t1; +select TEST1(2); +select * from t1; +DROP FUNCTION TEST1; +DROP FUNCTION TEST2; +drop table t1; + +CREATE TABLE t1(a int, b timestamp DEFAULT current_timestamp on update current_timestamp); +CREATE TABLE t2(a int, b timestamp DEFAULT current_timestamp, c varchar, d timestamp on update current_timestamp); +CREATE TABLE t3(a int, b char, c timestamp on update current_timestamp, d text); +INSERT INTO t1 VALUES(1); +INSERT INTO t2(a,c) VALUES(1,'test'); +INSERT INTO t3(a,b,d) VALUES(1,'T','asfdsaf'); +SELECT * FROM t1; +SELECT * FROM t2; +SELECT * FROM t3; +CREATE OR REPLACE FUNCTION test1(src int, dest int) RETURNS void AS $$ + BEGIN + UPDATE t1 SET a = dest WHERE a = src; + UPDATE t2 SET a = dest WHERE a = src; + UPDATE t3 SET a = dest WHERE a = src; + END; +$$ LANGUAGE PLPGSQL; +SELECT test1(1,10); +SELECT * FROM t1; +SELECT * FROM t2; +SELECT * FROM t3; +DROP FUNCTION test1; +DROP TABLE t1; +DROP TABLE t2; +DROP TABLE t3; + +CREATE TABLE t1(a int, b timestamp on update current_timestamp); +CREATE TABLE t2(a int, b timestamp on update current_timestamp); +INSERT INTO t1 VALUES(1); +INSERT INTO t2 VALUES(2); +SELECT * FROM t1; +SELECT * FROM t2; +with a as (update t1 set a=a+10 returning 1), b as (update t2 set a=a+10 returning 1) select * from a,b; +SELECT * FROM t1; +SELECT * FROM t2; +with a as (update t1 set a=a+10), b as (update t2 set a=a+10) select 1; +SELECT * FROM t1; +SELECT * FROM t2; +DROP TABLE t1; +DROP TABLE t2; + +CREATE TABLE t4(a int, b timestamp); +\d t4 +alter table t4 modify b timestamp on update current_timestamp; +\d t4 +alter table t4 modify b timestamp; +\d t4 +alter table t4 alter b set default now(); +\d t4 +alter table t4 alter b drop default; +\d t4 + +alter table t4 alter b set default now(); +\d t4 +alter table t4 modify b timestamp on update current_timestamp; +\d t4 +alter table t4 alter b drop default; +\d t4 +alter table t4 modify b timestamp; +\d t4 + +alter table t4 modify b timestamp on update current_timestamp; +\d t4 +alter table t4 alter b set default now(); +\d t4; +alter table t4 modify b timestamp; +\d t4 +alter table t4 alter b drop default; +\d t4 +alter table t4 modify b not null; +alter table t4 modify b timestamp on update current_timestamp; +\d t4 +alter table t4 modify b null; +alter table t4 modify b timestamp; +\d t4 + +alter table t4 modify b timestamp on update current_timestamp; +\d t4 +alter table t4 modify b timestamp on update localtimestamp; +\d t4 + +CREATE TABLE t5(id int, a timestamp default now() on update current_timestamp, b timestamp on update current_timestamp, c timestamp default now()); +\d t5 +create table t6 (like t5 including defaults); +\d t6 +alter table t6 modify b timestamp on update localtimestamp; +\d t6 +alter table t6 modify b timestamp; +\d t6 + +-- \! @abs_bindir@/gs_dump mysql -p @portstring@ -f @abs_bindir@/dump_type.sql -F p >/dev/null 2>&1; + +-- create table test_feature(a int, b timestamp on update current_timestamp); +-- insert into test_feature values (1); +-- update test_feature set a=2 where a=1; +-- select * from test_feature; +-- \! @abs_bindir@/gsql -d mysql -p @portstring@ -c "update test_feature set a=3;" >/dev/null 2>&1; +-- select * from test_feature; + +\c regression +DROP database mysql; \ No newline at end of file