mirror of
https://github.com/postgres/postgres.git
synced 2025-07-29 00:03:13 -04:00
Compare commits
5 Commits
007693f2a3
...
9391f71523
Author | SHA1 | Date | |
---|---|---|---|
|
9391f71523 | ||
|
14dd0f27d7 | ||
|
5e8674dc83 | ||
|
5d06e99a3c | ||
|
ae69c4fcf1 |
@ -675,12 +675,14 @@ DECLARE
|
||||
<title>Copying Types</title>
|
||||
|
||||
<synopsis>
|
||||
<replaceable>variable</replaceable>%TYPE
|
||||
<replaceable>name</replaceable> <replaceable>table</replaceable>.<replaceable>column</replaceable>%TYPE
|
||||
<replaceable>name</replaceable> <replaceable>variable</replaceable>%TYPE
|
||||
</synopsis>
|
||||
|
||||
<para>
|
||||
<literal>%TYPE</literal> provides the data type of a variable or
|
||||
table column. You can use this to declare variables that will hold
|
||||
<literal>%TYPE</literal> provides the data type of a table column
|
||||
or a previously-declared <application>PL/pgSQL</application>
|
||||
variable. You can use this to declare variables that will hold
|
||||
database values. For example, let's say you have a column named
|
||||
<literal>user_id</literal> in your <literal>users</literal>
|
||||
table. To declare a variable with the same data type as
|
||||
@ -690,6 +692,21 @@ user_id users.user_id%TYPE;
|
||||
</programlisting>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
It is also possible to write array decoration
|
||||
after <literal>%TYPE</literal>, thereby creating a variable that holds
|
||||
an array of the referenced type:
|
||||
<programlisting>
|
||||
user_ids users.user_id%TYPE[];
|
||||
user_ids users.user_id%TYPE ARRAY[4]; -- equivalent to the above
|
||||
</programlisting>
|
||||
Just as when declaring table columns that are arrays, it doesn't
|
||||
matter whether you write multiple bracket pairs or specific array
|
||||
dimensions: <productname>PostgreSQL</productname> treats all arrays of
|
||||
a given element type as the same type, regardless of dimensionality.
|
||||
(See <xref linkend="arrays-declaration"/>.)
|
||||
</para>
|
||||
|
||||
<para>
|
||||
By using <literal>%TYPE</literal> you don't need to know the data
|
||||
type of the structure you are referencing, and most importantly,
|
||||
@ -739,6 +756,12 @@ user_id users.user_id%TYPE;
|
||||
<literal>%ROWTYPE</literal> is more portable.)
|
||||
</para>
|
||||
|
||||
<para>
|
||||
As with <literal>%TYPE</literal>, <literal>%ROWTYPE</literal> can be
|
||||
followed by array decoration to declare a variable that holds an array
|
||||
of the referenced composite type.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Parameters to a function can be
|
||||
composite types (complete table rows). In that case, the
|
||||
|
@ -46,6 +46,7 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
|
||||
ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> SET DEFAULT <replaceable class="parameter">expression</replaceable>
|
||||
ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> DROP DEFAULT
|
||||
ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> { SET | DROP } NOT NULL
|
||||
ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> SET EXPRESSION AS ( <replaceable class="parameter">expression</replaceable> )
|
||||
ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> DROP EXPRESSION [ IF EXISTS ]
|
||||
ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> ADD GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ]
|
||||
ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> { SET GENERATED { ALWAYS | BY DEFAULT } | SET <replaceable>sequence_option</replaceable> | RESTART [ [ WITH ] <replaceable class="parameter">restart</replaceable> ] } [...]
|
||||
@ -256,6 +257,17 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry id="sql-altertable-desc-set-expression">
|
||||
<term><literal>SET EXPRESSION AS</literal></term>
|
||||
<listitem>
|
||||
<para>
|
||||
This form replaces the expression of a generated column. Existing data
|
||||
in the column is rewritten and all the future changes will apply the new
|
||||
generation expression.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry id="sql-altertable-desc-drop-expression">
|
||||
<term><literal>DROP EXPRESSION [ IF EXISTS ]</literal></term>
|
||||
<listitem>
|
||||
|
@ -147,10 +147,11 @@ typedef enum AlterTablePass
|
||||
AT_PASS_UNSET = -1, /* UNSET will cause ERROR */
|
||||
AT_PASS_DROP, /* DROP (all flavors) */
|
||||
AT_PASS_ALTER_TYPE, /* ALTER COLUMN TYPE */
|
||||
AT_PASS_ADD_COL, /* ADD COLUMN */
|
||||
AT_PASS_SET_EXPRESSION, /* ALTER SET EXPRESSION */
|
||||
AT_PASS_OLD_INDEX, /* re-add existing indexes */
|
||||
AT_PASS_OLD_CONSTR, /* re-add existing constraints */
|
||||
/* We could support a RENAME COLUMN pass here, but not currently used */
|
||||
AT_PASS_ADD_COL, /* ADD COLUMN */
|
||||
AT_PASS_ADD_CONSTR, /* ADD constraints (initial examination) */
|
||||
AT_PASS_COL_ATTRS, /* set column attributes, eg NOT NULL */
|
||||
AT_PASS_ADD_INDEXCONSTR, /* ADD index-based constraints */
|
||||
@ -459,6 +460,8 @@ static ObjectAddress ATExecAddIdentity(Relation rel, const char *colName,
|
||||
static ObjectAddress ATExecSetIdentity(Relation rel, const char *colName,
|
||||
Node *def, LOCKMODE lockmode);
|
||||
static ObjectAddress ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode);
|
||||
static ObjectAddress ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName,
|
||||
Node *newExpr, LOCKMODE lockmode);
|
||||
static void ATPrepDropExpression(Relation rel, AlterTableCmd *cmd, bool recurse, bool recursing, LOCKMODE lockmode);
|
||||
static ObjectAddress ATExecDropExpression(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode);
|
||||
static ObjectAddress ATExecSetStatistics(Relation rel, const char *colName, int16 colNum,
|
||||
@ -561,7 +564,7 @@ static void ATPrepAlterColumnType(List **wqueue,
|
||||
static bool ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno);
|
||||
static ObjectAddress ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
|
||||
AlterTableCmd *cmd, LOCKMODE lockmode);
|
||||
static void RememberAllDependentForRebuilding(AlteredTableInfo *tab,
|
||||
static void RememberAllDependentForRebuilding(AlteredTableInfo *tab, AlterTableType subtype,
|
||||
Relation rel, AttrNumber attnum, const char *colName);
|
||||
static void RememberConstraintForRebuilding(Oid conoid, AlteredTableInfo *tab);
|
||||
static void RememberIndexForRebuilding(Oid indoid, AlteredTableInfo *tab);
|
||||
@ -4551,6 +4554,7 @@ AlterTableGetLockLevel(List *cmds)
|
||||
case AT_AddIdentity:
|
||||
case AT_DropIdentity:
|
||||
case AT_SetIdentity:
|
||||
case AT_SetExpression:
|
||||
case AT_DropExpression:
|
||||
case AT_SetCompression:
|
||||
cmd_lockmode = AccessExclusiveLock;
|
||||
@ -4852,6 +4856,11 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
|
||||
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
|
||||
pass = AT_PASS_COL_ATTRS;
|
||||
break;
|
||||
case AT_SetExpression: /* ALTER COLUMN SET EXPRESSION */
|
||||
ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
|
||||
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
|
||||
pass = AT_PASS_SET_EXPRESSION;
|
||||
break;
|
||||
case AT_DropExpression: /* ALTER COLUMN DROP EXPRESSION */
|
||||
ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
|
||||
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
|
||||
@ -5153,11 +5162,11 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
|
||||
lockmode, pass, context);
|
||||
|
||||
/*
|
||||
* After the ALTER TYPE pass, do cleanup work (this is not done in
|
||||
* ATExecAlterColumnType since it should be done only once if
|
||||
* multiple columns of a table are altered).
|
||||
* After the ALTER TYPE or SET EXPRESSION pass, do cleanup work
|
||||
* (this is not done in ATExecAlterColumnType since it should be
|
||||
* done only once if multiple columns of a table are altered).
|
||||
*/
|
||||
if (pass == AT_PASS_ALTER_TYPE)
|
||||
if (pass == AT_PASS_ALTER_TYPE || pass == AT_PASS_SET_EXPRESSION)
|
||||
ATPostAlterTypeCleanup(wqueue, tab, lockmode);
|
||||
|
||||
if (tab->rel)
|
||||
@ -5236,6 +5245,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
|
||||
case AT_SetAttNotNull: /* set pg_attribute.attnotnull */
|
||||
address = ATExecSetAttNotNull(wqueue, rel, cmd->name, lockmode);
|
||||
break;
|
||||
case AT_SetExpression:
|
||||
address = ATExecSetExpression(tab, rel, cmd->name, cmd->def, lockmode);
|
||||
break;
|
||||
case AT_DropExpression:
|
||||
address = ATExecDropExpression(rel, cmd->name, cmd->missing_ok, lockmode);
|
||||
break;
|
||||
@ -6363,6 +6375,8 @@ alter_table_type_to_string(AlterTableType cmdtype)
|
||||
return "ALTER COLUMN ... SET NOT NULL";
|
||||
case AT_SetAttNotNull:
|
||||
return NULL; /* not real grammar */
|
||||
case AT_SetExpression:
|
||||
return "ALTER COLUMN ... SET EXPRESSION";
|
||||
case AT_DropExpression:
|
||||
return "ALTER COLUMN ... DROP EXPRESSION";
|
||||
case AT_SetStatistics:
|
||||
@ -8013,10 +8027,11 @@ ATExecColumnDefault(Relation rel, const char *colName,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("column \"%s\" of relation \"%s\" is a generated column",
|
||||
colName, RelationGetRelationName(rel)),
|
||||
newDefault || TupleDescAttr(tupdesc, attnum - 1)->attgenerated != ATTRIBUTE_GENERATED_STORED ? 0 :
|
||||
newDefault ?
|
||||
/* translator: %s is an SQL ALTER command */
|
||||
errhint("Use %s instead.",
|
||||
"ALTER TABLE ... ALTER COLUMN ... DROP EXPRESSION")));
|
||||
errhint("Use %s instead.", "ALTER TABLE ... ALTER COLUMN ... SET EXPRESSION") :
|
||||
(TupleDescAttr(tupdesc, attnum - 1)->attgenerated == ATTRIBUTE_GENERATED_STORED ?
|
||||
errhint("Use %s instead.", "ALTER TABLE ... ALTER COLUMN ... DROP EXPRESSION") : 0)));
|
||||
|
||||
/*
|
||||
* Remove any old default for the column. We use RESTRICT here for
|
||||
@ -8313,6 +8328,121 @@ ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE
|
||||
return address;
|
||||
}
|
||||
|
||||
/*
|
||||
* ALTER TABLE ALTER COLUMN SET EXPRESSION
|
||||
*
|
||||
* Return the address of the affected column.
|
||||
*/
|
||||
static ObjectAddress
|
||||
ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName,
|
||||
Node *newExpr, LOCKMODE lockmode)
|
||||
{
|
||||
HeapTuple tuple;
|
||||
Form_pg_attribute attTup;
|
||||
AttrNumber attnum;
|
||||
Oid attrdefoid;
|
||||
ObjectAddress address;
|
||||
Expr *defval;
|
||||
NewColumnValue *newval;
|
||||
RawColumnDefault *rawEnt;
|
||||
|
||||
tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
|
||||
if (!HeapTupleIsValid(tuple))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
||||
errmsg("column \"%s\" of relation \"%s\" does not exist",
|
||||
colName, RelationGetRelationName(rel))));
|
||||
|
||||
attTup = (Form_pg_attribute) GETSTRUCT(tuple);
|
||||
attnum = attTup->attnum;
|
||||
|
||||
if (attnum <= 0)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot alter system column \"%s\"",
|
||||
colName)));
|
||||
|
||||
if (attTup->attgenerated != ATTRIBUTE_GENERATED_STORED)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
||||
errmsg("column \"%s\" of relation \"%s\" is not a generated column",
|
||||
colName, RelationGetRelationName(rel))));
|
||||
ReleaseSysCache(tuple);
|
||||
|
||||
/*
|
||||
* Clear all the missing values if we're rewriting the table, since this
|
||||
* renders them pointless.
|
||||
*/
|
||||
RelationClearMissing(rel);
|
||||
|
||||
/* make sure we don't conflict with later attribute modifications */
|
||||
CommandCounterIncrement();
|
||||
|
||||
/*
|
||||
* Find everything that depends on the column (constraints, indexes, etc),
|
||||
* and record enough information to let us recreate the objects after
|
||||
* rewrite.
|
||||
*/
|
||||
RememberAllDependentForRebuilding(tab, AT_SetExpression, rel, attnum, colName);
|
||||
|
||||
/*
|
||||
* Drop the dependency records of the GENERATED expression, in particular
|
||||
* its INTERNAL dependency on the column, which would otherwise cause
|
||||
* dependency.c to refuse to perform the deletion.
|
||||
*/
|
||||
attrdefoid = GetAttrDefaultOid(RelationGetRelid(rel), attnum);
|
||||
if (!OidIsValid(attrdefoid))
|
||||
elog(ERROR, "could not find attrdef tuple for relation %u attnum %d",
|
||||
RelationGetRelid(rel), attnum);
|
||||
(void) deleteDependencyRecordsFor(AttrDefaultRelationId, attrdefoid, false);
|
||||
|
||||
/* Make above changes visible */
|
||||
CommandCounterIncrement();
|
||||
|
||||
/*
|
||||
* Get rid of the GENERATED expression itself. We use RESTRICT here for
|
||||
* safety, but at present we do not expect anything to depend on the
|
||||
* expression.
|
||||
*/
|
||||
RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT,
|
||||
false, false);
|
||||
|
||||
/* Prepare to store the new expression, in the catalogs */
|
||||
rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
|
||||
rawEnt->attnum = attnum;
|
||||
rawEnt->raw_default = newExpr;
|
||||
rawEnt->missingMode = false;
|
||||
rawEnt->generated = ATTRIBUTE_GENERATED_STORED;
|
||||
|
||||
/* Store the generated expression */
|
||||
AddRelationNewConstraints(rel, list_make1(rawEnt), NIL,
|
||||
false, true, false, NULL);
|
||||
|
||||
/* Make above new expression visible */
|
||||
CommandCounterIncrement();
|
||||
|
||||
/* Prepare for table rewrite */
|
||||
defval = (Expr *) build_column_default(rel, attnum);
|
||||
|
||||
newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
|
||||
newval->attnum = attnum;
|
||||
newval->expr = expression_planner(defval);
|
||||
newval->is_generated = true;
|
||||
|
||||
tab->newvals = lappend(tab->newvals, newval);
|
||||
tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
|
||||
|
||||
/* Drop any pg_statistic entry for the column */
|
||||
RemoveStatistics(RelationGetRelid(rel), attnum);
|
||||
|
||||
InvokeObjectPostAlterHook(RelationRelationId,
|
||||
RelationGetRelid(rel), attnum);
|
||||
|
||||
ObjectAddressSubSet(address, RelationRelationId,
|
||||
RelationGetRelid(rel), attnum);
|
||||
return address;
|
||||
}
|
||||
|
||||
/*
|
||||
* ALTER TABLE ALTER COLUMN DROP EXPRESSION
|
||||
*/
|
||||
@ -13300,7 +13430,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
|
||||
* the info before executing ALTER TYPE, though, else the deparser will
|
||||
* get confused.
|
||||
*/
|
||||
RememberAllDependentForRebuilding(tab, rel, attnum, colName);
|
||||
RememberAllDependentForRebuilding(tab, AT_AlterColumnType, rel, attnum, colName);
|
||||
|
||||
/*
|
||||
* Now scan for dependencies of this column on other things. The only
|
||||
@ -13497,18 +13627,21 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
|
||||
}
|
||||
|
||||
/*
|
||||
* Subroutine for ATExecAlterColumnType: Find everything that depends on the
|
||||
* column (constraints, indexes, etc), and record enough information to let us
|
||||
* recreate the objects.
|
||||
* Subroutine for ATExecAlterColumnType and ATExecSetExpression: Find everything
|
||||
* that depends on the column (constraints, indexes, etc), and record enough
|
||||
* information to let us recreate the objects.
|
||||
*/
|
||||
static void
|
||||
RememberAllDependentForRebuilding(AlteredTableInfo *tab, Relation rel, AttrNumber attnum, const char *colName)
|
||||
RememberAllDependentForRebuilding(AlteredTableInfo *tab, AlterTableType subtype,
|
||||
Relation rel, AttrNumber attnum, const char *colName)
|
||||
{
|
||||
Relation depRel;
|
||||
ScanKeyData key[3];
|
||||
SysScanDesc scan;
|
||||
HeapTuple depTup;
|
||||
|
||||
Assert(subtype == AT_AlterColumnType || subtype == AT_SetExpression);
|
||||
|
||||
depRel = table_open(DependRelationId, RowExclusiveLock);
|
||||
|
||||
ScanKeyInit(&key[0],
|
||||
@ -13572,12 +13705,13 @@ RememberAllDependentForRebuilding(AlteredTableInfo *tab, Relation rel, AttrNumbe
|
||||
|
||||
case OCLASS_REWRITE:
|
||||
/* XXX someday see if we can cope with revising views */
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot alter type of a column used by a view or rule"),
|
||||
errdetail("%s depends on column \"%s\"",
|
||||
getObjectDescription(&foundObject, false),
|
||||
colName)));
|
||||
if (subtype == AT_AlterColumnType)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot alter type of a column used by a view or rule"),
|
||||
errdetail("%s depends on column \"%s\"",
|
||||
getObjectDescription(&foundObject, false),
|
||||
colName)));
|
||||
break;
|
||||
|
||||
case OCLASS_TRIGGER:
|
||||
@ -13591,12 +13725,13 @@ RememberAllDependentForRebuilding(AlteredTableInfo *tab, Relation rel, AttrNumbe
|
||||
* significant amount of new code. Since we can't easily tell
|
||||
* which case applies, we punt for both. FIXME someday.
|
||||
*/
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot alter type of a column used in a trigger definition"),
|
||||
errdetail("%s depends on column \"%s\"",
|
||||
getObjectDescription(&foundObject, false),
|
||||
colName)));
|
||||
if (subtype == AT_AlterColumnType)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot alter type of a column used in a trigger definition"),
|
||||
errdetail("%s depends on column \"%s\"",
|
||||
getObjectDescription(&foundObject, false),
|
||||
colName)));
|
||||
break;
|
||||
|
||||
case OCLASS_POLICY:
|
||||
@ -13609,12 +13744,13 @@ RememberAllDependentForRebuilding(AlteredTableInfo *tab, Relation rel, AttrNumbe
|
||||
* easy enough to remove and recreate the policy; still, FIXME
|
||||
* someday.
|
||||
*/
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot alter type of a column used in a policy definition"),
|
||||
errdetail("%s depends on column \"%s\"",
|
||||
getObjectDescription(&foundObject, false),
|
||||
colName)));
|
||||
if (subtype == AT_AlterColumnType)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot alter type of a column used in a policy definition"),
|
||||
errdetail("%s depends on column \"%s\"",
|
||||
getObjectDescription(&foundObject, false),
|
||||
colName)));
|
||||
break;
|
||||
|
||||
case OCLASS_DEFAULT:
|
||||
@ -13634,19 +13770,20 @@ RememberAllDependentForRebuilding(AlteredTableInfo *tab, Relation rel, AttrNumbe
|
||||
/*
|
||||
* This must be a reference from the expression of a
|
||||
* generated column elsewhere in the same table.
|
||||
* Changing the type of a column that is used by a
|
||||
* generated column is not allowed by SQL standard, so
|
||||
* just punt for now. It might be doable with some
|
||||
* thinking and effort.
|
||||
* Changing the type/generated expression of a column
|
||||
* that is used by a generated column is not allowed
|
||||
* by SQL standard, so just punt for now. It might be
|
||||
* doable with some thinking and effort.
|
||||
*/
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot alter type of a column used by a generated column"),
|
||||
errdetail("Column \"%s\" is used by generated column \"%s\".",
|
||||
colName,
|
||||
get_attname(col.objectId,
|
||||
col.objectSubId,
|
||||
false))));
|
||||
if (subtype == AT_AlterColumnType)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot alter type of a column used by a generated column"),
|
||||
errdetail("Column \"%s\" is used by generated column \"%s\".",
|
||||
colName,
|
||||
get_attname(col.objectId,
|
||||
col.objectSubId,
|
||||
false))));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -13863,11 +14000,11 @@ RememberStatisticsForRebuilding(Oid stxoid, AlteredTableInfo *tab)
|
||||
}
|
||||
|
||||
/*
|
||||
* Cleanup after we've finished all the ALTER TYPE operations for a
|
||||
* particular relation. We have to drop and recreate all the indexes
|
||||
* and constraints that depend on the altered columns. We do the
|
||||
* actual dropping here, but re-creation is managed by adding work
|
||||
* queue entries to do those steps later.
|
||||
* Cleanup after we've finished all the ALTER TYPE or SET EXPRESSION
|
||||
* operations for a particular relation. We have to drop and recreate all the
|
||||
* indexes and constraints that depend on the altered columns. We do the
|
||||
* actual dropping here, but re-creation is managed by adding work queue
|
||||
* entries to do those steps later.
|
||||
*/
|
||||
static void
|
||||
ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode)
|
||||
|
@ -216,7 +216,6 @@ ExecInitQual(List *qual, PlanState *parent)
|
||||
ExprState *state;
|
||||
ExprEvalStep scratch = {0};
|
||||
List *adjust_jumps = NIL;
|
||||
ListCell *lc;
|
||||
|
||||
/* short-circuit (here and in ExecQual) for empty restriction list */
|
||||
if (qual == NIL)
|
||||
@ -250,10 +249,8 @@ ExecInitQual(List *qual, PlanState *parent)
|
||||
scratch.resvalue = &state->resvalue;
|
||||
scratch.resnull = &state->resnull;
|
||||
|
||||
foreach(lc, qual)
|
||||
foreach_ptr(Expr, node, qual)
|
||||
{
|
||||
Expr *node = (Expr *) lfirst(lc);
|
||||
|
||||
/* first evaluate expression */
|
||||
ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
|
||||
|
||||
@ -265,9 +262,9 @@ ExecInitQual(List *qual, PlanState *parent)
|
||||
}
|
||||
|
||||
/* adjust jump targets */
|
||||
foreach(lc, adjust_jumps)
|
||||
foreach_int(jump, adjust_jumps)
|
||||
{
|
||||
ExprEvalStep *as = &state->steps[lfirst_int(lc)];
|
||||
ExprEvalStep *as = &state->steps[jump];
|
||||
|
||||
Assert(as->opcode == EEOP_QUAL);
|
||||
Assert(as->d.qualexpr.jumpdone == -1);
|
||||
|
@ -4579,12 +4579,16 @@ ExecEvalPreOrderedDistinctSingle(AggState *aggstate, AggStatePerTrans pertrans)
|
||||
/*
|
||||
* ExecEvalPreOrderedDistinctMulti
|
||||
* Returns true when the aggregate input is distinct from the previous
|
||||
* input and returns false when the input matches the previous input.
|
||||
* input and returns false when the input matches the previous input, or
|
||||
* when there was no previous input.
|
||||
*/
|
||||
bool
|
||||
ExecEvalPreOrderedDistinctMulti(AggState *aggstate, AggStatePerTrans pertrans)
|
||||
{
|
||||
ExprContext *tmpcontext = aggstate->tmpcontext;
|
||||
bool isdistinct = false; /* for now */
|
||||
TupleTableSlot *save_outer;
|
||||
TupleTableSlot *save_inner;
|
||||
|
||||
for (int i = 0; i < pertrans->numTransInputs; i++)
|
||||
{
|
||||
@ -4596,6 +4600,10 @@ ExecEvalPreOrderedDistinctMulti(AggState *aggstate, AggStatePerTrans pertrans)
|
||||
pertrans->sortslot->tts_nvalid = pertrans->numInputs;
|
||||
ExecStoreVirtualTuple(pertrans->sortslot);
|
||||
|
||||
/* save the previous slots before we overwrite them */
|
||||
save_outer = tmpcontext->ecxt_outertuple;
|
||||
save_inner = tmpcontext->ecxt_innertuple;
|
||||
|
||||
tmpcontext->ecxt_outertuple = pertrans->sortslot;
|
||||
tmpcontext->ecxt_innertuple = pertrans->uniqslot;
|
||||
|
||||
@ -4607,9 +4615,15 @@ ExecEvalPreOrderedDistinctMulti(AggState *aggstate, AggStatePerTrans pertrans)
|
||||
|
||||
pertrans->haslast = true;
|
||||
ExecCopySlot(pertrans->uniqslot, pertrans->sortslot);
|
||||
return true;
|
||||
|
||||
isdistinct = true;
|
||||
}
|
||||
return false;
|
||||
|
||||
/* restore the original slots */
|
||||
tmpcontext->ecxt_outertuple = save_outer;
|
||||
tmpcontext->ecxt_innertuple = save_inner;
|
||||
|
||||
return isdistinct;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1256,7 +1256,7 @@ cost_tidscan(Path *path, PlannerInfo *root,
|
||||
QualCost qpqual_cost;
|
||||
Cost cpu_per_tuple;
|
||||
QualCost tid_qual_cost;
|
||||
int ntuples;
|
||||
double ntuples;
|
||||
ListCell *l;
|
||||
double spc_random_page_cost;
|
||||
|
||||
@ -1283,7 +1283,7 @@ cost_tidscan(Path *path, PlannerInfo *root,
|
||||
ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) qual;
|
||||
Node *arraynode = (Node *) lsecond(saop->args);
|
||||
|
||||
ntuples += estimate_array_length(arraynode);
|
||||
ntuples += estimate_array_length(root, arraynode);
|
||||
}
|
||||
else if (IsA(qual, CurrentOfExpr))
|
||||
{
|
||||
@ -4770,7 +4770,7 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
|
||||
Node *arraynode = (Node *) lsecond(saop->args);
|
||||
QualCost sacosts;
|
||||
QualCost hcosts;
|
||||
int estarraylen = estimate_array_length(arraynode);
|
||||
double estarraylen = estimate_array_length(context->root, arraynode);
|
||||
|
||||
set_sa_opfuncid(saop);
|
||||
sacosts.startup = sacosts.per_tuple = 0;
|
||||
@ -4808,7 +4808,7 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
|
||||
*/
|
||||
context->total.startup += sacosts.startup;
|
||||
context->total.per_tuple += sacosts.per_tuple *
|
||||
estimate_array_length(arraynode) * 0.5;
|
||||
estimate_array_length(context->root, arraynode) * 0.5;
|
||||
}
|
||||
}
|
||||
else if (IsA(node, Aggref) ||
|
||||
@ -4859,7 +4859,7 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
|
||||
context->total.startup += perelemcost.startup;
|
||||
if (perelemcost.per_tuple > 0)
|
||||
context->total.per_tuple += perelemcost.per_tuple *
|
||||
estimate_array_length((Node *) acoerce->arg);
|
||||
estimate_array_length(context->root, (Node *) acoerce->arg);
|
||||
}
|
||||
else if (IsA(node, RowCompareExpr))
|
||||
{
|
||||
|
@ -2417,6 +2417,16 @@ alter_table_cmd:
|
||||
n->name = $3;
|
||||
$$ = (Node *) n;
|
||||
}
|
||||
/* ALTER TABLE <name> ALTER [COLUMN] <colname> SET EXPRESSION AS <expr> */
|
||||
| ALTER opt_column ColId SET EXPRESSION AS '(' a_expr ')'
|
||||
{
|
||||
AlterTableCmd *n = makeNode(AlterTableCmd);
|
||||
|
||||
n->subtype = AT_SetExpression;
|
||||
n->name = $3;
|
||||
n->def = $8;
|
||||
$$ = (Node *) n;
|
||||
}
|
||||
/* ALTER TABLE <name> ALTER [COLUMN] <colname> DROP EXPRESSION */
|
||||
| ALTER opt_column ColId DROP EXPRESSION
|
||||
{
|
||||
|
@ -746,11 +746,9 @@ static Oid
|
||||
FindUsableIndexForReplicaIdentityFull(Relation localrel, AttrMap *attrmap)
|
||||
{
|
||||
List *idxlist = RelationGetIndexList(localrel);
|
||||
ListCell *lc;
|
||||
|
||||
foreach(lc, idxlist)
|
||||
foreach_oid(idxoid, idxlist)
|
||||
{
|
||||
Oid idxoid = lfirst_oid(lc);
|
||||
bool isUsableIdx;
|
||||
Relation idxRel;
|
||||
IndexInfo *idxInfo;
|
||||
|
@ -1036,11 +1036,11 @@ fetch_remote_table_info(char *nspname, char *relname,
|
||||
|
||||
/* Build the pubname list. */
|
||||
initStringInfo(&pub_names);
|
||||
foreach(lc, MySubscription->publications)
|
||||
foreach_node(String, pubstr, MySubscription->publications)
|
||||
{
|
||||
char *pubname = strVal(lfirst(lc));
|
||||
char *pubname = strVal(pubstr);
|
||||
|
||||
if (foreach_current_index(lc) > 0)
|
||||
if (foreach_current_index(pubstr) > 0)
|
||||
appendStringInfoString(&pub_names, ", ");
|
||||
|
||||
appendStringInfoString(&pub_names, quote_literal_cstr(pubname));
|
||||
|
@ -2234,7 +2234,6 @@ cleanup_rel_sync_cache(TransactionId xid, bool is_commit)
|
||||
{
|
||||
HASH_SEQ_STATUS hash_seq;
|
||||
RelationSyncEntry *entry;
|
||||
ListCell *lc;
|
||||
|
||||
Assert(RelationSyncCache != NULL);
|
||||
|
||||
@ -2247,15 +2246,15 @@ cleanup_rel_sync_cache(TransactionId xid, bool is_commit)
|
||||
* corresponding schema and we don't need to send it unless there is
|
||||
* any invalidation for that relation.
|
||||
*/
|
||||
foreach(lc, entry->streamed_txns)
|
||||
foreach_xid(streamed_txn, entry->streamed_txns)
|
||||
{
|
||||
if (xid == lfirst_xid(lc))
|
||||
if (xid == streamed_txn)
|
||||
{
|
||||
if (is_commit)
|
||||
entry->schema_sent = true;
|
||||
|
||||
entry->streamed_txns =
|
||||
foreach_delete_current(entry->streamed_txns, lc);
|
||||
foreach_delete_current(entry->streamed_txns, streamed_txn);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -6340,7 +6340,7 @@ array_unnest_support(PG_FUNCTION_ARGS)
|
||||
/* We can use estimated argument values here */
|
||||
arg1 = estimate_expression_value(req->root, linitial(args));
|
||||
|
||||
req->rows = estimate_array_length(arg1);
|
||||
req->rows = estimate_array_length(req->root, arg1);
|
||||
ret = (Node *) req;
|
||||
}
|
||||
}
|
||||
|
@ -2128,10 +2128,11 @@ scalararraysel(PlannerInfo *root,
|
||||
/*
|
||||
* Estimate number of elements in the array yielded by an expression.
|
||||
*
|
||||
* It's important that this agree with scalararraysel.
|
||||
* Note: the result is integral, but we use "double" to avoid overflow
|
||||
* concerns. Most callers will use it in double-type expressions anyway.
|
||||
*/
|
||||
int
|
||||
estimate_array_length(Node *arrayexpr)
|
||||
double
|
||||
estimate_array_length(PlannerInfo *root, Node *arrayexpr)
|
||||
{
|
||||
/* look through any binary-compatible relabeling of arrayexpr */
|
||||
arrayexpr = strip_array_coercion(arrayexpr);
|
||||
@ -2152,11 +2153,39 @@ estimate_array_length(Node *arrayexpr)
|
||||
{
|
||||
return list_length(((ArrayExpr *) arrayexpr)->elements);
|
||||
}
|
||||
else
|
||||
else if (arrayexpr)
|
||||
{
|
||||
/* default guess --- see also scalararraysel */
|
||||
return 10;
|
||||
/* See if we can find any statistics about it */
|
||||
VariableStatData vardata;
|
||||
AttStatsSlot sslot;
|
||||
double nelem = 0;
|
||||
|
||||
examine_variable(root, arrayexpr, 0, &vardata);
|
||||
if (HeapTupleIsValid(vardata.statsTuple))
|
||||
{
|
||||
/*
|
||||
* Found stats, so use the average element count, which is stored
|
||||
* in the last stanumbers element of the DECHIST statistics.
|
||||
* Actually that is the average count of *distinct* elements;
|
||||
* perhaps we should scale it up somewhat?
|
||||
*/
|
||||
if (get_attstatsslot(&sslot, vardata.statsTuple,
|
||||
STATISTIC_KIND_DECHIST, InvalidOid,
|
||||
ATTSTATSSLOT_NUMBERS))
|
||||
{
|
||||
if (sslot.nnumbers > 0)
|
||||
nelem = clamp_row_est(sslot.numbers[sslot.nnumbers - 1]);
|
||||
free_attstatsslot(&sslot);
|
||||
}
|
||||
}
|
||||
ReleaseVariableStats(vardata);
|
||||
|
||||
if (nelem > 0)
|
||||
return nelem;
|
||||
}
|
||||
|
||||
/* Else use a default guess --- this should match scalararraysel */
|
||||
return 10;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -6540,7 +6569,7 @@ genericcostestimate(PlannerInfo *root,
|
||||
if (IsA(rinfo->clause, ScalarArrayOpExpr))
|
||||
{
|
||||
ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) rinfo->clause;
|
||||
int alength = estimate_array_length(lsecond(saop->args));
|
||||
double alength = estimate_array_length(root, lsecond(saop->args));
|
||||
|
||||
if (alength > 1)
|
||||
num_sa_scans *= alength;
|
||||
@ -6820,7 +6849,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
||||
{
|
||||
ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
|
||||
Node *other_operand = (Node *) lsecond(saop->args);
|
||||
int alength = estimate_array_length(other_operand);
|
||||
double alength = estimate_array_length(root, other_operand);
|
||||
|
||||
clause_op = saop->opno;
|
||||
found_saop = true;
|
||||
@ -7414,7 +7443,7 @@ gincost_scalararrayopexpr(PlannerInfo *root,
|
||||
{
|
||||
counts->exactEntries++;
|
||||
counts->searchEntries++;
|
||||
counts->arrayScans *= estimate_array_length(rightop);
|
||||
counts->arrayScans *= estimate_array_length(root, rightop);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -2504,7 +2504,7 @@ psql_completion(const char *text, int start, int end)
|
||||
/* ALTER TABLE ALTER [COLUMN] <foo> SET */
|
||||
else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
|
||||
Matches("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET"))
|
||||
COMPLETE_WITH("(", "COMPRESSION", "DEFAULT", "GENERATED", "NOT NULL", "STATISTICS", "STORAGE",
|
||||
COMPLETE_WITH("(", "COMPRESSION", "DEFAULT", "EXPRESSION", "GENERATED", "NOT NULL", "STATISTICS", "STORAGE",
|
||||
/* a subset of ALTER SEQUENCE options */
|
||||
"INCREMENT", "MINVALUE", "MAXVALUE", "START", "NO", "CACHE", "CYCLE");
|
||||
/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
|
||||
@ -2515,6 +2515,14 @@ psql_completion(const char *text, int start, int end)
|
||||
else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "COMPRESSION") ||
|
||||
Matches("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "COMPRESSION"))
|
||||
COMPLETE_WITH("DEFAULT", "PGLZ", "LZ4");
|
||||
/* ALTER TABLE ALTER [COLUMN] <foo> SET EXPRESSION */
|
||||
else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "EXPRESSION") ||
|
||||
Matches("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "EXPRESSION"))
|
||||
COMPLETE_WITH("AS");
|
||||
/* ALTER TABLE ALTER [COLUMN] <foo> SET EXPRESSION AS */
|
||||
else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "EXPRESSION", "AS") ||
|
||||
Matches("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "EXPRESSION", "AS"))
|
||||
COMPLETE_WITH("(");
|
||||
/* ALTER TABLE ALTER [COLUMN] <foo> SET GENERATED */
|
||||
else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "GENERATED") ||
|
||||
Matches("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "GENERATED"))
|
||||
|
@ -2192,6 +2192,7 @@ typedef enum AlterTableType
|
||||
AT_DropNotNull, /* alter column drop not null */
|
||||
AT_SetNotNull, /* alter column set not null */
|
||||
AT_SetAttNotNull, /* set attnotnull w/o a constraint */
|
||||
AT_SetExpression, /* alter column set expression */
|
||||
AT_DropExpression, /* alter column drop expression */
|
||||
AT_SetStatistics, /* alter column set statistics */
|
||||
AT_SetOptions, /* alter column set ( options ) */
|
||||
|
@ -381,26 +381,26 @@ lnext(const List *l, const ListCell *c)
|
||||
/*
|
||||
* foreach_delete_current -
|
||||
* delete the current list element from the List associated with a
|
||||
* surrounding foreach() loop, returning the new List pointer.
|
||||
* surrounding foreach() or foreach_*() loop, returning the new List
|
||||
* pointer; pass the name of the iterator variable.
|
||||
*
|
||||
* This is equivalent to list_delete_cell(), but it also adjusts the foreach
|
||||
* loop's state so that no list elements will be missed. Do not delete
|
||||
* elements from an active foreach loop's list in any other way!
|
||||
* This is similar to list_delete_cell(), but it also adjusts the loop's state
|
||||
* so that no list elements will be missed. Do not delete elements from an
|
||||
* active foreach or foreach_* loop's list in any other way!
|
||||
*/
|
||||
#define foreach_delete_current(lst, cell) \
|
||||
(cell##__state.i--, \
|
||||
(List *) (cell##__state.l = list_delete_cell(lst, cell)))
|
||||
#define foreach_delete_current(lst, var_or_cell) \
|
||||
((List *) (var_or_cell##__state.l = list_delete_nth_cell(lst, var_or_cell##__state.i--)))
|
||||
|
||||
/*
|
||||
* foreach_current_index -
|
||||
* get the zero-based list index of a surrounding foreach() loop's
|
||||
* current element; pass the name of the "ListCell *" iterator variable.
|
||||
* get the zero-based list index of a surrounding foreach() or foreach_*()
|
||||
* loop's current element; pass the name of the iterator variable.
|
||||
*
|
||||
* Beware of using this after foreach_delete_current(); the value will be
|
||||
* out of sync for the rest of the current loop iteration. Anyway, since
|
||||
* you just deleted the current element, the value is pretty meaningless.
|
||||
*/
|
||||
#define foreach_current_index(cell) (cell##__state.i)
|
||||
#define foreach_current_index(var_or_cell) (var_or_cell##__state.i)
|
||||
|
||||
/*
|
||||
* for_each_from -
|
||||
@ -452,6 +452,57 @@ for_each_cell_setup(const List *lst, const ListCell *initcell)
|
||||
return r;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convenience macros that loop through a list without needing a separate
|
||||
* "ListCell *" variable. Instead, the macros declare a locally-scoped loop
|
||||
* variable with the provided name and the appropriate type.
|
||||
*
|
||||
* Since the variable is scoped to the loop, it's not possible to detect an
|
||||
* early break by checking its value after the loop completes, as is common
|
||||
* practice. If you need to do this, you can either use foreach() instead or
|
||||
* manually track early breaks with a separate variable declared outside of the
|
||||
* loop.
|
||||
*
|
||||
* Note that the caveats described in the comment above the foreach() macro
|
||||
* also apply to these convenience macros.
|
||||
*/
|
||||
#define foreach_ptr(type, var, lst) foreach_internal(type, *, var, lst, lfirst)
|
||||
#define foreach_int(var, lst) foreach_internal(int, , var, lst, lfirst_int)
|
||||
#define foreach_oid(var, lst) foreach_internal(Oid, , var, lst, lfirst_oid)
|
||||
#define foreach_xid(var, lst) foreach_internal(TransactionId, , var, lst, lfirst_xid)
|
||||
|
||||
/*
|
||||
* The internal implementation of the above macros. Do not use directly.
|
||||
*
|
||||
* This macro actually generates two loops in order to declare two variables of
|
||||
* different types. The outer loop only iterates once, so we expect optimizing
|
||||
* compilers will unroll it, thereby optimizing it away.
|
||||
*/
|
||||
#define foreach_internal(type, pointer, var, lst, func) \
|
||||
for (type pointer var = 0, pointer var##__outerloop = (type pointer) 1; \
|
||||
var##__outerloop; \
|
||||
var##__outerloop = 0) \
|
||||
for (ForEachState var##__state = {(lst), 0}; \
|
||||
(var##__state.l != NIL && \
|
||||
var##__state.i < var##__state.l->length && \
|
||||
(var = func(&var##__state.l->elements[var##__state.i]), true)); \
|
||||
var##__state.i++)
|
||||
|
||||
/*
|
||||
* foreach_node -
|
||||
* The same as foreach_ptr, but asserts that the element is of the specified
|
||||
* node type.
|
||||
*/
|
||||
#define foreach_node(type, var, lst) \
|
||||
for (type * var = 0, *var##__outerloop = (type *) 1; \
|
||||
var##__outerloop; \
|
||||
var##__outerloop = 0) \
|
||||
for (ForEachState var##__state = {(lst), 0}; \
|
||||
(var##__state.l != NIL && \
|
||||
var##__state.i < var##__state.l->length && \
|
||||
(var = lfirst_node(type, &var##__state.l->elements[var##__state.i]), true)); \
|
||||
var##__state.i++)
|
||||
|
||||
/*
|
||||
* forboth -
|
||||
* a convenience macro for advancing through two linked lists
|
||||
|
@ -200,7 +200,7 @@ extern Selectivity scalararraysel(PlannerInfo *root,
|
||||
ScalarArrayOpExpr *clause,
|
||||
bool is_join_clause,
|
||||
int varRelid, JoinType jointype, SpecialJoinInfo *sjinfo);
|
||||
extern int estimate_array_length(Node *arrayexpr);
|
||||
extern double estimate_array_length(PlannerInfo *root, Node *arrayexpr);
|
||||
extern Selectivity rowcomparesel(PlannerInfo *root,
|
||||
RowCompareExpr *clause,
|
||||
int varRelid, JoinType jointype, SpecialJoinInfo *sjinfo);
|
||||
|
@ -93,3 +93,82 @@ LINE 1: a.r[1] := 2
|
||||
^
|
||||
QUERY: a.r[1] := 2
|
||||
CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
|
||||
--
|
||||
-- test of %type[] and %rowtype[] syntax
|
||||
--
|
||||
-- check supported syntax
|
||||
do $$
|
||||
declare
|
||||
v int;
|
||||
v1 v%type;
|
||||
v2 v%type[];
|
||||
v3 v%type[1];
|
||||
v4 v%type[][];
|
||||
v5 v%type[1][3];
|
||||
v6 v%type array;
|
||||
v7 v%type array[];
|
||||
v8 v%type array[1];
|
||||
v9 v%type array[1][1];
|
||||
v10 pg_catalog.pg_class%rowtype[];
|
||||
begin
|
||||
raise notice '%', pg_typeof(v1);
|
||||
raise notice '%', pg_typeof(v2);
|
||||
raise notice '%', pg_typeof(v3);
|
||||
raise notice '%', pg_typeof(v4);
|
||||
raise notice '%', pg_typeof(v5);
|
||||
raise notice '%', pg_typeof(v6);
|
||||
raise notice '%', pg_typeof(v7);
|
||||
raise notice '%', pg_typeof(v8);
|
||||
raise notice '%', pg_typeof(v9);
|
||||
raise notice '%', pg_typeof(v10);
|
||||
end;
|
||||
$$;
|
||||
NOTICE: integer
|
||||
NOTICE: integer[]
|
||||
NOTICE: integer[]
|
||||
NOTICE: integer[]
|
||||
NOTICE: integer[]
|
||||
NOTICE: integer[]
|
||||
NOTICE: integer[]
|
||||
NOTICE: integer[]
|
||||
NOTICE: integer[]
|
||||
NOTICE: pg_class[]
|
||||
-- some types don't support arrays
|
||||
do $$
|
||||
declare
|
||||
v pg_node_tree;
|
||||
v1 v%type[];
|
||||
begin
|
||||
end;
|
||||
$$;
|
||||
ERROR: could not find array type for data type pg_node_tree
|
||||
CONTEXT: compilation of PL/pgSQL function "inline_code_block" near line 4
|
||||
-- check functionality
|
||||
do $$
|
||||
declare
|
||||
v1 int;
|
||||
v2 varchar;
|
||||
a1 v1%type[];
|
||||
a2 v2%type[];
|
||||
begin
|
||||
v1 := 10;
|
||||
v2 := 'Hi';
|
||||
a1 := array[v1,v1];
|
||||
a2 := array[v2,v2];
|
||||
raise notice '% %', a1, a2;
|
||||
end;
|
||||
$$;
|
||||
NOTICE: {10,10} {Hi,Hi}
|
||||
create table array_test_table(a int, b varchar);
|
||||
insert into array_test_table values(1, 'first'), (2, 'second');
|
||||
do $$
|
||||
declare tg array_test_table%rowtype[];
|
||||
begin
|
||||
tg := array(select array_test_table from array_test_table);
|
||||
raise notice '%', tg;
|
||||
tg := array(select row(a,b) from array_test_table);
|
||||
raise notice '%', tg;
|
||||
end;
|
||||
$$;
|
||||
NOTICE: {"(1,first)","(2,second)"}
|
||||
NOTICE: {"(1,first)","(2,second)"}
|
||||
|
@ -2208,6 +2208,33 @@ build_datatype(HeapTuple typeTup, int32 typmod,
|
||||
return typ;
|
||||
}
|
||||
|
||||
/*
|
||||
* Build an array type for the element type specified as argument.
|
||||
*/
|
||||
PLpgSQL_type *
|
||||
plpgsql_build_datatype_arrayof(PLpgSQL_type *dtype)
|
||||
{
|
||||
Oid array_typeid;
|
||||
|
||||
/*
|
||||
* If it's already an array type, use it as-is: Postgres doesn't do nested
|
||||
* arrays.
|
||||
*/
|
||||
if (dtype->typisarray)
|
||||
return dtype;
|
||||
|
||||
array_typeid = get_array_type(dtype->typoid);
|
||||
if (!OidIsValid(array_typeid))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
||||
errmsg("could not find array type for data type %s",
|
||||
format_type_be(dtype->typoid))));
|
||||
|
||||
/* Note we inherit typmod and collation, if any, from the element type */
|
||||
return plpgsql_build_datatype(array_typeid, dtype->atttypmod,
|
||||
dtype->collation, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* plpgsql_recognize_err_condition
|
||||
* Check condition name and translate it to SQLSTATE.
|
||||
|
@ -757,8 +757,9 @@ decl_const :
|
||||
decl_datatype :
|
||||
{
|
||||
/*
|
||||
* If there's a lookahead token, read_datatype
|
||||
* should consume it.
|
||||
* If there's a lookahead token, read_datatype() will
|
||||
* consume it, and then we must tell bison to forget
|
||||
* it.
|
||||
*/
|
||||
$$ = read_datatype(yychar);
|
||||
yyclearin;
|
||||
@ -2783,13 +2784,17 @@ read_sql_construct(int until,
|
||||
return expr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read a datatype declaration, consuming the current lookahead token if any.
|
||||
* Returns a PLpgSQL_type struct.
|
||||
*/
|
||||
static PLpgSQL_type *
|
||||
read_datatype(int tok)
|
||||
{
|
||||
StringInfoData ds;
|
||||
char *type_name;
|
||||
int startlocation;
|
||||
PLpgSQL_type *result;
|
||||
PLpgSQL_type *result = NULL;
|
||||
int parenlevel = 0;
|
||||
|
||||
/* Should only be called while parsing DECLARE sections */
|
||||
@ -2799,11 +2804,15 @@ read_datatype(int tok)
|
||||
if (tok == YYEMPTY)
|
||||
tok = yylex();
|
||||
|
||||
/* The current token is the start of what we'll pass to parse_datatype */
|
||||
startlocation = yylloc;
|
||||
|
||||
/*
|
||||
* If we have a simple or composite identifier, check for %TYPE
|
||||
* and %ROWTYPE constructs.
|
||||
* If we have a simple or composite identifier, check for %TYPE and
|
||||
* %ROWTYPE constructs. (Note that if plpgsql_parse_wordtype et al fail
|
||||
* to recognize the identifier, we'll fall through and pass the whole
|
||||
* string to parse_datatype, which will assuredly give an unhelpful
|
||||
* "syntax error". Should we try to give a more specific error?)
|
||||
*/
|
||||
if (tok == T_WORD)
|
||||
{
|
||||
@ -2815,18 +2824,10 @@ read_datatype(int tok)
|
||||
tok = yylex();
|
||||
if (tok_is_keyword(tok, &yylval,
|
||||
K_TYPE, "type"))
|
||||
{
|
||||
result = plpgsql_parse_wordtype(dtname);
|
||||
if (result)
|
||||
return result;
|
||||
}
|
||||
else if (tok_is_keyword(tok, &yylval,
|
||||
K_ROWTYPE, "rowtype"))
|
||||
{
|
||||
result = plpgsql_parse_wordrowtype(dtname);
|
||||
if (result)
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (plpgsql_token_is_unreserved_keyword(tok))
|
||||
@ -2839,18 +2840,10 @@ read_datatype(int tok)
|
||||
tok = yylex();
|
||||
if (tok_is_keyword(tok, &yylval,
|
||||
K_TYPE, "type"))
|
||||
{
|
||||
result = plpgsql_parse_wordtype(dtname);
|
||||
if (result)
|
||||
return result;
|
||||
}
|
||||
else if (tok_is_keyword(tok, &yylval,
|
||||
K_ROWTYPE, "rowtype"))
|
||||
{
|
||||
result = plpgsql_parse_wordrowtype(dtname);
|
||||
if (result)
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (tok == T_CWORD)
|
||||
@ -2863,21 +2856,56 @@ read_datatype(int tok)
|
||||
tok = yylex();
|
||||
if (tok_is_keyword(tok, &yylval,
|
||||
K_TYPE, "type"))
|
||||
{
|
||||
result = plpgsql_parse_cwordtype(dtnames);
|
||||
if (result)
|
||||
return result;
|
||||
}
|
||||
else if (tok_is_keyword(tok, &yylval,
|
||||
K_ROWTYPE, "rowtype"))
|
||||
{
|
||||
result = plpgsql_parse_cwordrowtype(dtnames);
|
||||
if (result)
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If we recognized a %TYPE or %ROWTYPE construct, see if it is followed
|
||||
* by array decoration: [ ARRAY ] [ '[' [ iconst ] ']' [ ... ] ]
|
||||
*
|
||||
* Like the core parser, we ignore the specific numbers and sizes of
|
||||
* dimensions; arrays of different dimensionality are still the same type
|
||||
* in Postgres.
|
||||
*/
|
||||
if (result)
|
||||
{
|
||||
bool is_array = false;
|
||||
|
||||
tok = yylex();
|
||||
if (tok_is_keyword(tok, &yylval,
|
||||
K_ARRAY, "array"))
|
||||
{
|
||||
is_array = true;
|
||||
tok = yylex();
|
||||
}
|
||||
while (tok == '[')
|
||||
{
|
||||
is_array = true;
|
||||
tok = yylex();
|
||||
if (tok == ICONST)
|
||||
tok = yylex();
|
||||
if (tok != ']')
|
||||
yyerror("syntax error, expected \"]\"");
|
||||
tok = yylex();
|
||||
}
|
||||
plpgsql_push_back_token(tok);
|
||||
|
||||
if (is_array)
|
||||
result = plpgsql_build_datatype_arrayof(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Not %TYPE or %ROWTYPE, so scan to the end of the datatype declaration,
|
||||
* which could include typmod or array decoration. We are not very picky
|
||||
* here, instead relying on parse_datatype to complain about garbage. But
|
||||
* we must count parens to handle typmods within cursor_arg correctly.
|
||||
*/
|
||||
while (tok != ';')
|
||||
{
|
||||
if (tok == 0)
|
||||
|
@ -1249,6 +1249,7 @@ extern PLpgSQL_type *plpgsql_parse_cwordrowtype(List *idents);
|
||||
extern PGDLLEXPORT PLpgSQL_type *plpgsql_build_datatype(Oid typeOid, int32 typmod,
|
||||
Oid collation,
|
||||
TypeName *origtypname);
|
||||
extern PLpgSQL_type *plpgsql_build_datatype_arrayof(PLpgSQL_type *dtype);
|
||||
extern PLpgSQL_variable *plpgsql_build_variable(const char *refname, int lineno,
|
||||
PLpgSQL_type *dtype,
|
||||
bool add2namespace);
|
||||
|
@ -77,3 +77,74 @@ begin a[1] := 2; raise notice 'a = %', a; end$$;
|
||||
|
||||
do $$ declare a complex;
|
||||
begin a.r[1] := 2; raise notice 'a = %', a; end$$;
|
||||
|
||||
--
|
||||
-- test of %type[] and %rowtype[] syntax
|
||||
--
|
||||
|
||||
-- check supported syntax
|
||||
do $$
|
||||
declare
|
||||
v int;
|
||||
v1 v%type;
|
||||
v2 v%type[];
|
||||
v3 v%type[1];
|
||||
v4 v%type[][];
|
||||
v5 v%type[1][3];
|
||||
v6 v%type array;
|
||||
v7 v%type array[];
|
||||
v8 v%type array[1];
|
||||
v9 v%type array[1][1];
|
||||
v10 pg_catalog.pg_class%rowtype[];
|
||||
begin
|
||||
raise notice '%', pg_typeof(v1);
|
||||
raise notice '%', pg_typeof(v2);
|
||||
raise notice '%', pg_typeof(v3);
|
||||
raise notice '%', pg_typeof(v4);
|
||||
raise notice '%', pg_typeof(v5);
|
||||
raise notice '%', pg_typeof(v6);
|
||||
raise notice '%', pg_typeof(v7);
|
||||
raise notice '%', pg_typeof(v8);
|
||||
raise notice '%', pg_typeof(v9);
|
||||
raise notice '%', pg_typeof(v10);
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- some types don't support arrays
|
||||
do $$
|
||||
declare
|
||||
v pg_node_tree;
|
||||
v1 v%type[];
|
||||
begin
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- check functionality
|
||||
do $$
|
||||
declare
|
||||
v1 int;
|
||||
v2 varchar;
|
||||
a1 v1%type[];
|
||||
a2 v2%type[];
|
||||
begin
|
||||
v1 := 10;
|
||||
v2 := 'Hi';
|
||||
a1 := array[v1,v1];
|
||||
a2 := array[v2,v2];
|
||||
raise notice '% %', a1, a2;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create table array_test_table(a int, b varchar);
|
||||
|
||||
insert into array_test_table values(1, 'first'), (2, 'second');
|
||||
|
||||
do $$
|
||||
declare tg array_test_table%rowtype[];
|
||||
begin
|
||||
tg := array(select array_test_table from array_test_table);
|
||||
raise notice '%', tg;
|
||||
tg := array(select row(a,b) from array_test_table);
|
||||
raise notice '%', tg;
|
||||
end;
|
||||
$$;
|
||||
|
@ -132,6 +132,9 @@ get_altertable_subcmdinfo(PG_FUNCTION_ARGS)
|
||||
case AT_SetAttNotNull:
|
||||
strtype = "SET ATTNOTNULL";
|
||||
break;
|
||||
case AT_SetExpression:
|
||||
strtype = "SET EXPRESSION";
|
||||
break;
|
||||
case AT_DropExpression:
|
||||
strtype = "DROP EXPRESSION";
|
||||
break;
|
||||
|
@ -1694,6 +1694,19 @@ select aggfns(distinct a,b,c order by a,c using ~<~,b)
|
||||
{"(0,,)","(1,3,foo)","(2,2,bar)","(3,1,baz)"}
|
||||
(1 row)
|
||||
|
||||
-- test a more complex permutation that has previous caused issues
|
||||
select
|
||||
string_agg(distinct 'a', ','),
|
||||
sum((
|
||||
select sum(1)
|
||||
from (values(1)) b(id)
|
||||
where a.id = b.id
|
||||
)) from unnest(array[1]) a(id);
|
||||
string_agg | sum
|
||||
------------+-----
|
||||
a | 1
|
||||
(1 row)
|
||||
|
||||
-- check node I/O via view creation and usage, also deparsing logic
|
||||
create view agg_view1 as
|
||||
select aggfns(a,b,c)
|
||||
|
@ -578,6 +578,9 @@ 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).
|
||||
ALTER TABLE gtest20 ALTER COLUMN b SET EXPRESSION AS (a * 100); -- violates constraint
|
||||
ERROR: check constraint "gtest20_b_check" of relation "gtest20" is violated by some row
|
||||
ALTER TABLE gtest20 ALTER COLUMN b SET EXPRESSION AS (a * 3); -- ok
|
||||
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);
|
||||
@ -673,6 +676,47 @@ SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
|
||||
1 | 2
|
||||
(1 row)
|
||||
|
||||
ALTER TABLE gtest22c ALTER COLUMN b SET EXPRESSION AS (a * 4);
|
||||
ANALYZE gtest22c;
|
||||
EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 8;
|
||||
QUERY PLAN
|
||||
---------------------------------------------
|
||||
Index Scan using gtest22c_b_idx on gtest22c
|
||||
Index Cond: (b = 8)
|
||||
(2 rows)
|
||||
|
||||
SELECT * FROM gtest22c WHERE b = 8;
|
||||
a | b
|
||||
---+---
|
||||
2 | 8
|
||||
(1 row)
|
||||
|
||||
EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 12;
|
||||
QUERY PLAN
|
||||
------------------------------------------------
|
||||
Index Scan using gtest22c_expr_idx on gtest22c
|
||||
Index Cond: ((b * 3) = 12)
|
||||
(2 rows)
|
||||
|
||||
SELECT * FROM gtest22c WHERE b * 3 = 12;
|
||||
a | b
|
||||
---+---
|
||||
1 | 4
|
||||
(1 row)
|
||||
|
||||
EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
|
||||
QUERY PLAN
|
||||
------------------------------------------------
|
||||
Index Scan using gtest22c_pred_idx on gtest22c
|
||||
Index Cond: (a = 1)
|
||||
(2 rows)
|
||||
|
||||
SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
|
||||
a | b
|
||||
---+---
|
||||
1 | 4
|
||||
(1 row)
|
||||
|
||||
RESET enable_seqscan;
|
||||
RESET enable_bitmapscan;
|
||||
-- foreign keys
|
||||
@ -698,6 +742,10 @@ 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".
|
||||
ALTER TABLE gtest23b ALTER COLUMN b SET EXPRESSION AS (a * 5); -- error
|
||||
ERROR: insert or update on table "gtest23b" violates foreign key constraint "gtest23b_b_fkey"
|
||||
DETAIL: Key (b)=(5) is not present in table "gtest23a".
|
||||
ALTER TABLE gtest23b ALTER COLUMN b SET EXPRESSION AS (a * 1); -- ok
|
||||
DROP TABLE gtest23b;
|
||||
DROP TABLE gtest23a;
|
||||
CREATE TABLE gtest23p (x int, y int GENERATED ALWAYS AS (x * 2) STORED, PRIMARY KEY (y));
|
||||
@ -785,30 +833,119 @@ Partition of: gtest_parent FOR VALUES FROM ('08-01-2016') TO ('09-01-2016')
|
||||
Partition of: gtest_parent FOR VALUES FROM ('09-01-2016') TO ('10-01-2016')
|
||||
|
||||
INSERT INTO gtest_parent (f1, f2) VALUES ('2016-07-15', 1);
|
||||
SELECT * FROM gtest_parent;
|
||||
f1 | f2 | f3
|
||||
------------+----+----
|
||||
07-15-2016 | 1 | 2
|
||||
(1 row)
|
||||
INSERT INTO gtest_parent (f1, f2) VALUES ('2016-07-15', 2);
|
||||
INSERT INTO gtest_parent (f1, f2) VALUES ('2016-08-15', 3);
|
||||
SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3;
|
||||
tableoid | f1 | f2 | f3
|
||||
--------------+------------+----+----
|
||||
gtest_child | 07-15-2016 | 1 | 2
|
||||
gtest_child | 07-15-2016 | 2 | 4
|
||||
gtest_child2 | 08-15-2016 | 3 | 66
|
||||
(3 rows)
|
||||
|
||||
SELECT * FROM gtest_child;
|
||||
f1 | f2 | f3
|
||||
------------+----+----
|
||||
07-15-2016 | 1 | 2
|
||||
(1 row)
|
||||
UPDATE gtest_parent SET f1 = f1 + 60 WHERE f2 = 1;
|
||||
SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3;
|
||||
tableoid | f1 | f2 | f3
|
||||
--------------+------------+----+----
|
||||
gtest_child | 07-15-2016 | 2 | 4
|
||||
gtest_child2 | 08-15-2016 | 3 | 66
|
||||
gtest_child3 | 09-13-2016 | 1 | 33
|
||||
(3 rows)
|
||||
|
||||
UPDATE gtest_parent SET f1 = f1 + 60;
|
||||
SELECT * FROM gtest_parent;
|
||||
f1 | f2 | f3
|
||||
------------+----+----
|
||||
09-13-2016 | 1 | 33
|
||||
(1 row)
|
||||
-- alter only parent's and one child's generation expression
|
||||
ALTER TABLE ONLY gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 4);
|
||||
ALTER TABLE gtest_child ALTER COLUMN f3 SET EXPRESSION AS (f2 * 10);
|
||||
\d gtest_parent
|
||||
Partitioned table "public.gtest_parent"
|
||||
Column | Type | Collation | Nullable | Default
|
||||
--------+--------+-----------+----------+-------------------------------------
|
||||
f1 | date | | not null |
|
||||
f2 | bigint | | |
|
||||
f3 | bigint | | | generated always as (f2 * 4) stored
|
||||
Partition key: RANGE (f1)
|
||||
Number of partitions: 3 (Use \d+ to list them.)
|
||||
|
||||
SELECT * FROM gtest_child3;
|
||||
f1 | f2 | f3
|
||||
------------+----+----
|
||||
09-13-2016 | 1 | 33
|
||||
(1 row)
|
||||
\d gtest_child
|
||||
Table "public.gtest_child"
|
||||
Column | Type | Collation | Nullable | Default
|
||||
--------+--------+-----------+----------+--------------------------------------
|
||||
f1 | date | | not null |
|
||||
f2 | bigint | | |
|
||||
f3 | bigint | | | generated always as (f2 * 10) stored
|
||||
Partition of: gtest_parent FOR VALUES FROM ('07-01-2016') TO ('08-01-2016')
|
||||
|
||||
\d gtest_child2
|
||||
Table "public.gtest_child2"
|
||||
Column | Type | Collation | Nullable | Default
|
||||
--------+--------+-----------+----------+--------------------------------------
|
||||
f1 | date | | not null |
|
||||
f2 | bigint | | |
|
||||
f3 | bigint | | | generated always as (f2 * 22) stored
|
||||
Partition of: gtest_parent FOR VALUES FROM ('08-01-2016') TO ('09-01-2016')
|
||||
|
||||
\d gtest_child3
|
||||
Table "public.gtest_child3"
|
||||
Column | Type | Collation | Nullable | Default
|
||||
--------+--------+-----------+----------+--------------------------------------
|
||||
f1 | date | | not null |
|
||||
f2 | bigint | | |
|
||||
f3 | bigint | | | generated always as (f2 * 33) stored
|
||||
Partition of: gtest_parent FOR VALUES FROM ('09-01-2016') TO ('10-01-2016')
|
||||
|
||||
SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3;
|
||||
tableoid | f1 | f2 | f3
|
||||
--------------+------------+----+----
|
||||
gtest_child | 07-15-2016 | 2 | 20
|
||||
gtest_child2 | 08-15-2016 | 3 | 66
|
||||
gtest_child3 | 09-13-2016 | 1 | 33
|
||||
(3 rows)
|
||||
|
||||
-- alter generation expression of parent and all its children altogether
|
||||
ALTER TABLE gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 2);
|
||||
\d gtest_parent
|
||||
Partitioned table "public.gtest_parent"
|
||||
Column | Type | Collation | Nullable | Default
|
||||
--------+--------+-----------+----------+-------------------------------------
|
||||
f1 | date | | not null |
|
||||
f2 | bigint | | |
|
||||
f3 | bigint | | | generated always as (f2 * 2) stored
|
||||
Partition key: RANGE (f1)
|
||||
Number of partitions: 3 (Use \d+ to list them.)
|
||||
|
||||
\d gtest_child
|
||||
Table "public.gtest_child"
|
||||
Column | Type | Collation | Nullable | Default
|
||||
--------+--------+-----------+----------+-------------------------------------
|
||||
f1 | date | | not null |
|
||||
f2 | bigint | | |
|
||||
f3 | bigint | | | generated always as (f2 * 2) stored
|
||||
Partition of: gtest_parent FOR VALUES FROM ('07-01-2016') TO ('08-01-2016')
|
||||
|
||||
\d gtest_child2
|
||||
Table "public.gtest_child2"
|
||||
Column | Type | Collation | Nullable | Default
|
||||
--------+--------+-----------+----------+-------------------------------------
|
||||
f1 | date | | not null |
|
||||
f2 | bigint | | |
|
||||
f3 | bigint | | | generated always as (f2 * 2) stored
|
||||
Partition of: gtest_parent FOR VALUES FROM ('08-01-2016') TO ('09-01-2016')
|
||||
|
||||
\d gtest_child3
|
||||
Table "public.gtest_child3"
|
||||
Column | Type | Collation | Nullable | Default
|
||||
--------+--------+-----------+----------+-------------------------------------
|
||||
f1 | date | | not null |
|
||||
f2 | bigint | | |
|
||||
f3 | bigint | | | generated always as (f2 * 2) stored
|
||||
Partition of: gtest_parent FOR VALUES FROM ('09-01-2016') TO ('10-01-2016')
|
||||
|
||||
SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3;
|
||||
tableoid | f1 | f2 | f3
|
||||
--------------+------------+----+----
|
||||
gtest_child | 07-15-2016 | 2 | 4
|
||||
gtest_child2 | 08-15-2016 | 3 | 6
|
||||
gtest_child3 | 09-13-2016 | 1 | 2
|
||||
(3 rows)
|
||||
|
||||
-- we leave these tables around for purposes of testing dump/reload/upgrade
|
||||
-- generated columns in partition key (not allowed)
|
||||
@ -825,7 +962,7 @@ DETAIL: Column "f3" is a generated column.
|
||||
-- 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;
|
||||
ALTER TABLE gtest25 ADD COLUMN b int GENERATED ALWAYS AS (a * 2) STORED, ALTER COLUMN b SET EXPRESSION AS (a * 3);
|
||||
SELECT * FROM gtest25 ORDER BY a;
|
||||
a | b
|
||||
---+----
|
||||
@ -935,18 +1072,50 @@ CREATE TABLE gtest29 (
|
||||
b int GENERATED ALWAYS AS (a * 2) STORED
|
||||
);
|
||||
INSERT INTO gtest29 (a) VALUES (3), (4);
|
||||
SELECT * FROM gtest29;
|
||||
a | b
|
||||
---+---
|
||||
3 | 6
|
||||
4 | 8
|
||||
(2 rows)
|
||||
|
||||
\d gtest29
|
||||
Table "public.gtest29"
|
||||
Column | Type | Collation | Nullable | Default
|
||||
--------+---------+-----------+----------+------------------------------------
|
||||
a | integer | | |
|
||||
b | integer | | | generated always as (a * 2) stored
|
||||
|
||||
ALTER TABLE gtest29 ALTER COLUMN a SET EXPRESSION AS (a * 3); -- error
|
||||
ERROR: column "a" of relation "gtest29" is not a generated column
|
||||
ALTER TABLE gtest29 ALTER COLUMN a DROP EXPRESSION; -- error
|
||||
ERROR: column "a" of relation "gtest29" is not a stored generated column
|
||||
ALTER TABLE gtest29 ALTER COLUMN a DROP EXPRESSION IF EXISTS; -- notice
|
||||
NOTICE: column "a" of relation "gtest29" is not a stored generated column, skipping
|
||||
-- Change the expression
|
||||
ALTER TABLE gtest29 ALTER COLUMN b SET EXPRESSION AS (a * 3);
|
||||
SELECT * FROM gtest29;
|
||||
a | b
|
||||
---+----
|
||||
3 | 9
|
||||
4 | 12
|
||||
(2 rows)
|
||||
|
||||
\d gtest29
|
||||
Table "public.gtest29"
|
||||
Column | Type | Collation | Nullable | Default
|
||||
--------+---------+-----------+----------+------------------------------------
|
||||
a | integer | | |
|
||||
b | integer | | | generated always as (a * 3) stored
|
||||
|
||||
ALTER TABLE gtest29 ALTER COLUMN b DROP EXPRESSION;
|
||||
INSERT INTO gtest29 (a) VALUES (5);
|
||||
INSERT INTO gtest29 (a, b) VALUES (6, 66);
|
||||
SELECT * FROM gtest29;
|
||||
a | b
|
||||
---+----
|
||||
3 | 6
|
||||
4 | 8
|
||||
3 | 9
|
||||
4 | 12
|
||||
5 |
|
||||
6 | 66
|
||||
(4 rows)
|
||||
|
@ -643,6 +643,15 @@ select aggfns(distinct a,b,c order by a,c using ~<~,b)
|
||||
from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
|
||||
generate_series(1,2) i;
|
||||
|
||||
-- test a more complex permutation that has previous caused issues
|
||||
select
|
||||
string_agg(distinct 'a', ','),
|
||||
sum((
|
||||
select sum(1)
|
||||
from (values(1)) b(id)
|
||||
where a.id = b.id
|
||||
)) from unnest(array[1]) a(id);
|
||||
|
||||
-- check node I/O via view creation and usage, also deparsing logic
|
||||
|
||||
create view agg_view1 as
|
||||
|
@ -293,6 +293,9 @@ CREATE TABLE gtest20 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORE
|
||||
INSERT INTO gtest20 (a) VALUES (10); -- ok
|
||||
INSERT INTO gtest20 (a) VALUES (30); -- violates constraint
|
||||
|
||||
ALTER TABLE gtest20 ALTER COLUMN b SET EXPRESSION AS (a * 100); -- violates constraint
|
||||
ALTER TABLE gtest20 ALTER COLUMN b SET EXPRESSION AS (a * 3); -- ok
|
||||
|
||||
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);
|
||||
@ -341,6 +344,15 @@ 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;
|
||||
|
||||
ALTER TABLE gtest22c ALTER COLUMN b SET EXPRESSION AS (a * 4);
|
||||
ANALYZE gtest22c;
|
||||
EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 8;
|
||||
SELECT * FROM gtest22c WHERE b = 8;
|
||||
EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 12;
|
||||
SELECT * FROM gtest22c WHERE b * 3 = 12;
|
||||
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;
|
||||
|
||||
@ -356,6 +368,8 @@ CREATE TABLE gtest23b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STOR
|
||||
|
||||
INSERT INTO gtest23b VALUES (1); -- ok
|
||||
INSERT INTO gtest23b VALUES (5); -- error
|
||||
ALTER TABLE gtest23b ALTER COLUMN b SET EXPRESSION AS (a * 5); -- error
|
||||
ALTER TABLE gtest23b ALTER COLUMN b SET EXPRESSION AS (a * 1); -- ok
|
||||
|
||||
DROP TABLE gtest23b;
|
||||
DROP TABLE gtest23a;
|
||||
@ -414,11 +428,28 @@ ALTER TABLE gtest_parent ATTACH PARTITION gtest_child3 FOR VALUES FROM ('2016-09
|
||||
\d gtest_child2
|
||||
\d gtest_child3
|
||||
INSERT INTO gtest_parent (f1, f2) VALUES ('2016-07-15', 1);
|
||||
SELECT * FROM gtest_parent;
|
||||
SELECT * FROM gtest_child;
|
||||
UPDATE gtest_parent SET f1 = f1 + 60;
|
||||
SELECT * FROM gtest_parent;
|
||||
SELECT * FROM gtest_child3;
|
||||
INSERT INTO gtest_parent (f1, f2) VALUES ('2016-07-15', 2);
|
||||
INSERT INTO gtest_parent (f1, f2) VALUES ('2016-08-15', 3);
|
||||
SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3;
|
||||
UPDATE gtest_parent SET f1 = f1 + 60 WHERE f2 = 1;
|
||||
SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3;
|
||||
|
||||
-- alter only parent's and one child's generation expression
|
||||
ALTER TABLE ONLY gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 4);
|
||||
ALTER TABLE gtest_child ALTER COLUMN f3 SET EXPRESSION AS (f2 * 10);
|
||||
\d gtest_parent
|
||||
\d gtest_child
|
||||
\d gtest_child2
|
||||
\d gtest_child3
|
||||
SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3;
|
||||
|
||||
-- alter generation expression of parent and all its children altogether
|
||||
ALTER TABLE gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 2);
|
||||
\d gtest_parent
|
||||
\d gtest_child
|
||||
\d gtest_child2
|
||||
\d gtest_child3
|
||||
SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3;
|
||||
-- we leave these tables around for purposes of testing dump/reload/upgrade
|
||||
|
||||
-- generated columns in partition key (not allowed)
|
||||
@ -428,7 +459,7 @@ CREATE TABLE gtest_part_key (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED AL
|
||||
-- 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;
|
||||
ALTER TABLE gtest25 ADD COLUMN b int GENERATED ALWAYS AS (a * 2) STORED, ALTER COLUMN b SET EXPRESSION AS (a * 3);
|
||||
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
|
||||
@ -473,8 +504,17 @@ CREATE TABLE gtest29 (
|
||||
b int GENERATED ALWAYS AS (a * 2) STORED
|
||||
);
|
||||
INSERT INTO gtest29 (a) VALUES (3), (4);
|
||||
SELECT * FROM gtest29;
|
||||
\d gtest29
|
||||
ALTER TABLE gtest29 ALTER COLUMN a SET EXPRESSION AS (a * 3); -- error
|
||||
ALTER TABLE gtest29 ALTER COLUMN a DROP EXPRESSION; -- error
|
||||
ALTER TABLE gtest29 ALTER COLUMN a DROP EXPRESSION IF EXISTS; -- notice
|
||||
|
||||
-- Change the expression
|
||||
ALTER TABLE gtest29 ALTER COLUMN b SET EXPRESSION AS (a * 3);
|
||||
SELECT * FROM gtest29;
|
||||
\d gtest29
|
||||
|
||||
ALTER TABLE gtest29 ALTER COLUMN b DROP EXPRESSION;
|
||||
INSERT INTO gtest29 (a) VALUES (5);
|
||||
INSERT INTO gtest29 (a, b) VALUES (6, 66);
|
||||
|
Loading…
x
Reference in New Issue
Block a user