mirror of
https://github.com/postgres/postgres.git
synced 2025-07-03 20:02:46 +03:00
Generated columns
This is an SQL-standard feature that allows creating columns that are computed from expressions rather than assigned, similar to a view or materialized view but on a column basis. This implements one kind of generated column: stored (computed on write). Another kind, virtual (computed on read), is planned for the future, and some room is left for it. Reviewed-by: Michael Paquier <michael@paquier.xyz> Reviewed-by: Pavel Stehule <pavel.stehule@gmail.com> Discussion: https://www.postgresql.org/message-id/flat/b151f851-4019-bdb1-699e-ebab07d2f40a@2ndquadrant.com
This commit is contained in:
@ -102,7 +102,7 @@ static void EvalPlanQualStart(EPQState *epqstate, EState *parentestate,
|
||||
Plan *planTree);
|
||||
|
||||
/*
|
||||
* Note that GetUpdatedColumns() also exists in commands/trigger.c. There does
|
||||
* Note that GetAllUpdatedColumns() also exists in commands/trigger.c. There does
|
||||
* not appear to be any good header to put it into, given the structures that
|
||||
* it uses, so we let them be duplicated. Be sure to update both if one needs
|
||||
* to be changed, however.
|
||||
@ -111,6 +111,9 @@ static void EvalPlanQualStart(EPQState *epqstate, EState *parentestate,
|
||||
(exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->insertedCols)
|
||||
#define GetUpdatedColumns(relinfo, estate) \
|
||||
(exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->updatedCols)
|
||||
#define GetAllUpdatedColumns(relinfo, estate) \
|
||||
(bms_union(exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->updatedCols, \
|
||||
exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->extraUpdatedCols))
|
||||
|
||||
/* end of local decls */
|
||||
|
||||
@ -1316,6 +1319,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
|
||||
resultRelInfo->ri_FdwState = NULL;
|
||||
resultRelInfo->ri_usesFdwDirectModify = false;
|
||||
resultRelInfo->ri_ConstraintExprs = NULL;
|
||||
resultRelInfo->ri_GeneratedExprs = NULL;
|
||||
resultRelInfo->ri_junkFilter = NULL;
|
||||
resultRelInfo->ri_projectReturning = NULL;
|
||||
resultRelInfo->ri_onConflictArbiterIndexes = NIL;
|
||||
@ -2328,7 +2332,7 @@ ExecUpdateLockMode(EState *estate, ResultRelInfo *relinfo)
|
||||
* been modified, then we can use a weaker lock, allowing for better
|
||||
* concurrency.
|
||||
*/
|
||||
updatedCols = GetUpdatedColumns(relinfo, estate);
|
||||
updatedCols = GetAllUpdatedColumns(relinfo, estate);
|
||||
keyCols = RelationGetIndexAttrBitmap(relinfo->ri_RelationDesc,
|
||||
INDEX_ATTR_BITMAP_KEY);
|
||||
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "access/xact.h"
|
||||
#include "commands/trigger.h"
|
||||
#include "executor/executor.h"
|
||||
#include "executor/nodeModifyTable.h"
|
||||
#include "nodes/nodeFuncs.h"
|
||||
#include "parser/parse_relation.h"
|
||||
#include "parser/parsetree.h"
|
||||
@ -412,6 +413,11 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
|
||||
{
|
||||
List *recheckIndexes = NIL;
|
||||
|
||||
/* Compute stored generated columns */
|
||||
if (rel->rd_att->constr &&
|
||||
rel->rd_att->constr->has_generated_stored)
|
||||
ExecComputeStoredGenerated(estate, slot);
|
||||
|
||||
/* Check the constraints of the tuple */
|
||||
if (rel->rd_att->constr)
|
||||
ExecConstraints(resultRelInfo, slot, estate);
|
||||
@ -473,6 +479,11 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
|
||||
List *recheckIndexes = NIL;
|
||||
bool update_indexes;
|
||||
|
||||
/* Compute stored generated columns */
|
||||
if (rel->rd_att->constr &&
|
||||
rel->rd_att->constr->has_generated_stored)
|
||||
ExecComputeStoredGenerated(estate, slot);
|
||||
|
||||
/* Check the constraints of the tuple */
|
||||
if (rel->rd_att->constr)
|
||||
ExecConstraints(resultRelInfo, slot, estate);
|
||||
|
@ -49,6 +49,7 @@
|
||||
#include "foreign/fdwapi.h"
|
||||
#include "miscadmin.h"
|
||||
#include "nodes/nodeFuncs.h"
|
||||
#include "rewrite/rewriteHandler.h"
|
||||
#include "storage/bufmgr.h"
|
||||
#include "storage/lmgr.h"
|
||||
#include "utils/builtins.h"
|
||||
@ -240,6 +241,89 @@ ExecCheckTIDVisible(EState *estate,
|
||||
ExecClearTuple(tempSlot);
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute stored generated columns for a tuple
|
||||
*/
|
||||
void
|
||||
ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot)
|
||||
{
|
||||
ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
|
||||
Relation rel = resultRelInfo->ri_RelationDesc;
|
||||
TupleDesc tupdesc = RelationGetDescr(rel);
|
||||
int natts = tupdesc->natts;
|
||||
MemoryContext oldContext;
|
||||
Datum *values;
|
||||
bool *nulls;
|
||||
bool *replaces;
|
||||
HeapTuple oldtuple, newtuple;
|
||||
bool should_free;
|
||||
|
||||
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.
|
||||
*/
|
||||
if (resultRelInfo->ri_GeneratedExprs == NULL)
|
||||
{
|
||||
oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
|
||||
|
||||
resultRelInfo->ri_GeneratedExprs =
|
||||
(ExprState **) palloc(natts * sizeof(ExprState *));
|
||||
|
||||
for (int i = 0; i < natts; i++)
|
||||
{
|
||||
if (TupleDescAttr(tupdesc, i)->attgenerated == ATTRIBUTE_GENERATED_STORED)
|
||||
{
|
||||
Expr *expr;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
MemoryContextSwitchTo(oldContext);
|
||||
}
|
||||
|
||||
oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
|
||||
|
||||
values = palloc(sizeof(*values) * natts);
|
||||
nulls = palloc(sizeof(*nulls) * natts);
|
||||
replaces = palloc0(sizeof(*replaces) * natts);
|
||||
|
||||
for (int i = 0; i < natts; i++)
|
||||
{
|
||||
if (TupleDescAttr(tupdesc, i)->attgenerated == ATTRIBUTE_GENERATED_STORED)
|
||||
{
|
||||
ExprContext *econtext;
|
||||
Datum val;
|
||||
bool isnull;
|
||||
|
||||
econtext = GetPerTupleExprContext(estate);
|
||||
econtext->ecxt_scantuple = slot;
|
||||
|
||||
val = ExecEvalExpr(resultRelInfo->ri_GeneratedExprs[i], econtext, &isnull);
|
||||
|
||||
values[i] = val;
|
||||
nulls[i] = isnull;
|
||||
replaces[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
oldtuple = ExecFetchSlotHeapTuple(slot, true, &should_free);
|
||||
newtuple = heap_modify_tuple(oldtuple, tupdesc, values, nulls, replaces);
|
||||
ExecForceStoreHeapTuple(newtuple, slot);
|
||||
if (should_free)
|
||||
heap_freetuple(oldtuple);
|
||||
|
||||
MemoryContextSwitchTo(oldContext);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* ExecInsert
|
||||
*
|
||||
@ -297,6 +381,13 @@ ExecInsert(ModifyTableState *mtstate,
|
||||
}
|
||||
else if (resultRelInfo->ri_FdwRoutine)
|
||||
{
|
||||
/*
|
||||
* Compute stored generated columns
|
||||
*/
|
||||
if (resultRelationDesc->rd_att->constr &&
|
||||
resultRelationDesc->rd_att->constr->has_generated_stored)
|
||||
ExecComputeStoredGenerated(estate, slot);
|
||||
|
||||
/*
|
||||
* insert into foreign table: let the FDW do it
|
||||
*/
|
||||
@ -326,6 +417,13 @@ ExecInsert(ModifyTableState *mtstate,
|
||||
*/
|
||||
slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
|
||||
|
||||
/*
|
||||
* Compute stored generated columns
|
||||
*/
|
||||
if (resultRelationDesc->rd_att->constr &&
|
||||
resultRelationDesc->rd_att->constr->has_generated_stored)
|
||||
ExecComputeStoredGenerated(estate, slot);
|
||||
|
||||
/*
|
||||
* Check any RLS WITH CHECK policies.
|
||||
*
|
||||
@ -964,6 +1062,13 @@ ExecUpdate(ModifyTableState *mtstate,
|
||||
}
|
||||
else if (resultRelInfo->ri_FdwRoutine)
|
||||
{
|
||||
/*
|
||||
* Compute stored generated columns
|
||||
*/
|
||||
if (resultRelationDesc->rd_att->constr &&
|
||||
resultRelationDesc->rd_att->constr->has_generated_stored)
|
||||
ExecComputeStoredGenerated(estate, slot);
|
||||
|
||||
/*
|
||||
* update in foreign table: let the FDW do it
|
||||
*/
|
||||
@ -994,6 +1099,13 @@ ExecUpdate(ModifyTableState *mtstate,
|
||||
*/
|
||||
slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
|
||||
|
||||
/*
|
||||
* Compute stored generated columns
|
||||
*/
|
||||
if (resultRelationDesc->rd_att->constr &&
|
||||
resultRelationDesc->rd_att->constr->has_generated_stored)
|
||||
ExecComputeStoredGenerated(estate, slot);
|
||||
|
||||
/*
|
||||
* Check any RLS UPDATE WITH CHECK policies
|
||||
*
|
||||
|
Reference in New Issue
Block a user