forked from huawei/openGauss-server
generated col
This commit is contained in:
parent
0236725f3f
commit
b8e6ff7a81
|
|
@ -1,3 +1,4 @@
|
|||
/.gitee/
|
||||
/.vscode/
|
||||
/.idea/
|
||||
.vscode
|
||||
|
|
|
|||
|
|
@ -967,9 +967,10 @@ static List *postgresPlanForeignModify(PlannerInfo *root, ModifyTable *plan, Ind
|
|||
}
|
||||
} else if (operation == CMD_UPDATE) {
|
||||
Bitmapset *tmpset = bms_copy(rte->updatedCols);
|
||||
Bitmapset *allUpdatedCols = bms_union(tmpset, rte->extraUpdatedCols);
|
||||
AttrNumber col;
|
||||
|
||||
while ((col = bms_first_member(tmpset)) >= 0) {
|
||||
while ((col = bms_first_member(allUpdatedCols)) >= 0) {
|
||||
col += FirstLowInvalidHeapAttributeNumber;
|
||||
if (col <= InvalidAttrNumber) { /* shouldn't happen */
|
||||
elog(ERROR, "system-column update is not supported");
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ where column_constraint can be:
|
|||
NULL |
|
||||
CHECK ( expression ) |
|
||||
DEFAULT default_expr |
|
||||
GENERATED ALWAYS AS ( generation_expr ) STORED |
|
||||
UNIQUE index_parameters |
|
||||
PRIMARY KEY index_parameters |
|
||||
REFERENCES reftable [ ( refcolumn ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ where column_constraint can be:
|
|||
NULL |
|
||||
CHECK ( expression ) |
|
||||
DEFAULT default_expr |
|
||||
GENERATED ALWAYS AS ( generation_expr ) STORED |
|
||||
UNIQUE index_parameters |
|
||||
PRIMARY KEY index_parameters |
|
||||
REFERENCES reftable [ ( refcolumn ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
|
||||
|
|
@ -47,7 +48,7 @@ where table_constraint can be:
|
|||
where compress_mode can be:
|
||||
{ DELTA | PREFIX | DICTIONARY | NUMSTR | NOCOMPRESS }
|
||||
where like_option can be:
|
||||
{ INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | PARTITION | RELOPTIONS | DISTRIBUTION | ALL }
|
||||
{ INCLUDING | EXCLUDING } { DEFAULTS | GENERATED | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | PARTITION | RELOPTIONS | DISTRIBUTION | ALL }
|
||||
where index_parameters can be:
|
||||
[ WITH ( {storage_parameter = value} [, ... ] ) ]
|
||||
[ USING INDEX TABLESPACE tablespace_name ]
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ where column_constraint can be:
|
|||
NULL |
|
||||
CHECK ( expression ) |
|
||||
DEFAULT default_expr |
|
||||
GENERATED ALWAYS AS ( generation_expr ) STORED |
|
||||
UNIQUE index_parameters |
|
||||
PRIMARY KEY index_parameters |
|
||||
REFERENCES reftable [ ( refcolumn ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
|
||||
|
|
@ -51,7 +52,7 @@ where index_parameters can be:
|
|||
[ WITH ( {storage_parameter = value} [, ... ] ) ]
|
||||
[ USING INDEX TABLESPACE tablespace_name ]
|
||||
where like_option can be:
|
||||
{ INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | RELOPTIONS | DISTRIBUTION | ALL }
|
||||
{ INCLUDING | EXCLUDING } { DEFAULTS | GENERATED | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | RELOPTIONS | DISTRIBUTION | ALL }
|
||||
where partition_less_than_item can be:
|
||||
PARTITION partition_name VALUES LESS THAN ( { partition_value | MAXVALUE } ) [TABLESPACE tablespace_name]
|
||||
where partition_start_end_item can be:
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@
|
|||
#include "catalog/pg_rlspolicy.h"
|
||||
#include "catalog/pg_trigger.h"
|
||||
#include "catalog/pg_type.h"
|
||||
#include "catalog/pg_attrdef.h"
|
||||
#include "libpq/libpq-fs.h"
|
||||
#include "libpq/libpq-int.h"
|
||||
#include "catalog/pgxc_node.h"
|
||||
|
|
@ -2426,6 +2427,11 @@ static int dumpTableData_insert(Archive* fout, void* dcontext)
|
|||
for (field = 0; field < nfields; field++) {
|
||||
if (field > 0)
|
||||
archprintf(fout, ", ");
|
||||
if (tbinfo->attrdefs[field] &&
|
||||
tbinfo->attrdefs[field]->generatedCol) {
|
||||
(void)archputs("DEFAULT", fout);
|
||||
continue;
|
||||
}
|
||||
if (PQgetisnull(res, tuple, field)) {
|
||||
archprintf(fout, "NULL");
|
||||
continue;
|
||||
|
|
@ -8281,6 +8287,8 @@ void getTableAttrs(Archive* fout, TableInfo* tblinfo, int numTables)
|
|||
if (hasdefaults) {
|
||||
AttrDefInfo* attrdefs = NULL;
|
||||
int numDefaults;
|
||||
ArchiveHandle* AH = (ArchiveHandle*)fout;
|
||||
bool hasGenColFeature = is_column_exists(AH->connection, AttrDefaultRelationId, "adgencol");
|
||||
|
||||
if (g_verbose)
|
||||
write_msg(NULL,
|
||||
|
|
@ -8288,10 +8296,19 @@ void getTableAttrs(Archive* fout, TableInfo* tblinfo, int numTables)
|
|||
fmtQualifiedId(fout, tbinfo->dobj.nmspace->dobj.name, tbinfo->dobj.name));
|
||||
|
||||
resetPQExpBuffer(q);
|
||||
if (fout->remoteVersion >= 70300) {
|
||||
if (hasGenColFeature) {
|
||||
appendPQExpBuffer(q,
|
||||
"SELECT tableoid, oid, adnum, "
|
||||
"pg_catalog.pg_get_expr(adbin, adrelid) AS adsrc "
|
||||
", adgencol AS generatedCol "
|
||||
"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, "
|
||||
"pg_catalog.pg_get_expr(adbin, adrelid) AS adsrc "
|
||||
", '' AS generatedCol "
|
||||
"FROM pg_catalog.pg_attrdef "
|
||||
"WHERE adrelid = '%u'::pg_catalog.oid",
|
||||
tbinfo->dobj.catId.oid);
|
||||
|
|
@ -8300,6 +8317,7 @@ void getTableAttrs(Archive* fout, TableInfo* tblinfo, int numTables)
|
|||
appendPQExpBuffer(q,
|
||||
"SELECT tableoid, 0 AS oid, adnum, "
|
||||
"pg_get_expr(adbin, adrelid) AS adsrc "
|
||||
", '' AS generatedCol "
|
||||
"FROM pg_attrdef "
|
||||
"WHERE adrelid = '%u'::oid",
|
||||
tbinfo->dobj.catId.oid);
|
||||
|
|
@ -8307,6 +8325,7 @@ void getTableAttrs(Archive* fout, TableInfo* tblinfo, int numTables)
|
|||
/* no pg_get_expr, so must rely on adsrc */
|
||||
appendPQExpBuffer(q,
|
||||
"SELECT tableoid, oid, adnum, adsrc "
|
||||
", '' AS generatedCol "
|
||||
"FROM pg_attrdef "
|
||||
"WHERE adrelid = '%u'::oid",
|
||||
tbinfo->dobj.catId.oid);
|
||||
|
|
@ -8316,6 +8335,7 @@ void getTableAttrs(Archive* fout, TableInfo* tblinfo, int numTables)
|
|||
"SELECT "
|
||||
"(SELECT oid FROM pg_class WHERE relname = 'pg_attrdef') AS tableoid, "
|
||||
"oid, adnum, adsrc "
|
||||
", '' AS generatedCol "
|
||||
"FROM pg_attrdef "
|
||||
"WHERE adrelid = '%u'::oid",
|
||||
tbinfo->dobj.catId.oid);
|
||||
|
|
@ -8346,6 +8366,7 @@ void getTableAttrs(Archive* fout, TableInfo* tblinfo, int numTables)
|
|||
AssignDumpId(&attrdefs[j].dobj);
|
||||
attrdefs[j].adtable = tbinfo;
|
||||
attrdefs[j].adnum = adnum;
|
||||
attrdefs[j].generatedCol = *(PQgetvalue(res, j, 4));
|
||||
attrdefs[j].adef_expr = gs_strdup(PQgetvalue(res, j, 3));
|
||||
|
||||
attrdefs[j].dobj.name = gs_strdup(tbinfo->dobj.name);
|
||||
|
|
@ -17290,7 +17311,11 @@ static void dumpTableSchema(Archive* fout, TableInfo* tbinfo)
|
|||
default_value = (char *)plaintext;
|
||||
}
|
||||
#endif
|
||||
appendPQExpBuffer(q, " DEFAULT %s", default_value);
|
||||
if (tbinfo->attrdefs[j]->generatedCol == ATTRIBUTE_GENERATED_STORED)
|
||||
appendPQExpBuffer(q, " GENERATED ALWAYS AS (%s) STORED",
|
||||
default_value);
|
||||
else
|
||||
appendPQExpBuffer(q, " DEFAULT %s", default_value);
|
||||
}
|
||||
if (has_notnull)
|
||||
appendPQExpBuffer(q, " NOT NULL");
|
||||
|
|
@ -20448,10 +20473,15 @@ static const char* fmtCopyColumnList(const TableInfo* ti)
|
|||
appendPQExpBuffer(q, "(");
|
||||
|
||||
for (i = 0; i < numatts; i++) {
|
||||
if (attisdropped[i])
|
||||
if (attisdropped[i]) {
|
||||
continue;
|
||||
if (needComma)
|
||||
}
|
||||
if (ti->attrdefs[i] != NULL && ti->attrdefs[i]->generatedCol) {
|
||||
continue;
|
||||
}
|
||||
if (needComma) {
|
||||
appendPQExpBuffer(q, ", ");
|
||||
}
|
||||
appendPQExpBuffer(q, "%s", fmtId(attnames[i]));
|
||||
needComma = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -274,6 +274,7 @@ typedef struct _attrDefInfo {
|
|||
int adnum;
|
||||
char* adef_expr; /* decompiled DEFAULT expression */
|
||||
bool separate; /* TRUE if must dump as separate item */
|
||||
char generatedCol;
|
||||
} AttrDefInfo;
|
||||
|
||||
typedef struct _tableDataInfo {
|
||||
|
|
|
|||
|
|
@ -935,6 +935,22 @@ static void repairDependencyLoop(DumpableObject** loop, int nLoop)
|
|||
if (true == repairDependencyDomainConstraintLoops(loop, nLoop))
|
||||
return;
|
||||
|
||||
/*
|
||||
* Loop of table with itself --- just ignore it.
|
||||
*
|
||||
* (Actually, what this arises from is a dependency of a table column on
|
||||
* another column, which happens with generated columns; or a dependency
|
||||
* of a table column on the whole table, which happens with partitioning.
|
||||
* But we didn't pay attention to sub-object IDs while collecting the
|
||||
* dependency data, so we can't see that here.)
|
||||
*/
|
||||
if (nLoop == 1) {
|
||||
if (loop[0]->objType == DO_TABLE) {
|
||||
removeObjectDependency(loop[0], loop[0]->dumpId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If all the objects are TABLE_DATA items, what we must have is a
|
||||
* circular set of foreign key constraints (or a single self-referential
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
#include "catalog/pg_class.h"
|
||||
#include "catalog/pg_default_acl.h"
|
||||
#include "catalog/pg_attrdef.h"
|
||||
#include "common.h"
|
||||
#include "describe.h"
|
||||
#include "dumputils.h"
|
||||
|
|
@ -1278,6 +1279,7 @@ static bool describeOneTableDetails(const char* schemaname, const char* relation
|
|||
bool show_modifiers = false;
|
||||
bool retval = false;
|
||||
bool hasreplident = is_column_exists(pset.db, RelationRelationId, "relreplident");
|
||||
bool hasGenColFeature = is_column_exists(pset.db, AttrDefaultRelationId, "adgencol");
|
||||
|
||||
bool has_rlspolicy = false;
|
||||
const char* has_rlspolicy_sql =
|
||||
|
|
@ -1511,6 +1513,14 @@ static bool describeOneTableDetails(const char* schemaname, const char* relation
|
|||
}
|
||||
appendPQExpBuffer(&buf, ",\n a.attkvtype");
|
||||
}
|
||||
if (hasGenColFeature) {
|
||||
appendPQExpBuffer(&buf, ",\n (SELECT h.adgencol"
|
||||
"\n FROM pg_catalog.pg_attrdef h"
|
||||
"\n WHERE h.adrelid = a.attrelid AND h.adnum = a.attnum AND a.atthasdef)"
|
||||
" AS generated_column");
|
||||
} else {
|
||||
appendPQExpBuffer(&buf, ", '' AS generated_column ");
|
||||
}
|
||||
appendPQExpBuffer(&buf, "\nFROM pg_catalog.pg_attribute a");
|
||||
appendPQExpBuffer(&buf, "\nWHERE a.attrelid = '%s' AND a.attnum > 0 AND NOT a.attisdropped AND "
|
||||
"a.attkvtype != 4 AND a.attname <> 'tableoid'", oid);
|
||||
|
|
@ -1682,7 +1692,11 @@ static bool describeOneTableDetails(const char* schemaname, const char* relation
|
|||
appendPQExpBufferStr(&tmpbuf, " ");
|
||||
}
|
||||
/* translator: default values of column definitions */
|
||||
appendPQExpBuffer(&tmpbuf, _("default %s"), default_value);
|
||||
if (strlen(PQgetvalue(res, i, PQfnumber(res, "generated_column"))) > 0) {
|
||||
appendPQExpBuffer(&tmpbuf, _("generated always as (%s) stored"), default_value);
|
||||
} else {
|
||||
appendPQExpBuffer(&tmpbuf, _("default %s"), default_value);
|
||||
}
|
||||
}
|
||||
#ifdef HAVE_CE
|
||||
if (hasFullEncryptFeature &&
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@
|
|||
#include "parser/parse_collate.h"
|
||||
#include "parser/parse_expr.h"
|
||||
#include "parser/parse_relation.h"
|
||||
#include "parser/parsetree.h"
|
||||
#include "pgxc/groupmgr.h"
|
||||
#include "storage/buf/buf.h"
|
||||
#include "storage/predicate.h"
|
||||
|
|
@ -157,6 +158,9 @@ static Oid binary_upgrade_get_next_part_toast_pg_class_oid();
|
|||
static Oid binary_upgrade_get_next_part_toast_pg_class_rfoid();
|
||||
static Oid binary_upgrade_get_next_part_pg_partition_rfoid();
|
||||
static void LockSeqConstraints(Relation rel, List* Constraints);
|
||||
static bool ReferenceGenerated(const List *rawdefaultlist, AttrNumber attnum);
|
||||
static bool CheckNestedGeneratedWalker(Node *node, ParseState *context);
|
||||
static bool CheckNestedGenerated(ParseState *pstate, Node *node);
|
||||
extern void getErrorTableFilePath(char* buf, int len, Oid databaseid, Oid reid);
|
||||
extern void make_tmptable_cache_key(Oid relNode);
|
||||
|
||||
|
|
@ -3468,13 +3472,13 @@ 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)
|
||||
void StoreAttrDefault(Relation rel, AttrNumber attnum, Node* expr, char generatedCol)
|
||||
{
|
||||
char* adbin = NULL;
|
||||
char* adsrc = NULL;
|
||||
Relation adrel;
|
||||
HeapTuple tuple;
|
||||
Datum values[4];
|
||||
Datum values[Natts_pg_attrdef];
|
||||
Relation attrrel;
|
||||
HeapTuple atttup;
|
||||
Form_pg_attribute attStruct;
|
||||
|
|
@ -3499,6 +3503,9 @@ void StoreAttrDefault(Relation rel, AttrNumber attnum, Node* expr)
|
|||
values[Anum_pg_attrdef_adnum - 1] = attnum;
|
||||
values[Anum_pg_attrdef_adbin - 1] = CStringGetTextDatum(adbin);
|
||||
values[Anum_pg_attrdef_adsrc - 1] = CStringGetTextDatum(adsrc);
|
||||
if (t_thrd.proc->workingVersionNum >= GENERATED_COL_VERSION_NUM) {
|
||||
values[Anum_pg_attrdef_adgencol - 1] = CharGetDatum(generatedCol);
|
||||
}
|
||||
|
||||
adrel = heap_open(AttrDefaultRelationId, RowExclusiveLock);
|
||||
|
||||
|
|
@ -3555,7 +3562,22 @@ void StoreAttrDefault(Relation rel, AttrNumber attnum, Node* expr)
|
|||
/*
|
||||
* Record dependencies on objects used in the expression, too.
|
||||
*/
|
||||
recordDependencyOnExpr(&defobject, expr, NIL, DEPENDENCY_NORMAL);
|
||||
if (generatedCol == ATTRIBUTE_GENERATED_STORED)
|
||||
{
|
||||
/*
|
||||
* Generated column: Dropping anything that the generation expression
|
||||
* refers to automatically drops the generated column.
|
||||
*/
|
||||
recordDependencyOnSingleRelExpr(&colobject, expr, RelationGetRelid(rel),
|
||||
DEPENDENCY_AUTO,
|
||||
DEPENDENCY_AUTO);
|
||||
} else {
|
||||
/*
|
||||
* Normal default: Dropping anything that the default refers to
|
||||
* requires CASCADE and drops the default only.
|
||||
*/
|
||||
recordDependencyOnExpr(&defobject, expr, NIL, DEPENDENCY_NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -3678,7 +3700,10 @@ static void StoreConstraints(Relation rel, List* cooked_constraints)
|
|||
|
||||
switch (con->contype) {
|
||||
case CONSTR_DEFAULT:
|
||||
StoreAttrDefault(rel, con->attnum, con->expr);
|
||||
StoreAttrDefault(rel, con->attnum, con->expr, 0);
|
||||
break;
|
||||
case CONSTR_GENERATED:
|
||||
StoreAttrDefault(rel, con->attnum, con->expr, ATTRIBUTE_GENERATED_STORED);
|
||||
break;
|
||||
case CONSTR_CHECK:
|
||||
StoreRelCheck(
|
||||
|
|
@ -3754,6 +3779,8 @@ List* AddRelationNewConstraints(
|
|||
rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
|
||||
addRTEtoQuery(pstate, rte, true, true, true);
|
||||
|
||||
pstate->p_rawdefaultlist = newColDefaults;
|
||||
|
||||
/*
|
||||
* Process column default expressions.
|
||||
*/
|
||||
|
|
@ -3761,12 +3788,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));
|
||||
expr = cookDefault(pstate, colDef->raw_default, atp->atttypid, atp->atttypmod, NameStr(atp->attname),
|
||||
colDef->generatedCol);
|
||||
|
||||
/*
|
||||
* If the expression is just a NULL constant, we do not bother to make
|
||||
* an explicit pg_attrdef entry, since the default behavior is
|
||||
* equivalent.
|
||||
* equivalent. This applies to column defaults, but not for
|
||||
* generation expressions.
|
||||
*
|
||||
* Note a nonobvious property of this test: if the column is of a
|
||||
* domain type, what we'll get is not a bare null Const but a
|
||||
|
|
@ -3774,10 +3803,10 @@ List* AddRelationNewConstraints(
|
|||
* critical because the column default needs to be retained to
|
||||
* override any default that the domain might have.
|
||||
*/
|
||||
if (expr == NULL || (IsA(expr, Const) && ((Const*)expr)->constisnull))
|
||||
if (expr == NULL || (!colDef->generatedCol && IsA(expr, Const) && ((Const *)expr)->constisnull))
|
||||
continue;
|
||||
|
||||
StoreAttrDefault(rel, colDef->attnum, expr);
|
||||
StoreAttrDefault(rel, colDef->attnum, expr, colDef->generatedCol);
|
||||
|
||||
cooked = (CookedConstraint*)palloc(sizeof(CookedConstraint));
|
||||
cooked->contype = CONSTR_DEFAULT;
|
||||
|
|
@ -3790,6 +3819,8 @@ List* AddRelationNewConstraints(
|
|||
cooked->is_no_inherit = false;
|
||||
cookedConstraints = lappend(cookedConstraints, cooked);
|
||||
}
|
||||
|
||||
pstate->p_rawdefaultlist = NIL;
|
||||
/*
|
||||
* Process constraint expressions.
|
||||
*/
|
||||
|
|
@ -4045,6 +4076,54 @@ static void SetRelationNumChecks(Relation rel, int numchecks)
|
|||
heap_close(relrel, RowExclusiveLock);
|
||||
}
|
||||
|
||||
static bool ReferenceGenerated(const List *rawdefaultlist, AttrNumber attnum)
|
||||
{
|
||||
ListCell *cell = NULL;
|
||||
|
||||
foreach (cell, rawdefaultlist) {
|
||||
RawColumnDefault *colDef = (RawColumnDefault *)lfirst(cell);
|
||||
if (colDef->attnum == attnum && colDef->generatedCol) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check for references to generated columns
|
||||
*/
|
||||
static bool CheckNestedGeneratedWalker(Node *node, ParseState *context)
|
||||
{
|
||||
ParseState *pstate = context;
|
||||
if (node == NULL) {
|
||||
return false;
|
||||
} else if (IsA(node, Var)) {
|
||||
Var *var = (Var *)node;
|
||||
RangeTblEntry *rte = rt_fetch(var->varno, pstate->p_rtable);
|
||||
Oid relid = rte->relid;
|
||||
AttrNumber attnum = var->varattno;
|
||||
|
||||
if (OidIsValid(relid) && AttributeNumberIsValid(attnum) &&
|
||||
(ReferenceGenerated(pstate->p_rawdefaultlist, attnum) || GetGenerated(relid, attnum))) {
|
||||
ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("cannot use generated column \"%s\" in column generation expression",
|
||||
get_attname(relid, attnum)),
|
||||
errdetail("A generated column cannot reference another generated column."),
|
||||
parser_errposition(pstate, var->location)));
|
||||
}
|
||||
|
||||
return false;
|
||||
} else {
|
||||
return expression_tree_walker(node, (bool (*)())CheckNestedGeneratedWalker, context);
|
||||
}
|
||||
}
|
||||
|
||||
static bool CheckNestedGenerated(ParseState *pstate, Node *node)
|
||||
{
|
||||
return CheckNestedGeneratedWalker(node, pstate);
|
||||
}
|
||||
|
||||
Node* parseParamRef(ParseState* pstate, ParamRef* pref)
|
||||
{
|
||||
Node* param_expr = NULL;
|
||||
|
|
@ -4087,7 +4166,8 @@ Node* parseParamRef(ParseState* pstate, ParamRef* pref)
|
|||
* type (and typmod atttypmod). attname is only needed in this case:
|
||||
* it is used in the error message, if any.
|
||||
*/
|
||||
Node* cookDefault(ParseState* pstate, Node* raw_default, Oid atttypid, int32 atttypmod, char* attname)
|
||||
Node *cookDefault(ParseState *pstate, Node *raw_default, Oid atttypid, int32 atttypmod, char *attname,
|
||||
char generatedCol)
|
||||
{
|
||||
Node* expr = NULL;
|
||||
|
||||
|
|
@ -4099,7 +4179,18 @@ Node* cookDefault(ParseState* pstate, Node* raw_default, Oid atttypid, int32 att
|
|||
/*
|
||||
* Transform raw parsetree to executable expression.
|
||||
*/
|
||||
pstate->p_expr_kind = generatedCol ? EXPR_KIND_GENERATED_COLUMN : EXPR_KIND_COLUMN_DEFAULT;
|
||||
expr = transformExpr(pstate, raw_default);
|
||||
pstate->p_expr_kind = EXPR_KIND_NONE;
|
||||
|
||||
if (generatedCol == ATTRIBUTE_GENERATED_STORED)
|
||||
{
|
||||
(void)CheckNestedGenerated(pstate, expr);
|
||||
|
||||
if (contain_mutable_functions(expr))
|
||||
ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||
errmsg("generation expression is not immutable")));
|
||||
}
|
||||
|
||||
/* Version control for DDL PBE */
|
||||
if (t_thrd.proc->workingVersionNum >= DDL_PBE_VERSION_NUM)
|
||||
|
|
@ -4107,7 +4198,7 @@ Node* cookDefault(ParseState* pstate, Node* raw_default, Oid atttypid, int32 att
|
|||
/*
|
||||
* Make sure default expr does not refer to any vars.
|
||||
*/
|
||||
if (contain_var_clause(expr))
|
||||
if (contain_var_clause(expr) && !generatedCol)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE), errmsg("cannot use column references in default expression")));
|
||||
|
||||
|
|
@ -4121,19 +4212,21 @@ Node* cookDefault(ParseState* pstate, Node* raw_default, Oid atttypid, int32 att
|
|||
* It can't return a set either.
|
||||
*/
|
||||
if (expression_returns_set(expr))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("default expression must not return a set")));
|
||||
ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("%s expression must not return a set", generatedCol ? "generated column" : "default")));
|
||||
|
||||
/*
|
||||
* No subplans or aggregates, either...
|
||||
*/
|
||||
if (pstate->p_hasSubLinks)
|
||||
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot use subquery in default expression")));
|
||||
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot use subquery in %s expression", generatedCol ? "generated column" : "default")));
|
||||
if (pstate->p_hasAggs)
|
||||
ereport(
|
||||
ERROR, (errcode(ERRCODE_GROUPING_ERROR), errmsg("cannot use aggregate function in default expression")));
|
||||
ereport(ERROR, (errcode(ERRCODE_GROUPING_ERROR),
|
||||
errmsg("cannot use aggregate function in %s expression", generatedCol ? "generated column" : "default")));
|
||||
if (pstate->p_hasWindowFuncs)
|
||||
ereport(ERROR, (errcode(ERRCODE_WINDOWING_ERROR), errmsg("cannot use window function in default expression")));
|
||||
ereport(ERROR, (errcode(ERRCODE_WINDOWING_ERROR),
|
||||
errmsg("cannot use window function in %s expression", generatedCol ? "generated column" : "default")));
|
||||
|
||||
/*
|
||||
* Coerce the expression to the correct type and typmod, if given. This
|
||||
|
|
@ -4143,17 +4236,13 @@ Node* cookDefault(ParseState* pstate, Node* raw_default, Oid atttypid, int32 att
|
|||
if (OidIsValid(atttypid)) {
|
||||
Oid type_id = exprType(expr);
|
||||
|
||||
expr = coerce_to_target_type(
|
||||
pstate, expr, type_id, atttypid, atttypmod, COERCION_ASSIGNMENT, COERCE_IMPLICIT_CAST, -1);
|
||||
expr = coerce_to_target_type(pstate, expr, type_id, atttypid, atttypmod, COERCION_ASSIGNMENT,
|
||||
COERCE_IMPLICIT_CAST, -1);
|
||||
if (expr == NULL)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("column \"%s\" is of type %s"
|
||||
" but default expression is of type %s",
|
||||
attname,
|
||||
format_type_be(atttypid),
|
||||
format_type_be(type_id)),
|
||||
errhint("You will need to rewrite or cast the expression.")));
|
||||
ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("column \"%s\" is of type %s but %s expression is of type %s", attname, format_type_be(atttypid),
|
||||
generatedCol ? "generated column" : "default", format_type_be(type_id)),
|
||||
errhint("You will need to rewrite or cast the expression.")));
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -648,7 +648,7 @@ CREATE VIEW columns AS
|
|||
CAST(c.relname AS sql_identifier) AS table_name,
|
||||
CAST(a.attname AS sql_identifier) AS column_name,
|
||||
CAST(a.attnum AS cardinal_number) AS ordinal_position,
|
||||
CAST(pg_get_expr(ad.adbin, ad.adrelid) AS character_data) AS column_default,
|
||||
CAST(CASE WHEN ad.adgencol <> 's' THEN pg_get_expr(ad.adbin, ad.adrelid) END AS character_data) AS column_default,
|
||||
CAST(CASE WHEN a.attnotnull OR (t.typtype = 'd' AND t.typnotnull) THEN 'NO' ELSE 'YES' END
|
||||
AS yes_or_no)
|
||||
AS is_nullable,
|
||||
|
|
@ -737,8 +737,8 @@ CREATE VIEW columns AS
|
|||
CAST(null AS character_data) AS identity_minimum,
|
||||
CAST(null AS yes_or_no) AS identity_cycle,
|
||||
|
||||
CAST('NEVER' AS character_data) AS is_generated,
|
||||
CAST(null AS character_data) AS generation_expression,
|
||||
CAST(CASE WHEN ad.adgencol = 's' THEN 'ALWAYS' ELSE 'NEVER' END AS character_data) AS is_generated,
|
||||
CAST(CASE WHEN ad.adgencol = 's' THEN pg_get_expr(ad.adbin, ad.adrelid) END AS character_data) AS generation_expression,
|
||||
|
||||
CAST(CASE WHEN c.relkind = 'r'
|
||||
OR (c.relkind = 'v'
|
||||
|
|
|
|||
|
|
@ -3382,6 +3382,7 @@ static RangeTblEntry* _copyRangeTblEntry(const RangeTblEntry* from)
|
|||
COPY_BITMAPSET_FIELD(modifiedCols);
|
||||
COPY_BITMAPSET_FIELD(insertedCols);
|
||||
COPY_BITMAPSET_FIELD(updatedCols);
|
||||
COPY_BITMAPSET_FIELD(extraUpdatedCols);
|
||||
COPY_SCALAR_FIELD(orientation);
|
||||
COPY_STRING_FIELD(mainRelName);
|
||||
COPY_STRING_FIELD(mainRelNameSpace);
|
||||
|
|
@ -3817,6 +3818,7 @@ static ColumnDef* _copyColumnDef(const ColumnDef* from)
|
|||
COPY_SCALAR_FIELD(storage);
|
||||
COPY_SCALAR_FIELD(cmprs_mode);
|
||||
COPY_NODE_FIELD(raw_default);
|
||||
COPY_SCALAR_FIELD(generatedCol);
|
||||
COPY_NODE_FIELD(cooked_default);
|
||||
COPY_NODE_FIELD(collClause);
|
||||
COPY_SCALAR_FIELD(collOid);
|
||||
|
|
|
|||
|
|
@ -2428,6 +2428,7 @@ static bool _equalColumnDef(const ColumnDef* a, const ColumnDef* b)
|
|||
COMPARE_SCALAR_FIELD(cmprs_mode);
|
||||
COMPARE_NODE_FIELD(raw_default);
|
||||
COMPARE_NODE_FIELD(cooked_default);
|
||||
COMPARE_SCALAR_FIELD(generatedCol);
|
||||
COMPARE_NODE_FIELD(collClause);
|
||||
COMPARE_NODE_FIELD(clientLogicColumnRef);
|
||||
COMPARE_SCALAR_FIELD(collOid);
|
||||
|
|
@ -2527,6 +2528,7 @@ static bool _equalRangeTblEntry(const RangeTblEntry* a, const RangeTblEntry* b)
|
|||
COMPARE_BITMAPSET_FIELD(selectedCols);
|
||||
COMPARE_BITMAPSET_FIELD(insertedCols);
|
||||
COMPARE_BITMAPSET_FIELD(updatedCols);
|
||||
COMPARE_BITMAPSET_FIELD(extraUpdatedCols);
|
||||
COMPARE_SCALAR_FIELD(orientation);
|
||||
COMPARE_NODE_FIELD(securityQuals);
|
||||
COMPARE_SCALAR_FIELD(subquery_pull_up);
|
||||
|
|
|
|||
|
|
@ -3637,6 +3637,10 @@ static void _outColumnDef(StringInfo str, ColumnDef* node)
|
|||
WRITE_NODE_FIELD(constraints);
|
||||
WRITE_NODE_FIELD(fdwoptions);
|
||||
WRITE_NODE_FIELD(clientLogicColumnRef);
|
||||
if (t_thrd.proc->workingVersionNum >= GENERATED_COL_VERSION_NUM) {
|
||||
if (node->generatedCol)
|
||||
WRITE_CHAR_FIELD(generatedCol);
|
||||
}
|
||||
}
|
||||
|
||||
static void _outTypeName(StringInfo str, TypeName* node)
|
||||
|
|
@ -4172,6 +4176,52 @@ static void _outSetOperationStmt(StringInfo str, SetOperationStmt* node)
|
|||
WRITE_TYPEINFO_LIST(colTypes);
|
||||
}
|
||||
|
||||
static void _outRteRelation(StringInfo str, const RangeTblEntry *node)
|
||||
{
|
||||
WRITE_OID_FIELD(relid);
|
||||
WRITE_CHAR_FIELD(relkind);
|
||||
if (t_thrd.proc->workingVersionNum >= MATVIEW_VERSION_NUM) {
|
||||
WRITE_BOOL_FIELD(isResultRel);
|
||||
}
|
||||
WRITE_NODE_FIELD(tablesample);
|
||||
WRITE_OID_FIELD(partitionOid);
|
||||
WRITE_BOOL_FIELD(isContainPartition);
|
||||
if (t_thrd.proc->workingVersionNum >= SYNONYM_VERSION_NUM) {
|
||||
WRITE_OID_FIELD(refSynOid);
|
||||
}
|
||||
WRITE_BOOL_FIELD(ispartrel);
|
||||
WRITE_BOOL_FIELD(ignoreResetRelid);
|
||||
WRITE_NODE_FIELD(pname);
|
||||
WRITE_NODE_FIELD(partid_list);
|
||||
WRITE_NODE_FIELD(plist);
|
||||
if (node->relid >= FirstBootstrapObjectId && IsStatisfyUpdateCompatibility(node->relid) &&
|
||||
node->mainRelName == NULL) {
|
||||
/*
|
||||
* For inherit table, the relname will be different
|
||||
*/
|
||||
// shipping out for dn, relid will be different on dn, so relname and relnamespace string will be must.
|
||||
|
||||
char *rteRelname = NULL;
|
||||
char *rteRelnamespace = NULL;
|
||||
|
||||
getNameById(node->relid, "RTE", &rteRelnamespace, &rteRelname);
|
||||
|
||||
appendStringInfo(str, " :relname ");
|
||||
_outToken(str, rteRelname);
|
||||
appendStringInfo(str, " :relnamespace ");
|
||||
_outToken(str, rteRelnamespace);
|
||||
|
||||
/*
|
||||
* Same reason as above,
|
||||
* we need to serialize the namespace and synonym name instead of refSynOid
|
||||
* when relation name is referenced from one synonym,
|
||||
*/
|
||||
if (t_thrd.proc->workingVersionNum >= SYNONYM_VERSION_NUM) {
|
||||
WRITE_SYNINFO_FIELD(refSynOid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void _outRangeTblEntry(StringInfo str, RangeTblEntry* node)
|
||||
{
|
||||
WRITE_NODE_TYPE("RTE");
|
||||
|
|
@ -4192,48 +4242,7 @@ static void _outRangeTblEntry(StringInfo str, RangeTblEntry* node)
|
|||
|
||||
switch (node->rtekind) {
|
||||
case RTE_RELATION:
|
||||
WRITE_OID_FIELD(relid);
|
||||
WRITE_CHAR_FIELD(relkind);
|
||||
if (t_thrd.proc->workingVersionNum >= MATVIEW_VERSION_NUM) {
|
||||
WRITE_BOOL_FIELD(isResultRel);
|
||||
}
|
||||
WRITE_NODE_FIELD(tablesample);
|
||||
WRITE_OID_FIELD(partitionOid);
|
||||
WRITE_BOOL_FIELD(isContainPartition);
|
||||
if (t_thrd.proc->workingVersionNum >= SYNONYM_VERSION_NUM) {
|
||||
WRITE_OID_FIELD(refSynOid);
|
||||
}
|
||||
WRITE_BOOL_FIELD(ispartrel);
|
||||
WRITE_BOOL_FIELD(ignoreResetRelid);
|
||||
WRITE_NODE_FIELD(pname);
|
||||
WRITE_NODE_FIELD(partid_list);
|
||||
WRITE_NODE_FIELD(plist);
|
||||
if (node->relid >= FirstBootstrapObjectId && IsStatisfyUpdateCompatibility(node->relid) &&
|
||||
node->mainRelName == NULL) {
|
||||
/*
|
||||
* For inherit table, the relname will be different
|
||||
*/
|
||||
// shipping out for dn, relid will be different on dn, so relname and relnamespace string will be must.
|
||||
|
||||
char* rte_relname = NULL;
|
||||
char* rte_relnamespace = NULL;
|
||||
|
||||
getNameById(node->relid, "RTE", &rte_relnamespace, &rte_relname);
|
||||
|
||||
appendStringInfo(str, " :relname ");
|
||||
_outToken(str, rte_relname);
|
||||
appendStringInfo(str, " :relnamespace ");
|
||||
_outToken(str, rte_relnamespace);
|
||||
|
||||
/*
|
||||
* Same reason as above,
|
||||
* we need to serialize the namespace and synonym name instead of refSynOid
|
||||
* when relation name is referenced from one synonym,
|
||||
*/
|
||||
if (t_thrd.proc->workingVersionNum >= SYNONYM_VERSION_NUM) {
|
||||
WRITE_SYNINFO_FIELD(refSynOid);
|
||||
}
|
||||
}
|
||||
_outRteRelation(str, node);
|
||||
break;
|
||||
case RTE_SUBQUERY:
|
||||
WRITE_NODE_FIELD(subquery);
|
||||
|
|
@ -4302,6 +4311,10 @@ static void _outRangeTblEntry(StringInfo str, RangeTblEntry* node)
|
|||
if (t_thrd.proc->workingVersionNum >= SUBLINKPULLUP_VERSION_NUM) {
|
||||
WRITE_BOOL_FIELD(sublink_pull_up);
|
||||
}
|
||||
|
||||
if (t_thrd.proc->workingVersionNum >= GENERATED_COL_VERSION_NUM) {
|
||||
WRITE_BITMAPSET_FIELD(extraUpdatedCols);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -2821,6 +2821,11 @@ static RangeTblEntry* _readRangeTblEntry(void)
|
|||
READ_BOOL_FIELD(sublink_pull_up);
|
||||
}
|
||||
|
||||
IF_EXIST(extraUpdatedCols)
|
||||
{
|
||||
READ_BITMAPSET_FIELD(extraUpdatedCols);
|
||||
}
|
||||
|
||||
READ_DONE();
|
||||
}
|
||||
|
||||
|
|
@ -4823,6 +4828,10 @@ static ColumnDef* _readColumnDef()
|
|||
local_node->storage = 0;
|
||||
}
|
||||
|
||||
IF_EXIST(generatedCol) {
|
||||
READ_CHAR_FIELD(generatedCol);
|
||||
}
|
||||
|
||||
READ_DONE();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3284,6 +3284,8 @@ static List* transformUpdateTargetList(ParseState* pstate, List* qryTlist, List*
|
|||
ERROR, (errcode(ERRCODE_NOT_NULL_VIOLATION), errmsg("UPDATE target count mismatch --- internal error")));
|
||||
}
|
||||
|
||||
setExtraUpdatedCols(pstate);
|
||||
|
||||
return tlist;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -730,7 +730,7 @@ static void parameter_check_execute_direct(const char* query);
|
|||
FALSE_P FAMILY FAST FENCED FETCH FILEHEADER_P FILL_MISSING_FIELDS FILTER FIRST_P FIXED_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORMATTER FORWARD
|
||||
FREEZE FROM FULL FUNCTION FUNCTIONS
|
||||
|
||||
GLOBAL GLOBAL_FUNCTION GRANT GRANTED GREATEST GROUP_P GROUPING_P
|
||||
GENERATED GLOBAL GLOBAL_FUNCTION GRANT GRANTED GREATEST GROUP_P GROUPING_P
|
||||
|
||||
HANDLER HAVING HDFSDIRECTORY HEADER_P HOLD HOUR_P
|
||||
|
||||
|
|
@ -770,7 +770,7 @@ static void parameter_check_execute_direct(const char* query);
|
|||
SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
|
||||
SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHIPPABLE SHOW SHUTDOWN
|
||||
SIMILAR SIMPLE SIZE SLICE SMALLDATETIME SMALLDATETIME_FORMAT_P SMALLINT SNAPSHOT SOME SOURCE_P SPACE SPILL SPLIT STABLE STANDALONE_P START
|
||||
STATEMENT STATEMENT_ID STATISTICS STDIN STDOUT STORAGE STORE_P STREAM STRICT_P STRIP_P SUBSTRING
|
||||
STATEMENT STATEMENT_ID STATISTICS STDIN STDOUT STORAGE STORE_P STORED STREAM STRICT_P STRIP_P SUBSTRING
|
||||
SYMMETRIC SYNONYM SYSDATE SYSID SYSTEM_P SYS_REFCURSOR
|
||||
|
||||
TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THAN THEN TIME TIME_FORMAT_P TIMESTAMP TIMESTAMP_FORMAT_P TIMESTAMPDIFF TINYINT
|
||||
|
|
@ -851,7 +851,7 @@ static void parameter_check_execute_direct(const char* query);
|
|||
* blame any funny behavior of UNBOUNDED on the SQL standard, though.
|
||||
*/
|
||||
%nonassoc UNBOUNDED /* ideally should have same precedence as IDENT */
|
||||
%nonassoc IDENT NULL_P PARTITION RANGE ROWS PRECEDING FOLLOWING CUBE ROLLUP
|
||||
%nonassoc IDENT GENERATED NULL_P PARTITION RANGE ROWS PRECEDING FOLLOWING CUBE ROLLUP
|
||||
%left Op OPERATOR /* multi-character ops and user-defined operators */
|
||||
%nonassoc NOTNULL
|
||||
%nonassoc ISNULL
|
||||
|
|
@ -5090,6 +5090,20 @@ ColConstraintElem:
|
|||
n->cooked_expr = NULL;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
| GENERATED ALWAYS AS '(' a_expr ')' STORED
|
||||
{
|
||||
#ifdef ENABLE_MULTIPLE_NODES
|
||||
ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("Generated column is not yet supported.")));
|
||||
#endif
|
||||
Constraint *n = makeNode(Constraint);
|
||||
n->contype = CONSTR_GENERATED;
|
||||
n->generated_when = ATTRIBUTE_IDENTITY_ALWAYS;
|
||||
n->raw_expr = $5;
|
||||
n->cooked_expr = NULL;
|
||||
n->location = @1;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
| REFERENCES qualified_name opt_column_list key_match key_actions
|
||||
{
|
||||
#ifdef ENABLE_MULTIPLE_NODES
|
||||
|
|
@ -5230,6 +5244,7 @@ TableLikeIncludingOption:
|
|||
| RELOPTIONS { $$ = CREATE_TABLE_LIKE_RELOPTIONS; }
|
||||
| DISTRIBUTION { $$ = CREATE_TABLE_LIKE_DISTRIBUTION; }
|
||||
| OIDS { $$ = CREATE_TABLE_LIKE_OIDS;}
|
||||
| GENERATED { $$ = CREATE_TABLE_LIKE_GENERATED; }
|
||||
;
|
||||
|
||||
TableLikeExcludingOption:
|
||||
|
|
@ -5242,6 +5257,7 @@ TableLikeExcludingOption:
|
|||
| RELOPTIONS { $$ = CREATE_TABLE_LIKE_RELOPTIONS; }
|
||||
| DISTRIBUTION { $$ = CREATE_TABLE_LIKE_DISTRIBUTION; }
|
||||
| OIDS { $$ = CREATE_TABLE_LIKE_OIDS; }
|
||||
| GENERATED { $$ = CREATE_TABLE_LIKE_GENERATED; }
|
||||
| ALL { $$ = CREATE_TABLE_LIKE_ALL; }
|
||||
;
|
||||
|
||||
|
|
@ -20184,6 +20200,7 @@ unreserved_keyword:
|
|||
| FORWARD
|
||||
| FUNCTION
|
||||
| FUNCTIONS
|
||||
| GENERATED
|
||||
| GLOBAL
|
||||
| GLOBAL_FUNCTION
|
||||
| GRANTED
|
||||
|
|
@ -20390,6 +20407,7 @@ unreserved_keyword:
|
|||
| STDOUT
|
||||
| STORAGE
|
||||
| STORE_P
|
||||
| STORED
|
||||
| STREAM
|
||||
| STRICT_P
|
||||
| STRIP_P
|
||||
|
|
|
|||
|
|
@ -640,6 +640,38 @@ void fix_merge_stmt_for_insert_update(ParseState* pstate, MergeStmt* stmt)
|
|||
fill_join_expr(pstate, stmt);
|
||||
}
|
||||
|
||||
void setExtraUpdatedCols(ParseState* pstate)
|
||||
{
|
||||
TupleDesc tupdesc = pstate->p_target_relation->rd_att;
|
||||
RangeTblEntry* target_rte = pstate->p_target_rangetblentry;
|
||||
|
||||
/*
|
||||
* Record in extraUpdatedCols generated columns referencing updated base
|
||||
* columns.
|
||||
*/
|
||||
if (tupdesc->constr &&
|
||||
tupdesc->constr->has_generated_stored)
|
||||
{
|
||||
for (int i = 0; i < tupdesc->constr->num_defval; i++)
|
||||
{
|
||||
AttrDefault defval = tupdesc->constr->defval[i];
|
||||
Node *expr;
|
||||
Bitmapset *attrs_used = NULL;
|
||||
|
||||
/* skip if not generated column */
|
||||
if (!defval.generatedCol)
|
||||
continue;
|
||||
|
||||
expr = (Node *)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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* transformUpdateTargetList -
|
||||
* handle SET clause in UPDATE clause
|
||||
|
|
@ -716,6 +748,8 @@ static List* transformUpdateTargetList(ParseState* pstate, List* origTlist)
|
|||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("UPDATE target count mismatch --- internal error")));
|
||||
}
|
||||
|
||||
setExtraUpdatedCols(pstate);
|
||||
|
||||
return tlist;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ ParseState* make_parsestate(ParseState* parentParseState)
|
|||
pstate->p_resolve_unknowns = true;
|
||||
pstate->ignoreplus = false;
|
||||
pstate->p_plusjoin_rte_info = NULL;
|
||||
pstate->p_rawdefaultlist = NIL;
|
||||
|
||||
if (parentParseState != NULL) {
|
||||
pstate->p_sourcetext = parentParseState->p_sourcetext;
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@ static void set_rte_flags(RangeTblEntry* rte, bool inh, bool inFromCl, int perm)
|
|||
rte->selectedCols = NULL;
|
||||
rte->insertedCols = NULL;
|
||||
rte->updatedCols = NULL;
|
||||
rte->extraUpdatedCols = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -566,6 +567,15 @@ Node* scanRTEForColumn(ParseState* pstate, RangeTblEntry* rte, char* colname, in
|
|||
if (rte->rtekind == RTE_RELATION && rte->relkind != RELKIND_FOREIGN_TABLE && rte->relkind != RELKIND_STREAM) {
|
||||
/* quick check to see if name could be a system column */
|
||||
attnum = specialAttNum(colname);
|
||||
|
||||
/*
|
||||
* In generated column, no system column is allowed except tableOid.
|
||||
*/
|
||||
if (pstate->p_expr_kind == EXPR_KIND_GENERATED_COLUMN && attnum < InvalidAttrNumber) {
|
||||
ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
|
||||
errmsg("cannot use system column \"%s\" in column generation expression", colname),
|
||||
parser_errposition(pstate, location)));
|
||||
}
|
||||
if (attnum != InvalidAttrNumber) {
|
||||
/*
|
||||
* Now check to see if column actually is defined. Because of
|
||||
|
|
@ -1735,6 +1745,7 @@ RangeTblEntry* addRangeTableEntryForCTE(
|
|||
rte->selectedCols = NULL;
|
||||
rte->insertedCols = NULL;
|
||||
rte->updatedCols = NULL;
|
||||
rte->extraUpdatedCols = NULL;
|
||||
rte->orientation = REL_ORIENT_UNKNOWN;
|
||||
|
||||
/*
|
||||
|
|
@ -1791,6 +1802,7 @@ RangeTblEntry* getRangeTableEntryByRelation(Relation rel)
|
|||
rte->insertedCols = NULL;
|
||||
rte->updatedCols = NULL;
|
||||
rte->lateral = false;
|
||||
rte->extraUpdatedCols = NULL;
|
||||
|
||||
setRteOrientation(rel, rte);
|
||||
|
||||
|
|
|
|||
|
|
@ -351,6 +351,7 @@ List* transformCreateStmt(CreateStmt* stmt, const char* queryString, const List*
|
|||
cxt.bucketOid = InvalidOid;
|
||||
cxt.relnodelist = NULL;
|
||||
cxt.toastnodelist = NULL;
|
||||
cxt.ofType = (stmt->ofTypename != NULL);
|
||||
|
||||
/* We have gen uuids, so use it */
|
||||
if (stmt->uuids != NIL)
|
||||
|
|
@ -772,6 +773,7 @@ static void transformColumnDefinition(CreateStmtContext* cxt, ColumnDef* column,
|
|||
bool is_serial = false;
|
||||
bool saw_nullable = false;
|
||||
bool saw_default = false;
|
||||
bool saw_generated = false;
|
||||
Constraint* constraint = NULL;
|
||||
ListCell* clist = NULL;
|
||||
ClientLogicColumnRef* clientLogicColumnRef = NULL;
|
||||
|
|
@ -888,6 +890,23 @@ static void transformColumnDefinition(CreateStmtContext* cxt, ColumnDef* column,
|
|||
saw_default = true;
|
||||
break;
|
||||
|
||||
case CONSTR_GENERATED:
|
||||
if (cxt->ofType) {
|
||||
ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("generated columns are not supported on typed tables")));
|
||||
}
|
||||
if (saw_generated) {
|
||||
ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("multiple generation clauses specified for column \"%s\" of table \"%s\"",
|
||||
column->colname, cxt->relation->relname),
|
||||
parser_errposition(cxt->pstate, constraint->location)));
|
||||
}
|
||||
column->generatedCol = ATTRIBUTE_GENERATED_STORED;
|
||||
column->raw_default = constraint->raw_expr;
|
||||
Assert(constraint->cooked_expr == NULL);
|
||||
saw_generated = true;
|
||||
break;
|
||||
|
||||
case CONSTR_CHECK:
|
||||
cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
|
||||
break;
|
||||
|
|
@ -946,6 +965,15 @@ static void transformColumnDefinition(CreateStmtContext* cxt, ColumnDef* column,
|
|||
transformColumnType(cxt, column);
|
||||
}
|
||||
}
|
||||
|
||||
if (saw_default && saw_generated)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("both default and generation expression specified for column \"%s\" of table \"%s\"",
|
||||
column->colname, cxt->relation->relname),
|
||||
parser_errposition(cxt->pstate,
|
||||
constraint->location)));
|
||||
|
||||
/*
|
||||
* Generate ALTER FOREIGN TABLE ALTER COLUMN statement which adds
|
||||
* per-column foreign data wrapper options for this column.
|
||||
|
|
@ -999,6 +1027,7 @@ static void transformTableConstraint(CreateStmtContext* cxt, Constraint* constra
|
|||
case CONSTR_NULL:
|
||||
case CONSTR_NOTNULL:
|
||||
case CONSTR_DEFAULT:
|
||||
case CONSTR_GENERATED:
|
||||
case CONSTR_ATTR_DEFERRABLE:
|
||||
case CONSTR_ATTR_NOT_DEFERRABLE:
|
||||
case CONSTR_ATTR_DEFERRED:
|
||||
|
|
@ -1417,12 +1446,26 @@ static void transformTableLikeClause(
|
|||
}
|
||||
}
|
||||
|
||||
if (!def->is_serial && (table_like_clause->options & CREATE_TABLE_LIKE_DEFAULTS)) {
|
||||
if (!def->is_serial && (table_like_clause->options & CREATE_TABLE_LIKE_DEFAULTS) &&
|
||||
!GetGeneratedCol(tupleDesc, parent_attno - 1)) {
|
||||
/*
|
||||
* If default expr could contain any vars, we'd need to fix 'em,
|
||||
* but it can't; so default is ready to apply to child.
|
||||
*/
|
||||
def->cooked_default = this_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 (found_whole_row) {
|
||||
ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot convert whole-row table reference"),
|
||||
errdetail(
|
||||
"Generation expression for column \"%s\" contains a whole-row reference to table \"%s\".",
|
||||
attributeName, RelationGetRelationName(relation))));
|
||||
}
|
||||
def->generatedCol = GetGeneratedCol(tupleDesc, parent_attno - 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2026,6 +2069,7 @@ static void transformOfType(CreateStmtContext* cxt, TypeName* ofTypename)
|
|||
n->colname = pstrdup(NameStr(attr->attname));
|
||||
n->typname = makeTypeNameFromOid(attr->atttypid, attr->atttypmod);
|
||||
n->kvtype = ATT_KV_UNDEFINED;
|
||||
n->generatedCol = '\0';
|
||||
n->inhcount = 0;
|
||||
n->is_local = true;
|
||||
n->is_not_null = false;
|
||||
|
|
@ -3782,6 +3826,7 @@ List* transformAlterTableStmt(Oid relid, AlterTableStmt* stmt, const char* query
|
|||
cxt.bucketOid = InvalidOid;
|
||||
cxt.relnodelist = NULL;
|
||||
cxt.toastnodelist = NULL;
|
||||
cxt.ofType = false;
|
||||
|
||||
if (RelationIsForeignTable(rel) || RelationIsStream(rel)) {
|
||||
cxt.canInfomationalConstraint = CAN_BUILD_INFORMATIONAL_CONSTRAINT_BY_RELID(RelationGetRelid(rel));
|
||||
|
|
|
|||
|
|
@ -834,6 +834,34 @@ AttrNumber get_attnum(Oid relid, const char* attname)
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* GetGenerated
|
||||
*
|
||||
* Given the relation id and the attribute number,
|
||||
* return the "generated" field from the attrdef relation.
|
||||
*
|
||||
* Errors if not found.
|
||||
*
|
||||
* Since not generated is represented by '\0', this can also be used as a
|
||||
* Boolean test.
|
||||
*/
|
||||
char GetGenerated(Oid relid, AttrNumber attnum)
|
||||
{
|
||||
char result = '\0';
|
||||
Relation relation;
|
||||
TupleDesc tupdesc;
|
||||
|
||||
/* Assume we already have adequate lock */
|
||||
relation = heap_open(relid, NoLock);
|
||||
|
||||
tupdesc = RelationGetDescr(relation);
|
||||
result = GetGeneratedCol(tupdesc, attnum - 1);
|
||||
|
||||
heap_close(relation, NoLock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* get_atttype
|
||||
*
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@
|
|||
#include "optimizer/prep.h"
|
||||
#include "optimizer/var.h"
|
||||
#include "rewrite/rewriteDefine.h"
|
||||
#include "rewrite/rewriteHandler.h"
|
||||
#include "storage/lmgr.h"
|
||||
#include "storage/smgr.h"
|
||||
#include "catalog/storage.h"
|
||||
|
|
|
|||
|
|
@ -1381,6 +1381,7 @@ static void RelationBuildTupleDesc(Relation relation, bool onlyLoadInitDefVal)
|
|||
|
||||
constr = (TupleConstr*)MemoryContextAllocZero(u_sess->cache_mem_cxt, sizeof(TupleConstr));
|
||||
constr->has_not_null = false;
|
||||
constr->has_generated_stored = false;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -1572,10 +1573,13 @@ static void RelationBuildTupleDesc(Relation relation, bool onlyLoadInitDefVal)
|
|||
else
|
||||
constr->defval = attrdef;
|
||||
constr->num_defval = ndef;
|
||||
constr->generatedCols = (char *)MemoryContextAllocZero(u_sess->cache_mem_cxt,
|
||||
RelationGetNumberOfAttributes(relation) * sizeof(char));
|
||||
AttrDefaultFetch(relation);
|
||||
} else {
|
||||
constr->num_defval = 0;
|
||||
constr->defval = NULL;
|
||||
constr->generatedCols = NULL;
|
||||
}
|
||||
|
||||
if (relation->rd_rel->relchecks > 0) /* CHECKs */
|
||||
|
|
@ -5017,28 +5021,46 @@ TupleDesc GetDefaultPgIndexDesc(void)
|
|||
return GetPgIndexDescriptor();
|
||||
}
|
||||
|
||||
/*
|
||||
* Load generated column attribute value definitions for the relation.
|
||||
*/
|
||||
static void GeneratedColFetch(TupleConstr *constr, HeapTuple htup, Relation adrel, int attrdefIndex)
|
||||
{
|
||||
char *genCols = constr->generatedCols;
|
||||
AttrDefault *attrdef = constr->defval;
|
||||
char generatedCol = '\0';
|
||||
if (HeapTupleHeaderGetNatts(htup->t_data, adrel->rd_att) >= Anum_pg_attrdef_adgencol) {
|
||||
bool isnull = false;
|
||||
Datum val = fastgetattr(htup, Anum_pg_attrdef_adgencol, adrel->rd_att, &isnull);
|
||||
if (!isnull) {
|
||||
generatedCol = DatumGetChar(val);
|
||||
}
|
||||
}
|
||||
attrdef[attrdefIndex].generatedCol = generatedCol;
|
||||
genCols[attrdef[attrdefIndex].adnum - 1] = generatedCol;
|
||||
if (generatedCol == ATTRIBUTE_GENERATED_STORED) {
|
||||
constr->has_generated_stored = true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Load any default attribute value definitions for the relation.
|
||||
*/
|
||||
static void AttrDefaultFetch(Relation relation)
|
||||
{
|
||||
AttrDefault* attrdef = relation->rd_att->constr->defval;
|
||||
AttrDefault *attrdef = relation->rd_att->constr->defval;
|
||||
int ndef = relation->rd_att->constr->num_defval;
|
||||
Relation adrel;
|
||||
SysScanDesc adscan;
|
||||
ScanKeyData skey;
|
||||
HeapTuple htup;
|
||||
Datum val;
|
||||
bool isnull = false;
|
||||
int found;
|
||||
int i;
|
||||
int found = 0;
|
||||
|
||||
ScanKeyInit(
|
||||
&skey, Anum_pg_attrdef_adrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(relation)));
|
||||
|
||||
adrel = heap_open(AttrDefaultRelationId, AccessShareLock);
|
||||
adscan = systable_beginscan(adrel, AttrDefaultIndexId, true, NULL, 1, &skey);
|
||||
found = 0;
|
||||
ScanKeyInit(&skey, Anum_pg_attrdef_adrelid, BTEqualStrategyNumber, F_OIDEQ,
|
||||
ObjectIdGetDatum(RelationGetRelid(relation)));
|
||||
Relation adrel = heap_open(AttrDefaultRelationId, AccessShareLock);
|
||||
SysScanDesc adscan = systable_beginscan(adrel, AttrDefaultIndexId, true, SnapshotNow, 1, &skey);
|
||||
|
||||
while (HeapTupleIsValid(htup = systable_getnext(adscan))) {
|
||||
Form_pg_attrdef adform = (Form_pg_attrdef)GETSTRUCT(htup);
|
||||
|
|
@ -5046,30 +5068,30 @@ static void AttrDefaultFetch(Relation relation)
|
|||
for (i = 0; i < ndef; i++) {
|
||||
if (adform->adnum != attrdef[i].adnum)
|
||||
continue;
|
||||
|
||||
if (attrdef[i].adbin != NULL)
|
||||
ereport(WARNING,
|
||||
(errmsg("multiple attrdef records found for attr %s of rel %s",
|
||||
NameStr(relation->rd_att->attrs[adform->adnum - 1]->attname),
|
||||
RelationGetRelationName(relation))));
|
||||
ereport(WARNING, (errmsg("multiple attrdef records found for attr %s of rel %s",
|
||||
NameStr(relation->rd_att->attrs[adform->adnum - 1]->attname), RelationGetRelationName(relation))));
|
||||
else
|
||||
found++;
|
||||
|
||||
if (t_thrd.proc->workingVersionNum >= GENERATED_COL_VERSION_NUM) {
|
||||
GeneratedColFetch(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))));
|
||||
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(u_sess->cache_mem_cxt, TextDatumGetCString(val));
|
||||
break;
|
||||
}
|
||||
|
||||
if (i >= ndef)
|
||||
ereport(WARNING,
|
||||
(errmsg("unexpected attrdef record found for attr %d of rel %s",
|
||||
adform->adnum,
|
||||
RelationGetRelationName(relation))));
|
||||
if (i >= ndef) {
|
||||
ereport(WARNING, (errmsg("unexpected attrdef record found for attr %d of rel %s", adform->adnum,
|
||||
RelationGetRelationName(relation))));
|
||||
}
|
||||
}
|
||||
|
||||
systable_endscan(adscan);
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ const module_data module_map[] = {{MOD_ALL, "ALL"},
|
|||
{MOD_HOTKEY, "HOTKEY"},
|
||||
{MOD_THREAD_POOL, "THREAD_POOL"},
|
||||
{MOD_OPT_AI, "OPT_AI"},
|
||||
{MOD_GEN_COL, "GEN_COL"},
|
||||
|
||||
/* add your module name above */
|
||||
{MOD_MAX, "BACKEND"}};
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ bool open_join_children = true;
|
|||
bool will_shutdown = false;
|
||||
|
||||
/* hard-wired binary version number */
|
||||
const uint32 GRAND_VERSION_NUM = 92302;
|
||||
const uint32 GRAND_VERSION_NUM = 92303;
|
||||
|
||||
const uint32 MATVIEW_VERSION_NUM = 92213;
|
||||
const uint32 PARTIALPUSH_VERSION_NUM = 92087;
|
||||
|
|
@ -77,6 +77,8 @@ const uint32 RANGE_LIST_DISTRIBUTION_VERSION_NUM = 92272;
|
|||
const uint32 BACKUP_SLOT_VERSION_NUM = 92282;
|
||||
const uint32 ML_OPT_MODEL_VERSION_NUM = 92284;
|
||||
const uint32 FIX_SQL_ADD_RELATION_REF_COUNT = 92291;
|
||||
const uint32 GENERATED_COL_VERSION_NUM = 92303;
|
||||
|
||||
/* This variable indicates wheather the instance is in progress of upgrade as a whole */
|
||||
uint32 volatile WorkingGrandVersionNum = GRAND_VERSION_NUM;
|
||||
|
||||
|
|
|
|||
|
|
@ -521,7 +521,7 @@ extern THR_LOCAL bool stmt_contains_operator_plus;
|
|||
FALSE_P FAMILY FAST FENCED FETCH FILEHEADER_P FILL_MISSING_FIELDS FILTER FIRST_P FIXED_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORMATTER FORWARD
|
||||
FREEZE FROM FULL FUNCTION FUNCTIONS
|
||||
|
||||
GLOBAL GLOBAL_FUNCTION GRANT GRANTED GREATEST GROUP_P GROUPING_P
|
||||
GENERATED GLOBAL GLOBAL_FUNCTION GRANT GRANTED GREATEST GROUP_P GROUPING_P
|
||||
|
||||
HANDLER HAVING HDFSDIRECTORY HEADER_P HOLD HOUR_P
|
||||
|
||||
|
|
@ -560,7 +560,7 @@ extern THR_LOCAL bool stmt_contains_operator_plus;
|
|||
SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
|
||||
SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHIPPABLE SHOW SHUTDOWN
|
||||
SIMILAR SIMPLE SIZE SLICE SMALLDATETIME SMALLDATETIME_FORMAT_P SMALLINT SNAPSHOT SOME SOURCE_P SPACE SPILL SPLIT STABLE STANDALONE_P START
|
||||
STATEMENT STATEMENT_ID STATISTICS STDIN STDOUT STORAGE STORE_P STREAM STRICT_P STRIP_P SUBSTRING
|
||||
STATEMENT STATEMENT_ID STATISTICS STDIN STDOUT STORAGE STORE_P STORED STREAM STRICT_P STRIP_P SUBSTRING
|
||||
SYMMETRIC SYNONYM SYSDATE SYSID SYSTEM_P SYS_REFCURSOR
|
||||
|
||||
TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THAN THEN TIME TIME_FORMAT_P TIMESTAMP TIMESTAMP_FORMAT_P TIMESTAMPDIFF TINYINT
|
||||
|
|
@ -642,7 +642,7 @@ extern THR_LOCAL bool stmt_contains_operator_plus;
|
|||
* blame any funny behavior of UNBOUNDED on the SQL standard, though.
|
||||
*/
|
||||
%nonassoc UNBOUNDED /* ideally should have same precedence as IDENT */
|
||||
%nonassoc IDENT NULL_P PARTITION RANGE ROWS PRECEDING FOLLOWING CUBE ROLLUP
|
||||
%nonassoc IDENT GENERATED NULL_P PARTITION RANGE ROWS PRECEDING FOLLOWING CUBE ROLLUP
|
||||
%left Op OPERATOR /* multi-character ops and user-defined operators */
|
||||
%nonassoc NOTNULL
|
||||
%nonassoc ISNULL
|
||||
|
|
@ -4904,6 +4904,20 @@ ColConstraintElem:
|
|||
n->cooked_expr = NULL;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
| GENERATED ALWAYS AS '(' a_expr ')' STORED
|
||||
{
|
||||
#ifdef ENABLE_MULTIPLE_NODES
|
||||
ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("Generated column is not yet supported.")));
|
||||
#endif
|
||||
Constraint *n = makeNode(Constraint);
|
||||
n->contype = CONSTR_GENERATED;
|
||||
n->generated_when = ATTRIBUTE_IDENTITY_ALWAYS;
|
||||
n->raw_expr = $5;
|
||||
n->cooked_expr = NULL;
|
||||
n->location = @1;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
| REFERENCES qualified_name opt_column_list key_match key_actions
|
||||
{
|
||||
Constraint *n = makeNode(Constraint);
|
||||
|
|
@ -5044,6 +5058,7 @@ TableLikeIncludingOption:
|
|||
| RELOPTIONS { $$ = CREATE_TABLE_LIKE_RELOPTIONS; }
|
||||
| DISTRIBUTION { $$ = CREATE_TABLE_LIKE_DISTRIBUTION; }
|
||||
| OIDS { $$ = CREATE_TABLE_LIKE_OIDS;}
|
||||
| GENERATED { $$ = CREATE_TABLE_LIKE_GENERATED; }
|
||||
;
|
||||
|
||||
TableLikeExcludingOption:
|
||||
|
|
@ -5056,6 +5071,7 @@ TableLikeExcludingOption:
|
|||
| RELOPTIONS { $$ = CREATE_TABLE_LIKE_RELOPTIONS; }
|
||||
| DISTRIBUTION { $$ = CREATE_TABLE_LIKE_DISTRIBUTION; }
|
||||
| OIDS { $$ = CREATE_TABLE_LIKE_OIDS; }
|
||||
| GENERATED { $$ = CREATE_TABLE_LIKE_GENERATED; }
|
||||
| ALL { $$ = CREATE_TABLE_LIKE_ALL; }
|
||||
;
|
||||
|
||||
|
|
@ -10706,6 +10722,7 @@ unreserved_keyword:
|
|||
| FORCE
|
||||
| FORMATTER
|
||||
| FORWARD
|
||||
| GENERATED
|
||||
| GLOBAL
|
||||
| GLOBAL_FUNCTION
|
||||
| GRANTED
|
||||
|
|
@ -10905,6 +10922,7 @@ unreserved_keyword:
|
|||
| STDOUT
|
||||
| STORAGE
|
||||
| STORE_P
|
||||
| STORED
|
||||
| STRICT_P
|
||||
| STRIP_P
|
||||
| SYNONYM
|
||||
|
|
|
|||
|
|
@ -204,6 +204,7 @@ static void rebuild_exception_subtransaction_chain(PLpgSQL_execstate* estate);
|
|||
|
||||
static void stp_check_transaction_and_set_resource_owner(ResourceOwner oldResourceOwner,TransactionId oldTransactionId);
|
||||
static void stp_check_transaction_and_create_econtext(PLpgSQL_execstate* estate,TransactionId oldTransactionId);
|
||||
static void RecordSetGeneratedField(PLpgSQL_rec *recNew);
|
||||
|
||||
bool plpgsql_get_current_value_stp_with_exception();
|
||||
void plpgsql_restore_current_value_stp_with_exception(bool saved_current_stp_with_exception);
|
||||
|
|
@ -631,6 +632,47 @@ Datum plpgsql_exec_function(PLpgSQL_function* func, FunctionCallInfo fcinfo, boo
|
|||
return estate.retval;
|
||||
}
|
||||
|
||||
static void RecordSetGeneratedField(PLpgSQL_rec *recNew)
|
||||
{
|
||||
int natts = recNew->tupdesc->natts;
|
||||
Datum* values = NULL;
|
||||
bool* nulls = NULL;
|
||||
bool* replaces = NULL;
|
||||
HeapTuple newtup = NULL;
|
||||
errno_t rc = EOK;
|
||||
|
||||
/*
|
||||
* Set up values/control arrays for heap_modify_tuple. For all
|
||||
* the attributes except the one we want to replace, use the
|
||||
* value that's in the old tuple.
|
||||
*/
|
||||
values = (Datum*)palloc(sizeof(Datum) * natts);
|
||||
nulls = (bool*)palloc(sizeof(bool) * natts);
|
||||
replaces = (bool*)palloc(sizeof(bool) * natts);
|
||||
|
||||
rc = memset_s(replaces, sizeof(bool) * natts, false, sizeof(bool) * natts);
|
||||
securec_check(rc, "\0", "\0");
|
||||
|
||||
for (int i = 0; i < recNew->tupdesc->natts; i++) {
|
||||
if (GetGeneratedCol(recNew->tupdesc, i) == ATTRIBUTE_GENERATED_STORED) {
|
||||
replaces[i] = true;
|
||||
values[i] = (Datum)0;
|
||||
nulls[i] = true;
|
||||
}
|
||||
}
|
||||
newtup = heap_modify_tuple(recNew->tup, recNew->tupdesc, values, nulls, replaces);
|
||||
if (recNew->freetup) {
|
||||
heap_freetuple_ext(recNew->tup);
|
||||
}
|
||||
|
||||
recNew->tup = newtup;
|
||||
recNew->freetup = true;
|
||||
|
||||
pfree_ext(values);
|
||||
pfree_ext(nulls);
|
||||
pfree_ext(replaces);
|
||||
}
|
||||
|
||||
/* ----------
|
||||
* plpgsql_exec_trigger Called by the call handler for
|
||||
* trigger execution.
|
||||
|
|
@ -698,6 +740,19 @@ HeapTuple plpgsql_exec_trigger(PLpgSQL_function* func, TriggerData* trigdata)
|
|||
} else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) {
|
||||
rec_new->tup = trigdata->tg_newtuple;
|
||||
rec_old->tup = trigdata->tg_trigtuple;
|
||||
|
||||
/*
|
||||
* In BEFORE trigger, stored generated columns are not computed yet,
|
||||
* so make them null in the NEW row. (Only needed in UPDATE branch;
|
||||
* in the INSERT case, they are already null, but in UPDATE, the field
|
||||
* still contains the old value.) Alternatively, we could construct a
|
||||
* whole new row structure without the generated columns, but this way
|
||||
* seems more efficient and potentially less confusing.
|
||||
*/
|
||||
if (rec_new->tupdesc->constr && rec_new->tupdesc->constr->has_generated_stored &&
|
||||
TRIGGER_FIRED_BEFORE(trigdata->tg_event)) {
|
||||
RecordSetGeneratedField(rec_new);
|
||||
}
|
||||
} else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event)) {
|
||||
rec_new->tup = NULL;
|
||||
rec_old->tup = trigdata->tg_trigtuple;
|
||||
|
|
|
|||
|
|
@ -337,7 +337,7 @@ static PyObject* PLy_cursor_iternext(PyObject* self)
|
|||
PLy_input_tuple_funcs(&cursor->result, SPI_tuptable->tupdesc);
|
||||
}
|
||||
|
||||
ret = PLyDict_FromTuple(&cursor->result, SPI_tuptable->vals[0], SPI_tuptable->tupdesc);
|
||||
ret = PLyDict_FromTuple(&cursor->result, SPI_tuptable->vals[0], SPI_tuptable->tupdesc, true);
|
||||
}
|
||||
|
||||
SPI_freetuptable(SPI_tuptable);
|
||||
|
|
@ -411,8 +411,7 @@ static PyObject* PLy_cursor_fetch(PyObject* self, PyObject* args)
|
|||
ret->rows = PyList_New(SPI_processed);
|
||||
|
||||
for (uint32 i = 0; i < SPI_processed; i++) {
|
||||
PyObject* row = PLyDict_FromTuple(&cursor->result, SPI_tuptable->vals[i], SPI_tuptable->tupdesc);
|
||||
|
||||
PyObject *row = PLyDict_FromTuple(&cursor->result, SPI_tuptable->vals[i], SPI_tuptable->tupdesc, true);
|
||||
PyList_SetItem(ret->rows, i, row);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -344,7 +344,7 @@ static PyObject* PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure*
|
|||
tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
|
||||
tmptup.t_data = td;
|
||||
|
||||
arg = PLyDict_FromTuple(&(proc->args[i]), &tmptup, tupdesc);
|
||||
arg = PLyDict_FromTuple(&(proc->args[i]), &tmptup, tupdesc, true);
|
||||
ReleaseTupleDesc(tupdesc);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -489,7 +489,8 @@ static PyObject* PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure* p
|
|||
pltevent = PyString_FromString("INSERT");
|
||||
|
||||
PyDict_SetItemString(pltdata, "old", Py_None);
|
||||
pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple, tdata->tg_relation->rd_att);
|
||||
pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple, tdata->tg_relation->rd_att,
|
||||
!TRIGGER_FIRED_BEFORE(tdata->tg_event));
|
||||
PyDict_SetItemString(pltdata, "new", pytnew);
|
||||
Py_DECREF(pytnew);
|
||||
*rv = tdata->tg_trigtuple;
|
||||
|
|
@ -497,17 +498,18 @@ static PyObject* PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure* p
|
|||
pltevent = PyString_FromString("DELETE");
|
||||
|
||||
PyDict_SetItemString(pltdata, "new", Py_None);
|
||||
pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple, tdata->tg_relation->rd_att);
|
||||
pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple, tdata->tg_relation->rd_att, true);
|
||||
PyDict_SetItemString(pltdata, "old", pytold);
|
||||
Py_DECREF(pytold);
|
||||
*rv = tdata->tg_trigtuple;
|
||||
} else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event)) {
|
||||
pltevent = PyString_FromString("UPDATE");
|
||||
|
||||
pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_newtuple, tdata->tg_relation->rd_att);
|
||||
pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_newtuple, tdata->tg_relation->rd_att,
|
||||
!TRIGGER_FIRED_BEFORE(tdata->tg_event));
|
||||
PyDict_SetItemString(pltdata, "new", pytnew);
|
||||
Py_DECREF(pytnew);
|
||||
pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple, tdata->tg_relation->rd_att);
|
||||
pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple, tdata->tg_relation->rd_att, true);
|
||||
PyDict_SetItemString(pltdata, "old", pytold);
|
||||
Py_DECREF(pytold);
|
||||
*rv = tdata->tg_newtuple;
|
||||
|
|
@ -636,6 +638,10 @@ static HeapTuple PLy_modify_tuple(PLyProcedure* proc, PyObject* pltd, TriggerDat
|
|||
}
|
||||
atti = attn - 1;
|
||||
|
||||
if (ISGENERATEDCOL(tupdesc, atti))
|
||||
ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
|
||||
errmsg("cannot set generated column \"%s\"", plattstr)));
|
||||
|
||||
plval = PyDict_GetItem(plntup, platt);
|
||||
if (plval == NULL) {
|
||||
elog(FATAL, "Python interpreter is probably corrupted");
|
||||
|
|
|
|||
|
|
@ -397,7 +397,7 @@ static PyObject* PLy_spi_execute_fetch_result(SPITupleTable* tuptable, int rows,
|
|||
|
||||
PLy_input_tuple_funcs(&args, tuptable->tupdesc);
|
||||
for (i = 0; i < rows; i++) {
|
||||
PyObject* row = PLyDict_FromTuple(&args, tuptable->vals[i], tuptable->tupdesc);
|
||||
PyObject *row = PLyDict_FromTuple(&args, tuptable->vals[i], tuptable->tupdesc, true);
|
||||
|
||||
PyList_SetItem(result->rows, i, row);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -274,7 +274,7 @@ void PLy_output_record_funcs(PLyTypeInfo* arg, TupleDesc desc)
|
|||
/*
|
||||
* Transform a tuple into a Python dict object.
|
||||
*/
|
||||
PyObject* PLyDict_FromTuple(PLyTypeInfo* info, HeapTuple tuple, TupleDesc desc)
|
||||
PyObject *PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc, bool include_generated)
|
||||
{
|
||||
PyObject* volatile dict = NULL;
|
||||
PLyExecutionContext* exec_ctx = PLy_current_execution_context();
|
||||
|
|
@ -307,6 +307,12 @@ PyObject* PLyDict_FromTuple(PLyTypeInfo* info, HeapTuple tuple, TupleDesc desc)
|
|||
continue;
|
||||
}
|
||||
|
||||
if (ISGENERATEDCOL(desc, i)) {
|
||||
/* don't include unless requested */
|
||||
if (!include_generated)
|
||||
continue;
|
||||
}
|
||||
|
||||
key = NameStr(desc->attrs[i]->attname);
|
||||
vattr = heap_getattr(tuple, (i + 1), desc, &is_null);
|
||||
|
||||
|
|
|
|||
|
|
@ -95,6 +95,6 @@ extern void PLy_output_record_funcs(PLyTypeInfo* arg, TupleDesc desc);
|
|||
extern Datum PLyObject_ToCompositeDatum(PLyTypeInfo* info, TupleDesc desc, PyObject* plrv);
|
||||
|
||||
/* conversion from heap tuples to Python dictionaries */
|
||||
extern PyObject* PLyDict_FromTuple(PLyTypeInfo* info, HeapTuple tuple, TupleDesc desc);
|
||||
extern PyObject *PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc, bool include_generated);
|
||||
|
||||
#endif /* PLPY_TYPEIO_H */
|
||||
|
|
|
|||
|
|
@ -4074,6 +4074,15 @@ static uint64 CopyFrom(CopyState cstate)
|
|||
resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate, resultRelInfo, slot, NULL);
|
||||
processed++;
|
||||
} else if (!skip_tuple) {
|
||||
/*
|
||||
* Compute stored generated columns
|
||||
*/
|
||||
if (resultRelInfo->ri_RelationDesc->rd_att->constr &&
|
||||
resultRelInfo->ri_RelationDesc->rd_att->constr->has_generated_stored) {
|
||||
ExecComputeStoredGenerated(resultRelInfo, estate, slot, tuple, CMD_INSERT);
|
||||
tuple = (HeapTuple)slot->tts_tuple;
|
||||
}
|
||||
|
||||
/* Check the constraints of the tuple */
|
||||
if (cstate->rel->rd_att->constr)
|
||||
ExecConstraints(resultRelInfo, slot, estate);
|
||||
|
|
@ -4911,10 +4920,10 @@ CopyState BeginCopyFrom(Relation rel, const char* filename, List* attnamelist, L
|
|||
fmgr_info(in_func_oid, &in_functions[attnum - 1]);
|
||||
|
||||
/* Get default info if needed */
|
||||
if (!list_member_int(cstate->attnumlist, attnum)) {
|
||||
if (!list_member_int(cstate->attnumlist, attnum) && !ISGENERATEDCOL(tupDesc, attnum - 1)) {
|
||||
/* attribute is NOT to be copied from input */
|
||||
/* use default value if one exists */
|
||||
Expr* defexpr = (Expr*)build_column_default(cstate->rel, attnum);
|
||||
Expr *defexpr = (Expr *)build_column_default(cstate->rel, attnum);
|
||||
|
||||
if (defexpr != NULL) {
|
||||
#ifdef PGXC
|
||||
|
|
@ -7101,6 +7110,11 @@ List* CopyGetAllAttnums(TupleDesc tupDesc, Relation rel)
|
|||
* or NIL if there was none (in which case we want all the non-dropped
|
||||
* columns).
|
||||
*
|
||||
* We don't include generated columns in the generated full list and we don't
|
||||
* allow them to be specified explicitly. They don't make sense for COPY
|
||||
* FROM, but we could possibly allow them for COPY TO. But this way it's at
|
||||
* least ensured that whatever we copy out can be copied back in.
|
||||
*
|
||||
* rel can be NULL ... it's only used for error reports.
|
||||
*/
|
||||
List* CopyGetAttnums(TupleDesc tupDesc, Relation rel, List* attnamelist)
|
||||
|
|
@ -7109,13 +7123,15 @@ List* CopyGetAttnums(TupleDesc tupDesc, Relation rel, List* attnamelist)
|
|||
|
||||
if (attnamelist == NIL) {
|
||||
/* Generate default column list */
|
||||
Form_pg_attribute* attr = tupDesc->attrs;
|
||||
Form_pg_attribute *attr = tupDesc->attrs;
|
||||
int attr_count = tupDesc->natts;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < attr_count; i++) {
|
||||
if (attr[i]->attisdropped)
|
||||
continue;
|
||||
if (ISGENERATEDCOL(tupDesc, i))
|
||||
continue;
|
||||
attnums = lappend_int(attnums, i + 1);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -7133,6 +7149,11 @@ List* CopyGetAttnums(TupleDesc tupDesc, Relation rel, List* attnamelist)
|
|||
if (tupDesc->attrs[i]->attisdropped)
|
||||
continue;
|
||||
if (namestrcmp(&(tupDesc->attrs[i]->attname), name) == 0) {
|
||||
if (ISGENERATEDCOL(tupDesc, i)) {
|
||||
ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
|
||||
errmsg("column \"%s\" is a generated column", name),
|
||||
errdetail("Generated columns cannot be used in COPY.")));
|
||||
}
|
||||
attnum = tupDesc->attrs[i]->attnum;
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -339,6 +339,7 @@ static void intorel_startup(DestReceiver* self, int operation, TupleDesc typeinf
|
|||
col->is_not_null = false;
|
||||
col->is_from_type = false;
|
||||
col->storage = 0;
|
||||
col->generatedCol = '\0';
|
||||
col->kvtype = attribute->attkvtype;
|
||||
col->cmprs_mode = attribute->attcmprmode;
|
||||
col->raw_default = NULL;
|
||||
|
|
|
|||
|
|
@ -1368,6 +1368,7 @@ ColumnDef* makeColumnDef(const char* colname, char* coltype)
|
|||
col->colname = pstrdup(colname);
|
||||
col->typname = SystemTypeName(coltype);
|
||||
col->kvtype = 0;
|
||||
col->generatedCol = '\0';
|
||||
col->inhcount = 0;
|
||||
col->is_local = true;
|
||||
col->is_not_null = false;
|
||||
|
|
|
|||
|
|
@ -695,6 +695,7 @@ static bool WLMRelationCanTruncate(Relation rel);
|
|||
static OnCommitAction GttOncommitOption(const List *options);
|
||||
static void ATCheckDuplicateColumn(const AlterTableCmd* cmd, const List* tabCmds);
|
||||
static void ATCheckNotNullConstr(const AlterTableCmd* cmd, const AlteredTableInfo* tab);
|
||||
static void DelDependencONDataType(Relation rel, Relation depRel, const Form_pg_attribute attTup);
|
||||
|
||||
/* get all partitions oid */
|
||||
static List* get_all_part_oid(Oid relid)
|
||||
|
|
@ -2202,14 +2203,20 @@ Oid DefineRelation(CreateStmt* stmt, char relkind, Oid ownerId)
|
|||
isMOTTableFromSrvName(((CreateForeignTableStmt*)stmt)->servername) ||
|
||||
#endif
|
||||
isPostgresFDWFromSrvName(((CreateForeignTableStmt*)stmt)->servername))))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("default values on foreign tables are not supported")));
|
||||
ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("%s on foreign tables are not supported",
|
||||
colDef->generatedCol ? "generated column" : "default values")));
|
||||
#ifdef ENABLE_MOT
|
||||
if (IsA(stmt, CreateForeignTableStmt) &&
|
||||
isMOTTableFromSrvName(((CreateForeignTableStmt *)stmt)->servername) && colDef->generatedCol) {
|
||||
ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("generated column on foreign tables are not supported")));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (relkind == RELKIND_STREAM) {
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("default values on streams are not supported")));
|
||||
ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("%s on streams are not supported",
|
||||
colDef->generatedCol ? "generated column" : "default values")));
|
||||
}
|
||||
|
||||
Assert(colDef->cooked_default == NULL);
|
||||
|
|
@ -2217,13 +2224,18 @@ Oid DefineRelation(CreateStmt* stmt, char relkind, Oid ownerId)
|
|||
rawEnt = (RawColumnDefault*)palloc(sizeof(RawColumnDefault));
|
||||
rawEnt->attnum = attnum;
|
||||
rawEnt->raw_default = colDef->raw_default;
|
||||
rawEnt->generatedCol = colDef->generatedCol;
|
||||
rawDefaults = lappend(rawDefaults, rawEnt);
|
||||
descriptor->attrs[attnum - 1]->atthasdef = true;
|
||||
} else if (colDef->cooked_default != NULL) {
|
||||
CookedConstraint* cooked = NULL;
|
||||
|
||||
cooked = (CookedConstraint*)palloc(sizeof(CookedConstraint));
|
||||
cooked->contype = CONSTR_DEFAULT;
|
||||
|
||||
if (colDef->generatedCol)
|
||||
cooked->contype = CONSTR_GENERATED;
|
||||
else
|
||||
cooked->contype = CONSTR_DEFAULT;
|
||||
cooked->name = NULL;
|
||||
cooked->attnum = attnum;
|
||||
cooked->expr = colDef->cooked_default;
|
||||
|
|
@ -2417,7 +2429,7 @@ Oid DefineRelation(CreateStmt* stmt, char relkind, Oid ownerId)
|
|||
rel = relation_open(relationId, AccessExclusiveLock);
|
||||
|
||||
/*
|
||||
* Now add any newly specified column default values and CHECK constraints
|
||||
* Now add any newly specified column default and generation expressions
|
||||
* to the new relation. These are passed to us in the form of raw
|
||||
* parsetrees; we need to transform them to executable expression trees
|
||||
* before they can be added. The most convenient way to do that is to
|
||||
|
|
@ -4181,6 +4193,7 @@ static List* MergeAttributes(
|
|||
coldef->constraints = restdef->constraints;
|
||||
coldef->is_from_type = false;
|
||||
coldef->kvtype = restdef->kvtype;
|
||||
coldef->generatedCol = restdef->generatedCol;
|
||||
coldef->cmprs_mode = restdef->cmprs_mode;
|
||||
list_delete_cell(schema, rest, prev);
|
||||
} else {
|
||||
|
|
@ -4343,6 +4356,12 @@ static List* MergeAttributes(
|
|||
}
|
||||
/* Default and other constraints are handled below */
|
||||
newattno[parent_attno - 1] = exist_attno;
|
||||
|
||||
/* Check for GENERATED conflicts */
|
||||
if (def->generatedCol != GetGeneratedCol(tupleDesc, parent_attno - 1)) {
|
||||
ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("inherited column \"%s\" has a generation conflict", attributeName)));
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* No, create a new inherited column
|
||||
|
|
@ -4358,6 +4377,7 @@ static List* MergeAttributes(
|
|||
def->kvtype = attribute->attkvtype;
|
||||
def->cmprs_mode = attribute->attcmprmode;
|
||||
def->raw_default = NULL;
|
||||
def->generatedCol = '\0';
|
||||
def->cooked_default = NULL;
|
||||
def->collClause = NULL;
|
||||
def->collOid = attribute->attcollation;
|
||||
|
|
@ -4577,11 +4597,17 @@ static List* MergeAttributes(
|
|||
foreach (entry, schema) {
|
||||
ColumnDef* def = (ColumnDef*)lfirst(entry);
|
||||
|
||||
if (def->cooked_default == &u_sess->cmd_cxt.bogus_marker)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
|
||||
if (def->cooked_default == &u_sess->cmd_cxt.bogus_marker) {
|
||||
if (def->generatedCol) {
|
||||
ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
|
||||
errmsg("column \"%s\" inherits conflicting generated column", def->colname),
|
||||
errhint("To resolve the conflict, specify a generated expr explicitly.")));
|
||||
} else {
|
||||
ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
|
||||
errmsg("column \"%s\" inherits conflicting default values", def->colname),
|
||||
errhint("To resolve the conflict, specify a default explicitly.")));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -8684,12 +8710,12 @@ static void ATExecAddColumn(List** wqueue, AlteredTableInfo* tab, Relation rel,
|
|||
#else
|
||||
if (!isPostgresFDWFromTblOid(RelationGetRelid(rel))) {
|
||||
#endif
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("default values on foreign tables are not supported")));
|
||||
ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("%s on foreign tables are not supported",
|
||||
colDef->generatedCol ? "generated column" : "default values")));
|
||||
}
|
||||
} else if (relkind == RELKIND_STREAM) {
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("default values on streams are not supported")));
|
||||
ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("%s on streams are not supported",
|
||||
colDef->generatedCol ? "generated column" : "default values")));
|
||||
} else if (RelationIsTsStore(rel)) {
|
||||
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("It's not supported to add column with default value for timeseries tables.")));
|
||||
|
|
@ -8699,6 +8725,8 @@ static void ATExecAddColumn(List** wqueue, AlteredTableInfo* tab, Relation rel,
|
|||
rawEnt->attnum = attribute.attnum;
|
||||
rawEnt->raw_default = (Node*)copyObject(colDef->raw_default);
|
||||
|
||||
rawEnt->generatedCol = colDef->generatedCol;
|
||||
|
||||
/*
|
||||
* This function is intended for CREATE TABLE, so it processes a
|
||||
* _list_ of defaults, but we just do one.
|
||||
|
|
@ -8788,7 +8816,7 @@ static void ATExecAddColumn(List** wqueue, AlteredTableInfo* tab, Relation rel,
|
|||
* also exclude temp table and column table.
|
||||
*/
|
||||
if (RelationIsCUFormat(rel) || tab->rewrite ||
|
||||
RelationUsesSpaceType(rel->rd_rel->relpersistence) == SP_TEMP) {
|
||||
RelationUsesSpaceType(rel->rd_rel->relpersistence) == SP_TEMP || colDef->generatedCol) {
|
||||
ATExecAppendDefValExpr(attribute.attnum, defval, tab);
|
||||
} else {
|
||||
bytea* value = NULL;
|
||||
|
|
@ -9183,6 +9211,7 @@ static void ATExecSetNotNull(AlteredTableInfo* tab, Relation rel, const char* co
|
|||
static void ATExecColumnDefault(Relation rel, const char* colName, Node* newDefault, LOCKMODE lockmode)
|
||||
{
|
||||
AttrNumber attnum;
|
||||
TupleDesc tupdesc = RelationGetDescr(rel);
|
||||
|
||||
/*
|
||||
* get the number of the attribute
|
||||
|
|
@ -9206,6 +9235,11 @@ static void ATExecColumnDefault(Relation rel, const char* colName, Node* newDefa
|
|||
if (attnum <= 0)
|
||||
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot alter system column \"%s\"", colName)));
|
||||
|
||||
if (ISGENERATEDCOL(tupdesc, attnum - 1)) {
|
||||
ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("column \"%s\" of relation \"%s\" is a generated column", colName, RelationGetRelationName(rel))));
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
|
||||
|
|
@ -9224,6 +9258,7 @@ static void ATExecColumnDefault(Relation rel, const char* colName, Node* newDefa
|
|||
rawEnt = (RawColumnDefault*)palloc(sizeof(RawColumnDefault));
|
||||
rawEnt->attnum = attnum;
|
||||
rawEnt->raw_default = newDefault;
|
||||
rawEnt->generatedCol = '\0';
|
||||
|
||||
/*
|
||||
* This function is intended for CREATE TABLE, so it processes a
|
||||
|
|
@ -9557,6 +9592,8 @@ static bool CheckLastColumn(Relation rel, AttrNumber attrnum)
|
|||
for (int col = 0; col < rel->rd_att->natts; ++col) {
|
||||
if (rel->rd_att->attrs[col]->attisdropped)
|
||||
continue;
|
||||
if (ISGENERATEDCOL(rel->rd_att, col))
|
||||
continue;
|
||||
if (col != (attrnum - 1)) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -9564,6 +9601,7 @@ static bool CheckLastColumn(Relation rel, AttrNumber attrnum)
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
#ifdef ENABLE_MULTIPLE_NODES
|
||||
/* Check if the ALTER TABLE DROP COLUMN operation is valid in timeseries table.
|
||||
* Invalid case:
|
||||
|
|
@ -10337,6 +10375,34 @@ static void ATAddForeignKeyConstraint(AlteredTableInfo* tab, Relation rel, Const
|
|||
checkFkeyPermissions(pkrel, pkattnum, numpks);
|
||||
checkFkeyPermissions(rel, fkattnum, numfks);
|
||||
|
||||
/*
|
||||
* Check some things for generated columns.
|
||||
*/
|
||||
for (i = 0; i < numfks; i++)
|
||||
{
|
||||
char generated = GetGeneratedCol(RelationGetDescr(rel), fkattnum[i] - 1);
|
||||
|
||||
if (generated)
|
||||
{
|
||||
/*
|
||||
* Check restrictions on UPDATE/DELETE actions, per SQL standard
|
||||
*/
|
||||
if (fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETNULL ||
|
||||
fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETDEFAULT ||
|
||||
fkconstraint->fk_upd_action == FKCONSTR_ACTION_CASCADE)
|
||||
ereport(ERROR,
|
||||
(errmodule(MOD_GEN_COL), errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("invalid %s action for foreign key constraint containing generated column",
|
||||
"ON UPDATE")));
|
||||
if (fkconstraint->fk_del_action == FKCONSTR_ACTION_SETNULL ||
|
||||
fkconstraint->fk_del_action == FKCONSTR_ACTION_SETDEFAULT)
|
||||
ereport(ERROR,
|
||||
(errmodule(MOD_GEN_COL), errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("invalid %s action for foreign key constraint containing generated column",
|
||||
"ON DELETE")));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Look up the equality operators to use in the constraint.
|
||||
*
|
||||
|
|
@ -11855,6 +11921,50 @@ static bool ATColumnChangeRequiresRewrite(Node* expr, AttrNumber varattno)
|
|||
}
|
||||
}
|
||||
|
||||
static void DelDependencONDataType(const Relation rel, Relation depRel, const Form_pg_attribute attTup)
|
||||
{
|
||||
ScanKeyData key[3];
|
||||
SysScanDesc scan;
|
||||
HeapTuple depTup;
|
||||
AttrNumber attnum = attTup->attnum;
|
||||
|
||||
/*
|
||||
* Now scan for dependencies of this column on other things. The only
|
||||
* thing we should find is the dependency on the column datatype, which we
|
||||
* want to remove, possibly a collation dependency, and dependencies on
|
||||
* other columns if it is a generated column.
|
||||
*/
|
||||
ScanKeyInit(&key[0], Anum_pg_depend_classid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationRelationId));
|
||||
ScanKeyInit(&key[1], Anum_pg_depend_objid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(rel)));
|
||||
ScanKeyInit(&key[2], Anum_pg_depend_objsubid, BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum((int32)attnum));
|
||||
|
||||
scan = systable_beginscan(depRel, DependDependerIndexId, true, SnapshotNow, 3, key);
|
||||
|
||||
while (HeapTupleIsValid(depTup = systable_getnext(scan))) {
|
||||
Form_pg_depend foundDep = (Form_pg_depend)GETSTRUCT(depTup);
|
||||
ObjectAddress foundObject;
|
||||
foundObject.classId = foundDep->refclassid;
|
||||
foundObject.objectId = foundDep->refobjid;
|
||||
foundObject.objectSubId = foundDep->refobjsubid;
|
||||
|
||||
if (foundDep->deptype != DEPENDENCY_NORMAL && foundDep->deptype != DEPENDENCY_AUTO) {
|
||||
ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("found unexpected dependency type '%c'", foundDep->deptype)));
|
||||
}
|
||||
if (!(foundDep->refclassid == TypeRelationId && foundDep->refobjid == attTup->atttypid) &&
|
||||
!(foundDep->refclassid == CollationRelationId && foundDep->refobjid == attTup->attcollation) &&
|
||||
!(foundDep->refclassid == RelationRelationId && foundDep->refobjid == RelationGetRelid(rel) &&
|
||||
foundDep->refobjsubid != 0)) {
|
||||
ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("found unexpected dependency for column")));
|
||||
}
|
||||
|
||||
simple_heap_delete(depRel, &depTup->t_self);
|
||||
}
|
||||
|
||||
systable_endscan(scan);
|
||||
}
|
||||
|
||||
static void ATExecAlterColumnType(AlteredTableInfo* tab, Relation rel, AlterTableCmd* cmd, LOCKMODE lockmode)
|
||||
{
|
||||
char* colName = cmd->name;
|
||||
|
|
@ -11874,6 +11984,7 @@ static void ATExecAlterColumnType(AlteredTableInfo* tab, Relation rel, AlterTabl
|
|||
ScanKeyData key[3];
|
||||
SysScanDesc scan;
|
||||
HeapTuple depTup;
|
||||
char generatedCol = '\0';
|
||||
|
||||
attrelation = heap_open(AttributeRelationId, RowExclusiveLock);
|
||||
|
||||
|
|
@ -11913,6 +12024,8 @@ static void ATExecAlterColumnType(AlteredTableInfo* tab, Relation rel, AlterTabl
|
|||
/* And the collation */
|
||||
targetcollid = GetColumnDefCollation(NULL, def, targettype);
|
||||
|
||||
generatedCol = GetGeneratedCol(rel->rd_att, attnum -1);
|
||||
|
||||
/*
|
||||
* If there is a default expression for the column, get it and ensure we
|
||||
* can coerce it to the new datatype. (We must do this before changing
|
||||
|
|
@ -11937,10 +12050,17 @@ static void ATExecAlterColumnType(AlteredTableInfo* tab, Relation rel, AlterTabl
|
|||
COERCION_ASSIGNMENT,
|
||||
COERCE_IMPLICIT_CAST,
|
||||
-1);
|
||||
if (defaultexpr == NULL)
|
||||
ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("default for column \"%s\" cannot be cast automatically to type %s",
|
||||
colName, format_type_be(targettype))));
|
||||
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
|
||||
defaultexpr = NULL;
|
||||
|
||||
|
|
@ -12011,7 +12131,18 @@ static void ATExecAlterColumnType(AlteredTableInfo* tab, Relation rel, AlterTabl
|
|||
* not do anything to it.
|
||||
*/
|
||||
Assert(foundObject.objectSubId == 0);
|
||||
} else {
|
||||
} else if (relKind == RELKIND_RELATION && foundObject.objectSubId != 0 &&
|
||||
GetGenerated(foundObject.objectId, foundObject.objectSubId)) {
|
||||
/*
|
||||
* Changing the type of a column that is used by a
|
||||
* generated column is not allowed by SQL standard. It
|
||||
* might be doable with some thinking and effort.
|
||||
*/
|
||||
ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("cannot alter type of a column used by a generated column"),
|
||||
errdetail("Column \"%s\" is used by generated column \"%s\".", colName,
|
||||
get_attname(foundObject.objectId, foundObject.objectSubId))));
|
||||
}else {
|
||||
/* Not expecting any other direct dependencies... */
|
||||
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("unexpected object depending on column: %s", getObjectDescription(&foundObject))));
|
||||
|
|
@ -12119,31 +12250,7 @@ static void ATExecAlterColumnType(AlteredTableInfo* tab, Relation rel, AlterTabl
|
|||
|
||||
systable_endscan(scan);
|
||||
|
||||
/*
|
||||
* Now scan for dependencies of this column on other things. The only
|
||||
* thing we should find is the dependency on the column datatype, which we
|
||||
* want to remove, and possibly a collation dependency.
|
||||
*/
|
||||
ScanKeyInit(&key[0], Anum_pg_depend_classid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationRelationId));
|
||||
ScanKeyInit(&key[1], Anum_pg_depend_objid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(rel)));
|
||||
ScanKeyInit(&key[2], Anum_pg_depend_objsubid, BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum((int32)attnum));
|
||||
|
||||
scan = systable_beginscan(depRel, DependDependerIndexId, true, SnapshotNow, 3, key);
|
||||
|
||||
while (HeapTupleIsValid(depTup = systable_getnext(scan))) {
|
||||
Form_pg_depend foundDep = (Form_pg_depend)GETSTRUCT(depTup);
|
||||
|
||||
if (foundDep->deptype != DEPENDENCY_NORMAL)
|
||||
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("found unexpected dependency type '%c'", foundDep->deptype)));
|
||||
if (!(foundDep->refclassid == TypeRelationId && foundDep->refobjid == attTup->atttypid) &&
|
||||
!(foundDep->refclassid == CollationRelationId && foundDep->refobjid == attTup->attcollation))
|
||||
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("found unexpected dependency for column")));
|
||||
|
||||
simple_heap_delete(depRel, &depTup->t_self);
|
||||
}
|
||||
|
||||
systable_endscan(scan);
|
||||
DelDependencONDataType(rel, depRel, attTup);
|
||||
|
||||
heap_close(depRel, RowExclusiveLock);
|
||||
|
||||
|
|
@ -12199,7 +12306,7 @@ static void ATExecAlterColumnType(AlteredTableInfo* tab, Relation rel, AlterTabl
|
|||
*/
|
||||
RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, true, true);
|
||||
|
||||
StoreAttrDefault(rel, attnum, defaultexpr);
|
||||
StoreAttrDefault(rel, attnum, defaultexpr, generatedCol);
|
||||
}
|
||||
|
||||
/* Cleanup */
|
||||
|
|
@ -16995,37 +17102,43 @@ List* GetPartitionkeyPos(List* partitionkeys, List* schema)
|
|||
{
|
||||
ListCell* partitionkey_cell = NULL;
|
||||
ListCell* schema_cell = NULL;
|
||||
ColumnRef* partitionkey_ref = NULL;
|
||||
ColumnDef* schema_def = NULL;
|
||||
int column_count = 0;
|
||||
bool* is_exist = NULL;
|
||||
char* partitonkey_name = NULL;
|
||||
List* pos = NULL;
|
||||
int len = -1;
|
||||
|
||||
if (schema == NIL) {
|
||||
ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION), errmsg("there is no column for a partitioned table!")));
|
||||
}
|
||||
|
||||
len = schema->length;
|
||||
int len = schema->length;
|
||||
if (partitionkeys == NIL) {
|
||||
ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION), errmsg("there is no partition key!")));
|
||||
}
|
||||
|
||||
is_exist = (bool*)palloc(len * sizeof(bool));
|
||||
bool* is_exist = (bool*)palloc(len * sizeof(bool));
|
||||
errno_t rc = EOK;
|
||||
rc = memset_s(is_exist, len * sizeof(bool), 0, len * sizeof(bool));
|
||||
securec_check(rc, "\0", "\0");
|
||||
|
||||
foreach (partitionkey_cell, partitionkeys) {
|
||||
partitionkey_ref = (ColumnRef*)lfirst(partitionkey_cell);
|
||||
partitonkey_name = ((Value*)linitial(partitionkey_ref->fields))->val.str;
|
||||
ColumnRef* partitionkey_ref = (ColumnRef*)lfirst(partitionkey_cell);
|
||||
char* partitonkey_name = ((Value*)linitial(partitionkey_ref->fields))->val.str;
|
||||
|
||||
foreach (schema_cell, schema) {
|
||||
schema_def = (ColumnDef*)lfirst(schema_cell);
|
||||
ColumnDef* schema_def = (ColumnDef*)lfirst(schema_cell);
|
||||
|
||||
/* find the column that has the same name as the partitionkey */
|
||||
if (!strcmp(partitonkey_name, schema_def->colname)) {
|
||||
|
||||
/*
|
||||
* Generated columns cannot work: They are computed after BEFORE
|
||||
* triggers, but partition routing is done before all triggers.
|
||||
*/
|
||||
if (schema_def->generatedCol) {
|
||||
ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||
errmsg("cannot use generated column in partition key"),
|
||||
errdetail("Column \"%s\" is a generated column.", partitonkey_name)));
|
||||
}
|
||||
|
||||
/* duplicate partitionkey name */
|
||||
if (is_exist != NULL && is_exist[column_count]) {
|
||||
pfree_ext(is_exist);
|
||||
|
|
|
|||
|
|
@ -77,8 +77,9 @@
|
|||
* they use, so we let them be duplicated. Be sure to update all if one needs
|
||||
* to be changed, however.
|
||||
*/
|
||||
#define GetUpdatedColumns(relinfo, estate) \
|
||||
(rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->updatedCols)
|
||||
#define GET_ALL_UPDATED_COLUMNS(relinfo, estate) \
|
||||
(bms_union(exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->updatedCols, \
|
||||
exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->extraUpdatedCols))
|
||||
|
||||
/* Local function prototypes */
|
||||
static void ConvertTriggerToFK(CreateTrigStmt* stmt, Oid funcoid);
|
||||
|
|
@ -368,6 +369,19 @@ Oid CreateTrigger(CreateTrigStmt* stmt, const char* queryString, Oid relOid, Oid
|
|||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("BEFORE trigger's WHEN condition cannot reference NEW system columns"),
|
||||
parser_errposition(pstate, var->location)));
|
||||
if (TRIGGER_FOR_BEFORE(tgtype) && var->varattno == 0 && RelationGetDescr(rel)->constr &&
|
||||
RelationGetDescr(rel)->constr->has_generated_stored)
|
||||
ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||
errmsg("BEFORE trigger's WHEN condition cannot reference NEW generated columns"),
|
||||
errdetail("A whole-row reference is used and the table contains generated columns."),
|
||||
parser_errposition(pstate, var->location)));
|
||||
if (TRIGGER_FOR_BEFORE(tgtype) && var->varattno > 0 &&
|
||||
ISGENERATEDCOL(RelationGetDescr(rel), var->varattno - 1))
|
||||
ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||
errmsg("BEFORE trigger's WHEN condition cannot reference NEW generated columns"),
|
||||
errdetail("Column \"%s\" is a generated column.",
|
||||
NameStr(TupleDescAttr(RelationGetDescr(rel), var->varattno - 1)->attname)),
|
||||
parser_errposition(pstate, var->location)));
|
||||
break;
|
||||
default:
|
||||
/* can't happen without add_missing_from, so just elog */
|
||||
|
|
@ -2255,7 +2269,7 @@ void ExecBSUpdateTriggers(EState* estate, ResultRelInfo* relinfo)
|
|||
if (!trigdesc->trig_update_before_statement)
|
||||
return;
|
||||
|
||||
updatedCols = GetUpdatedColumns(relinfo, estate);
|
||||
updatedCols = GET_ALL_UPDATED_COLUMNS(relinfo, estate);
|
||||
|
||||
LocTriggerData.type = T_TriggerData;
|
||||
LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE | TRIGGER_EVENT_BEFORE;
|
||||
|
|
@ -2302,7 +2316,7 @@ void ExecASUpdateTriggers(EState* estate, ResultRelInfo* relinfo)
|
|||
NULL,
|
||||
NULL,
|
||||
NIL,
|
||||
GetUpdatedColumns(relinfo, estate));
|
||||
GET_ALL_UPDATED_COLUMNS(relinfo, estate));
|
||||
}
|
||||
|
||||
TupleTableSlot* ExecBRUpdateTriggers(EState* estate, EPQState* epqstate, ResultRelInfo* relinfo, Oid oldPartitionOid,
|
||||
|
|
@ -2376,7 +2390,7 @@ TupleTableSlot* ExecBRUpdateTriggers(EState* estate, EPQState* epqstate, ResultR
|
|||
}
|
||||
#endif
|
||||
|
||||
updatedCols = GetUpdatedColumns(relinfo, estate);
|
||||
updatedCols = GET_ALL_UPDATED_COLUMNS(relinfo, estate);
|
||||
|
||||
LocTriggerData.type = T_TriggerData;
|
||||
LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE | TRIGGER_EVENT_ROW | TRIGGER_EVENT_BEFORE;
|
||||
|
|
@ -2465,7 +2479,7 @@ void ExecARUpdateTriggers(EState* estate, ResultRelInfo* relinfo, Oid oldPartiti
|
|||
trigtuple,
|
||||
newtuple,
|
||||
recheckIndexes,
|
||||
GetUpdatedColumns(relinfo, estate));
|
||||
GET_ALL_UPDATED_COLUMNS(relinfo, estate));
|
||||
tableam_tops_free_tuple(trigtuple);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -854,7 +854,7 @@ void DefineDomain(CreateDomainStmt* stmt)
|
|||
* Cook the constr->raw_expr into an expression. Note:
|
||||
* name is strictly for error message
|
||||
*/
|
||||
defaultExpr = cookDefault(pstate, constr->raw_expr, basetypeoid, basetypeMod, domainName);
|
||||
defaultExpr = cookDefault(pstate, constr->raw_expr, basetypeoid, basetypeMod, domainName, 0);
|
||||
|
||||
/*
|
||||
* If the expression is just a NULL constant, we treat it
|
||||
|
|
@ -2015,8 +2015,8 @@ void AlterDomainDefault(List* names, Node* defaultRaw)
|
|||
* Cook the colDef->raw_expr into an expression. Note: Name is
|
||||
* strictly for error message
|
||||
*/
|
||||
defaultExpr = cookDefault(pstate, defaultRaw, typTup->typbasetype, typTup->typtypmod, NameStr(typTup->typname));
|
||||
|
||||
defaultExpr =
|
||||
cookDefault(pstate, defaultRaw, typTup->typbasetype, typTup->typtypmod, NameStr(typTup->typname), 0);
|
||||
/*
|
||||
* If the expression is just a NULL constant, we treat the command
|
||||
* like ALTER ... DROP DEFAULT. (But see note for same test in
|
||||
|
|
|
|||
|
|
@ -1589,6 +1589,7 @@ static void expand_inherited_rtentry(PlannerInfo* root, RangeTblEntry* rte, Inde
|
|||
childrte->selectedCols = translate_col_privs(rte->selectedCols, appinfo->translated_vars);
|
||||
childrte->insertedCols = translate_col_privs(rte->insertedCols, appinfo->translated_vars);
|
||||
childrte->updatedCols = translate_col_privs(rte->updatedCols, appinfo->translated_vars);
|
||||
childrte->extraUpdatedCols = translate_col_privs(rte->extraUpdatedCols, appinfo->translated_vars);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -2347,6 +2348,7 @@ void expand_internal_rtentry(PlannerInfo* root, RangeTblEntry* rte, Index rti)
|
|||
childrte->selectedCols = translate_col_privs(rte->selectedCols, appinfo->translated_vars);
|
||||
childrte->insertedCols = translate_col_privs(rte->insertedCols, appinfo->translated_vars);
|
||||
childrte->updatedCols = translate_col_privs(rte->updatedCols, appinfo->translated_vars);
|
||||
childrte->extraUpdatedCols = translate_col_privs(rte->extraUpdatedCols, appinfo->translated_vars);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -692,6 +692,8 @@ static List* rewriteTargetListIU(List* targetList, CmdType commandType, Relation
|
|||
|
||||
for (attrno = 1; attrno <= numattrs; attrno++) {
|
||||
TargetEntry* new_tle = new_tles[attrno - 1];
|
||||
bool applyDefault = false;
|
||||
bool generateCol = ISGENERATEDCOL(target_relation->rd_att, attrno - 1);
|
||||
|
||||
att_tup = target_relation->rd_att->attrs[attrno - 1];
|
||||
|
||||
|
|
@ -704,8 +706,28 @@ static List* rewriteTargetListIU(List* targetList, CmdType commandType, Relation
|
|||
* it's an INSERT and there's no tlist entry for the column, or the
|
||||
* tlist entry is a DEFAULT placeholder node.
|
||||
*/
|
||||
if ((new_tle == NULL && commandType == CMD_INSERT) ||
|
||||
(new_tle != NULL && new_tle->expr != NULL && IsA(new_tle->expr, SetToDefault))) {
|
||||
applyDefault = ((new_tle == NULL && commandType == CMD_INSERT) ||
|
||||
(new_tle != NULL && new_tle->expr != NULL && IsA(new_tle->expr, SetToDefault)));
|
||||
|
||||
if (generateCol && !applyDefault) {
|
||||
if (commandType == CMD_INSERT && attrno_list == NULL) {
|
||||
ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("cannot insert into column \"%s\"", NameStr(att_tup->attname)),
|
||||
errdetail("Column \"%s\" is a generated column.", NameStr(att_tup->attname))));
|
||||
}
|
||||
if (commandType == CMD_UPDATE && new_tle) {
|
||||
ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("column \"%s\" can only be updated to DEFAULT", NameStr(att_tup->attname)),
|
||||
errdetail("Column \"%s\" is a generated column.", NameStr(att_tup->attname))));
|
||||
}
|
||||
}
|
||||
|
||||
if (generateCol) {
|
||||
/*
|
||||
* stored generated column will be fixed in executor
|
||||
*/
|
||||
new_tle = NULL;
|
||||
} else if (applyDefault) {
|
||||
Node* new_expr = NULL;
|
||||
|
||||
new_expr = build_column_default(target_relation, attrno, true);
|
||||
|
|
@ -927,9 +949,10 @@ Node* build_column_default(Relation rel, int attrno, bool isInsertCmd)
|
|||
}
|
||||
}
|
||||
|
||||
if (expr == NULL) {
|
||||
if (expr == NULL && !ISGENERATEDCOL(rd_att, attrno - 1)) {
|
||||
/*
|
||||
* No per-column default, so look for a default for the type itself.
|
||||
* No per-column default, so look for a default for the type itself.But
|
||||
* not for generated columns.
|
||||
*/
|
||||
expr = get_typdefault(atttype);
|
||||
}
|
||||
|
|
@ -971,14 +994,10 @@ Node* build_column_default(Relation rel, int attrno, bool isInsertCmd)
|
|||
}
|
||||
|
||||
if (expr == NULL)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("column \"%s\" is of type %s"
|
||||
" but default expression is of type %s",
|
||||
NameStr(att_tup->attname),
|
||||
format_type_be(atttype),
|
||||
format_type_be(exprtype)),
|
||||
errhint("You will need to rewrite or cast the expression.")));
|
||||
ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("column \"%s\" is of type %s but %s expression is of type %s", NameStr(att_tup->attname),
|
||||
format_type_be(atttype), ISGENERATEDCOL(rd_att, attrno - 1) ? "generated column" : "deault",
|
||||
format_type_be(exprtype)), errhint("You will need to rewrite or cast the expression.")));
|
||||
/*
|
||||
* If there is nextval FuncExpr, we should lock the quoted sequence to avoid deadlock,
|
||||
* this has beed done in transformFuncExpr. See lockNextvalOnCn for more details.
|
||||
|
|
@ -1042,19 +1061,26 @@ static void rewriteValuesRTE(RangeTblEntry* rte, Relation target_relation, List*
|
|||
|
||||
forboth(lc2, sublist, lc3, attrnos)
|
||||
{
|
||||
Node* col = (Node*)lfirst(lc2);
|
||||
Node *col = (Node *)lfirst(lc2);
|
||||
int attrno = lfirst_int(lc3);
|
||||
Form_pg_attribute att_tup = target_relation->rd_att->attrs[attrno - 1];
|
||||
bool generatedCol = ISGENERATEDCOL(target_relation->rd_att, attrno - 1);
|
||||
bool applyDefault = IsA(col, SetToDefault);
|
||||
|
||||
if (IsA(col, SetToDefault)) {
|
||||
Form_pg_attribute att_tup;
|
||||
Node* new_expr = NULL;
|
||||
if (generatedCol && !applyDefault) {
|
||||
ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("cannot insert into column \"%s\"", NameStr(att_tup->attname)),
|
||||
errdetail("Column \"%s\" is a generated column.", NameStr(att_tup->attname))));
|
||||
}
|
||||
|
||||
att_tup = target_relation->rd_att->attrs[attrno - 1];
|
||||
if (applyDefault) {
|
||||
Node *new_expr = NULL;
|
||||
|
||||
if (!att_tup->attisdropped)
|
||||
new_expr = build_column_default(target_relation, attrno, true);
|
||||
/* stored generated column will be computed in executor */
|
||||
if (att_tup->attisdropped || generatedCol)
|
||||
new_expr = NULL;
|
||||
else
|
||||
new_expr = NULL; /* force a NULL if dropped */
|
||||
new_expr = build_column_default(target_relation, attrno, true);
|
||||
|
||||
/*
|
||||
* If there is no default (ie, default is effectively NULL),
|
||||
|
|
@ -1073,8 +1099,9 @@ static void rewriteValuesRTE(RangeTblEntry* rte, Relation target_relation, List*
|
|||
new_expr, InvalidOid, -1, att_tup->atttypid, COERCE_IMPLICIT_CAST, -1, false, false);
|
||||
}
|
||||
newList = lappend(newList, new_expr);
|
||||
} else
|
||||
} else {
|
||||
newList = lappend(newList, col);
|
||||
}
|
||||
}
|
||||
newValues = lappend(newValues, newList);
|
||||
}
|
||||
|
|
@ -1501,6 +1528,7 @@ static Query* ApplyRetrieveRule(Query* parsetree, RewriteRule* rule, int rt_inde
|
|||
rte->selectedCols = NULL;
|
||||
rte->insertedCols = NULL;
|
||||
rte->updatedCols = NULL;
|
||||
rte->extraUpdatedCols = NULL;
|
||||
|
||||
/*
|
||||
* For the most part, Vars referencing the view should remain as
|
||||
|
|
@ -1565,12 +1593,14 @@ static Query* ApplyRetrieveRule(Query* parsetree, RewriteRule* rule, int rt_inde
|
|||
subrte->selectedCols = rte->selectedCols;
|
||||
subrte->insertedCols = rte->insertedCols;
|
||||
subrte->updatedCols = rte->updatedCols;
|
||||
subrte->extraUpdatedCols = rte->extraUpdatedCols;
|
||||
|
||||
rte->requiredPerms = 0; /* no permission check on subquery itself */
|
||||
rte->checkAsUser = InvalidOid;
|
||||
rte->selectedCols = NULL;
|
||||
rte->insertedCols = NULL;
|
||||
rte->updatedCols = NULL;
|
||||
rte->extraUpdatedCols = NULL;
|
||||
|
||||
/*
|
||||
* If FOR UPDATE/SHARE of view, mark all the contained tables as implicit
|
||||
|
|
@ -1954,6 +1984,21 @@ static Query* CopyAndAddInvertedQual(Query* parsetree, Node* rule_qual, int rt_i
|
|||
|
||||
return parsetree;
|
||||
}
|
||||
/*
|
||||
* Generated column can not be manually insert or updated.
|
||||
*/
|
||||
static void CheckGeneratedColConstraint(CmdType commandType, Form_pg_attribute attTup, const TargetEntry *newTle)
|
||||
{
|
||||
if (commandType == CMD_INSERT) {
|
||||
ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("cannot insert into column \"%s\"", NameStr(attTup->attname)),
|
||||
errdetail("Column \"%s\" is a generated column.", NameStr(attTup->attname))));
|
||||
} else if (commandType == CMD_UPDATE && newTle) {
|
||||
ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("column \"%s\" can only be updated to DEFAULT", NameStr(attTup->attname)),
|
||||
errdetail("Column \"%s\" is a generated column.", NameStr(attTup->attname))));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* very same to pg's rewriteTargetListIU. we adapt it to use for MergeInto
|
||||
|
|
@ -2042,8 +2087,17 @@ static List* rewriteTargetListMergeInto(
|
|||
*/
|
||||
apply_default = ((new_tle == NULL && commandType == CMD_INSERT) ||
|
||||
(new_tle && new_tle->expr && IsA(new_tle->expr, SetToDefault)));
|
||||
|
||||
if (apply_default) {
|
||||
|
||||
bool isGeneratedCol = ISGENERATEDCOL(target_relation->rd_att, attrno - 1);
|
||||
if (isGeneratedCol) {
|
||||
if (!apply_default) {
|
||||
CheckGeneratedColConstraint(commandType, att_tup, new_tle);
|
||||
}
|
||||
/*
|
||||
* stored generated column will be fixed in executor
|
||||
*/
|
||||
new_tle = NULL;
|
||||
} else if (apply_default) {
|
||||
Node* new_expr = NULL;
|
||||
|
||||
new_expr = build_column_default(target_relation, attrno, (commandType == CMD_INSERT));
|
||||
|
|
|
|||
|
|
@ -147,6 +147,9 @@ extern bool anls_opt_is_on(AnalysisOpt dfx_opt);
|
|||
(rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->insertedCols)
|
||||
#define GetUpdatedColumns(relinfo, estate) \
|
||||
(rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->updatedCols)
|
||||
#define GET_ALL_UPDATED_COLUMNS(relinfo, estate) \
|
||||
(bms_union(exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->updatedCols, \
|
||||
exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->extraUpdatedCols))
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* report_iud_time
|
||||
|
|
@ -1717,6 +1720,7 @@ void InitResultRelInfo(ResultRelInfo *resultRelInfo, Relation resultRelationDesc
|
|||
}
|
||||
resultRelInfo->ri_FdwState = NULL;
|
||||
resultRelInfo->ri_ConstraintExprs = NULL;
|
||||
resultRelInfo->ri_GeneratedExprs = NULL;
|
||||
resultRelInfo->ri_junkFilter = NULL;
|
||||
resultRelInfo->ri_projectReturning = NULL;
|
||||
resultRelInfo->ri_mergeTargetRTI = 0;
|
||||
|
|
|
|||
|
|
@ -55,10 +55,12 @@
|
|||
#include "executor/executor.h"
|
||||
#include "executor/execMerge.h"
|
||||
#include "executor/nodeModifyTable.h"
|
||||
#include "executor/tuptable.h"
|
||||
#include "foreign/fdwapi.h"
|
||||
#include "miscadmin.h"
|
||||
#include "nodes/execnodes.h"
|
||||
#include "nodes/nodeFuncs.h"
|
||||
#include "rewrite/rewriteHandler.h"
|
||||
#ifdef PGXC
|
||||
#include "parser/parsetree.h"
|
||||
#include "pgxc/execRemote.h"
|
||||
|
|
@ -85,6 +87,7 @@
|
|||
|
||||
#ifdef PGXC
|
||||
static TupleTableSlot* fill_slot_with_oldvals(TupleTableSlot* slot, HeapTupleHeader oldtuphd, Bitmapset* modifiedCols);
|
||||
static void RecoredGeneratedExpr(ResultRelInfo *resultRelInfo, EState *estate, CmdType cmdtype);
|
||||
|
||||
/* Copied from trigger.c */
|
||||
#define GetUpdatedColumns(relinfo, estate) \
|
||||
|
|
@ -294,6 +297,121 @@ static void ExecCheckTIDVisible(EState* estate, Relation rel, ItemPointer tid)
|
|||
ReleaseBuffer(buffer);
|
||||
}
|
||||
|
||||
static void RecoredGeneratedExpr(ResultRelInfo *resultRelInfo, EState *estate, CmdType cmdtype)
|
||||
{
|
||||
Relation rel = resultRelInfo->ri_RelationDesc;
|
||||
TupleDesc tupdesc = RelationGetDescr(rel);
|
||||
int natts = tupdesc->natts;
|
||||
MemoryContext oldContext;
|
||||
|
||||
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 *));
|
||||
resultRelInfo->ri_NumGeneratedNeeded = 0;
|
||||
|
||||
for (int i = 0; i < natts; i++) {
|
||||
if (GetGeneratedCol(tupdesc, i) == 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,
|
||||
exec_rt_fetch(resultRelInfo->ri_RangeTableIndex, estate)->extraUpdatedCols)) {
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
||||
(void)MemoryContextSwitchTo(oldContext);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute stored generated columns for a tuple
|
||||
*/
|
||||
void ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo, EState *estate, TupleTableSlot *slot, HeapTuple oldtuple,
|
||||
CmdType cmdtype)
|
||||
{
|
||||
Relation rel = resultRelInfo->ri_RelationDesc;
|
||||
TupleDesc tupdesc = RelationGetDescr(rel);
|
||||
uint32 natts = (uint32)tupdesc->natts;
|
||||
MemoryContext oldContext;
|
||||
Datum *values;
|
||||
bool *nulls;
|
||||
bool *replaces;
|
||||
HeapTuple newtuple;
|
||||
errno_t rc = EOK;
|
||||
|
||||
RecoredGeneratedExpr(resultRelInfo, estate, cmdtype);
|
||||
|
||||
/*
|
||||
* If no generated columns have been affected by this change, then skip
|
||||
* the rest.
|
||||
*/
|
||||
if (resultRelInfo->ri_NumGeneratedNeeded == 0)
|
||||
return;
|
||||
|
||||
oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
|
||||
values = (Datum *)palloc(sizeof(*values) * natts);
|
||||
nulls = (bool *)palloc(sizeof(*nulls) * natts);
|
||||
replaces = (bool *)palloc0(sizeof(*replaces) * natts);
|
||||
rc = memset_s(replaces, sizeof(bool) * natts, 0, sizeof(bool) * natts);
|
||||
securec_check(rc, "\0", "\0");
|
||||
|
||||
for (uint32 i = 0; i < natts; i++) {
|
||||
Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
|
||||
|
||||
if (GetGeneratedCol(tupdesc, i) == ATTRIBUTE_GENERATED_STORED && resultRelInfo->ri_GeneratedExprs[i]) {
|
||||
ExprContext *econtext;
|
||||
Datum val;
|
||||
bool isnull;
|
||||
|
||||
econtext = GetPerTupleExprContext(estate);
|
||||
econtext->ecxt_scantuple = slot;
|
||||
|
||||
val = ExecEvalExpr(resultRelInfo->ri_GeneratedExprs[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 = heap_modify_tuple(oldtuple, tupdesc, values, nulls, replaces);
|
||||
(void)ExecStoreTuple(newtuple, slot, InvalidBuffer, false);
|
||||
|
||||
(void)MemoryContextSwitchTo(oldContext);
|
||||
}
|
||||
|
||||
static bool ExecConflictUpdate(ModifyTableState* mtstate, ResultRelInfo* resultRelInfo, ItemPointer conflictTid,
|
||||
TupleTableSlot* planSlot, TupleTableSlot* excludedSlot, EState* estate, Relation targetRel,
|
||||
Oid oldPartitionOid, int2 bucketid, bool canSetTag, TupleTableSlot** returning)
|
||||
|
|
@ -634,6 +752,14 @@ TupleTableSlot* ExecInsertT(ModifyTableState* state, TupleTableSlot* slot, Tuple
|
|||
|
||||
new_id = InvalidOid;
|
||||
} else if (result_rel_info->ri_FdwRoutine) {
|
||||
/*
|
||||
* Compute stored generated columns
|
||||
*/
|
||||
if (result_relation_desc->rd_att->constr && result_relation_desc->rd_att->constr->has_generated_stored) {
|
||||
ExecComputeStoredGenerated(result_rel_info, estate, slot, tuple, CMD_INSERT);
|
||||
tuple = (HeapTuple)slot->tts_tuple;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_MOT
|
||||
if (result_rel_info->ri_FdwRoutine->GetFdwType && result_rel_info->ri_FdwRoutine->GetFdwType() == MOT_ORC) {
|
||||
if (result_relation_desc->rd_att->constr) {
|
||||
|
|
@ -660,6 +786,13 @@ TupleTableSlot* ExecInsertT(ModifyTableState* state, TupleTableSlot* slot, Tuple
|
|||
|
||||
new_id = InvalidOid;
|
||||
} else {
|
||||
/*
|
||||
* Compute stored generated columns
|
||||
*/
|
||||
if (result_relation_desc->rd_att->constr && result_relation_desc->rd_att->constr->has_generated_stored) {
|
||||
ExecComputeStoredGenerated(result_rel_info, estate, slot, tuple, CMD_INSERT);
|
||||
tuple = (HeapTuple)slot->tts_tuple;
|
||||
}
|
||||
/*
|
||||
* Check the constraints of the tuple
|
||||
*/
|
||||
|
|
@ -1355,6 +1488,14 @@ TupleTableSlot* ExecUpdate(ItemPointer tupleid,
|
|||
/* trigger might have changed tuple */
|
||||
tuple = ExecMaterializeSlot(slot);
|
||||
} else if (result_rel_info->ri_FdwRoutine) {
|
||||
/*
|
||||
* Compute stored generated columns
|
||||
*/
|
||||
if (result_relation_desc->rd_att->constr && result_relation_desc->rd_att->constr->has_generated_stored) {
|
||||
ExecComputeStoredGenerated(result_rel_info, estate, slot, tuple, CMD_UPDATE);
|
||||
tuple = (HeapTuple)slot->tts_tuple;
|
||||
}
|
||||
|
||||
/*
|
||||
* update in foreign table: let the FDW do it
|
||||
*/
|
||||
|
|
@ -1381,6 +1522,14 @@ TupleTableSlot* ExecUpdate(ItemPointer tupleid,
|
|||
} else {
|
||||
bool update_indexes = false;
|
||||
|
||||
/*
|
||||
* Compute stored generated columns
|
||||
*/
|
||||
if (result_relation_desc->rd_att->constr && result_relation_desc->rd_att->constr->has_generated_stored) {
|
||||
ExecComputeStoredGenerated(result_rel_info, estate, slot, tuple, CMD_UPDATE);
|
||||
tuple = (HeapTuple)slot->tts_tuple;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check the constraints of the tuple
|
||||
*
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
#include "catalog/heap.h"
|
||||
#include "commands/copy.h"
|
||||
#include "executor/nodeIndexscan.h"
|
||||
#include "executor/nodeModifyTable.h"
|
||||
#include "gstrace/executer_gstrace.h"
|
||||
#include "instruments/instr_unique_sql.h"
|
||||
#include "libpq/pqformat.h"
|
||||
|
|
@ -1593,6 +1594,18 @@ bool InsertFusion::execute(long max_rows, char* completionTag)
|
|||
|
||||
(void)ExecStoreTuple(tuple, m_local.m_reslot, InvalidBuffer, false);
|
||||
|
||||
/*
|
||||
* Compute stored generated columns
|
||||
*/
|
||||
if (result_rel_info->ri_RelationDesc->rd_att->constr &&
|
||||
result_rel_info->ri_RelationDesc->rd_att->constr->has_generated_stored) {
|
||||
ExecComputeStoredGenerated(result_rel_info, m_c_local.m_estate, m_local.m_reslot, tuple, CMD_INSERT);
|
||||
if (tuple != (HeapTuple)m_local.m_reslot->tts_tuple) {
|
||||
tableam_tops_free_tuple(tuple);
|
||||
tuple = (HeapTuple)m_local.m_reslot->tts_tuple;
|
||||
}
|
||||
}
|
||||
|
||||
if (rel->rd_att->constr) {
|
||||
ExecConstraints(result_rel_info, m_local.m_reslot, m_c_local.m_estate);
|
||||
}
|
||||
|
|
@ -1996,6 +2009,16 @@ bool UpdateFusion::execute(long max_rows, char* completionTag)
|
|||
}
|
||||
|
||||
(void)ExecStoreTuple(tup, m_local.m_reslot, InvalidBuffer, false);
|
||||
|
||||
if (result_rel_info->ri_RelationDesc->rd_att->constr &&
|
||||
result_rel_info->ri_RelationDesc->rd_att->constr->has_generated_stored) {
|
||||
ExecComputeStoredGenerated(result_rel_info, m_c_local.m_estate, m_local.m_reslot, tup, CMD_UPDATE);
|
||||
if (tup != (HeapTuple)m_local.m_reslot->tts_tuple) {
|
||||
tableam_tops_free_tuple(tup);
|
||||
tup = (HeapTuple)m_local.m_reslot->tts_tuple;
|
||||
}
|
||||
}
|
||||
|
||||
if (rel->rd_att->constr)
|
||||
ExecConstraints(result_rel_info, m_local.m_reslot, m_c_local.m_estate);
|
||||
|
||||
|
|
|
|||
|
|
@ -191,6 +191,54 @@ TupleDesc CreateTupleDescCopy(TupleDesc tupdesc)
|
|||
return desc;
|
||||
}
|
||||
|
||||
/*
|
||||
* TupleConstrCopy
|
||||
* This function creates a new TupleConstr by copying from an existing TupleConstr.
|
||||
*/
|
||||
static TupleConstr *TupleConstrCopy(const TupleDesc tupdesc)
|
||||
{
|
||||
TupleConstr *constr = tupdesc->constr;
|
||||
TupleConstr *cpy = (TupleConstr *)palloc0(sizeof(TupleConstr));
|
||||
|
||||
cpy->has_not_null = constr->has_not_null;
|
||||
cpy->has_generated_stored = constr->has_generated_stored;
|
||||
|
||||
errno_t rc = EOK;
|
||||
if ((cpy->num_defval = constr->num_defval) > 0) {
|
||||
cpy->defval = (AttrDefault *)palloc(cpy->num_defval * sizeof(AttrDefault));
|
||||
rc = memcpy_s(cpy->defval, cpy->num_defval * sizeof(AttrDefault), constr->defval,
|
||||
cpy->num_defval * sizeof(AttrDefault));
|
||||
securec_check(rc, "\0", "\0");
|
||||
for (int i = cpy->num_defval - 1; i >= 0; i--) {
|
||||
if (constr->defval[i].adbin) {
|
||||
cpy->defval[i].adbin = pstrdup(constr->defval[i].adbin);
|
||||
}
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
if ((cpy->num_check = constr->num_check) > 0) {
|
||||
cpy->check = (ConstrCheck *)palloc(cpy->num_check * sizeof(ConstrCheck));
|
||||
rc = memcpy_s(cpy->check, cpy->num_check * sizeof(ConstrCheck), constr->check,
|
||||
cpy->num_check * sizeof(ConstrCheck));
|
||||
securec_check(rc, "\0", "\0");
|
||||
for (int i = cpy->num_check - 1; i >= 0; i--) {
|
||||
if (constr->check[i].ccname) {
|
||||
cpy->check[i].ccname = pstrdup(constr->check[i].ccname);
|
||||
}
|
||||
if (constr->check[i].ccbin) {
|
||||
cpy->check[i].ccbin = pstrdup(constr->check[i].ccbin);
|
||||
}
|
||||
cpy->check[i].ccvalid = constr->check[i].ccvalid;
|
||||
cpy->check[i].ccnoinherit = constr->check[i].ccnoinherit;
|
||||
}
|
||||
}
|
||||
return cpy;
|
||||
}
|
||||
|
||||
/*
|
||||
* CreateTupleDescCopyConstr
|
||||
* This function creates a new TupleDesc by copying from an existing
|
||||
|
|
@ -198,53 +246,16 @@ TupleDesc CreateTupleDescCopy(TupleDesc tupdesc)
|
|||
*/
|
||||
TupleDesc CreateTupleDescCopyConstr(TupleDesc tupdesc)
|
||||
{
|
||||
TupleDesc desc;
|
||||
TupleConstr *constr = tupdesc->constr;
|
||||
int i;
|
||||
errno_t rc = EOK;
|
||||
TupleDesc desc = CreateTemplateTupleDesc(tupdesc->natts, tupdesc->tdhasoid, tupdesc->tdTableAmType);
|
||||
|
||||
desc = CreateTemplateTupleDesc(tupdesc->natts, tupdesc->tdhasoid, tupdesc->tdTableAmType);
|
||||
|
||||
for (i = 0; i < desc->natts; i++) {
|
||||
for (int i = 0; i < desc->natts; i++) {
|
||||
rc = memcpy_s(desc->attrs[i], ATTRIBUTE_FIXED_PART_SIZE, tupdesc->attrs[i], ATTRIBUTE_FIXED_PART_SIZE);
|
||||
securec_check(rc, "\0", "\0");
|
||||
}
|
||||
|
||||
if (constr != NULL) {
|
||||
TupleConstr *cpy = (TupleConstr *)palloc0(sizeof(TupleConstr));
|
||||
|
||||
cpy->has_not_null = constr->has_not_null;
|
||||
|
||||
if ((cpy->num_defval = constr->num_defval) > 0) {
|
||||
cpy->defval = (AttrDefault *)palloc(cpy->num_defval * sizeof(AttrDefault));
|
||||
rc = memcpy_s(cpy->defval, cpy->num_defval * sizeof(AttrDefault), constr->defval,
|
||||
cpy->num_defval * sizeof(AttrDefault));
|
||||
securec_check(rc, "\0", "\0");
|
||||
for (i = cpy->num_defval - 1; i >= 0; i--) {
|
||||
if (constr->defval[i].adbin) {
|
||||
cpy->defval[i].adbin = pstrdup(constr->defval[i].adbin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((cpy->num_check = constr->num_check) > 0) {
|
||||
cpy->check = (ConstrCheck *)palloc(cpy->num_check * sizeof(ConstrCheck));
|
||||
rc = memcpy_s(cpy->check, cpy->num_check * sizeof(ConstrCheck), constr->check,
|
||||
cpy->num_check * sizeof(ConstrCheck));
|
||||
securec_check(rc, "\0", "\0");
|
||||
for (i = cpy->num_check - 1; i >= 0; i--) {
|
||||
if (constr->check[i].ccname) {
|
||||
cpy->check[i].ccname = pstrdup(constr->check[i].ccname);
|
||||
}
|
||||
if (constr->check[i].ccbin) {
|
||||
cpy->check[i].ccbin = pstrdup(constr->check[i].ccbin);
|
||||
}
|
||||
cpy->check[i].ccvalid = constr->check[i].ccvalid;
|
||||
cpy->check[i].ccnoinherit = constr->check[i].ccnoinherit;
|
||||
}
|
||||
}
|
||||
|
||||
desc->constr = cpy;
|
||||
if (tupdesc->constr != NULL) {
|
||||
desc->constr = TupleConstrCopy(tupdesc);
|
||||
}
|
||||
|
||||
/* copy the attinitdefval */
|
||||
|
|
@ -259,6 +270,17 @@ TupleDesc CreateTupleDescCopyConstr(TupleDesc tupdesc)
|
|||
return desc;
|
||||
}
|
||||
|
||||
char GetGeneratedCol(TupleDesc tupdesc, int atti)
|
||||
{
|
||||
if (!tupdesc->constr)
|
||||
return '\0';
|
||||
|
||||
if (tupdesc->constr->num_defval == 0)
|
||||
return '\0';
|
||||
|
||||
return tupdesc->constr->generatedCols[atti];
|
||||
}
|
||||
|
||||
/*
|
||||
* Free a TupleDesc including all substructure
|
||||
*/
|
||||
|
|
@ -281,6 +303,7 @@ void FreeTupleDesc(TupleDesc tupdesc)
|
|||
pfree(attrdef[i].adbin);
|
||||
}
|
||||
pfree(attrdef);
|
||||
pfree(tupdesc->constr->generatedCols);
|
||||
}
|
||||
if (tupdesc->constr->num_check > 0) {
|
||||
ConstrCheck *check = tupdesc->constr->check;
|
||||
|
|
@ -542,6 +565,10 @@ bool equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (constr1->has_generated_stored != constr2->has_generated_stored) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* check whether default values are eqaul to. */
|
||||
n = constr1->num_defval;
|
||||
if (n != (int)constr2->num_defval) {
|
||||
|
|
@ -567,6 +594,9 @@ bool equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
|
|||
if (strcmp(defval1->adbin, defval2->adbin) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (defval1->generatedCol != defval2->generatedCol) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* check whether check constraints are equal to. */
|
||||
|
|
@ -606,6 +636,60 @@ bool equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
|
|||
return compareInitdefvals(tupdesc1, tupdesc2);
|
||||
}
|
||||
|
||||
static bool ComparePgAttribute(Form_pg_attribute attr1, Form_pg_attribute attr2)
|
||||
{
|
||||
/*
|
||||
* We do not need to check every single field here: we can disregard
|
||||
* attrelid and attnum (which were used to place the row in the attrs
|
||||
* array in the first place). It might look like we could dispense
|
||||
* with checking attlen/attbyval/attalign, since these are derived
|
||||
* from atttypid; but in the case of dropped columns we must check
|
||||
* them (since atttypid will be zero for all dropped columns) and in
|
||||
* general it seems safer to check them always.
|
||||
*
|
||||
* attcacheoff must NOT be checked since it's possibly not set in both
|
||||
* copies.
|
||||
*/
|
||||
if (strcmp(NameStr(attr1->attname), NameStr(attr2->attname)) != 0)
|
||||
return false;
|
||||
if (attr1->atttypid != attr2->atttypid)
|
||||
return false;
|
||||
if (attr1->attstattarget != attr2->attstattarget)
|
||||
return false;
|
||||
if (attr1->attlen != attr2->attlen)
|
||||
return false;
|
||||
if (attr1->attndims != attr2->attndims)
|
||||
return false;
|
||||
if (attr1->atttypmod != attr2->atttypmod)
|
||||
return false;
|
||||
if (attr1->attbyval != attr2->attbyval)
|
||||
return false;
|
||||
if (attr1->attstorage != attr2->attstorage)
|
||||
return false;
|
||||
if (attr1->attkvtype != attr2->attkvtype)
|
||||
return false;
|
||||
if (attr1->attcmprmode != attr2->attcmprmode)
|
||||
return false;
|
||||
if (attr1->attalign != attr2->attalign)
|
||||
return false;
|
||||
if (attr1->attnotnull != attr2->attnotnull)
|
||||
return false;
|
||||
if (attr1->atthasdef != attr2->atthasdef)
|
||||
return false;
|
||||
|
||||
if (attr1->attisdropped != attr2->attisdropped)
|
||||
return false;
|
||||
if (attr1->attislocal != attr2->attislocal)
|
||||
return false;
|
||||
if (attr1->attinhcount != attr2->attinhcount)
|
||||
return false;
|
||||
if (attr1->attcollation != attr2->attcollation)
|
||||
return false;
|
||||
/* attacl, attoptions and attfdwoptions are not even present... */
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Compare the delta TupleDesc structures with column store main TupleDesc
|
||||
*
|
||||
|
|
@ -622,53 +706,13 @@ bool equalDeltaTupleDescs(TupleDesc mainTupdesc, TupleDesc deltaTupdesc)
|
|||
Form_pg_attribute attr1 = mainTupdesc->attrs[i];
|
||||
Form_pg_attribute attr2 = deltaTupdesc->attrs[i];
|
||||
|
||||
/*
|
||||
* We do not need to check every single field here: we can disregard
|
||||
* attrelid and attnum (which were used to place the row in the attrs
|
||||
* array in the first place). It might look like we could dispense
|
||||
* with checking attlen/attbyval/attalign, since these are derived
|
||||
* from atttypid; but in the case of dropped columns we must check
|
||||
* them (since atttypid will be zero for all dropped columns) and in
|
||||
* general it seems safer to check them always.
|
||||
*
|
||||
* attcacheoff must NOT be checked since it's possibly not set in both
|
||||
* copies.
|
||||
*/
|
||||
if (strcmp(NameStr(attr1->attname), NameStr(attr2->attname)) != 0)
|
||||
if (GetGeneratedCol(mainTupdesc, i) != GetGeneratedCol(deltaTupdesc, i)) {
|
||||
return false;
|
||||
if (attr1->atttypid != attr2->atttypid)
|
||||
}
|
||||
|
||||
if (!ComparePgAttribute(attr1, attr2)) {
|
||||
return false;
|
||||
if (attr1->attstattarget != attr2->attstattarget)
|
||||
return false;
|
||||
if (attr1->attlen != attr2->attlen)
|
||||
return false;
|
||||
if (attr1->attndims != attr2->attndims)
|
||||
return false;
|
||||
if (attr1->atttypmod != attr2->atttypmod)
|
||||
return false;
|
||||
if (attr1->attbyval != attr2->attbyval)
|
||||
return false;
|
||||
if (attr1->attstorage != attr2->attstorage)
|
||||
return false;
|
||||
if (attr1->attkvtype != attr2->attkvtype)
|
||||
return false;
|
||||
if (attr1->attcmprmode != attr2->attcmprmode)
|
||||
return false;
|
||||
if (attr1->attalign != attr2->attalign)
|
||||
return false;
|
||||
if (attr1->attnotnull != attr2->attnotnull)
|
||||
return false;
|
||||
if (attr1->atthasdef != attr2->atthasdef)
|
||||
return false;
|
||||
if (attr1->attisdropped != attr2->attisdropped)
|
||||
return false;
|
||||
if (attr1->attislocal != attr2->attislocal)
|
||||
return false;
|
||||
if (attr1->attinhcount != attr2->attinhcount)
|
||||
return false;
|
||||
if (attr1->attcollation != attr2->attcollation)
|
||||
return false;
|
||||
/* attacl, attoptions and attfdwoptions are not even present... */
|
||||
}
|
||||
}
|
||||
|
||||
/* compare the attinitdefval */
|
||||
|
|
@ -993,6 +1037,7 @@ TupleDesc BuildDescForRelation(List *schema, Node *orientedFrom, char relkind)
|
|||
constr->num_defval = 0;
|
||||
constr->check = NULL;
|
||||
constr->num_check = 0;
|
||||
constr->generatedCols = NULL;
|
||||
desc->constr = constr;
|
||||
} else {
|
||||
desc->constr = NULL;
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ const static uint32 UHEAP_TUPLE = 2;
|
|||
typedef struct attrDefault {
|
||||
AttrNumber adnum;
|
||||
char* adbin; /* nodeToString representation of expr */
|
||||
char generatedCol; /* generated column setting */
|
||||
} AttrDefault;
|
||||
|
||||
typedef struct constrCheck {
|
||||
|
|
@ -67,6 +68,8 @@ typedef struct tupleConstr {
|
|||
uint16 num_defval;
|
||||
uint16 num_check;
|
||||
bool has_not_null;
|
||||
bool has_generated_stored;
|
||||
char* generatedCols; /* attribute array */
|
||||
} TupleConstr;
|
||||
|
||||
/* This structure contains initdefval of a tuple */
|
||||
|
|
@ -136,6 +139,8 @@ typedef struct tupleDesc {
|
|||
|
||||
/* Accessor for the i'th attribute of tupdesc. */
|
||||
#define TupleDescAttr(tupdesc, i) (tupdesc->attrs[(i)])
|
||||
#define ISGENERATEDCOL(tupdesc, i) \
|
||||
((tupdesc)->constr != NULL && (tupdesc)->constr->num_defval > 0 && (tupdesc)->constr->generatedCols[(i)])
|
||||
|
||||
extern TupleDesc CreateTemplateTupleDesc(int natts, bool hasoid, TableAmType tam = TAM_HEAP);
|
||||
|
||||
|
|
@ -180,4 +185,6 @@ extern bool tupledesc_have_pck(TupleConstr* constr);
|
|||
|
||||
extern void copyDroppedAttribute(Form_pg_attribute target, Form_pg_attribute source);
|
||||
|
||||
extern char GetGeneratedCol(TupleDesc tupdesc, int atti);
|
||||
|
||||
#endif /* TUPDESC_H */
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
typedef struct RawColumnDefault {
|
||||
AttrNumber attnum; /* attribute to attach default to */
|
||||
Node *raw_default; /* default value (untransformed parse tree) */
|
||||
char generatedCol; /* generated column setting */
|
||||
} RawColumnDefault;
|
||||
|
||||
typedef struct CookedConstraint {
|
||||
|
|
@ -160,8 +161,9 @@ 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);
|
||||
extern Node *cookDefault(ParseState *pstate, Node *raw_default, Oid atttypid, int32 atttypmod, char *attname);
|
||||
extern void StoreAttrDefault(Relation rel, AttrNumber attnum, Node *expr, char generatedCol);
|
||||
extern Node *cookDefault(ParseState *pstate, Node *raw_default, Oid atttypid, int32 atttypmod, char *attname,
|
||||
char generatedCol);
|
||||
extern void DeleteRelationTuple(Oid relid);
|
||||
extern void DeleteAttributeTuples(Oid relid);
|
||||
extern void DeleteSystemAttributeTuples(Oid relid);
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ CATALOG(pg_attrdef,2604) BKI_SCHEMA_MACRO
|
|||
pg_node_tree adbin; /* nodeToString representation of default */
|
||||
text adsrc; /* human-readable representation of default */
|
||||
#endif
|
||||
char adgencol; /* generated column setting */
|
||||
} FormData_pg_attrdef;
|
||||
|
||||
/* ----------------
|
||||
|
|
@ -51,10 +52,11 @@ typedef FormData_pg_attrdef *Form_pg_attrdef;
|
|||
* compiler constants for pg_attrdef
|
||||
* ----------------
|
||||
*/
|
||||
#define Natts_pg_attrdef 4
|
||||
#define Natts_pg_attrdef 5
|
||||
#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
|
||||
|
||||
#endif /* PG_ATTRDEF_H */
|
||||
|
|
|
|||
|
|
@ -231,5 +231,7 @@ typedef FormData_pg_attribute *Form_pg_attribute;
|
|||
* genbki.pl. Only "bootstrapped" relations need be included.
|
||||
* ----------------
|
||||
*/
|
||||
#define ATTRIBUTE_IDENTITY_ALWAYS 'a'
|
||||
#define ATTRIBUTE_GENERATED_STORED 's'
|
||||
#endif /* PG_ATTRIBUTE_H */
|
||||
|
||||
|
|
|
|||
|
|
@ -367,6 +367,11 @@ extern bool ExecRelationIsTargetRelation(EState* estate, Index scanrelid);
|
|||
extern Relation ExecOpenScanRelation(EState* estate, Index scanrelid);
|
||||
extern void ExecCloseScanRelation(Relation scanrel);
|
||||
|
||||
static inline RangeTblEntry *exec_rt_fetch(Index rti, EState *estate)
|
||||
{
|
||||
return (RangeTblEntry *)list_nth(estate->es_range_table, rti - 1);
|
||||
}
|
||||
|
||||
extern Partition ExecOpenScanParitition(
|
||||
EState* estate, Relation parent, PartitionIdentifier* partID, LOCKMODE lockmode);
|
||||
|
||||
|
|
|
|||
|
|
@ -61,4 +61,7 @@ ExecHBucketInsertT(ModifyTableState* state, TupleTableSlot *slot,
|
|||
|
||||
extern void ExecCheckPlanOutput(Relation resultRel, List* targetList);
|
||||
|
||||
extern void ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo, EState *estate, TupleTableSlot *slot,
|
||||
HeapTuple oldtuple, CmdType cmdtype);
|
||||
|
||||
#endif /* NODEMODIFYTABLE_H */
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ extern const uint32 PRIVS_VERSION_NUM;
|
|||
extern const uint32 ML_OPT_MODEL_VERSION_NUM;
|
||||
extern const uint32 RANGE_LIST_DISTRIBUTION_VERSION_NUM;
|
||||
extern const uint32 FIX_SQL_ADD_RELATION_REF_COUNT;
|
||||
extern const uint32 GENERATED_COL_VERSION_NUM;
|
||||
|
||||
#define INPLACE_UPGRADE_PRECOMMIT_VERSION 1
|
||||
|
||||
|
|
|
|||
|
|
@ -383,6 +383,53 @@ typedef struct MergeState {
|
|||
List* notMatchedActionStates;
|
||||
} MergeState;
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* Expression State Trees
|
||||
*
|
||||
* Each executable expression tree has a parallel ExprState tree.
|
||||
*
|
||||
* Unlike PlanState, there is not an exact one-for-one correspondence between
|
||||
* ExprState node types and Expr node types. Many Expr node types have no
|
||||
* need for node-type-specific run-time state, and so they can use plain
|
||||
* ExprState or GenericExprState as their associated ExprState node type.
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/* ----------------
|
||||
* ExprState node
|
||||
*
|
||||
* ExprState is the common superclass for all ExprState-type nodes.
|
||||
*
|
||||
* It can also be instantiated directly for leaf Expr nodes that need no
|
||||
* local run-time state (such as Var, Const, or Param).
|
||||
*
|
||||
* To save on dispatch overhead, each ExprState node contains a function
|
||||
* pointer to the routine to execute to evaluate the node.
|
||||
* ----------------
|
||||
*/
|
||||
typedef struct ExprState ExprState;
|
||||
|
||||
typedef Datum (*ExprStateEvalFunc)(ExprState* expression, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone);
|
||||
typedef ScalarVector* (*VectorExprFun)(
|
||||
ExprState* expression, ExprContext* econtext, bool* selVector, ScalarVector* inputVector, ExprDoneCond* isDone);
|
||||
|
||||
typedef void* (*exprFakeCodeGenSig)(void*);
|
||||
struct ExprState {
|
||||
NodeTag type;
|
||||
Expr* expr; /* associated Expr node */
|
||||
ExprStateEvalFunc evalfunc; /* routine to run to execute node */
|
||||
|
||||
// vectorized evaluator
|
||||
//
|
||||
VectorExprFun vecExprFun;
|
||||
|
||||
exprFakeCodeGenSig exprCodeGen; /* routine to run llvm assembler function */
|
||||
|
||||
ScalarVector tmpVector;
|
||||
|
||||
Oid resultType;
|
||||
};
|
||||
|
||||
/* ----------------
|
||||
* ResultRelInfo information
|
||||
*
|
||||
|
|
@ -447,6 +494,12 @@ typedef struct ResultRelInfo {
|
|||
*/
|
||||
Index ri_mergeTargetRTI;
|
||||
ProjectionInfo* ri_updateProj;
|
||||
|
||||
/* array of stored generated columns expr states */
|
||||
ExprState **ri_GeneratedExprs;
|
||||
|
||||
/* number of stored generated columns we need to compute */
|
||||
int ri_NumGeneratedNeeded;
|
||||
} ResultRelInfo;
|
||||
|
||||
/* bloom filter controller */
|
||||
|
|
@ -680,53 +733,6 @@ typedef HASH_SEQ_STATUS TupleHashIterator;
|
|||
} while (0)
|
||||
#define ScanTupleHashTable(iter) ((TupleHashEntry)hash_seq_search(iter))
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* Expression State Trees
|
||||
*
|
||||
* Each executable expression tree has a parallel ExprState tree.
|
||||
*
|
||||
* Unlike PlanState, there is not an exact one-for-one correspondence between
|
||||
* ExprState node types and Expr node types. Many Expr node types have no
|
||||
* need for node-type-specific run-time state, and so they can use plain
|
||||
* ExprState or GenericExprState as their associated ExprState node type.
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/* ----------------
|
||||
* ExprState node
|
||||
*
|
||||
* ExprState is the common superclass for all ExprState-type nodes.
|
||||
*
|
||||
* It can also be instantiated directly for leaf Expr nodes that need no
|
||||
* local run-time state (such as Var, Const, or Param).
|
||||
*
|
||||
* To save on dispatch overhead, each ExprState node contains a function
|
||||
* pointer to the routine to execute to evaluate the node.
|
||||
* ----------------
|
||||
*/
|
||||
typedef struct ExprState ExprState;
|
||||
|
||||
typedef Datum (*ExprStateEvalFunc)(ExprState* expression, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone);
|
||||
typedef ScalarVector* (*VectorExprFun)(
|
||||
ExprState* expression, ExprContext* econtext, bool* selVector, ScalarVector* inputVector, ExprDoneCond* isDone);
|
||||
|
||||
typedef void* (*exprFakeCodeGenSig)(void*);
|
||||
struct ExprState {
|
||||
NodeTag type;
|
||||
Expr* expr; /* associated Expr node */
|
||||
ExprStateEvalFunc evalfunc; /* routine to run to execute node */
|
||||
|
||||
// vectorized evaluator
|
||||
//
|
||||
VectorExprFun vecExprFun;
|
||||
|
||||
exprFakeCodeGenSig exprCodeGen; /* routine to run llvm assembler function */
|
||||
|
||||
ScalarVector tmpVector;
|
||||
|
||||
Oid resultType;
|
||||
};
|
||||
|
||||
/* ----------------
|
||||
* GenericExprState node
|
||||
*
|
||||
|
|
|
|||
|
|
@ -342,6 +342,7 @@ typedef struct RangeTblEntry {
|
|||
bool isexcluded; /* the rel is the EXCLUDED relation for UPSERT */
|
||||
/* For sublink in targetlist pull up */
|
||||
bool sublink_pull_up; /* mark the subquery is sublink pulled up */
|
||||
Bitmapset *extraUpdatedCols; /* generated columns being updated */
|
||||
} RangeTblEntry;
|
||||
|
||||
/*
|
||||
|
|
@ -751,6 +752,9 @@ typedef struct RangePartitionindexDefState {
|
|||
case CONSTR_ATTR_IMMEDIATE: \
|
||||
tname = "ATTR IMMEDIATE"; \
|
||||
break; \
|
||||
case CONSTR_GENERATED: \
|
||||
tname = "GENERATED COL"; \
|
||||
break; \
|
||||
} \
|
||||
tname; \
|
||||
})
|
||||
|
|
|
|||
|
|
@ -860,6 +860,7 @@ typedef struct ColumnDef {
|
|||
ClientLogicColumnRef *clientLogicColumnRef;
|
||||
Position *position;
|
||||
Form_pg_attribute dropped_attr; /* strcuture for dropped attribute during create table like OE */
|
||||
char generatedCol; /* generated column setting */
|
||||
} ColumnDef;
|
||||
|
||||
/*
|
||||
|
|
@ -1068,7 +1069,8 @@ typedef enum ConstrType { /* types of constraints */
|
|||
CONSTR_ATTR_DEFERRABLE, /* attributes for previous constraint node */
|
||||
CONSTR_ATTR_NOT_DEFERRABLE,
|
||||
CONSTR_ATTR_DEFERRED,
|
||||
CONSTR_ATTR_IMMEDIATE
|
||||
CONSTR_ATTR_IMMEDIATE,
|
||||
CONSTR_GENERATED
|
||||
} ConstrType;
|
||||
|
||||
typedef struct Constraint {
|
||||
|
|
@ -1120,6 +1122,8 @@ typedef struct Constraint {
|
|||
* Field used for soft constraint, which works on HDFS foreign table.
|
||||
*/
|
||||
InformationalConstraint *inforConstraint;
|
||||
char generated_when; /* ALWAYS or BY DEFAULT */
|
||||
char generated_kind; /* currently always STORED */
|
||||
} Constraint;
|
||||
|
||||
/*
|
||||
|
|
@ -1131,7 +1135,7 @@ typedef struct TableLikeClause {
|
|||
bits32 options; /* OR of TableLikeOption flags */
|
||||
} TableLikeClause;
|
||||
|
||||
#define MAX_TABLE_LIKE_OPTIONS (10)
|
||||
#define MAX_TABLE_LIKE_OPTIONS (11)
|
||||
typedef enum TableLikeOption {
|
||||
CREATE_TABLE_LIKE_DEFAULTS = 1 << 0,
|
||||
CREATE_TABLE_LIKE_CONSTRAINTS = 1 << 1,
|
||||
|
|
@ -1143,6 +1147,7 @@ typedef enum TableLikeOption {
|
|||
CREATE_TABLE_LIKE_DISTRIBUTION = 1 << 7,
|
||||
CREATE_TABLE_LIKE_OIDS = 1 << 8,
|
||||
CREATE_TABLE_LIKE_DEFAULTS_SERIAL = 1 << 9, /* Backward compatibility. Inherits serial defaults by default. */
|
||||
CREATE_TABLE_LIKE_GENERATED = 1 << 10,
|
||||
CREATE_TABLE_LIKE_ALL = 0x7FFFFFFF
|
||||
} TableLikeOption;
|
||||
|
||||
|
|
|
|||
|
|
@ -48,4 +48,6 @@ extern Selectivity join_selectivity(
|
|||
extern void estimatePartitionSize(
|
||||
Relation relation, Oid partitionid, int32* attr_widths, RelPageType* pages, double* tuples, double* allvisfrac);
|
||||
|
||||
extern bool HasStoredGeneratedColumns(const PlannerInfo *root, Index rti);
|
||||
|
||||
#endif /* PLANCAT_H */
|
||||
|
|
|
|||
|
|
@ -245,6 +245,7 @@ PG_KEYWORD("from", FROM, RESERVED_KEYWORD)
|
|||
PG_KEYWORD("full", FULL, TYPE_FUNC_NAME_KEYWORD)
|
||||
PG_KEYWORD("function", FUNCTION, UNRESERVED_KEYWORD)
|
||||
PG_KEYWORD("functions", FUNCTIONS, UNRESERVED_KEYWORD)
|
||||
PG_KEYWORD("generated", GENERATED, UNRESERVED_KEYWORD)
|
||||
PG_KEYWORD("global", GLOBAL, UNRESERVED_KEYWORD)
|
||||
PG_KEYWORD("global_function", GLOBAL_FUNCTION, UNRESERVED_KEYWORD)
|
||||
PG_KEYWORD("grant", GRANT, RESERVED_KEYWORD)
|
||||
|
|
@ -526,6 +527,7 @@ PG_KEYWORD("stdin", STDIN, UNRESERVED_KEYWORD)
|
|||
PG_KEYWORD("stdout", STDOUT, UNRESERVED_KEYWORD)
|
||||
PG_KEYWORD("storage", STORAGE, UNRESERVED_KEYWORD)
|
||||
PG_KEYWORD("store", STORE_P, UNRESERVED_KEYWORD)
|
||||
PG_KEYWORD("stored", STORED, UNRESERVED_KEYWORD)
|
||||
PG_KEYWORD("stream", STREAM, UNRESERVED_KEYWORD)
|
||||
PG_KEYWORD("strict", STRICT_P, UNRESERVED_KEYWORD)
|
||||
PG_KEYWORD("strip", STRIP_P, UNRESERVED_KEYWORD)
|
||||
|
|
|
|||
|
|
@ -20,4 +20,5 @@ extern List* expandTargetTL(List* te_list, Query* parsetree);
|
|||
extern List* expandActionTL(List* te_list, Query* parsetree);
|
||||
extern List* expandQualTL(List* te_list, Query* parsetree);
|
||||
extern bool check_unique_constraint(List*& index_list);
|
||||
extern void setExtraUpdatedCols(ParseState* pstate);
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -23,6 +23,62 @@ typedef struct PlusJoinRTEInfo {
|
|||
List* info; /* List of PlusJoinRTEItem we record */
|
||||
} PlusJoinRTEInfo;
|
||||
|
||||
/*
|
||||
* Expression kinds distinguished by transformExpr(). Many of these are not
|
||||
* semantically distinct so far as expression transformation goes; rather,
|
||||
* we distinguish them so that context-specific error messages can be printed.
|
||||
*
|
||||
* Note: EXPR_KIND_OTHER is not used in the core code, but is left for use
|
||||
* by extension code that might need to call transformExpr(). The core code
|
||||
* will not enforce any context-driven restrictions on EXPR_KIND_OTHER
|
||||
* expressions, so the caller would have to check for sub-selects, aggregates,
|
||||
* window functions, SRFs, etc if those need to be disallowed.
|
||||
*/
|
||||
typedef enum ParseExprKind
|
||||
{
|
||||
EXPR_KIND_NONE = 0, /* "not in an expression" */
|
||||
EXPR_KIND_OTHER, /* reserved for extensions */
|
||||
EXPR_KIND_JOIN_ON, /* JOIN ON */
|
||||
EXPR_KIND_JOIN_USING, /* JOIN USING */
|
||||
EXPR_KIND_FROM_SUBSELECT, /* sub-SELECT in FROM clause */
|
||||
EXPR_KIND_FROM_FUNCTION, /* function in FROM clause */
|
||||
EXPR_KIND_WHERE, /* WHERE */
|
||||
EXPR_KIND_HAVING, /* HAVING */
|
||||
EXPR_KIND_FILTER, /* FILTER */
|
||||
EXPR_KIND_WINDOW_PARTITION, /* window definition PARTITION BY */
|
||||
EXPR_KIND_WINDOW_ORDER, /* window definition ORDER BY */
|
||||
EXPR_KIND_WINDOW_FRAME_RANGE, /* window frame clause with RANGE */
|
||||
EXPR_KIND_WINDOW_FRAME_ROWS, /* window frame clause with ROWS */
|
||||
EXPR_KIND_WINDOW_FRAME_GROUPS, /* window frame clause with GROUPS */
|
||||
EXPR_KIND_SELECT_TARGET, /* SELECT target list item */
|
||||
EXPR_KIND_INSERT_TARGET, /* INSERT target list item */
|
||||
EXPR_KIND_UPDATE_SOURCE, /* UPDATE assignment source item */
|
||||
EXPR_KIND_UPDATE_TARGET, /* UPDATE assignment target item */
|
||||
EXPR_KIND_GROUP_BY, /* GROUP BY */
|
||||
EXPR_KIND_ORDER_BY, /* ORDER BY */
|
||||
EXPR_KIND_DISTINCT_ON, /* DISTINCT ON */
|
||||
EXPR_KIND_LIMIT, /* LIMIT */
|
||||
EXPR_KIND_OFFSET, /* OFFSET */
|
||||
EXPR_KIND_RETURNING, /* RETURNING */
|
||||
EXPR_KIND_VALUES, /* VALUES */
|
||||
EXPR_KIND_VALUES_SINGLE, /* single-row VALUES (in INSERT only) */
|
||||
EXPR_KIND_CHECK_CONSTRAINT, /* CHECK constraint for a table */
|
||||
EXPR_KIND_DOMAIN_CHECK, /* CHECK constraint for a domain */
|
||||
EXPR_KIND_COLUMN_DEFAULT, /* default value for a table column */
|
||||
EXPR_KIND_FUNCTION_DEFAULT, /* default parameter value for function */
|
||||
EXPR_KIND_INDEX_EXPRESSION, /* index expression */
|
||||
EXPR_KIND_INDEX_PREDICATE, /* index predicate */
|
||||
EXPR_KIND_ALTER_COL_TRANSFORM, /* transform expr in ALTER COLUMN TYPE */
|
||||
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
|
||||
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
|
||||
EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
|
||||
EXPR_KIND_PARTITION_BOUND, /* partition bound expression */
|
||||
EXPR_KIND_PARTITION_EXPRESSION, /* PARTITION BY expression */
|
||||
EXPR_KIND_CALL_ARGUMENT, /* procedure argument in CALL */
|
||||
EXPR_KIND_COPY_WHERE, /* WHERE condition in COPY FROM */
|
||||
EXPR_KIND_GENERATED_COLUMN, /* generation expression for a column */
|
||||
} ParseExprKind;
|
||||
|
||||
/*
|
||||
* Function signatures for parser hooks
|
||||
*/
|
||||
|
|
@ -99,6 +155,8 @@ struct ParseState {
|
|||
List* p_future_ctes; /* common table exprs not yet in namespace */
|
||||
CommonTableExpr* p_parent_cte; /* this query's containing CTE */
|
||||
List* p_windowdefs; /* raw representations of window clauses */
|
||||
ParseExprKind p_expr_kind; /* what kind of expression we're parsing */
|
||||
List* p_rawdefaultlist; /* raw default list */
|
||||
int p_next_resno; /* next targetlist resno to assign */
|
||||
List* p_locking_clause; /* raw FOR UPDATE/FOR SHARE info */
|
||||
Node* p_value_substitute; /* what to replace VALUE with, if any */
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ typedef struct {
|
|||
Oid bucketOid; /* bucket oid of the resizing table */
|
||||
List *relnodelist; /* filenode of the resizing table */
|
||||
List *toastnodelist; /* toast node of the resizing table */
|
||||
bool ofType; /* true if statement contains OF typename */
|
||||
} CreateStmtContext;
|
||||
|
||||
extern void checkPartitionSynax(CreateStmt *stmt);
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ enum ModuleId {
|
|||
MOD_HOTKEY, /* hotkey */
|
||||
MOD_THREAD_POOL, /* thread_pool */
|
||||
MOD_OPT_AI, /* ai optimizer */
|
||||
MOD_GEN_COL, /* generated column */
|
||||
|
||||
/* add your module id above */
|
||||
MOD_MAX
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ extern Oid get_opfamily_proc(Oid opfamily, Oid lefttype, Oid righttype, int16 pr
|
|||
extern char* get_attname(Oid relid, AttrNumber attnum);
|
||||
extern char* get_relid_attribute_name(Oid relid, AttrNumber attnum);
|
||||
extern AttrNumber get_attnum(Oid relid, const char* attname);
|
||||
extern char GetGenerated(Oid relid, AttrNumber attnum);
|
||||
extern Oid get_atttype(Oid relid, AttrNumber attnum);
|
||||
extern int32 get_atttypmod(Oid relid, AttrNumber attnum);
|
||||
extern void get_atttypetypmodcoll(Oid relid, AttrNumber attnum, Oid* typid, int32* typmod, Oid* collid);
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ where column_constraint can be:
|
|||
NULL |
|
||||
CHECK ( expression ) |
|
||||
DEFAULT default_expr |
|
||||
GENERATED ALWAYS AS ( generation_expr ) STORED |
|
||||
UNIQUE index_parameters |
|
||||
PRIMARY KEY index_parameters |
|
||||
REFERENCES reftable [ ( refcolumn ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,930 @@
|
|||
----------------------------------------------------------
|
||||
---------------generated column test
|
||||
----------------------------------------------------------
|
||||
CREATE TABLE gtest0 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (55) STORED);
|
||||
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "gtest0_pkey" for table "gtest0"
|
||||
CREATE TABLE gtest1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED);
|
||||
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "gtest1_pkey" for table "gtest1"
|
||||
SELECT table_name, column_name, column_default, is_nullable, is_generated, generation_expression FROM information_schema.columns WHERE table_name LIKE 'gtest_' ORDER BY 1, 2;
|
||||
table_name | column_name | column_default | is_nullable | is_generated | generation_expression
|
||||
------------+-------------+----------------+-------------+--------------+-----------------------
|
||||
gtest0 | a | | NO | NEVER |
|
||||
gtest0 | b | | YES | ALWAYS | 55
|
||||
gtest1 | a | | NO | NEVER |
|
||||
gtest1 | b | | YES | ALWAYS | (a * 2)
|
||||
(4 rows)
|
||||
|
||||
\d gtest1
|
||||
Table "public.gtest1"
|
||||
Column | Type | Modifiers
|
||||
--------+---------+--------------------------------------
|
||||
a | integer | not null
|
||||
b | integer | generated always as ((a * 2)) stored
|
||||
Indexes:
|
||||
"gtest1_pkey" PRIMARY KEY, btree (a) TABLESPACE pg_default
|
||||
|
||||
-- duplicate generated
|
||||
CREATE TABLE gtest_err_1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED GENERATED ALWAYS AS (a * 3) STORED);
|
||||
ERROR: multiple generation clauses specified for column "b" of table "gtest_err_1"
|
||||
LINE 1: ...ARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED GENERATED ...
|
||||
^
|
||||
-- references to other generated columns, including self-references
|
||||
CREATE TABLE gtest_err_2a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (b * 2) STORED);
|
||||
ERROR: cannot use generated column "b" in column generation expression
|
||||
DETAIL: A generated column cannot reference another generated column.
|
||||
CREATE TABLE gtest_err_2b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED, c int GENERATED ALWAYS AS (b * 3) STORED);
|
||||
ERROR: cannot use generated column "b" in column generation expression
|
||||
DETAIL: A generated column cannot reference another generated column.
|
||||
-- invalid reference
|
||||
CREATE TABLE gtest_err_3 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (c * 2) STORED);
|
||||
ERROR: column "c" does not exist
|
||||
-- generation expression must be immutable
|
||||
CREATE TABLE gtest_err_4 (a int PRIMARY KEY, b double precision GENERATED ALWAYS AS (random()) STORED);
|
||||
ERROR: generation expression is not immutable
|
||||
-- cannot have default/identity and generated
|
||||
CREATE TABLE gtest_err_5a (a int PRIMARY KEY, b int DEFAULT 5 GENERATED ALWAYS AS (a * 2) STORED);
|
||||
ERROR: both default and generation expression specified for column "b" of table "gtest_err_5a"
|
||||
LINE 1: ... gtest_err_5a (a int PRIMARY KEY, b int DEFAULT 5 GENERATED ...
|
||||
^
|
||||
CREATE TABLE gtest_err_5b (a int PRIMARY KEY, b int GENERATED ALWAYS AS identity GENERATED ALWAYS AS (a * 2) STORED);
|
||||
ERROR: syntax error at or near "identity"
|
||||
LINE 1: ..._5b (a int PRIMARY KEY, b int GENERATED ALWAYS AS identity G...
|
||||
^
|
||||
-- reference to system column not allowed in generated column
|
||||
CREATE TABLE gtest_err_6a (a int PRIMARY KEY, b bool GENERATED ALWAYS AS (xmin <> 37) STORED);
|
||||
ERROR: cannot use system column "xmin" in column generation expression
|
||||
-- various prohibited constructs
|
||||
CREATE TABLE gtest_err_7a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (avg(a)) STORED);
|
||||
ERROR: cannot use aggregate function in generated column expression
|
||||
CREATE TABLE gtest_err_7b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (row_number() OVER (ORDER BY a)) STORED);
|
||||
ERROR: cannot use window function in generated column expression
|
||||
CREATE TABLE gtest_err_7c (a int PRIMARY KEY, b int GENERATED ALWAYS AS ((SELECT a)) STORED);
|
||||
ERROR: cannot use subquery in generated column expression
|
||||
CREATE TABLE gtest_err_7d (a int PRIMARY KEY, b int GENERATED ALWAYS AS (generate_series(1, a)) STORED);
|
||||
ERROR: generated column expression must not return a set
|
||||
INSERT INTO gtest1 VALUES (1);
|
||||
INSERT INTO gtest1 VALUES (2, DEFAULT);
|
||||
INSERT INTO gtest1 VALUES (21, DEFAULT), (22, DEFAULT), (23, DEFAULT), (24, DEFAULT);
|
||||
DELETE FROM gtest1 WHERE a >20;
|
||||
INSERT INTO gtest1 VALUES (3, 33); -- error
|
||||
ERROR: cannot insert into column "b"
|
||||
DETAIL: Column "b" is a generated column.
|
||||
SELECT * FROM gtest1 ORDER BY a;
|
||||
a | b
|
||||
---+---
|
||||
1 | 2
|
||||
2 | 4
|
||||
(2 rows)
|
||||
|
||||
UPDATE gtest1 SET b = DEFAULT WHERE a = 1;
|
||||
UPDATE gtest1 SET b = 11 WHERE a = 1; -- error
|
||||
ERROR: column "b" can only be updated to DEFAULT
|
||||
DETAIL: Column "b" is a generated column.
|
||||
SELECT * FROM gtest1 ORDER BY a;
|
||||
a | b
|
||||
---+---
|
||||
1 | 2
|
||||
2 | 4
|
||||
(2 rows)
|
||||
|
||||
SELECT a, b, b * 2 AS b2 FROM gtest1 ORDER BY a;
|
||||
a | b | b2
|
||||
---+---+----
|
||||
1 | 2 | 4
|
||||
2 | 4 | 8
|
||||
(2 rows)
|
||||
|
||||
SELECT a, b FROM gtest1 WHERE b = 4 ORDER BY a;
|
||||
a | b
|
||||
---+---
|
||||
2 | 4
|
||||
(1 row)
|
||||
|
||||
-- test that overflow error happens on write
|
||||
INSERT INTO gtest1 VALUES (2000000000);
|
||||
ERROR: integer out of range
|
||||
SELECT * FROM gtest1;
|
||||
a | b
|
||||
---+---
|
||||
2 | 4
|
||||
1 | 2
|
||||
(2 rows)
|
||||
|
||||
DELETE FROM gtest1 WHERE a = 2000000000;
|
||||
-- test with joins
|
||||
CREATE TABLE gtestx (x int, y int);
|
||||
INSERT INTO gtestx VALUES (11, 1), (22, 2), (33, 3);
|
||||
SELECT * FROM gtestx, gtest1 WHERE gtestx.y = gtest1.a;
|
||||
x | y | a | b
|
||||
----+---+---+---
|
||||
11 | 1 | 1 | 2
|
||||
22 | 2 | 2 | 4
|
||||
(2 rows)
|
||||
|
||||
DROP TABLE gtestx;
|
||||
-- test UPDATE/DELETE quals
|
||||
SELECT * FROM gtest1 ORDER BY a;
|
||||
a | b
|
||||
---+---
|
||||
1 | 2
|
||||
2 | 4
|
||||
(2 rows)
|
||||
|
||||
UPDATE gtest1 SET a = 3 WHERE b = 4;
|
||||
SELECT * FROM gtest1 ORDER BY a;
|
||||
a | b
|
||||
---+---
|
||||
1 | 2
|
||||
3 | 6
|
||||
(2 rows)
|
||||
|
||||
DELETE FROM gtest1 WHERE b = 2;
|
||||
SELECT * FROM gtest1 ORDER BY a;
|
||||
a | b
|
||||
---+---
|
||||
3 | 6
|
||||
(1 row)
|
||||
|
||||
-- views
|
||||
CREATE VIEW gtest1v AS SELECT * FROM gtest1;
|
||||
SELECT * FROM gtest1v;
|
||||
a | b
|
||||
---+---
|
||||
3 | 6
|
||||
(1 row)
|
||||
|
||||
INSERT INTO gtest1v VALUES (4, 8); -- fails
|
||||
ERROR: cannot insert into view "gtest1v"
|
||||
HINT: You need an unconditional ON INSERT DO INSTEAD rule or an INSTEAD OF INSERT trigger.
|
||||
DROP VIEW gtest1v;
|
||||
-- CTEs
|
||||
WITH foo AS (SELECT * FROM gtest1) SELECT * FROM foo;
|
||||
a | b
|
||||
---+---
|
||||
3 | 6
|
||||
(1 row)
|
||||
|
||||
-- test stored update
|
||||
CREATE TABLE gtest3 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 3) STORED);
|
||||
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "gtest3_pkey" for table "gtest3"
|
||||
INSERT INTO gtest3 (a) VALUES (1), (2), (3);
|
||||
SELECT * FROM gtest3 ORDER BY a;
|
||||
a | b
|
||||
---+---
|
||||
1 | 3
|
||||
2 | 6
|
||||
3 | 9
|
||||
(3 rows)
|
||||
|
||||
UPDATE gtest3 SET a = 22 WHERE a = 2;
|
||||
SELECT * FROM gtest3 ORDER BY a;
|
||||
a | b
|
||||
----+----
|
||||
1 | 3
|
||||
3 | 9
|
||||
22 | 66
|
||||
(3 rows)
|
||||
|
||||
-- COPY
|
||||
TRUNCATE gtest1;
|
||||
INSERT INTO gtest1 (a) VALUES (1), (2);
|
||||
COPY gtest1 TO stdout;
|
||||
1
|
||||
2
|
||||
COPY gtest1 (a, b) TO stdout;
|
||||
ERROR: column "b" is a generated column
|
||||
DETAIL: Generated columns cannot be used in COPY.
|
||||
COPY gtest1 FROM stdin;
|
||||
COPY gtest1 (a, b) FROM stdin;
|
||||
ERROR: column "b" is a generated column
|
||||
DETAIL: Generated columns cannot be used in COPY.
|
||||
SELECT * FROM gtest1 ORDER BY a;
|
||||
a | b
|
||||
---+---
|
||||
1 | 2
|
||||
2 | 4
|
||||
3 | 6
|
||||
4 | 8
|
||||
(4 rows)
|
||||
|
||||
TRUNCATE gtest3;
|
||||
INSERT INTO gtest3 (a) VALUES (1), (2);
|
||||
COPY gtest3 TO stdout;
|
||||
1
|
||||
2
|
||||
COPY gtest3 (a, b) TO stdout;
|
||||
ERROR: column "b" is a generated column
|
||||
DETAIL: Generated columns cannot be used in COPY.
|
||||
COPY gtest3 FROM stdin;
|
||||
COPY gtest3 (a, b) FROM stdin;
|
||||
ERROR: column "b" is a generated column
|
||||
DETAIL: Generated columns cannot be used in COPY.
|
||||
SELECT * FROM gtest3 ORDER BY a;
|
||||
a | b
|
||||
---+----
|
||||
1 | 3
|
||||
2 | 6
|
||||
3 | 9
|
||||
4 | 12
|
||||
(4 rows)
|
||||
|
||||
-- null values
|
||||
CREATE TABLE gtest2 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (NULL) STORED);
|
||||
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "gtest2_pkey" for table "gtest2"
|
||||
INSERT INTO gtest2 VALUES (1);
|
||||
SELECT * FROM gtest2;
|
||||
a | b
|
||||
---+---
|
||||
1 |
|
||||
(1 row)
|
||||
|
||||
-- composite types
|
||||
CREATE TYPE double_int as (a int, b int);
|
||||
CREATE TABLE gtest4 (
|
||||
a int,
|
||||
b double_int GENERATED ALWAYS AS ((a * 2, a * 3)) STORED
|
||||
);
|
||||
INSERT INTO gtest4 VALUES (1), (6);
|
||||
SELECT * FROM gtest4;
|
||||
a | b
|
||||
---+---------
|
||||
1 | (2,3)
|
||||
6 | (12,18)
|
||||
(2 rows)
|
||||
|
||||
DROP TABLE gtest4;
|
||||
DROP TYPE double_int;
|
||||
-- using tableoid is allowed
|
||||
CREATE TABLE gtest_tableoid (
|
||||
a int PRIMARY KEY,
|
||||
b bool GENERATED ALWAYS AS (tableoid <> 0) STORED
|
||||
);
|
||||
ERROR: cannot use system column "tableoid" in column generation expression
|
||||
-- drop column behavior
|
||||
CREATE TABLE gtest10 (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (b * 2) STORED);
|
||||
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "gtest10_pkey" for table "gtest10"
|
||||
ALTER TABLE gtest10 DROP COLUMN b;
|
||||
\d gtest10
|
||||
Table "public.gtest10"
|
||||
Column | Type | Modifiers
|
||||
--------+---------+-----------
|
||||
a | integer | not null
|
||||
Indexes:
|
||||
"gtest10_pkey" PRIMARY KEY, btree (a) TABLESPACE pg_default
|
||||
|
||||
CREATE TABLE gtest10a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED);
|
||||
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "gtest10a_pkey" for table "gtest10a"
|
||||
ALTER TABLE gtest10a DROP COLUMN b;
|
||||
INSERT INTO gtest10a (a) VALUES (1);
|
||||
DROP TABLE gtest10a;
|
||||
-- privileges
|
||||
CREATE USER regress_user11 with password 'Gauss_123';
|
||||
CREATE TABLE gtest11s (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (b * 2) STORED);
|
||||
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "gtest11s_pkey" for table "gtest11s"
|
||||
INSERT INTO gtest11s VALUES (1, 10), (2, 20);
|
||||
GRANT SELECT (a, c) ON gtest11s TO regress_user11;
|
||||
CREATE FUNCTION gf1(a int) RETURNS int AS $$ SELECT a * 3 $$ IMMUTABLE LANGUAGE SQL;
|
||||
REVOKE ALL ON FUNCTION gf1(int) FROM PUBLIC;
|
||||
CREATE TABLE gtest12s (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (gf1(b)) STORED);
|
||||
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "gtest12s_pkey" for table "gtest12s"
|
||||
INSERT INTO gtest12s VALUES (1, 10), (2, 20);
|
||||
GRANT SELECT (a, c) ON gtest12s TO regress_user11;
|
||||
SET ROLE regress_user11 PASSWORD 'Gauss_123';
|
||||
SELECT a, b FROM gtest11s; -- not allowed
|
||||
ERROR: permission denied for relation gtest11s
|
||||
SELECT a, c FROM gtest11s; -- allowed
|
||||
a | c
|
||||
---+----
|
||||
1 | 20
|
||||
2 | 40
|
||||
(2 rows)
|
||||
|
||||
SELECT gf1(10); -- not allowed
|
||||
ERROR: permission denied for function gf1
|
||||
CONTEXT: referenced column: gf1
|
||||
SELECT a, c FROM gtest12s; -- allowed
|
||||
a | c
|
||||
---+----
|
||||
1 | 30
|
||||
2 | 60
|
||||
(2 rows)
|
||||
|
||||
RESET ROLE;
|
||||
DROP TABLE gtest11s, gtest12s;
|
||||
DROP FUNCTION gf1(int);
|
||||
DROP USER regress_user11;
|
||||
-- check constraints
|
||||
CREATE TABLE gtest20 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED CHECK (b < 50));
|
||||
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "gtest20_pkey" for table "gtest20"
|
||||
INSERT INTO gtest20 (a) VALUES (10); -- ok
|
||||
INSERT INTO gtest20 (a) VALUES (30); -- violates constraint
|
||||
ERROR: new row for relation "gtest20" violates check constraint "gtest20_b_check"
|
||||
DETAIL: Failing row contains (30, 60).
|
||||
CREATE TABLE gtest20a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED);
|
||||
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "gtest20a_pkey" for table "gtest20a"
|
||||
INSERT INTO gtest20a (a) VALUES (10);
|
||||
INSERT INTO gtest20a (a) VALUES (30);
|
||||
ALTER TABLE gtest20a ADD CHECK (b < 50); -- fails on existing row
|
||||
ERROR: check constraint "gtest20a_b_check" is violated by some row
|
||||
CREATE TABLE gtest20b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED);
|
||||
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "gtest20b_pkey" for table "gtest20b"
|
||||
INSERT INTO gtest20b (a) VALUES (10);
|
||||
INSERT INTO gtest20b (a) VALUES (30);
|
||||
ALTER TABLE gtest20b ADD CONSTRAINT chk CHECK (b < 50) NOT VALID;
|
||||
ALTER TABLE gtest20b VALIDATE CONSTRAINT chk; -- fails on existing row
|
||||
ERROR: check constraint "chk" is violated by some row
|
||||
DROP TABLE gtest20a,gtest20b;
|
||||
DROP TABLE gtest20;
|
||||
-- not-null constraints
|
||||
CREATE TABLE gtest21a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) STORED NOT NULL);
|
||||
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "gtest21a_pkey" for table "gtest21a"
|
||||
INSERT INTO gtest21a (a) VALUES (1); -- ok
|
||||
INSERT INTO gtest21a (a) VALUES (0); -- violates constraint
|
||||
ERROR: null value in column "b" violates not-null constraint
|
||||
DETAIL: Failing row contains (0, null).
|
||||
CREATE TABLE gtest21b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) STORED);
|
||||
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "gtest21b_pkey" for table "gtest21b"
|
||||
ALTER TABLE gtest21b ALTER COLUMN b SET NOT NULL;
|
||||
INSERT INTO gtest21b (a) VALUES (1); -- ok
|
||||
INSERT INTO gtest21b (a) VALUES (0); -- violates constraint
|
||||
ERROR: null value in column "b" violates not-null constraint
|
||||
DETAIL: Failing row contains (0, null).
|
||||
ALTER TABLE gtest21b ALTER COLUMN b DROP NOT NULL;
|
||||
INSERT INTO gtest21b (a) VALUES (0); -- ok now
|
||||
DROP TABLE gtest21a,gtest21b;
|
||||
-- index constraints
|
||||
CREATE TABLE gtest22a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a / 2) STORED UNIQUE);
|
||||
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "gtest22a_pkey" for table "gtest22a"
|
||||
NOTICE: CREATE TABLE / UNIQUE will create implicit index "gtest22a_b_key" for table "gtest22a"
|
||||
INSERT INTO gtest22a VALUES (2);
|
||||
INSERT INTO gtest22a VALUES (3);
|
||||
INSERT INTO gtest22a VALUES (4);
|
||||
ERROR: duplicate key value violates unique constraint "gtest22a_b_key"
|
||||
DETAIL: Key (b)=(2) already exists.
|
||||
CREATE TABLE gtest22b (a int, b int GENERATED ALWAYS AS (a / 2) STORED, PRIMARY KEY (a, b));
|
||||
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "gtest22b_pkey" for table "gtest22b"
|
||||
INSERT INTO gtest22b VALUES (2);
|
||||
INSERT INTO gtest22b VALUES (2);
|
||||
ERROR: duplicate key value violates unique constraint "gtest22b_pkey"
|
||||
DETAIL: Key (a, b)=(2, 1) already exists.
|
||||
DROP TABLE gtest22b,gtest22a;
|
||||
-- indexes
|
||||
CREATE TABLE gtest22c (a int, b int GENERATED ALWAYS AS (a * 2) STORED);
|
||||
CREATE INDEX gtest22c_b_idx ON gtest22c (b);
|
||||
CREATE INDEX gtest22c_expr_idx ON gtest22c ((b * 3));
|
||||
CREATE INDEX gtest22c_pred_idx ON gtest22c (a) WHERE b > 0;
|
||||
\d gtest22c
|
||||
Table "public.gtest22c"
|
||||
Column | Type | Modifiers
|
||||
--------+---------+--------------------------------------
|
||||
a | integer |
|
||||
b | integer | generated always as ((a * 2)) stored
|
||||
Indexes:
|
||||
"gtest22c_b_idx" btree (b) TABLESPACE pg_default
|
||||
"gtest22c_expr_idx" btree ((b * 3)) TABLESPACE pg_default
|
||||
"gtest22c_pred_idx" btree (a) TABLESPACE pg_default WHERE b > 0
|
||||
|
||||
INSERT INTO gtest22c VALUES (1), (2), (3);
|
||||
SET enable_seqscan TO off;
|
||||
SET enable_bitmapscan TO off;
|
||||
EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 4;
|
||||
QUERY PLAN
|
||||
---------------------------------------------
|
||||
[Bypass]
|
||||
Index Scan using gtest22c_b_idx on gtest22c
|
||||
Index Cond: (b = 4)
|
||||
(3 rows)
|
||||
|
||||
SELECT * FROM gtest22c WHERE b = 4;
|
||||
a | b
|
||||
---+---
|
||||
2 | 4
|
||||
(1 row)
|
||||
|
||||
EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 6;
|
||||
QUERY PLAN
|
||||
------------------------------------------------
|
||||
[Bypass]
|
||||
Index Scan using gtest22c_expr_idx on gtest22c
|
||||
Index Cond: ((b * 3) = 6)
|
||||
(3 rows)
|
||||
|
||||
SELECT * FROM gtest22c WHERE b * 3 = 6;
|
||||
a | b
|
||||
---+---
|
||||
1 | 2
|
||||
(1 row)
|
||||
|
||||
EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
|
||||
QUERY PLAN
|
||||
------------------------------------------------
|
||||
[Bypass]
|
||||
Index Scan using gtest22c_pred_idx on gtest22c
|
||||
Index Cond: (a = 1)
|
||||
(3 rows)
|
||||
|
||||
SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
|
||||
a | b
|
||||
---+---
|
||||
1 | 2
|
||||
(1 row)
|
||||
|
||||
RESET enable_seqscan;
|
||||
RESET enable_bitmapscan;
|
||||
DROP TABLE gtest22c;
|
||||
-- foreign keys
|
||||
CREATE TABLE gtest23a (x int PRIMARY KEY, y int);
|
||||
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "gtest23a_pkey" for table "gtest23a"
|
||||
INSERT INTO gtest23a VALUES (1, 11), (2, 22), (3, 33);
|
||||
CREATE TABLE gtest23x (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED REFERENCES gtest23a (x) ON UPDATE CASCADE); -- error
|
||||
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "gtest23x_pkey" for table "gtest23x"
|
||||
ERROR: invalid ON UPDATE action for foreign key constraint containing generated column
|
||||
CREATE TABLE gtest23x (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED REFERENCES gtest23a (x) ON DELETE SET NULL); -- error
|
||||
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "gtest23x_pkey" for table "gtest23x"
|
||||
ERROR: invalid ON DELETE action for foreign key constraint containing generated column
|
||||
CREATE TABLE gtest23b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED REFERENCES gtest23a (x));
|
||||
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "gtest23b_pkey" for table "gtest23b"
|
||||
\d gtest23b
|
||||
Table "public.gtest23b"
|
||||
Column | Type | Modifiers
|
||||
--------+---------+--------------------------------------
|
||||
a | integer | not null
|
||||
b | integer | generated always as ((a * 2)) stored
|
||||
Indexes:
|
||||
"gtest23b_pkey" PRIMARY KEY, btree (a) TABLESPACE pg_default
|
||||
Foreign-key constraints:
|
||||
"gtest23b_b_fkey" FOREIGN KEY (b) REFERENCES gtest23a(x)
|
||||
|
||||
INSERT INTO gtest23b VALUES (1); -- ok
|
||||
INSERT INTO gtest23b VALUES (5); -- error
|
||||
ERROR: insert or update on table "gtest23b" violates foreign key constraint "gtest23b_b_fkey"
|
||||
DETAIL: Key (b)=(10) is not present in table "gtest23a".
|
||||
DROP TABLE gtest23b;
|
||||
DROP TABLE gtest23a;
|
||||
CREATE TABLE gtest23p (x int, y int GENERATED ALWAYS AS (x * 2) STORED, PRIMARY KEY (y));
|
||||
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "gtest23p_pkey" for table "gtest23p"
|
||||
INSERT INTO gtest23p VALUES (1), (2), (3);
|
||||
CREATE TABLE gtest23q (a int PRIMARY KEY, b int REFERENCES gtest23p (y));
|
||||
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "gtest23q_pkey" for table "gtest23q"
|
||||
INSERT INTO gtest23q VALUES (1, 2); -- ok
|
||||
INSERT INTO gtest23q VALUES (2, 5); -- error
|
||||
ERROR: insert or update on table "gtest23q" violates foreign key constraint "gtest23q_b_fkey"
|
||||
DETAIL: Key (b)=(5) is not present in table "gtest23p".
|
||||
DROP TABLE gtest23q;
|
||||
-- domains
|
||||
CREATE DOMAIN gtestdomain1 AS int CHECK (VALUE < 10);
|
||||
CREATE TABLE gtest24 (a int PRIMARY KEY, b gtestdomain1 GENERATED ALWAYS AS (a * 2) STORED);
|
||||
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "gtest24_pkey" for table "gtest24"
|
||||
INSERT INTO gtest24 (a) VALUES (4); -- ok
|
||||
INSERT INTO gtest24 (a) VALUES (6); -- error
|
||||
ERROR: value for domain gtestdomain1 violates check constraint "gtestdomain1_check"
|
||||
DROP TABLE gtest24;
|
||||
DROP DOMAIN gtestdomain1;
|
||||
-- typed tables (currently not supported)
|
||||
CREATE TYPE gtest_type AS (f1 integer, f2 text, f3 bigint);
|
||||
CREATE TABLE gtest28 OF gtest_type (f1 WITH OPTIONS GENERATED ALWAYS AS (f2 *2) STORED);
|
||||
ERROR: generated columns are not supported on typed tables
|
||||
DROP TYPE gtest_type CASCADE;
|
||||
-- partitioned table
|
||||
CREATE TABLE gtest_parent (f1 date NOT NULL, f2 text, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f1) ( PARTITION p1 VALUES LESS THAN ('2016-07-16'));
|
||||
INSERT INTO gtest_parent (f1, f2) VALUES ('2016-07-15', 1);
|
||||
SELECT * FROM gtest_parent;
|
||||
f1 | f2 | f3
|
||||
--------------------------+----+----
|
||||
Fri Jul 15 00:00:00 2016 | 1 | 2
|
||||
(1 row)
|
||||
|
||||
DROP TABLE gtest_parent;
|
||||
-- generated columns in partition key (not allowed)
|
||||
CREATE TABLE gtest_parent (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f3) ( PARTITION p1 VALUES LESS THAN ('2016-07-16'));
|
||||
ERROR: cannot use generated column in partition key
|
||||
DETAIL: Column "f3" is a generated column.
|
||||
-- ALTER TABLE ... ADD COLUMN
|
||||
CREATE TABLE gtest25 (a int PRIMARY KEY);
|
||||
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "gtest25_pkey" for table "gtest25"
|
||||
INSERT INTO gtest25 VALUES (3), (4);
|
||||
ALTER TABLE gtest25 ADD COLUMN b int GENERATED ALWAYS AS (a * 3) STORED;
|
||||
SELECT * FROM gtest25 ORDER BY a;
|
||||
a | b
|
||||
---+----
|
||||
3 | 9
|
||||
4 | 12
|
||||
(2 rows)
|
||||
|
||||
ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (b * 4) STORED; -- error
|
||||
ERROR: cannot use generated column "b" in column generation expression
|
||||
DETAIL: A generated column cannot reference another generated column.
|
||||
ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (z * 4) STORED; -- error
|
||||
ERROR: column "z" does not exist
|
||||
DROP TABLE gtest25;
|
||||
-- ALTER TABLE ... ALTER COLUMN
|
||||
CREATE TABLE gtest27 (
|
||||
a int,
|
||||
b int GENERATED ALWAYS AS (a * 2) STORED
|
||||
);
|
||||
INSERT INTO gtest27 (a) VALUES (3), (4);
|
||||
ALTER TABLE gtest27 ALTER COLUMN a TYPE text; -- error
|
||||
ERROR: cannot alter type of a column used by a generated column
|
||||
DETAIL: Column "a" is used by generated column "b".
|
||||
ALTER TABLE gtest27 ALTER COLUMN b TYPE numeric;
|
||||
\d gtest27
|
||||
Table "public.gtest27"
|
||||
Column | Type | Modifiers
|
||||
--------+---------+--------------------------------------
|
||||
a | integer |
|
||||
b | numeric | generated always as ((a * 2)) stored
|
||||
|
||||
SELECT * FROM gtest27;
|
||||
a | b
|
||||
---+---
|
||||
3 | 6
|
||||
4 | 8
|
||||
(2 rows)
|
||||
|
||||
ALTER TABLE gtest27 ALTER COLUMN b TYPE boolean USING b <> 0;
|
||||
ALTER TABLE gtest27 ALTER COLUMN b DROP DEFAULT; -- error
|
||||
ERROR: column "b" of relation "gtest27" is a generated column
|
||||
\d gtest27
|
||||
Table "public.gtest27"
|
||||
Column | Type | Modifiers
|
||||
--------+---------+--------------------------------------
|
||||
a | integer |
|
||||
b | boolean | generated always as ((a * 2)) stored
|
||||
|
||||
DROP TABLE gtest27;
|
||||
-- triggers
|
||||
CREATE TABLE gtest26 (
|
||||
a int PRIMARY KEY,
|
||||
b int GENERATED ALWAYS AS (a * 2) STORED
|
||||
);
|
||||
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "gtest26_pkey" for table "gtest26"
|
||||
CREATE FUNCTION gtest_trigger_func() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
IF tg_op IN ('DELETE', 'UPDATE') THEN
|
||||
RAISE INFO '%: %: old = %', TG_NAME, TG_WHEN, OLD;
|
||||
END IF;
|
||||
IF tg_op IN ('INSERT', 'UPDATE') THEN
|
||||
RAISE INFO '%: %: new = %', TG_NAME, TG_WHEN, NEW;
|
||||
END IF;
|
||||
IF tg_op = 'DELETE' THEN
|
||||
RETURN OLD;
|
||||
ELSE
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
CREATE TRIGGER gtest1 BEFORE DELETE OR UPDATE ON gtest26
|
||||
FOR EACH ROW
|
||||
WHEN (OLD.b < 0) -- ok
|
||||
EXECUTE PROCEDURE gtest_trigger_func();
|
||||
CREATE TRIGGER gtest2a BEFORE INSERT OR UPDATE ON gtest26
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.b < 0) -- error
|
||||
EXECUTE PROCEDURE gtest_trigger_func();
|
||||
ERROR: BEFORE trigger's WHEN condition cannot reference NEW generated columns
|
||||
LINE 3: WHEN (NEW.b < 0)
|
||||
^
|
||||
DETAIL: Column "b" is a generated column.
|
||||
CREATE TRIGGER gtest2b BEFORE INSERT OR UPDATE ON gtest26
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.* IS NOT NULL) -- error
|
||||
EXECUTE PROCEDURE gtest_trigger_func();
|
||||
ERROR: BEFORE trigger's WHEN condition cannot reference NEW generated columns
|
||||
LINE 3: WHEN (NEW.* IS NOT NULL)
|
||||
^
|
||||
DETAIL: A whole-row reference is used and the table contains generated columns.
|
||||
CREATE TRIGGER gtest2 BEFORE INSERT ON gtest26
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.a < 0)
|
||||
EXECUTE PROCEDURE gtest_trigger_func();
|
||||
CREATE TRIGGER gtest3 AFTER DELETE OR UPDATE ON gtest26
|
||||
FOR EACH ROW
|
||||
WHEN (OLD.b < 0) -- ok
|
||||
EXECUTE PROCEDURE gtest_trigger_func();
|
||||
CREATE TRIGGER gtest4 AFTER INSERT OR UPDATE ON gtest26
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.b < 0) -- ok
|
||||
EXECUTE PROCEDURE gtest_trigger_func();
|
||||
INSERT INTO gtest26 (a) VALUES (-2), (0), (3);
|
||||
INFO: gtest2: BEFORE: new = (-2,)
|
||||
INFO: gtest4: AFTER: new = (-2,-4)
|
||||
SELECT * FROM gtest26 ORDER BY a;
|
||||
a | b
|
||||
----+----
|
||||
-2 | -4
|
||||
0 | 0
|
||||
3 | 6
|
||||
(3 rows)
|
||||
|
||||
UPDATE gtest26 SET a = a * -2;
|
||||
INFO: gtest1: BEFORE: old = (-2,-4)
|
||||
INFO: gtest1: BEFORE: new = (4,)
|
||||
INFO: gtest3: AFTER: old = (-2,-4)
|
||||
INFO: gtest3: AFTER: new = (4,8)
|
||||
INFO: gtest4: AFTER: old = (3,6)
|
||||
INFO: gtest4: AFTER: new = (-6,-12)
|
||||
SELECT * FROM gtest26 ORDER BY a;
|
||||
a | b
|
||||
----+-----
|
||||
-6 | -12
|
||||
0 | 0
|
||||
4 | 8
|
||||
(3 rows)
|
||||
|
||||
DELETE FROM gtest26 WHERE a = -6;
|
||||
INFO: gtest1: BEFORE: old = (-6,-12)
|
||||
INFO: gtest3: AFTER: old = (-6,-12)
|
||||
SELECT * FROM gtest26 ORDER BY a;
|
||||
a | b
|
||||
---+---
|
||||
0 | 0
|
||||
4 | 8
|
||||
(2 rows)
|
||||
|
||||
DROP TRIGGER gtest1 ON gtest26;
|
||||
DROP TRIGGER gtest2 ON gtest26;
|
||||
DROP TRIGGER gtest3 ON gtest26;
|
||||
-- Check that an UPDATE of "a" fires the trigger for UPDATE OF b, per
|
||||
-- SQL standard.
|
||||
CREATE FUNCTION gtest_trigger_func3() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
RAISE NOTICE 'OK';
|
||||
RETURN NEW;
|
||||
END
|
||||
$$;
|
||||
CREATE TRIGGER gtest11 BEFORE UPDATE OF b ON gtest26
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE gtest_trigger_func3();
|
||||
UPDATE gtest26 SET a = 1 WHERE a = 0;
|
||||
NOTICE: OK
|
||||
DROP TRIGGER gtest11 ON gtest26;
|
||||
TRUNCATE gtest26;
|
||||
-- check that modifications of stored generated columns in triggers do
|
||||
-- not get propagated
|
||||
CREATE FUNCTION gtest_trigger_func4() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
NEW.a = 10;
|
||||
NEW.b = 300;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
CREATE TRIGGER gtest12_01 BEFORE UPDATE ON gtest26
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE gtest_trigger_func();
|
||||
CREATE TRIGGER gtest12_02 BEFORE UPDATE ON gtest26
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE gtest_trigger_func4();
|
||||
CREATE TRIGGER gtest12_03 BEFORE UPDATE ON gtest26
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE gtest_trigger_func();
|
||||
INSERT INTO gtest26 (a) VALUES (1);
|
||||
UPDATE gtest26 SET a = 11 WHERE a = 1;
|
||||
INFO: gtest12_01: BEFORE: old = (1,2)
|
||||
INFO: gtest12_01: BEFORE: new = (11,)
|
||||
INFO: gtest12_03: BEFORE: old = (1,2)
|
||||
INFO: gtest12_03: BEFORE: new = (10,)
|
||||
SELECT * FROM gtest26 ORDER BY a;
|
||||
a | b
|
||||
----+----
|
||||
10 | 20
|
||||
(1 row)
|
||||
|
||||
DROP TABLE gtest26;
|
||||
DROP FUNCTION gtest_trigger_func4;
|
||||
DROP FUNCTION gtest_trigger_func3;
|
||||
DROP FUNCTION gtest_trigger_func;
|
||||
-- LIKE INCLUDING GENERATED and dropped column handling
|
||||
CREATE TABLE gtest28a (
|
||||
a int,
|
||||
b int,
|
||||
c int,
|
||||
x int GENERATED ALWAYS AS (b * 2) STORED
|
||||
);
|
||||
ALTER TABLE gtest28a DROP COLUMN a;
|
||||
CREATE TABLE gtest28b (LIKE gtest28a INCLUDING GENERATED);
|
||||
\d gtest28*
|
||||
Table "public.gtest28a"
|
||||
Column | Type | Modifiers
|
||||
--------+---------+--------------------------------------
|
||||
b | integer |
|
||||
c | integer |
|
||||
x | integer | generated always as ((b * 2)) stored
|
||||
|
||||
Table "public.gtest28b"
|
||||
Column | Type | Modifiers
|
||||
--------+---------+--------------------------------------
|
||||
b | integer |
|
||||
c | integer |
|
||||
x | integer | generated always as ((b * 2)) stored
|
||||
|
||||
DROP TABLE gtest28a;
|
||||
DROP TABLE gtest28b;
|
||||
CREATE TABLE test_like_gen_1 (a int, b int GENERATED ALWAYS AS (a * 2) STORED);
|
||||
\d test_like_gen_1
|
||||
Table "public.test_like_gen_1"
|
||||
Column | Type | Modifiers
|
||||
--------+---------+--------------------------------------
|
||||
a | integer |
|
||||
b | integer | generated always as ((a * 2)) stored
|
||||
|
||||
INSERT INTO test_like_gen_1 (a) VALUES (1);
|
||||
SELECT * FROM test_like_gen_1;
|
||||
a | b
|
||||
---+---
|
||||
1 | 2
|
||||
(1 row)
|
||||
|
||||
CREATE TABLE test_like_gen_2 (LIKE test_like_gen_1);
|
||||
\d test_like_gen_2
|
||||
Table "public.test_like_gen_2"
|
||||
Column | Type | Modifiers
|
||||
--------+---------+-----------
|
||||
a | integer |
|
||||
b | integer |
|
||||
|
||||
INSERT INTO test_like_gen_2 (a) VALUES (1);
|
||||
SELECT * FROM test_like_gen_2;
|
||||
a | b
|
||||
---+---
|
||||
1 |
|
||||
(1 row)
|
||||
|
||||
CREATE TABLE test_like_gen_3 (LIKE test_like_gen_1 INCLUDING GENERATED);
|
||||
\d test_like_gen_3
|
||||
Table "public.test_like_gen_3"
|
||||
Column | Type | Modifiers
|
||||
--------+---------+--------------------------------------
|
||||
a | integer |
|
||||
b | integer | generated always as ((a * 2)) stored
|
||||
|
||||
INSERT INTO test_like_gen_3 (a) VALUES (1);
|
||||
SELECT * FROM test_like_gen_3;
|
||||
a | b
|
||||
---+---
|
||||
1 | 2
|
||||
(1 row)
|
||||
|
||||
DROP TABLE test_like_gen_1, test_like_gen_2, test_like_gen_3;
|
||||
-- function
|
||||
CREATE TABLE t1(height_cm int,height_in int GENERATED ALWAYS AS (height_cm * 2) STORED);
|
||||
CREATE OR REPLACE FUNCTION gen_test(a integer) RETURNS int as $$
|
||||
declare
|
||||
b int;
|
||||
begin
|
||||
DELETE t1 where height_cm = a;
|
||||
INSERT INTO t1 values(a);
|
||||
SELECT height_in INTO b FROM t1 where height_cm = a;
|
||||
return b;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
call gen_test(100);
|
||||
gen_test
|
||||
----------
|
||||
200
|
||||
(1 row)
|
||||
|
||||
drop function gen_test;
|
||||
DROP TABLE t1;
|
||||
-- sequence test
|
||||
CREATE SEQUENCE seq1 START WITH 33;
|
||||
-- error
|
||||
CREATE TABLE t1 (id serial,id1 integer GENERATED ALWAYS AS (nextval('seq1')) STORED);
|
||||
NOTICE: CREATE TABLE will create implicit sequence "t1_id_seq" for serial column "t1.id"
|
||||
ERROR: generation expression is not immutable
|
||||
DROP SEQUENCE seq1;
|
||||
--merage test
|
||||
CREATE TABLE products_base
|
||||
(
|
||||
product_id INTEGER DEFAULT 0,
|
||||
product_name VARCHAR(60) DEFAULT 'null',
|
||||
category VARCHAR(60) DEFAULT 'unknown',
|
||||
total INTEGER DEFAULT '0'
|
||||
);
|
||||
INSERT INTO products_base VALUES (1501, 'vivitar 35mm', 'electrncs', 100);
|
||||
INSERT INTO products_base VALUES (1502, 'olympus is50', 'electrncs', 100);
|
||||
INSERT INTO products_base VALUES (1600, 'play gym', 'toys', 100);
|
||||
INSERT INTO products_base VALUES (1601, 'lamaze', 'toys', 100);
|
||||
INSERT INTO products_base VALUES (1666, 'harry potter', 'dvd', 100);
|
||||
CREATE TABLE newproducts_base
|
||||
(
|
||||
product_id INTEGER DEFAULT 0,
|
||||
product_name VARCHAR(60) DEFAULT 'null',
|
||||
category VARCHAR(60) DEFAULT 'unknown',
|
||||
total INTEGER DEFAULT '0'
|
||||
);
|
||||
INSERT INTO newproducts_base VALUES (1502, 'olympus camera', 'electrncs', 200);
|
||||
INSERT INTO newproducts_base VALUES (1601, 'lamaze', 'toys', 200);
|
||||
INSERT INTO newproducts_base VALUES (1666, 'harry potter', 'toys', 200);
|
||||
INSERT INTO newproducts_base VALUES (1700, 'wait interface', 'books', 200);
|
||||
CREATE TABLE products_row
|
||||
(
|
||||
product_id INTEGER DEFAULT 0,
|
||||
product_name VARCHAR(60) DEFAULT 'null',
|
||||
category VARCHAR(60) DEFAULT 'unknown',
|
||||
total INTEGER DEFAULT '0',
|
||||
height_in int GENERATED ALWAYS AS (total * 2) STORED
|
||||
);
|
||||
INSERT INTO products_row SELECT * FROM products_base;
|
||||
CREATE TABLE newproducts_row
|
||||
(
|
||||
product_id INTEGER DEFAULT 0,
|
||||
product_name VARCHAR(60) DEFAULT 'null',
|
||||
category VARCHAR(60) DEFAULT 'unknown',
|
||||
total INTEGER DEFAULT '0',
|
||||
height_in int GENERATED ALWAYS AS (total * 3) STORED
|
||||
);
|
||||
INSERT INTO newproducts_row SELECT * FROM newproducts_base;
|
||||
MERGE INTO products_row p
|
||||
USING newproducts_row np
|
||||
ON p.product_id = np.product_id
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET product_name = np.product_name, category = np.category, total = np.total
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (np.product_id, np.product_name, np.category, np.total);
|
||||
SELECT * FROM products_row;
|
||||
product_id | product_name | category | total | height_in
|
||||
------------+----------------+-----------+-------+-----------
|
||||
1501 | vivitar 35mm | electrncs | 100 | 200
|
||||
1600 | play gym | toys | 100 | 200
|
||||
1502 | olympus camera | electrncs | 200 | 400
|
||||
1601 | lamaze | toys | 200 | 400
|
||||
1666 | harry potter | toys | 200 | 400
|
||||
1700 | wait interface | books | 200 | 400
|
||||
(6 rows)
|
||||
|
||||
CREATE TABLE hw_create_as_test2(C_INT) as SELECT height_in FROM newproducts_row;
|
||||
SELECT * FROM hw_create_as_test2;
|
||||
c_int
|
||||
-------
|
||||
600
|
||||
600
|
||||
600
|
||||
600
|
||||
(4 rows)
|
||||
|
||||
DROP TABLE hw_create_as_test2;
|
||||
DROP TABLE products_row;
|
||||
DROP TABLE newproducts_row;
|
||||
DROP TABLE newproducts_base;
|
||||
DROP TABLE products_base;
|
||||
--bypass test
|
||||
INSERT INTO gtest1 values (200);
|
||||
SELECT * FROM gtest1;
|
||||
a | b
|
||||
-----+-----
|
||||
1 | 2
|
||||
2 | 4
|
||||
3 | 6
|
||||
4 | 8
|
||||
200 | 400
|
||||
(5 rows)
|
||||
|
||||
UPDATE gtest1 set a = 300 where a = 200;
|
||||
SELECT * FROM gtest1;
|
||||
a | b
|
||||
-----+-----
|
||||
1 | 2
|
||||
2 | 4
|
||||
3 | 6
|
||||
4 | 8
|
||||
300 | 600
|
||||
(5 rows)
|
||||
|
||||
--null test
|
||||
CREATE TABLE t1 (id int,id1 integer GENERATED ALWAYS AS (id*2) STORED);
|
||||
INSERT INTO t1 values(100);
|
||||
SELECT * FROM t1;
|
||||
id | id1
|
||||
-----+-----
|
||||
100 | 200
|
||||
(1 row)
|
||||
|
||||
UPDATE t1 set id = 200;
|
||||
SELECT * FROM t1;
|
||||
id | id1
|
||||
-----+-----
|
||||
200 | 400
|
||||
(1 row)
|
||||
|
||||
UPDATE t1 set id = NULL;
|
||||
SELECT * FROM t1;
|
||||
id | id1
|
||||
----+-----
|
||||
|
|
||||
(1 row)
|
||||
|
||||
DROP TABLE t1;
|
||||
--cstore not support
|
||||
CREATE TABLE t2(height_cm int,height_in int GENERATED ALWAYS AS (height_cm * 2) STORED) WITH (ORIENTATION = COLUMN);
|
||||
ERROR: column/timeseries store unsupport constraint "GENERATED COL"
|
||||
DROP TABLE gtest0;
|
||||
DROP TABLE gtest1;
|
||||
DROP TABLE gtest2;
|
||||
DROP TABLE gtest3;
|
||||
DROP TABLE gtest10;
|
||||
|
|
@ -761,6 +761,10 @@ test: leaky_function_operator
|
|||
#test: gs_guc
|
||||
|
||||
test: smp
|
||||
|
||||
#generated column test
|
||||
test: generated_col
|
||||
|
||||
test: sequence_cache_test
|
||||
test: procedure_privilege_test
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,580 @@
|
|||
----------------------------------------------------------
|
||||
---------------generated column test
|
||||
----------------------------------------------------------
|
||||
CREATE TABLE gtest0 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (55) STORED);
|
||||
CREATE TABLE gtest1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED);
|
||||
|
||||
SELECT table_name, column_name, column_default, is_nullable, is_generated, generation_expression FROM information_schema.columns WHERE table_name LIKE 'gtest_' ORDER BY 1, 2;
|
||||
|
||||
\d gtest1
|
||||
|
||||
-- duplicate generated
|
||||
CREATE TABLE gtest_err_1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED GENERATED ALWAYS AS (a * 3) STORED);
|
||||
|
||||
-- references to other generated columns, including self-references
|
||||
CREATE TABLE gtest_err_2a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (b * 2) STORED);
|
||||
CREATE TABLE gtest_err_2b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED, c int GENERATED ALWAYS AS (b * 3) STORED);
|
||||
|
||||
-- invalid reference
|
||||
CREATE TABLE gtest_err_3 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (c * 2) STORED);
|
||||
|
||||
-- generation expression must be immutable
|
||||
CREATE TABLE gtest_err_4 (a int PRIMARY KEY, b double precision GENERATED ALWAYS AS (random()) STORED);
|
||||
|
||||
-- cannot have default/identity and generated
|
||||
CREATE TABLE gtest_err_5a (a int PRIMARY KEY, b int DEFAULT 5 GENERATED ALWAYS AS (a * 2) STORED);
|
||||
CREATE TABLE gtest_err_5b (a int PRIMARY KEY, b int GENERATED ALWAYS AS identity GENERATED ALWAYS AS (a * 2) STORED);
|
||||
|
||||
-- reference to system column not allowed in generated column
|
||||
CREATE TABLE gtest_err_6a (a int PRIMARY KEY, b bool GENERATED ALWAYS AS (xmin <> 37) STORED);
|
||||
|
||||
-- various prohibited constructs
|
||||
CREATE TABLE gtest_err_7a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (avg(a)) STORED);
|
||||
CREATE TABLE gtest_err_7b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (row_number() OVER (ORDER BY a)) STORED);
|
||||
CREATE TABLE gtest_err_7c (a int PRIMARY KEY, b int GENERATED ALWAYS AS ((SELECT a)) STORED);
|
||||
CREATE TABLE gtest_err_7d (a int PRIMARY KEY, b int GENERATED ALWAYS AS (generate_series(1, a)) STORED);
|
||||
|
||||
INSERT INTO gtest1 VALUES (1);
|
||||
INSERT INTO gtest1 VALUES (2, DEFAULT);
|
||||
INSERT INTO gtest1 VALUES (21, DEFAULT), (22, DEFAULT), (23, DEFAULT), (24, DEFAULT);
|
||||
DELETE FROM gtest1 WHERE a >20;
|
||||
INSERT INTO gtest1 VALUES (3, 33); -- error
|
||||
|
||||
SELECT * FROM gtest1 ORDER BY a;
|
||||
|
||||
UPDATE gtest1 SET b = DEFAULT WHERE a = 1;
|
||||
UPDATE gtest1 SET b = 11 WHERE a = 1; -- error
|
||||
|
||||
SELECT * FROM gtest1 ORDER BY a;
|
||||
|
||||
SELECT a, b, b * 2 AS b2 FROM gtest1 ORDER BY a;
|
||||
SELECT a, b FROM gtest1 WHERE b = 4 ORDER BY a;
|
||||
|
||||
-- test that overflow error happens on write
|
||||
INSERT INTO gtest1 VALUES (2000000000);
|
||||
SELECT * FROM gtest1;
|
||||
DELETE FROM gtest1 WHERE a = 2000000000;
|
||||
|
||||
-- test with joins
|
||||
CREATE TABLE gtestx (x int, y int);
|
||||
INSERT INTO gtestx VALUES (11, 1), (22, 2), (33, 3);
|
||||
SELECT * FROM gtestx, gtest1 WHERE gtestx.y = gtest1.a;
|
||||
DROP TABLE gtestx;
|
||||
|
||||
-- test UPDATE/DELETE quals
|
||||
SELECT * FROM gtest1 ORDER BY a;
|
||||
UPDATE gtest1 SET a = 3 WHERE b = 4;
|
||||
SELECT * FROM gtest1 ORDER BY a;
|
||||
DELETE FROM gtest1 WHERE b = 2;
|
||||
SELECT * FROM gtest1 ORDER BY a;
|
||||
|
||||
-- views
|
||||
CREATE VIEW gtest1v AS SELECT * FROM gtest1;
|
||||
SELECT * FROM gtest1v;
|
||||
INSERT INTO gtest1v VALUES (4, 8); -- fails
|
||||
DROP VIEW gtest1v;
|
||||
|
||||
-- CTEs
|
||||
WITH foo AS (SELECT * FROM gtest1) SELECT * FROM foo;
|
||||
|
||||
-- test stored update
|
||||
CREATE TABLE gtest3 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 3) STORED);
|
||||
INSERT INTO gtest3 (a) VALUES (1), (2), (3);
|
||||
SELECT * FROM gtest3 ORDER BY a;
|
||||
UPDATE gtest3 SET a = 22 WHERE a = 2;
|
||||
SELECT * FROM gtest3 ORDER BY a;
|
||||
|
||||
-- COPY
|
||||
TRUNCATE gtest1;
|
||||
INSERT INTO gtest1 (a) VALUES (1), (2);
|
||||
|
||||
COPY gtest1 TO stdout;
|
||||
|
||||
COPY gtest1 (a, b) TO stdout;
|
||||
|
||||
COPY gtest1 FROM stdin;
|
||||
3
|
||||
4
|
||||
\.
|
||||
|
||||
COPY gtest1 (a, b) FROM stdin;
|
||||
|
||||
SELECT * FROM gtest1 ORDER BY a;
|
||||
|
||||
TRUNCATE gtest3;
|
||||
INSERT INTO gtest3 (a) VALUES (1), (2);
|
||||
|
||||
COPY gtest3 TO stdout;
|
||||
|
||||
COPY gtest3 (a, b) TO stdout;
|
||||
|
||||
COPY gtest3 FROM stdin;
|
||||
3
|
||||
4
|
||||
\.
|
||||
|
||||
COPY gtest3 (a, b) FROM stdin;
|
||||
|
||||
SELECT * FROM gtest3 ORDER BY a;
|
||||
|
||||
-- null values
|
||||
CREATE TABLE gtest2 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (NULL) STORED);
|
||||
INSERT INTO gtest2 VALUES (1);
|
||||
SELECT * FROM gtest2;
|
||||
|
||||
-- composite types
|
||||
CREATE TYPE double_int as (a int, b int);
|
||||
CREATE TABLE gtest4 (
|
||||
a int,
|
||||
b double_int GENERATED ALWAYS AS ((a * 2, a * 3)) STORED
|
||||
);
|
||||
INSERT INTO gtest4 VALUES (1), (6);
|
||||
SELECT * FROM gtest4;
|
||||
|
||||
DROP TABLE gtest4;
|
||||
DROP TYPE double_int;
|
||||
|
||||
-- using tableoid is allowed
|
||||
CREATE TABLE gtest_tableoid (
|
||||
a int PRIMARY KEY,
|
||||
b bool GENERATED ALWAYS AS (tableoid <> 0) STORED
|
||||
);
|
||||
|
||||
-- drop column behavior
|
||||
CREATE TABLE gtest10 (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (b * 2) STORED);
|
||||
ALTER TABLE gtest10 DROP COLUMN b;
|
||||
|
||||
\d gtest10
|
||||
|
||||
CREATE TABLE gtest10a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED);
|
||||
ALTER TABLE gtest10a DROP COLUMN b;
|
||||
INSERT INTO gtest10a (a) VALUES (1);
|
||||
DROP TABLE gtest10a;
|
||||
|
||||
-- privileges
|
||||
CREATE USER regress_user11 with password 'Gauss_123';
|
||||
|
||||
CREATE TABLE gtest11s (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (b * 2) STORED);
|
||||
INSERT INTO gtest11s VALUES (1, 10), (2, 20);
|
||||
GRANT SELECT (a, c) ON gtest11s TO regress_user11;
|
||||
|
||||
CREATE FUNCTION gf1(a int) RETURNS int AS $$ SELECT a * 3 $$ IMMUTABLE LANGUAGE SQL;
|
||||
REVOKE ALL ON FUNCTION gf1(int) FROM PUBLIC;
|
||||
|
||||
CREATE TABLE gtest12s (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (gf1(b)) STORED);
|
||||
INSERT INTO gtest12s VALUES (1, 10), (2, 20);
|
||||
GRANT SELECT (a, c) ON gtest12s TO regress_user11;
|
||||
|
||||
SET ROLE regress_user11 PASSWORD 'Gauss_123';
|
||||
SELECT a, b FROM gtest11s; -- not allowed
|
||||
SELECT a, c FROM gtest11s; -- allowed
|
||||
SELECT gf1(10); -- not allowed
|
||||
SELECT a, c FROM gtest12s; -- allowed
|
||||
RESET ROLE;
|
||||
|
||||
DROP TABLE gtest11s, gtest12s;
|
||||
DROP FUNCTION gf1(int);
|
||||
DROP USER regress_user11;
|
||||
|
||||
-- check constraints
|
||||
CREATE TABLE gtest20 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED CHECK (b < 50));
|
||||
INSERT INTO gtest20 (a) VALUES (10); -- ok
|
||||
INSERT INTO gtest20 (a) VALUES (30); -- violates constraint
|
||||
|
||||
CREATE TABLE gtest20a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED);
|
||||
INSERT INTO gtest20a (a) VALUES (10);
|
||||
INSERT INTO gtest20a (a) VALUES (30);
|
||||
ALTER TABLE gtest20a ADD CHECK (b < 50); -- fails on existing row
|
||||
|
||||
CREATE TABLE gtest20b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED);
|
||||
INSERT INTO gtest20b (a) VALUES (10);
|
||||
INSERT INTO gtest20b (a) VALUES (30);
|
||||
ALTER TABLE gtest20b ADD CONSTRAINT chk CHECK (b < 50) NOT VALID;
|
||||
ALTER TABLE gtest20b VALIDATE CONSTRAINT chk; -- fails on existing row
|
||||
|
||||
DROP TABLE gtest20a,gtest20b;
|
||||
DROP TABLE gtest20;
|
||||
|
||||
-- not-null constraints
|
||||
CREATE TABLE gtest21a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) STORED NOT NULL);
|
||||
INSERT INTO gtest21a (a) VALUES (1); -- ok
|
||||
INSERT INTO gtest21a (a) VALUES (0); -- violates constraint
|
||||
|
||||
CREATE TABLE gtest21b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) STORED);
|
||||
ALTER TABLE gtest21b ALTER COLUMN b SET NOT NULL;
|
||||
INSERT INTO gtest21b (a) VALUES (1); -- ok
|
||||
INSERT INTO gtest21b (a) VALUES (0); -- violates constraint
|
||||
ALTER TABLE gtest21b ALTER COLUMN b DROP NOT NULL;
|
||||
INSERT INTO gtest21b (a) VALUES (0); -- ok now
|
||||
|
||||
DROP TABLE gtest21a,gtest21b;
|
||||
|
||||
-- index constraints
|
||||
CREATE TABLE gtest22a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a / 2) STORED UNIQUE);
|
||||
INSERT INTO gtest22a VALUES (2);
|
||||
INSERT INTO gtest22a VALUES (3);
|
||||
INSERT INTO gtest22a VALUES (4);
|
||||
CREATE TABLE gtest22b (a int, b int GENERATED ALWAYS AS (a / 2) STORED, PRIMARY KEY (a, b));
|
||||
INSERT INTO gtest22b VALUES (2);
|
||||
INSERT INTO gtest22b VALUES (2);
|
||||
|
||||
DROP TABLE gtest22b,gtest22a;
|
||||
|
||||
-- indexes
|
||||
CREATE TABLE gtest22c (a int, b int GENERATED ALWAYS AS (a * 2) STORED);
|
||||
CREATE INDEX gtest22c_b_idx ON gtest22c (b);
|
||||
CREATE INDEX gtest22c_expr_idx ON gtest22c ((b * 3));
|
||||
CREATE INDEX gtest22c_pred_idx ON gtest22c (a) WHERE b > 0;
|
||||
\d gtest22c
|
||||
|
||||
INSERT INTO gtest22c VALUES (1), (2), (3);
|
||||
SET enable_seqscan TO off;
|
||||
SET enable_bitmapscan TO off;
|
||||
EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 4;
|
||||
SELECT * FROM gtest22c WHERE b = 4;
|
||||
EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 6;
|
||||
SELECT * FROM gtest22c WHERE b * 3 = 6;
|
||||
EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
|
||||
SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
|
||||
RESET enable_seqscan;
|
||||
RESET enable_bitmapscan;
|
||||
|
||||
DROP TABLE gtest22c;
|
||||
|
||||
-- foreign keys
|
||||
CREATE TABLE gtest23a (x int PRIMARY KEY, y int);
|
||||
INSERT INTO gtest23a VALUES (1, 11), (2, 22), (3, 33);
|
||||
|
||||
CREATE TABLE gtest23x (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED REFERENCES gtest23a (x) ON UPDATE CASCADE); -- error
|
||||
CREATE TABLE gtest23x (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED REFERENCES gtest23a (x) ON DELETE SET NULL); -- error
|
||||
|
||||
CREATE TABLE gtest23b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED REFERENCES gtest23a (x));
|
||||
\d gtest23b
|
||||
|
||||
INSERT INTO gtest23b VALUES (1); -- ok
|
||||
INSERT INTO gtest23b VALUES (5); -- error
|
||||
|
||||
DROP TABLE gtest23b;
|
||||
DROP TABLE gtest23a;
|
||||
|
||||
CREATE TABLE gtest23p (x int, y int GENERATED ALWAYS AS (x * 2) STORED, PRIMARY KEY (y));
|
||||
INSERT INTO gtest23p VALUES (1), (2), (3);
|
||||
|
||||
CREATE TABLE gtest23q (a int PRIMARY KEY, b int REFERENCES gtest23p (y));
|
||||
INSERT INTO gtest23q VALUES (1, 2); -- ok
|
||||
INSERT INTO gtest23q VALUES (2, 5); -- error
|
||||
|
||||
DROP TABLE gtest23q;
|
||||
|
||||
-- domains
|
||||
CREATE DOMAIN gtestdomain1 AS int CHECK (VALUE < 10);
|
||||
CREATE TABLE gtest24 (a int PRIMARY KEY, b gtestdomain1 GENERATED ALWAYS AS (a * 2) STORED);
|
||||
INSERT INTO gtest24 (a) VALUES (4); -- ok
|
||||
INSERT INTO gtest24 (a) VALUES (6); -- error
|
||||
|
||||
DROP TABLE gtest24;
|
||||
DROP DOMAIN gtestdomain1;
|
||||
|
||||
-- typed tables (currently not supported)
|
||||
CREATE TYPE gtest_type AS (f1 integer, f2 text, f3 bigint);
|
||||
CREATE TABLE gtest28 OF gtest_type (f1 WITH OPTIONS GENERATED ALWAYS AS (f2 *2) STORED);
|
||||
DROP TYPE gtest_type CASCADE;
|
||||
|
||||
-- partitioned table
|
||||
CREATE TABLE gtest_parent (f1 date NOT NULL, f2 text, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f1) ( PARTITION p1 VALUES LESS THAN ('2016-07-16'));
|
||||
INSERT INTO gtest_parent (f1, f2) VALUES ('2016-07-15', 1);
|
||||
SELECT * FROM gtest_parent;
|
||||
DROP TABLE gtest_parent;
|
||||
|
||||
-- generated columns in partition key (not allowed)
|
||||
CREATE TABLE gtest_parent (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f3) ( PARTITION p1 VALUES LESS THAN ('2016-07-16'));
|
||||
|
||||
-- ALTER TABLE ... ADD COLUMN
|
||||
CREATE TABLE gtest25 (a int PRIMARY KEY);
|
||||
INSERT INTO gtest25 VALUES (3), (4);
|
||||
ALTER TABLE gtest25 ADD COLUMN b int GENERATED ALWAYS AS (a * 3) STORED;
|
||||
SELECT * FROM gtest25 ORDER BY a;
|
||||
ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (b * 4) STORED; -- error
|
||||
ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (z * 4) STORED; -- error
|
||||
|
||||
DROP TABLE gtest25;
|
||||
|
||||
-- ALTER TABLE ... ALTER COLUMN
|
||||
CREATE TABLE gtest27 (
|
||||
a int,
|
||||
b int GENERATED ALWAYS AS (a * 2) STORED
|
||||
);
|
||||
INSERT INTO gtest27 (a) VALUES (3), (4);
|
||||
ALTER TABLE gtest27 ALTER COLUMN a TYPE text; -- error
|
||||
ALTER TABLE gtest27 ALTER COLUMN b TYPE numeric;
|
||||
\d gtest27
|
||||
SELECT * FROM gtest27;
|
||||
ALTER TABLE gtest27 ALTER COLUMN b TYPE boolean USING b <> 0;
|
||||
|
||||
ALTER TABLE gtest27 ALTER COLUMN b DROP DEFAULT; -- error
|
||||
\d gtest27
|
||||
|
||||
DROP TABLE gtest27;
|
||||
|
||||
-- triggers
|
||||
CREATE TABLE gtest26 (
|
||||
a int PRIMARY KEY,
|
||||
b int GENERATED ALWAYS AS (a * 2) STORED
|
||||
);
|
||||
|
||||
CREATE FUNCTION gtest_trigger_func() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
IF tg_op IN ('DELETE', 'UPDATE') THEN
|
||||
RAISE INFO '%: %: old = %', TG_NAME, TG_WHEN, OLD;
|
||||
END IF;
|
||||
IF tg_op IN ('INSERT', 'UPDATE') THEN
|
||||
RAISE INFO '%: %: new = %', TG_NAME, TG_WHEN, NEW;
|
||||
END IF;
|
||||
IF tg_op = 'DELETE' THEN
|
||||
RETURN OLD;
|
||||
ELSE
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER gtest1 BEFORE DELETE OR UPDATE ON gtest26
|
||||
FOR EACH ROW
|
||||
WHEN (OLD.b < 0) -- ok
|
||||
EXECUTE PROCEDURE gtest_trigger_func();
|
||||
|
||||
CREATE TRIGGER gtest2a BEFORE INSERT OR UPDATE ON gtest26
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.b < 0) -- error
|
||||
EXECUTE PROCEDURE gtest_trigger_func();
|
||||
|
||||
CREATE TRIGGER gtest2b BEFORE INSERT OR UPDATE ON gtest26
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.* IS NOT NULL) -- error
|
||||
EXECUTE PROCEDURE gtest_trigger_func();
|
||||
|
||||
CREATE TRIGGER gtest2 BEFORE INSERT ON gtest26
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.a < 0)
|
||||
EXECUTE PROCEDURE gtest_trigger_func();
|
||||
|
||||
CREATE TRIGGER gtest3 AFTER DELETE OR UPDATE ON gtest26
|
||||
FOR EACH ROW
|
||||
WHEN (OLD.b < 0) -- ok
|
||||
EXECUTE PROCEDURE gtest_trigger_func();
|
||||
|
||||
CREATE TRIGGER gtest4 AFTER INSERT OR UPDATE ON gtest26
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.b < 0) -- ok
|
||||
EXECUTE PROCEDURE gtest_trigger_func();
|
||||
|
||||
INSERT INTO gtest26 (a) VALUES (-2), (0), (3);
|
||||
SELECT * FROM gtest26 ORDER BY a;
|
||||
UPDATE gtest26 SET a = a * -2;
|
||||
SELECT * FROM gtest26 ORDER BY a;
|
||||
DELETE FROM gtest26 WHERE a = -6;
|
||||
SELECT * FROM gtest26 ORDER BY a;
|
||||
|
||||
DROP TRIGGER gtest1 ON gtest26;
|
||||
DROP TRIGGER gtest2 ON gtest26;
|
||||
DROP TRIGGER gtest3 ON gtest26;
|
||||
|
||||
-- Check that an UPDATE of "a" fires the trigger for UPDATE OF b, per
|
||||
-- SQL standard.
|
||||
CREATE FUNCTION gtest_trigger_func3() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
RAISE NOTICE 'OK';
|
||||
RETURN NEW;
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER gtest11 BEFORE UPDATE OF b ON gtest26
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE gtest_trigger_func3();
|
||||
|
||||
UPDATE gtest26 SET a = 1 WHERE a = 0;
|
||||
|
||||
DROP TRIGGER gtest11 ON gtest26;
|
||||
TRUNCATE gtest26;
|
||||
|
||||
-- check that modifications of stored generated columns in triggers do
|
||||
-- not get propagated
|
||||
CREATE FUNCTION gtest_trigger_func4() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
NEW.a = 10;
|
||||
NEW.b = 300;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER gtest12_01 BEFORE UPDATE ON gtest26
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE gtest_trigger_func();
|
||||
|
||||
CREATE TRIGGER gtest12_02 BEFORE UPDATE ON gtest26
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE gtest_trigger_func4();
|
||||
|
||||
CREATE TRIGGER gtest12_03 BEFORE UPDATE ON gtest26
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE gtest_trigger_func();
|
||||
|
||||
INSERT INTO gtest26 (a) VALUES (1);
|
||||
UPDATE gtest26 SET a = 11 WHERE a = 1;
|
||||
SELECT * FROM gtest26 ORDER BY a;
|
||||
|
||||
DROP TABLE gtest26;
|
||||
DROP FUNCTION gtest_trigger_func4;
|
||||
DROP FUNCTION gtest_trigger_func3;
|
||||
DROP FUNCTION gtest_trigger_func;
|
||||
|
||||
|
||||
-- LIKE INCLUDING GENERATED and dropped column handling
|
||||
CREATE TABLE gtest28a (
|
||||
a int,
|
||||
b int,
|
||||
c int,
|
||||
x int GENERATED ALWAYS AS (b * 2) STORED
|
||||
);
|
||||
|
||||
ALTER TABLE gtest28a DROP COLUMN a;
|
||||
|
||||
CREATE TABLE gtest28b (LIKE gtest28a INCLUDING GENERATED);
|
||||
|
||||
\d gtest28*
|
||||
|
||||
DROP TABLE gtest28a;
|
||||
DROP TABLE gtest28b;
|
||||
|
||||
CREATE TABLE test_like_gen_1 (a int, b int GENERATED ALWAYS AS (a * 2) STORED);
|
||||
\d test_like_gen_1
|
||||
INSERT INTO test_like_gen_1 (a) VALUES (1);
|
||||
SELECT * FROM test_like_gen_1;
|
||||
CREATE TABLE test_like_gen_2 (LIKE test_like_gen_1);
|
||||
\d test_like_gen_2
|
||||
INSERT INTO test_like_gen_2 (a) VALUES (1);
|
||||
SELECT * FROM test_like_gen_2;
|
||||
CREATE TABLE test_like_gen_3 (LIKE test_like_gen_1 INCLUDING GENERATED);
|
||||
\d test_like_gen_3
|
||||
INSERT INTO test_like_gen_3 (a) VALUES (1);
|
||||
SELECT * FROM test_like_gen_3;
|
||||
DROP TABLE test_like_gen_1, test_like_gen_2, test_like_gen_3;
|
||||
|
||||
-- function
|
||||
CREATE TABLE t1(height_cm int,height_in int GENERATED ALWAYS AS (height_cm * 2) STORED);
|
||||
CREATE OR REPLACE FUNCTION gen_test(a integer) RETURNS int as $$
|
||||
declare
|
||||
b int;
|
||||
begin
|
||||
DELETE t1 where height_cm = a;
|
||||
INSERT INTO t1 values(a);
|
||||
SELECT height_in INTO b FROM t1 where height_cm = a;
|
||||
return b;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
call gen_test(100);
|
||||
drop function gen_test;
|
||||
DROP TABLE t1;
|
||||
|
||||
-- sequence test
|
||||
CREATE SEQUENCE seq1 START WITH 33;
|
||||
-- error
|
||||
CREATE TABLE t1 (id serial,id1 integer GENERATED ALWAYS AS (nextval('seq1')) STORED);
|
||||
DROP SEQUENCE seq1;
|
||||
|
||||
--merage test
|
||||
CREATE TABLE products_base
|
||||
(
|
||||
product_id INTEGER DEFAULT 0,
|
||||
product_name VARCHAR(60) DEFAULT 'null',
|
||||
category VARCHAR(60) DEFAULT 'unknown',
|
||||
total INTEGER DEFAULT '0'
|
||||
);
|
||||
|
||||
INSERT INTO products_base VALUES (1501, 'vivitar 35mm', 'electrncs', 100);
|
||||
INSERT INTO products_base VALUES (1502, 'olympus is50', 'electrncs', 100);
|
||||
INSERT INTO products_base VALUES (1600, 'play gym', 'toys', 100);
|
||||
INSERT INTO products_base VALUES (1601, 'lamaze', 'toys', 100);
|
||||
INSERT INTO products_base VALUES (1666, 'harry potter', 'dvd', 100);
|
||||
|
||||
CREATE TABLE newproducts_base
|
||||
(
|
||||
product_id INTEGER DEFAULT 0,
|
||||
product_name VARCHAR(60) DEFAULT 'null',
|
||||
category VARCHAR(60) DEFAULT 'unknown',
|
||||
total INTEGER DEFAULT '0'
|
||||
);
|
||||
|
||||
INSERT INTO newproducts_base VALUES (1502, 'olympus camera', 'electrncs', 200);
|
||||
INSERT INTO newproducts_base VALUES (1601, 'lamaze', 'toys', 200);
|
||||
INSERT INTO newproducts_base VALUES (1666, 'harry potter', 'toys', 200);
|
||||
INSERT INTO newproducts_base VALUES (1700, 'wait interface', 'books', 200);
|
||||
|
||||
CREATE TABLE products_row
|
||||
(
|
||||
product_id INTEGER DEFAULT 0,
|
||||
product_name VARCHAR(60) DEFAULT 'null',
|
||||
category VARCHAR(60) DEFAULT 'unknown',
|
||||
total INTEGER DEFAULT '0',
|
||||
height_in int GENERATED ALWAYS AS (total * 2) STORED
|
||||
);
|
||||
INSERT INTO products_row SELECT * FROM products_base;
|
||||
|
||||
CREATE TABLE newproducts_row
|
||||
(
|
||||
product_id INTEGER DEFAULT 0,
|
||||
product_name VARCHAR(60) DEFAULT 'null',
|
||||
category VARCHAR(60) DEFAULT 'unknown',
|
||||
total INTEGER DEFAULT '0',
|
||||
height_in int GENERATED ALWAYS AS (total * 3) STORED
|
||||
);
|
||||
INSERT INTO newproducts_row SELECT * FROM newproducts_base;
|
||||
|
||||
MERGE INTO products_row p
|
||||
USING newproducts_row np
|
||||
ON p.product_id = np.product_id
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET product_name = np.product_name, category = np.category, total = np.total
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (np.product_id, np.product_name, np.category, np.total);
|
||||
SELECT * FROM products_row;
|
||||
|
||||
CREATE TABLE hw_create_as_test2(C_INT) as SELECT height_in FROM newproducts_row;
|
||||
SELECT * FROM hw_create_as_test2;
|
||||
DROP TABLE hw_create_as_test2;
|
||||
DROP TABLE products_row;
|
||||
DROP TABLE newproducts_row;
|
||||
DROP TABLE newproducts_base;
|
||||
DROP TABLE products_base;
|
||||
|
||||
--bypass test
|
||||
INSERT INTO gtest1 values (200);
|
||||
SELECT * FROM gtest1;
|
||||
UPDATE gtest1 set a = 300 where a = 200;
|
||||
SELECT * FROM gtest1;
|
||||
|
||||
--null test
|
||||
CREATE TABLE t1 (id int,id1 integer GENERATED ALWAYS AS (id*2) STORED);
|
||||
INSERT INTO t1 values(100);
|
||||
SELECT * FROM t1;
|
||||
UPDATE t1 set id = 200;
|
||||
SELECT * FROM t1;
|
||||
UPDATE t1 set id = NULL;
|
||||
SELECT * FROM t1;
|
||||
DROP TABLE t1;
|
||||
|
||||
--cstore not support
|
||||
CREATE TABLE t2(height_cm int,height_in int GENERATED ALWAYS AS (height_cm * 2) STORED) WITH (ORIENTATION = COLUMN);
|
||||
|
||||
DROP TABLE gtest0;
|
||||
DROP TABLE gtest1;
|
||||
DROP TABLE gtest2;
|
||||
DROP TABLE gtest3;
|
||||
DROP TABLE gtest10;
|
||||
|
||||
Loading…
Reference in New Issue