diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index 46f5e1c400..b08b314f9f 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -31,6 +31,7 @@ #include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" +#include "optimizer/inherit.h" #include "optimizer/optimizer.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" @@ -1813,7 +1814,8 @@ postgresPlanForeignModify(PlannerInfo *root, else if (operation == CMD_UPDATE) { int col; - Bitmapset *allUpdatedCols = bms_union(rte->updatedCols, rte->extraUpdatedCols); + RelOptInfo *rel = find_base_rel(root, resultRelation); + Bitmapset *allUpdatedCols = get_rel_all_updated_cols(root, rel); col = -1; while ((col = bms_next_member(allUpdatedCols, col)) >= 0) diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index 358e582883..122de06802 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -132,6 +132,8 @@ CreateExecutorState(void) estate->es_insert_pending_result_relations = NIL; estate->es_insert_pending_modifytables = NIL; + estate->es_resultrelinfo_extra = NIL; + estate->es_param_list_info = NULL; estate->es_param_exec_vals = NULL; @@ -1323,26 +1325,25 @@ ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate) Bitmapset * ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate) { - /* see ExecGetInsertedCols() */ - if (relinfo->ri_RangeTableIndex != 0) - { - RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate); + Relation rel = relinfo->ri_RelationDesc; + TupleDesc tupdesc = RelationGetDescr(rel); - return rte->extraUpdatedCols; - } - else if (relinfo->ri_RootResultRelInfo) + if (tupdesc->constr && tupdesc->constr->has_generated_stored) { - ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo; - RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate); + ListCell *lc; - if (relinfo->ri_RootToPartitionMap != NULL) - return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap, - rte->extraUpdatedCols); - else - return rte->extraUpdatedCols; + /* Assert that ExecInitStoredGenerated has been called. */ + Assert(relinfo->ri_GeneratedExprs != NULL); + foreach(lc, estate->es_resultrelinfo_extra) + { + ResultRelInfoExtra *rextra = (ResultRelInfoExtra *) lfirst(lc); + + if (rextra->rinfo == relinfo) + return rextra->ri_extraUpdatedCols; + } + Assert(false); /* shouldn't get here */ } - else - return NULL; + return NULL; } /* Return columns being updated, including generated columns */ diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 8794528d6a..4c49edef25 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -54,6 +54,7 @@ #include "foreign/fdwapi.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" +#include "optimizer/optimizer.h" #include "rewrite/rewriteHandler.h" #include "storage/bufmgr.h" #include "storage/lmgr.h" @@ -352,6 +353,101 @@ ExecCheckTIDVisible(EState *estate, ExecClearTuple(tempSlot); } +/* + * Initialize to compute stored generated columns for a tuple + * + * This fills the resultRelInfo's ri_GeneratedExprs field and makes an + * associated ResultRelInfoExtra struct to hold ri_extraUpdatedCols. + * (Currently, ri_extraUpdatedCols is consulted only in UPDATE, but we might + * as well fill it for INSERT too.) + */ +static void +ExecInitStoredGenerated(ResultRelInfo *resultRelInfo, + EState *estate, + CmdType cmdtype) +{ + Relation rel = resultRelInfo->ri_RelationDesc; + TupleDesc tupdesc = RelationGetDescr(rel); + int natts = tupdesc->natts; + Bitmapset *updatedCols; + ResultRelInfoExtra *rextra; + MemoryContext oldContext; + + /* Don't call twice */ + Assert(resultRelInfo->ri_GeneratedExprs == NULL); + + /* Nothing to do if no generated columns */ + if (!(tupdesc->constr && tupdesc->constr->has_generated_stored)) + return; + + /* + * In an UPDATE, we can skip computing any generated columns that do not + * depend on any UPDATE target column. But if there is a BEFORE ROW + * UPDATE trigger, we cannot skip because the trigger might change more + * columns. + */ + if (cmdtype == CMD_UPDATE && + !(rel->trigdesc && rel->trigdesc->trig_update_before_row)) + updatedCols = ExecGetUpdatedCols(resultRelInfo, estate); + else + updatedCols = NULL; + + /* + * Make sure these data structures are built in the per-query memory + * context so they'll survive throughout the query. + */ + oldContext = MemoryContextSwitchTo(estate->es_query_cxt); + + resultRelInfo->ri_GeneratedExprs = + (ExprState **) palloc0(natts * sizeof(ExprState *)); + resultRelInfo->ri_NumGeneratedNeeded = 0; + + rextra = palloc_object(ResultRelInfoExtra); + rextra->rinfo = resultRelInfo; + rextra->ri_extraUpdatedCols = NULL; + estate->es_resultrelinfo_extra = lappend(estate->es_resultrelinfo_extra, + rextra); + + for (int i = 0; i < natts; i++) + { + if (TupleDescAttr(tupdesc, i)->attgenerated == ATTRIBUTE_GENERATED_STORED) + { + Expr *expr; + + /* Fetch the GENERATED AS expression tree */ + expr = (Expr *) build_column_default(rel, i + 1); + if (expr == NULL) + elog(ERROR, "no generation expression found for column number %d of table \"%s\"", + i + 1, RelationGetRelationName(rel)); + + /* + * If it's an update with a known set of update target columns, + * see if we can skip the computation. + */ + if (updatedCols) + { + Bitmapset *attrs_used = NULL; + + pull_varattnos((Node *) expr, 1, &attrs_used); + + if (!bms_overlap(updatedCols, attrs_used)) + continue; /* need not update this column */ + } + + /* No luck, so prepare the expression for execution */ + resultRelInfo->ri_GeneratedExprs[i] = ExecPrepareExpr(expr, estate); + resultRelInfo->ri_NumGeneratedNeeded++; + + /* And mark this column in rextra->ri_extraUpdatedCols */ + rextra->ri_extraUpdatedCols = + bms_add_member(rextra->ri_extraUpdatedCols, + i + 1 - FirstLowInvalidHeapAttributeNumber); + } + } + + MemoryContextSwitchTo(oldContext); +} + /* * Compute stored generated columns for a tuple */ @@ -363,58 +459,22 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo, Relation rel = resultRelInfo->ri_RelationDesc; TupleDesc tupdesc = RelationGetDescr(rel); int natts = tupdesc->natts; + ExprContext *econtext = GetPerTupleExprContext(estate); MemoryContext oldContext; Datum *values; bool *nulls; + /* We should not be called unless this is true */ Assert(tupdesc->constr && tupdesc->constr->has_generated_stored); /* - * 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. + * For relations named directly in the query, ExecInitStoredGenerated + * should have been called already; but this might not have happened yet + * for a partition child rel. Also, it's convenient for outside callers + * to not have to call ExecInitStoredGenerated explicitly. */ if (resultRelInfo->ri_GeneratedExprs == NULL) - { - oldContext = MemoryContextSwitchTo(estate->es_query_cxt); - - resultRelInfo->ri_GeneratedExprs = - (ExprState **) palloc(natts * sizeof(ExprState *)); - resultRelInfo->ri_NumGeneratedNeeded = 0; - - for (int i = 0; i < natts; i++) - { - if (TupleDescAttr(tupdesc, i)->attgenerated == ATTRIBUTE_GENERATED_STORED) - { - Expr *expr; - - /* - * If it's an update and the current column was not marked as - * being updated, then we can skip the computation. But if - * there is a BEFORE ROW UPDATE trigger, we cannot skip - * because the trigger might affect additional columns. - */ - if (cmdtype == CMD_UPDATE && - !(rel->trigdesc && rel->trigdesc->trig_update_before_row) && - !bms_is_member(i + 1 - FirstLowInvalidHeapAttributeNumber, - ExecGetExtraUpdatedCols(resultRelInfo, estate))) - { - resultRelInfo->ri_GeneratedExprs[i] = NULL; - continue; - } - - expr = (Expr *) build_column_default(rel, i + 1); - if (expr == NULL) - elog(ERROR, "no generation expression found for column number %d of table \"%s\"", - i + 1, RelationGetRelationName(rel)); - - resultRelInfo->ri_GeneratedExprs[i] = ExecPrepareExpr(expr, estate); - resultRelInfo->ri_NumGeneratedNeeded++; - } - } - - MemoryContextSwitchTo(oldContext); - } + ExecInitStoredGenerated(resultRelInfo, estate, cmdtype); /* * If no generated columns have been affected by this change, then skip @@ -435,14 +495,13 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo, { Form_pg_attribute attr = TupleDescAttr(tupdesc, i); - if (attr->attgenerated == ATTRIBUTE_GENERATED_STORED && - resultRelInfo->ri_GeneratedExprs[i]) + if (resultRelInfo->ri_GeneratedExprs[i]) { - ExprContext *econtext; Datum val; bool isnull; - econtext = GetPerTupleExprContext(estate); + Assert(attr->attgenerated == ATTRIBUTE_GENERATED_STORED); + econtext->ecxt_scantuple = slot; val = ExecEvalExpr(resultRelInfo->ri_GeneratedExprs[i], econtext, &isnull); @@ -4088,6 +4147,15 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) elog(ERROR, "could not find junk wholerow column"); } } + + /* + * For INSERT and UPDATE, prepare to evaluate any generated columns. + * We must do this now, even if we never insert or update any rows, + * because we have to fill resultRelInfo->ri_extraUpdatedCols for + * possible use by the trigger machinery. + */ + if (operation == CMD_INSERT || operation == CMD_UPDATE) + ExecInitStoredGenerated(resultRelInfo, estate, operation); } /* diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 6dbffe121a..3c11f5db5a 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -25,6 +25,7 @@ #include "optimizer/inherit.h" #include "optimizer/optimizer.h" #include "optimizer/pathnode.h" +#include "optimizer/plancat.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" @@ -47,6 +48,10 @@ static void expand_single_inheritance_child(PlannerInfo *root, Index *childRTindex_p); static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, List *translated_vars); +static Bitmapset *translate_col_privs_multilevel(PlannerInfo *root, + RelOptInfo *rel, + RelOptInfo *parent_rel, + Bitmapset *parent_cols); static void expand_appendrel_subquery(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte, Index rti); @@ -333,11 +338,6 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo, root->partColsUpdated = has_partition_attrs(parentrel, parentrte->updatedCols, NULL); - /* - * There shouldn't be any generated columns in the partition key. - */ - Assert(!has_partition_attrs(parentrel, parentrte->extraUpdatedCols, NULL)); - /* Nothing further to do here if there are no partitions. */ if (partdesc->nparts == 0) return; @@ -556,15 +556,12 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, appinfo->translated_vars); childrte->updatedCols = translate_col_privs(parentrte->updatedCols, appinfo->translated_vars); - childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols, - appinfo->translated_vars); } else { childrte->selectedCols = bms_copy(parentrte->selectedCols); childrte->insertedCols = bms_copy(parentrte->insertedCols); childrte->updatedCols = bms_copy(parentrte->updatedCols); - childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols); } /* @@ -648,6 +645,52 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, } } +/* + * get_rel_all_updated_cols + * Returns the set of columns of a given "simple" relation that are + * updated by this query. + */ +Bitmapset * +get_rel_all_updated_cols(PlannerInfo *root, RelOptInfo *rel) +{ + Index relid; + RangeTblEntry *rte; + Bitmapset *updatedCols, + *extraUpdatedCols; + + Assert(root->parse->commandType == CMD_UPDATE); + Assert(IS_SIMPLE_REL(rel)); + + /* + * We obtain updatedCols for the query's result relation. Then, if + * necessary, we map it to the column numbers of the relation for which + * they were requested. + */ + relid = root->parse->resultRelation; + rte = planner_rt_fetch(relid, root); + + updatedCols = rte->updatedCols; + + if (rel->relid != relid) + { + RelOptInfo *top_parent_rel = find_base_rel(root, relid); + + Assert(IS_OTHER_REL(rel)); + + updatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel, + updatedCols); + } + + /* + * Now we must check to see if there are any generated columns that depend + * on the updatedCols, and add them to the result. + */ + extraUpdatedCols = get_dependent_generated_columns(root, rel->relid, + updatedCols); + + return bms_union(updatedCols, extraUpdatedCols); +} + /* * translate_col_privs * Translate a bitmapset representing per-column privileges from the @@ -700,6 +743,44 @@ translate_col_privs(const Bitmapset *parent_privs, return child_privs; } +/* + * translate_col_privs_multilevel + * Recursively translates the column numbers contained in 'parent_cols' + * to the column numbers of a descendant relation given by 'rel' + * + * Note that because this is based on translate_col_privs, it will expand + * a whole-row reference into all inherited columns. This is not an issue + * for current usages, but beware. + */ +static Bitmapset * +translate_col_privs_multilevel(PlannerInfo *root, RelOptInfo *rel, + RelOptInfo *parent_rel, + Bitmapset *parent_cols) +{ + AppendRelInfo *appinfo; + + /* Fast path for easy case. */ + if (parent_cols == NULL) + return NULL; + + Assert(root->append_rel_array != NULL); + appinfo = root->append_rel_array[rel->relid]; + Assert(appinfo != NULL); + + /* Recurse if immediate parent is not the top parent. */ + if (appinfo->parent_relid != parent_rel->relid) + { + RelOptInfo *next_parent = find_base_rel(root, appinfo->parent_relid); + + parent_cols = translate_col_privs_multilevel(root, next_parent, + parent_rel, + parent_cols); + } + + /* Now translate for this child. */ + return translate_col_privs(parent_cols, appinfo->translated_vars); +} + /* * expand_appendrel_subquery * Add "other rel" RelOptInfos for the children of an appendrel baserel diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index c328a2f912..419f2ac55f 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -2198,6 +2198,11 @@ has_row_triggers(PlannerInfo *root, Index rti, CmdType event) return result; } +/* + * has_stored_generated_columns + * + * Does table identified by RTI have any STORED GENERATED columns? + */ bool has_stored_generated_columns(PlannerInfo *root, Index rti) { @@ -2217,6 +2222,57 @@ has_stored_generated_columns(PlannerInfo *root, Index rti) return result; } +/* + * get_dependent_generated_columns + * + * Get the column numbers of any STORED GENERATED columns of the relation + * that depend on any column listed in target_cols. Both the input and + * result bitmapsets contain column numbers offset by + * FirstLowInvalidHeapAttributeNumber. + */ +Bitmapset * +get_dependent_generated_columns(PlannerInfo *root, Index rti, + Bitmapset *target_cols) +{ + Bitmapset *dependentCols = NULL; + RangeTblEntry *rte = planner_rt_fetch(rti, root); + Relation relation; + TupleDesc tupdesc; + TupleConstr *constr; + + /* Assume we already have adequate lock */ + relation = table_open(rte->relid, NoLock); + + tupdesc = RelationGetDescr(relation); + constr = tupdesc->constr; + + if (constr && constr->has_generated_stored) + { + for (int i = 0; i < constr->num_defval; i++) + { + AttrDefault *defval = &constr->defval[i]; + Node *expr; + Bitmapset *attrs_used = NULL; + + /* skip if not generated column */ + if (!TupleDescAttr(tupdesc, defval->adnum - 1)->attgenerated) + continue; + + /* identify columns this generated column depends on */ + expr = stringToNode(defval->adbin); + pull_varattnos(expr, 1, &attrs_used); + + if (bms_overlap(target_cols, attrs_used)) + dependentCols = bms_add_member(dependentCols, + defval->adnum - FirstLowInvalidHeapAttributeNumber); + } + } + + table_close(relation, NoLock); + + return dependentCols; +} + /* * set_relation_partition_info * diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c index 1825daf903..e2e3da0591 100644 --- a/src/backend/replication/logical/worker.c +++ b/src/backend/replication/logical/worker.c @@ -1851,9 +1851,6 @@ apply_handle_update(StringInfo s) } } - /* Also populate extraUpdatedCols, in case we have generated columns */ - fill_extraUpdatedCols(target_rte, rel->localrel); - /* Build the search tuple. */ oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate)); slot_store_data(remoteslot, rel, diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index 45bcc84cf0..dcfcdd0dd4 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -1626,43 +1626,6 @@ rewriteValuesRTEToNulls(Query *parsetree, RangeTblEntry *rte) } -/* - * Record in target_rte->extraUpdatedCols the indexes of any generated columns - * that depend on any columns mentioned in target_rte->updatedCols. - */ -void -fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation) -{ - TupleDesc tupdesc = RelationGetDescr(target_relation); - TupleConstr *constr = tupdesc->constr; - - target_rte->extraUpdatedCols = NULL; - - if (constr && constr->has_generated_stored) - { - for (int i = 0; i < constr->num_defval; i++) - { - AttrDefault *defval = &constr->defval[i]; - Node *expr; - Bitmapset *attrs_used = NULL; - - /* skip if not generated column */ - if (!TupleDescAttr(tupdesc, defval->adnum - 1)->attgenerated) - continue; - - /* identify columns this generated column depends on */ - expr = stringToNode(defval->adbin); - pull_varattnos(expr, 1, &attrs_used); - - if (bms_overlap(target_rte->updatedCols, attrs_used)) - target_rte->extraUpdatedCols = - bms_add_member(target_rte->extraUpdatedCols, - defval->adnum - FirstLowInvalidHeapAttributeNumber); - } - } -} - - /* * matchLocks - * match the list of locks and returns the matching rules @@ -3831,9 +3794,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events, int orig_rt_length) parsetree->override, rt_entry_relation, NULL, 0, NULL); - - /* Also populate extraUpdatedCols (for generated columns) */ - fill_extraUpdatedCols(rt_entry, rt_entry_relation); } else if (event == CMD_MERGE) { diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 580e99242b..f34d06eff4 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -558,6 +558,19 @@ typedef struct ResultRelInfo List *ri_ancestorResultRels; } ResultRelInfo; +/* + * To avoid an ABI-breaking change in the size of ResultRelInfo in back + * branches, we create one of these for each result relation for which we've + * computed extraUpdatedCols, and store it in EState.es_resultrelinfo_extra. + */ +typedef struct ResultRelInfoExtra +{ + ResultRelInfo *rinfo; /* owning ResultRelInfo */ + + /* For INSERT/UPDATE, attnums of generated columns to be computed */ + Bitmapset *ri_extraUpdatedCols; +} ResultRelInfoExtra; + /* ---------------- * AsyncRequest * @@ -684,6 +697,9 @@ typedef struct EState */ List *es_insert_pending_result_relations; List *es_insert_pending_modifytables; + + /* List of ResultRelInfoExtra structs (see above) */ + List *es_resultrelinfo_extra; } EState; diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 1e7280340b..6944362c7a 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -977,14 +977,7 @@ typedef struct PartitionCmd * which triggers to fire and in FDWs to know which changed columns they * need to ship off. * - * Generated columns that are caused to be updated by an update to a base - * column are listed in extraUpdatedCols. This is not considered for - * permission checking, but it is useful in those places that want to know - * the full set of columns being updated as opposed to only the ones the - * user explicitly mentioned in the query. (There is currently no need for - * an extraInsertedCols, but it could exist.) Note that extraUpdatedCols - * is populated during query rewrite, NOT in the parser, since generated - * columns could be added after a rule has been parsed and stored. + * extraUpdatedCols is no longer used or maintained; it's always empty. * * securityQuals is a list of security barrier quals (boolean expressions), * to be tested in the listed order before returning a row from the diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h index adcb1d7372..8ebd42b757 100644 --- a/src/include/optimizer/inherit.h +++ b/src/include/optimizer/inherit.h @@ -20,6 +20,8 @@ extern void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte, Index rti); +extern Bitmapset *get_rel_all_updated_cols(PlannerInfo *root, RelOptInfo *rel); + extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel, RelOptInfo *childrel, RangeTblEntry *childRTE, AppendRelInfo *appinfo); diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index ad29b5d107..b0c06ca14e 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -74,4 +74,7 @@ extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event); extern bool has_stored_generated_columns(PlannerInfo *root, Index rti); +extern Bitmapset *get_dependent_generated_columns(PlannerInfo *root, Index rti, + Bitmapset *target_cols); + #endif /* PLANCAT_H */ diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h index 90ecf109af..b4f96f298b 100644 --- a/src/include/rewrite/rewriteHandler.h +++ b/src/include/rewrite/rewriteHandler.h @@ -24,9 +24,6 @@ extern void AcquireRewriteLocks(Query *parsetree, extern Node *build_column_default(Relation rel, int attrno); -extern void fill_extraUpdatedCols(RangeTblEntry *target_rte, - Relation target_relation); - extern Query *get_view_query(Relation view); extern const char *view_query_is_auto_updatable(Query *viewquery, bool check_cols); diff --git a/src/test/regress/expected/generated.out b/src/test/regress/expected/generated.out index bb4190340e..1db5f9ed47 100644 --- a/src/test/regress/expected/generated.out +++ b/src/test/regress/expected/generated.out @@ -337,6 +337,25 @@ CREATE TABLE gtest1_2 () INHERITS (gtest0, gtesty); -- error NOTICE: merging multiple inherited definitions of column "b" ERROR: inherited column "b" has a generation conflict DROP TABLE gtesty; +-- test correct handling of GENERATED column that's only in child +CREATE TABLE gtestp (f1 int); +CREATE TABLE gtestc (f2 int GENERATED ALWAYS AS (f1+1) STORED) INHERITS(gtestp); +INSERT INTO gtestc values(42); +TABLE gtestc; + f1 | f2 +----+---- + 42 | 43 +(1 row) + +UPDATE gtestp SET f1 = f1 * 10; +TABLE gtestc; + f1 | f2 +-----+----- + 420 | 421 +(1 row) + +DROP TABLE gtestp CASCADE; +NOTICE: drop cascades to table gtestc -- test stored update CREATE TABLE gtest3 (a int, b int GENERATED ALWAYS AS (a * 3) STORED); INSERT INTO gtest3 (a) VALUES (1), (2), (3), (NULL); diff --git a/src/test/regress/sql/generated.sql b/src/test/regress/sql/generated.sql index 378297e6ea..39eec40bce 100644 --- a/src/test/regress/sql/generated.sql +++ b/src/test/regress/sql/generated.sql @@ -149,6 +149,15 @@ CREATE TABLE gtesty (x int, b int DEFAULT 55); CREATE TABLE gtest1_2 () INHERITS (gtest0, gtesty); -- error DROP TABLE gtesty; +-- test correct handling of GENERATED column that's only in child +CREATE TABLE gtestp (f1 int); +CREATE TABLE gtestc (f2 int GENERATED ALWAYS AS (f1+1) STORED) INHERITS(gtestp); +INSERT INTO gtestc values(42); +TABLE gtestc; +UPDATE gtestp SET f1 = f1 * 10; +TABLE gtestc; +DROP TABLE gtestp CASCADE; + -- test stored update CREATE TABLE gtest3 (a int, b int GENERATED ALWAYS AS (a * 3) STORED); INSERT INTO gtest3 (a) VALUES (1), (2), (3), (NULL);