/* The following fields are set later if needed */
resultRelInfo->ri_RowIdAttNo = 0;
+ resultRelInfo->ri_extraUpdatedCols = NULL;
resultRelInfo->ri_projectNew = NULL;
resultRelInfo->ri_newTupleSlot = NULL;
resultRelInfo->ri_oldTupleSlot = NULL;
Bitmapset *
ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
{
- if (relinfo->ri_RangeTableIndex != 0)
- {
- RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+#ifdef USE_ASSERT_CHECKING
+ /* Verify that ExecInitStoredGenerated has been called if needed. */
+ Relation rel = relinfo->ri_RelationDesc;
+ TupleDesc tupdesc = RelationGetDescr(rel);
- return rte->extraUpdatedCols;
- }
- else if (relinfo->ri_RootResultRelInfo)
- {
- ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
- RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
- TupleConversionMap *map = ExecGetRootToChildMap(relinfo, estate);
+ if (tupdesc->constr && tupdesc->constr->has_generated_stored)
+ Assert(relinfo->ri_GeneratedExprs != NULL);
+#endif
- if (map != NULL)
- return execute_attr_map_cols(map->attrMap, rte->extraUpdatedCols);
- else
- return rte->extraUpdatedCols;
- }
- else
- return NULL;
+ return relinfo->ri_extraUpdatedCols;
}
/* Return columns being updated, including generated columns */
#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"
}
/*
- * Compute stored generated columns for a tuple
+ * Initialize to compute stored generated columns for a tuple
+ *
+ * This fills the resultRelInfo's ri_GeneratedExprs and ri_extraUpdatedCols
+ * fields. (Currently, ri_extraUpdatedCols is consulted only in UPDATE,
+ * but we might as well fill it for INSERT too.)
*/
-void
-ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
- EState *estate, TupleTableSlot *slot,
- CmdType cmdtype)
+static void
+ExecInitStoredGenerated(ResultRelInfo *resultRelInfo,
+ EState *estate,
+ CmdType cmdtype)
{
Relation rel = resultRelInfo->ri_RelationDesc;
TupleDesc tupdesc = RelationGetDescr(rel);
int natts = tupdesc->natts;
+ Bitmapset *updatedCols;
MemoryContext oldContext;
- Datum *values;
- bool *nulls;
- Assert(tupdesc->constr && tupdesc->constr->has_generated_stored);
+ /* 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;
/*
- * 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.
+ * 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 (resultRelInfo->ri_GeneratedExprs == NULL)
- {
- oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
+ if (cmdtype == CMD_UPDATE &&
+ !(rel->trigdesc && rel->trigdesc->trig_update_before_row))
+ updatedCols = ExecGetUpdatedCols(resultRelInfo, estate);
+ else
+ updatedCols = NULL;
- resultRelInfo->ri_GeneratedExprs =
- (ExprState **) palloc(natts * sizeof(ExprState *));
- resultRelInfo->ri_NumGeneratedNeeded = 0;
+ /*
+ * 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);
- for (int i = 0; i < natts; i++)
+ resultRelInfo->ri_GeneratedExprs =
+ (ExprState **) palloc0(natts * sizeof(ExprState *));
+ resultRelInfo->ri_NumGeneratedNeeded = 0;
+
+ for (int i = 0; i < natts; i++)
+ {
+ if (TupleDescAttr(tupdesc, i)->attgenerated == ATTRIBUTE_GENERATED_STORED)
{
- if (TupleDescAttr(tupdesc, i)->attgenerated == ATTRIBUTE_GENERATED_STORED)
- {
- Expr *expr;
+ 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;
- }
+ /* 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));
- 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);
- resultRelInfo->ri_GeneratedExprs[i] = ExecPrepareExpr(expr, estate);
- resultRelInfo->ri_NumGeneratedNeeded++;
+ if (!bms_overlap(updatedCols, attrs_used))
+ continue; /* need not update this column */
}
- }
- MemoryContextSwitchTo(oldContext);
+ /* No luck, so prepare the expression for execution */
+ resultRelInfo->ri_GeneratedExprs[i] = ExecPrepareExpr(expr, estate);
+ resultRelInfo->ri_NumGeneratedNeeded++;
+
+ /* And mark this column in resultRelInfo->ri_extraUpdatedCols */
+ resultRelInfo->ri_extraUpdatedCols =
+ bms_add_member(resultRelInfo->ri_extraUpdatedCols,
+ i + 1 - FirstLowInvalidHeapAttributeNumber);
+ }
}
+ MemoryContextSwitchTo(oldContext);
+}
+
+/*
+ * Compute stored generated columns for a tuple
+ */
+void
+ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
+ EState *estate, TupleTableSlot *slot,
+ CmdType cmdtype)
+{
+ 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);
+
+ /*
+ * 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)
+ ExecInitStoredGenerated(resultRelInfo, estate, cmdtype);
+
/*
* If no generated columns have been affected by this change, then skip
* the rest.
{
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);
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);
}
/*
WRITE_BOOL_FIELD(lateral);
WRITE_BOOL_FIELD(inh);
WRITE_BOOL_FIELD(inFromCl);
- WRITE_BITMAPSET_FIELD(extraUpdatedCols);
WRITE_NODE_FIELD(securityQuals);
}
READ_BOOL_FIELD(lateral);
READ_BOOL_FIELD(inh);
READ_BOOL_FIELD(inFromCl);
- READ_BITMAPSET_FIELD(extraUpdatedCols);
READ_NODE_FIELD(securityQuals);
READ_DONE();
#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"
List *translated_vars);
static Bitmapset *translate_col_privs_multilevel(PlannerInfo *root,
RelOptInfo *rel,
- RelOptInfo *top_parent_rel,
+ RelOptInfo *parent_rel,
Bitmapset *parent_cols);
static void expand_appendrel_subquery(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte, Index rti);
root->partColsUpdated =
has_partition_attrs(parentrel, parent_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;
childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
child_colnames);
- /* Translate the bitmapset of generated columns being updated. */
- if (childOID != parentOID)
- childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
- appinfo->translated_vars);
- else
- childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-
/*
* Store the RTE and appinfo in the respective PlannerInfo arrays, which
* the caller must already have allocated space for.
Assert(IS_SIMPLE_REL(rel));
/*
- * We obtain updatedCols and extraUpdatedCols for the query's result
- * relation. Then, if necessary, we map it to the column numbers of the
- * relation for which they were requested.
+ * 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);
perminfo = getRTEPermissionInfo(root->parse->rteperminfos, rte);
updatedCols = perminfo->updatedCols;
- extraUpdatedCols = rte->extraUpdatedCols;
- /*
- * For "other" rels, we must look up the root parent relation mentioned in
- * the query, and translate the column numbers.
- */
if (rel->relid != relid)
{
RelOptInfo *top_parent_rel = find_base_rel(root, relid);
updatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
updatedCols);
- extraUpdatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
- extraUpdatedCols);
}
+ /*
+ * 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);
}
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;
+
+ /* Recurse if immediate parent is not the top parent. */
+ if (rel->parent != parent_rel)
+ {
+ if (rel->parent)
+ parent_cols = translate_col_privs_multilevel(root, rel->parent,
+ parent_rel,
+ parent_cols);
+ else
+ elog(ERROR, "rel with relid %u is not a child rel", rel->relid);
+ }
+
+ /* Now translate for this child. */
+ Assert(root->append_rel_array != NULL);
+ appinfo = root->append_rel_array[rel->relid];
+ Assert(appinfo != NULL);
+
+ return translate_col_privs(parent_cols, appinfo->translated_vars);
+}
+
/*
* expand_appendrel_subquery
* Add "other rel" RelOptInfos for the children of an appendrel baserel
return true;
}
-
-/*
- * translate_col_privs_multilevel
- * Recursively translates the column numbers contained in 'parent_cols'
- * to the columns numbers of a descendent relation given by 'rel'
- */
-static Bitmapset *
-translate_col_privs_multilevel(PlannerInfo *root, RelOptInfo *rel,
- RelOptInfo *top_parent_rel,
- Bitmapset *parent_cols)
-{
- AppendRelInfo *appinfo;
-
- if (parent_cols == NULL)
- return NULL;
-
- /* Recurse if immediate parent is not the top parent. */
- if (rel->parent != top_parent_rel)
- {
- if (rel->parent)
- parent_cols = translate_col_privs_multilevel(root, rel->parent,
- top_parent_rel,
- parent_cols);
- else
- elog(ERROR, "rel with relid %u is not a child rel", rel->relid);
- }
-
- Assert(root->append_rel_array != NULL);
- appinfo = root->append_rel_array[rel->relid];
- Assert(appinfo != NULL);
-
- return translate_col_privs(parent_cols, appinfo->translated_vars);
-}
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)
{
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
*
LogicalRepTupleData newtup;
bool has_oldtup;
TupleTableSlot *remoteslot;
- RangeTblEntry *target_rte;
RTEPermissionInfo *target_perminfo;
MemoryContext oldctx;
* information. But it would for example exclude columns that only exist
* on the subscriber, since we are not touching those.
*/
- target_rte = list_nth(estate->es_range_table, 0);
target_perminfo = list_nth(estate->es_rteperminfos, 0);
for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
{
}
}
- /* Also populate extraUpdatedCols, in case we have generated columns */
- fill_extraUpdatedCols(target_rte, target_perminfo, rel->localrel);
-
/* Build the search tuple. */
oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
slot_store_data(remoteslot, rel,
}
-/*
- * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * columns that depend on any columns mentioned in
- * target_perminfo->updatedCols.
- */
-void
-fill_extraUpdatedCols(RangeTblEntry *target_rte,
- RTEPermissionInfo *target_perminfo,
- 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_perminfo->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
{
int result_relation;
RangeTblEntry *rt_entry;
- RTEPermissionInfo *rt_perminfo;
Relation rt_entry_relation;
List *locks;
int product_orig_rt_length;
Assert(result_relation != 0);
rt_entry = rt_fetch(result_relation, parsetree->rtable);
Assert(rt_entry->rtekind == RTE_RELATION);
- rt_perminfo = getRTEPermissionInfo(parsetree->rteperminfos, rt_entry);
/*
* We can use NoLock here since either the parser or
parsetree->override,
rt_entry_relation,
NULL, 0, NULL);
-
- /* Also populate extraUpdatedCols (for generated columns) */
- fill_extraUpdatedCols(rt_entry, rt_perminfo, rt_entry_relation);
}
else if (event == CMD_MERGE)
{
*/
AttrNumber ri_RowIdAttNo;
+ /* For INSERT/UPDATE, attnums of generated columns to be computed */
+ Bitmapset *ri_extraUpdatedCols;
+
/* Projection to generate new tuple in an INSERT/UPDATE */
ProjectionInfo *ri_projectNew;
/* Slot to hold that tuple */
bool lateral; /* subquery, function, or values is LATERAL? */
bool inh; /* inheritance requested? */
bool inFromCl; /* present in FROM clause? */
- Bitmapset *extraUpdatedCols; /* generated columns being updated */
List *securityQuals; /* security barrier quals to apply, if any */
} RangeTblEntry;
* updatedCols is also used in some other places, for example, to determine
* 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.
*/
typedef struct RTEPermissionInfo
{
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 */
extern Node *build_column_default(Relation rel, int attrno);
-extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
- RTEPermissionInfo *target_perminfo,
- Relation target_relation);
-
extern Query *get_view_query(Relation view);
extern const char *view_query_is_auto_updatable(Query *viewquery,
bool check_cols);
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);
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);