Compare commits

...

5 Commits

Author SHA1 Message Date
Tom Lane
9391f71523 Teach estimate_array_length() to use statistics where available.
If we have DECHIST statistics about the argument expression, use
the average number of distinct elements as the array length estimate.
(It'd be better to use the average total number of elements, but
that is not currently calculated by compute_array_stats(), and
it's unclear that it'd be worth extra effort to get.)

To do this, we have to change the signature of estimate_array_length
to pass the "root" pointer.  While at it, also change its result
type to "double".  That's probably not really necessary, but it
avoids any risk of overflow of the value extracted from DECHIST.
All existing callers are going to use the result in a "double"
calculation anyway.

Paul Jungwirth, reviewed by Jian He and myself

Discussion: https://postgr.es/m/CA+renyUnM2d+SmrxKpDuAdpiq6FOM=FByvi6aS6yi__qyf6j9A@mail.gmail.com
2024-01-04 18:36:19 -05:00
Nathan Bossart
14dd0f27d7 Add macros for looping through a List without a ListCell.
Many foreach loops only use the ListCell pointer to retrieve the
content of the cell, like so:

    ListCell   *lc;

    foreach(lc, mylist)
    {
        int         myint = lfirst_int(lc);

        ...
    }

This commit adds a few convenience macros that automatically
declare the loop variable and retrieve the current cell's contents.
This allows us to rewrite the previous loop like this:

    foreach_int(myint, mylist)
    {
        ...
    }

This commit also adjusts a few existing loops in order to add
coverage for the new/adjusted macros.  There is presently no plan
to bulk update all foreach loops, as that could introduce a
significant amount of back-patching pain.  Instead, these macros
are primarily intended for use in new code.

Author: Jelte Fennema-Nio
Reviewed-by: David Rowley, Alvaro Herrera, Vignesh C, Tom Lane
Discussion: https://postgr.es/m/CAGECzQSwXKnxGwW1_Q5JE%2B8Ja20kyAbhBHO04vVrQsLcDciwXA%40mail.gmail.com
2024-01-04 16:09:34 -06:00
Tom Lane
5e8674dc83 In plpgsql, allow %TYPE and %ROWTYPE to be followed by array decoration.
This provides the useful ability to declare a variable that is an array
of the type of some other variable or some table column.

Quan Zongliang, Pavel Stehule

Discussion: https://postgr.es/m/ec4523e1-9e7e-f3ef-f9ce-bafd680ad6f6@yeah.net
2024-01-04 15:24:15 -05:00
Peter Eisentraut
5d06e99a3c ALTER TABLE command to change generation expression
This adds a new ALTER TABLE subcommand ALTER COLUMN ... SET EXPRESSION
that changes the generation expression of a generated column.

The syntax is not standard but was adapted from other SQL
implementations.

This command causes a table rewrite, using the usual ALTER TABLE
mechanisms.  The implementation is similar to and makes use of some of
the infrastructure of the SET DATA TYPE subcommand (for example,
rebuilding constraints and indexes afterwards).  The new command
requires a new pass in AlterTablePass, and the ADD COLUMN pass had to
be moved earlier so that combinations of ADD COLUMN and SET EXPRESSION
can work.

Author: Amul Sul <sulamul@gmail.com>
Discussion: https://www.postgresql.org/message-id/flat/CAAJ_b94yyJeGA-5M951_Lr+KfZokOp-2kXicpmEhi5FXhBeTog@mail.gmail.com
2024-01-04 16:28:54 +01:00
David Rowley
ae69c4fcf1 Fix use of incorrect TupleTableSlot in DISTINCT aggregates
1349d2790 added code to allow DISTINCT and ORDER BY aggregates to work
more efficiently by using presorted input.  That commit added some code
that made use of the AggState's tmpcontext and adjusted the
ecxt_outertuple and ecxt_innertuple slots before checking if the current
row is distinct from the previously seen row.  That code forgot to set the
TupleTableSlots back to what they were originally, which could result in
errors such as:

ERROR:  attribute 1 of type record has wrong type

This only affects aggregate functions which have multiple arguments when
DISTINCT is used.  For example: string_agg(DISTINCT col, ', ')

Thanks to Tom Lane for identifying the breaking commit.

Bug: #18264
Reported-by: Vojtěch Beneš
Discussion: https://postgr.es/m/18264-e363593d7e9feb7d@postgresql.org
Backpatch-through: 16, where 1349d2790 was added
2024-01-04 20:38:25 +13:00
26 changed files with 876 additions and 157 deletions

View File

@ -675,12 +675,14 @@ DECLARE
<title>Copying Types</title> <title>Copying Types</title>
<synopsis> <synopsis>
<replaceable>variable</replaceable>%TYPE <replaceable>name</replaceable> <replaceable>table</replaceable>.<replaceable>column</replaceable>%TYPE
<replaceable>name</replaceable> <replaceable>variable</replaceable>%TYPE
</synopsis> </synopsis>
<para> <para>
<literal>%TYPE</literal> provides the data type of a variable or <literal>%TYPE</literal> provides the data type of a table column
table column. You can use this to declare variables that will hold 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 database values. For example, let's say you have a column named
<literal>user_id</literal> in your <literal>users</literal> <literal>user_id</literal> in your <literal>users</literal>
table. To declare a variable with the same data type as table. To declare a variable with the same data type as
@ -690,6 +692,21 @@ user_id users.user_id%TYPE;
</programlisting> </programlisting>
</para> </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> <para>
By using <literal>%TYPE</literal> you don't need to know the data By using <literal>%TYPE</literal> you don't need to know the data
type of the structure you are referencing, and most importantly, 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.) <literal>%ROWTYPE</literal> is more portable.)
</para> </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> <para>
Parameters to a function can be Parameters to a function can be
composite types (complete table rows). In that case, the composite types (complete table rows). In that case, the

View File

@ -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> 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> DROP DEFAULT
ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> { SET | DROP } NOT NULL 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> 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> 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> ] } [...] 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> </listitem>
</varlistentry> </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"> <varlistentry id="sql-altertable-desc-drop-expression">
<term><literal>DROP EXPRESSION [ IF EXISTS ]</literal></term> <term><literal>DROP EXPRESSION [ IF EXISTS ]</literal></term>
<listitem> <listitem>

View File

@ -147,10 +147,11 @@ typedef enum AlterTablePass
AT_PASS_UNSET = -1, /* UNSET will cause ERROR */ AT_PASS_UNSET = -1, /* UNSET will cause ERROR */
AT_PASS_DROP, /* DROP (all flavors) */ AT_PASS_DROP, /* DROP (all flavors) */
AT_PASS_ALTER_TYPE, /* ALTER COLUMN TYPE */ 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_INDEX, /* re-add existing indexes */
AT_PASS_OLD_CONSTR, /* re-add existing constraints */ AT_PASS_OLD_CONSTR, /* re-add existing constraints */
/* We could support a RENAME COLUMN pass here, but not currently used */ /* 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_ADD_CONSTR, /* ADD constraints (initial examination) */
AT_PASS_COL_ATTRS, /* set column attributes, eg NOT NULL */ AT_PASS_COL_ATTRS, /* set column attributes, eg NOT NULL */
AT_PASS_ADD_INDEXCONSTR, /* ADD index-based constraints */ 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, static ObjectAddress ATExecSetIdentity(Relation rel, const char *colName,
Node *def, LOCKMODE lockmode); Node *def, LOCKMODE lockmode);
static ObjectAddress ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, 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 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 ATExecDropExpression(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode);
static ObjectAddress ATExecSetStatistics(Relation rel, const char *colName, int16 colNum, 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 bool ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno);
static ObjectAddress ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, static ObjectAddress ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
AlterTableCmd *cmd, LOCKMODE lockmode); AlterTableCmd *cmd, LOCKMODE lockmode);
static void RememberAllDependentForRebuilding(AlteredTableInfo *tab, static void RememberAllDependentForRebuilding(AlteredTableInfo *tab, AlterTableType subtype,
Relation rel, AttrNumber attnum, const char *colName); Relation rel, AttrNumber attnum, const char *colName);
static void RememberConstraintForRebuilding(Oid conoid, AlteredTableInfo *tab); static void RememberConstraintForRebuilding(Oid conoid, AlteredTableInfo *tab);
static void RememberIndexForRebuilding(Oid indoid, AlteredTableInfo *tab); static void RememberIndexForRebuilding(Oid indoid, AlteredTableInfo *tab);
@ -4551,6 +4554,7 @@ AlterTableGetLockLevel(List *cmds)
case AT_AddIdentity: case AT_AddIdentity:
case AT_DropIdentity: case AT_DropIdentity:
case AT_SetIdentity: case AT_SetIdentity:
case AT_SetExpression:
case AT_DropExpression: case AT_DropExpression:
case AT_SetCompression: case AT_SetCompression:
cmd_lockmode = AccessExclusiveLock; cmd_lockmode = AccessExclusiveLock;
@ -4852,6 +4856,11 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context); ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
pass = AT_PASS_COL_ATTRS; pass = AT_PASS_COL_ATTRS;
break; 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 */ case AT_DropExpression: /* ALTER COLUMN DROP EXPRESSION */
ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE); ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context); ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
@ -5153,11 +5162,11 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
lockmode, pass, context); lockmode, pass, context);
/* /*
* After the ALTER TYPE pass, do cleanup work (this is not done in * After the ALTER TYPE or SET EXPRESSION pass, do cleanup work
* ATExecAlterColumnType since it should be done only once if * (this is not done in ATExecAlterColumnType since it should be
* multiple columns of a table are altered). * 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); ATPostAlterTypeCleanup(wqueue, tab, lockmode);
if (tab->rel) if (tab->rel)
@ -5236,6 +5245,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
case AT_SetAttNotNull: /* set pg_attribute.attnotnull */ case AT_SetAttNotNull: /* set pg_attribute.attnotnull */
address = ATExecSetAttNotNull(wqueue, rel, cmd->name, lockmode); address = ATExecSetAttNotNull(wqueue, rel, cmd->name, lockmode);
break; break;
case AT_SetExpression:
address = ATExecSetExpression(tab, rel, cmd->name, cmd->def, lockmode);
break;
case AT_DropExpression: case AT_DropExpression:
address = ATExecDropExpression(rel, cmd->name, cmd->missing_ok, lockmode); address = ATExecDropExpression(rel, cmd->name, cmd->missing_ok, lockmode);
break; break;
@ -6363,6 +6375,8 @@ alter_table_type_to_string(AlterTableType cmdtype)
return "ALTER COLUMN ... SET NOT NULL"; return "ALTER COLUMN ... SET NOT NULL";
case AT_SetAttNotNull: case AT_SetAttNotNull:
return NULL; /* not real grammar */ return NULL; /* not real grammar */
case AT_SetExpression:
return "ALTER COLUMN ... SET EXPRESSION";
case AT_DropExpression: case AT_DropExpression:
return "ALTER COLUMN ... DROP EXPRESSION"; return "ALTER COLUMN ... DROP EXPRESSION";
case AT_SetStatistics: case AT_SetStatistics:
@ -8013,10 +8027,11 @@ ATExecColumnDefault(Relation rel, const char *colName,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("column \"%s\" of relation \"%s\" is a generated column", errmsg("column \"%s\" of relation \"%s\" is a generated column",
colName, RelationGetRelationName(rel)), colName, RelationGetRelationName(rel)),
newDefault || TupleDescAttr(tupdesc, attnum - 1)->attgenerated != ATTRIBUTE_GENERATED_STORED ? 0 : newDefault ?
/* translator: %s is an SQL ALTER command */ /* translator: %s is an SQL ALTER command */
errhint("Use %s instead.", errhint("Use %s instead.", "ALTER TABLE ... ALTER COLUMN ... SET EXPRESSION") :
"ALTER TABLE ... ALTER COLUMN ... DROP 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 * 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; 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 * 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 * the info before executing ALTER TYPE, though, else the deparser will
* get confused. * 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 * 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 * Subroutine for ATExecAlterColumnType and ATExecSetExpression: Find everything
* column (constraints, indexes, etc), and record enough information to let us * that depends on the column (constraints, indexes, etc), and record enough
* recreate the objects. * information to let us recreate the objects.
*/ */
static void 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; Relation depRel;
ScanKeyData key[3]; ScanKeyData key[3];
SysScanDesc scan; SysScanDesc scan;
HeapTuple depTup; HeapTuple depTup;
Assert(subtype == AT_AlterColumnType || subtype == AT_SetExpression);
depRel = table_open(DependRelationId, RowExclusiveLock); depRel = table_open(DependRelationId, RowExclusiveLock);
ScanKeyInit(&key[0], ScanKeyInit(&key[0],
@ -13572,12 +13705,13 @@ RememberAllDependentForRebuilding(AlteredTableInfo *tab, Relation rel, AttrNumbe
case OCLASS_REWRITE: case OCLASS_REWRITE:
/* XXX someday see if we can cope with revising views */ /* XXX someday see if we can cope with revising views */
ereport(ERROR, if (subtype == AT_AlterColumnType)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ereport(ERROR,
errmsg("cannot alter type of a column used by a view or rule"), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errdetail("%s depends on column \"%s\"", errmsg("cannot alter type of a column used by a view or rule"),
getObjectDescription(&foundObject, false), errdetail("%s depends on column \"%s\"",
colName))); getObjectDescription(&foundObject, false),
colName)));
break; break;
case OCLASS_TRIGGER: case OCLASS_TRIGGER:
@ -13591,12 +13725,13 @@ RememberAllDependentForRebuilding(AlteredTableInfo *tab, Relation rel, AttrNumbe
* significant amount of new code. Since we can't easily tell * significant amount of new code. Since we can't easily tell
* which case applies, we punt for both. FIXME someday. * which case applies, we punt for both. FIXME someday.
*/ */
ereport(ERROR, if (subtype == AT_AlterColumnType)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ereport(ERROR,
errmsg("cannot alter type of a column used in a trigger definition"), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errdetail("%s depends on column \"%s\"", errmsg("cannot alter type of a column used in a trigger definition"),
getObjectDescription(&foundObject, false), errdetail("%s depends on column \"%s\"",
colName))); getObjectDescription(&foundObject, false),
colName)));
break; break;
case OCLASS_POLICY: case OCLASS_POLICY:
@ -13609,12 +13744,13 @@ RememberAllDependentForRebuilding(AlteredTableInfo *tab, Relation rel, AttrNumbe
* easy enough to remove and recreate the policy; still, FIXME * easy enough to remove and recreate the policy; still, FIXME
* someday. * someday.
*/ */
ereport(ERROR, if (subtype == AT_AlterColumnType)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ereport(ERROR,
errmsg("cannot alter type of a column used in a policy definition"), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errdetail("%s depends on column \"%s\"", errmsg("cannot alter type of a column used in a policy definition"),
getObjectDescription(&foundObject, false), errdetail("%s depends on column \"%s\"",
colName))); getObjectDescription(&foundObject, false),
colName)));
break; break;
case OCLASS_DEFAULT: case OCLASS_DEFAULT:
@ -13634,19 +13770,20 @@ RememberAllDependentForRebuilding(AlteredTableInfo *tab, Relation rel, AttrNumbe
/* /*
* This must be a reference from the expression of a * This must be a reference from the expression of a
* generated column elsewhere in the same table. * generated column elsewhere in the same table.
* Changing the type of a column that is used by a * Changing the type/generated expression of a column
* generated column is not allowed by SQL standard, so * that is used by a generated column is not allowed
* just punt for now. It might be doable with some * by SQL standard, so just punt for now. It might be
* thinking and effort. * doable with some thinking and effort.
*/ */
ereport(ERROR, if (subtype == AT_AlterColumnType)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ereport(ERROR,
errmsg("cannot alter type of a column used by a generated column"), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errdetail("Column \"%s\" is used by generated column \"%s\".", errmsg("cannot alter type of a column used by a generated column"),
colName, errdetail("Column \"%s\" is used by generated column \"%s\".",
get_attname(col.objectId, colName,
col.objectSubId, get_attname(col.objectId,
false)))); col.objectSubId,
false))));
} }
break; break;
} }
@ -13863,11 +14000,11 @@ RememberStatisticsForRebuilding(Oid stxoid, AlteredTableInfo *tab)
} }
/* /*
* Cleanup after we've finished all the ALTER TYPE operations for a * Cleanup after we've finished all the ALTER TYPE or SET EXPRESSION
* particular relation. We have to drop and recreate all the indexes * operations for a particular relation. We have to drop and recreate all the
* and constraints that depend on the altered columns. We do the * indexes and constraints that depend on the altered columns. We do the
* actual dropping here, but re-creation is managed by adding work * actual dropping here, but re-creation is managed by adding work queue
* queue entries to do those steps later. * entries to do those steps later.
*/ */
static void static void
ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode) ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode)

View File

@ -216,7 +216,6 @@ ExecInitQual(List *qual, PlanState *parent)
ExprState *state; ExprState *state;
ExprEvalStep scratch = {0}; ExprEvalStep scratch = {0};
List *adjust_jumps = NIL; List *adjust_jumps = NIL;
ListCell *lc;
/* short-circuit (here and in ExecQual) for empty restriction list */ /* short-circuit (here and in ExecQual) for empty restriction list */
if (qual == NIL) if (qual == NIL)
@ -250,10 +249,8 @@ ExecInitQual(List *qual, PlanState *parent)
scratch.resvalue = &state->resvalue; scratch.resvalue = &state->resvalue;
scratch.resnull = &state->resnull; scratch.resnull = &state->resnull;
foreach(lc, qual) foreach_ptr(Expr, node, qual)
{ {
Expr *node = (Expr *) lfirst(lc);
/* first evaluate expression */ /* first evaluate expression */
ExecInitExprRec(node, state, &state->resvalue, &state->resnull); ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
@ -265,9 +262,9 @@ ExecInitQual(List *qual, PlanState *parent)
} }
/* adjust jump targets */ /* 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->opcode == EEOP_QUAL);
Assert(as->d.qualexpr.jumpdone == -1); Assert(as->d.qualexpr.jumpdone == -1);

View File

@ -4579,12 +4579,16 @@ ExecEvalPreOrderedDistinctSingle(AggState *aggstate, AggStatePerTrans pertrans)
/* /*
* ExecEvalPreOrderedDistinctMulti * ExecEvalPreOrderedDistinctMulti
* Returns true when the aggregate input is distinct from the previous * 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 bool
ExecEvalPreOrderedDistinctMulti(AggState *aggstate, AggStatePerTrans pertrans) ExecEvalPreOrderedDistinctMulti(AggState *aggstate, AggStatePerTrans pertrans)
{ {
ExprContext *tmpcontext = aggstate->tmpcontext; ExprContext *tmpcontext = aggstate->tmpcontext;
bool isdistinct = false; /* for now */
TupleTableSlot *save_outer;
TupleTableSlot *save_inner;
for (int i = 0; i < pertrans->numTransInputs; i++) for (int i = 0; i < pertrans->numTransInputs; i++)
{ {
@ -4596,6 +4600,10 @@ ExecEvalPreOrderedDistinctMulti(AggState *aggstate, AggStatePerTrans pertrans)
pertrans->sortslot->tts_nvalid = pertrans->numInputs; pertrans->sortslot->tts_nvalid = pertrans->numInputs;
ExecStoreVirtualTuple(pertrans->sortslot); 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_outertuple = pertrans->sortslot;
tmpcontext->ecxt_innertuple = pertrans->uniqslot; tmpcontext->ecxt_innertuple = pertrans->uniqslot;
@ -4607,9 +4615,15 @@ ExecEvalPreOrderedDistinctMulti(AggState *aggstate, AggStatePerTrans pertrans)
pertrans->haslast = true; pertrans->haslast = true;
ExecCopySlot(pertrans->uniqslot, pertrans->sortslot); 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;
} }
/* /*

View File

@ -1256,7 +1256,7 @@ cost_tidscan(Path *path, PlannerInfo *root,
QualCost qpqual_cost; QualCost qpqual_cost;
Cost cpu_per_tuple; Cost cpu_per_tuple;
QualCost tid_qual_cost; QualCost tid_qual_cost;
int ntuples; double ntuples;
ListCell *l; ListCell *l;
double spc_random_page_cost; double spc_random_page_cost;
@ -1283,7 +1283,7 @@ cost_tidscan(Path *path, PlannerInfo *root,
ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) qual; ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) qual;
Node *arraynode = (Node *) lsecond(saop->args); Node *arraynode = (Node *) lsecond(saop->args);
ntuples += estimate_array_length(arraynode); ntuples += estimate_array_length(root, arraynode);
} }
else if (IsA(qual, CurrentOfExpr)) 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); Node *arraynode = (Node *) lsecond(saop->args);
QualCost sacosts; QualCost sacosts;
QualCost hcosts; QualCost hcosts;
int estarraylen = estimate_array_length(arraynode); double estarraylen = estimate_array_length(context->root, arraynode);
set_sa_opfuncid(saop); set_sa_opfuncid(saop);
sacosts.startup = sacosts.per_tuple = 0; 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.startup += sacosts.startup;
context->total.per_tuple += sacosts.per_tuple * 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) || 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; context->total.startup += perelemcost.startup;
if (perelemcost.per_tuple > 0) if (perelemcost.per_tuple > 0)
context->total.per_tuple += perelemcost.per_tuple * 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)) else if (IsA(node, RowCompareExpr))
{ {

View File

@ -2417,6 +2417,16 @@ alter_table_cmd:
n->name = $3; n->name = $3;
$$ = (Node *) n; $$ = (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 TABLE <name> ALTER [COLUMN] <colname> DROP EXPRESSION */
| ALTER opt_column ColId DROP EXPRESSION | ALTER opt_column ColId DROP EXPRESSION
{ {

View File

@ -746,11 +746,9 @@ static Oid
FindUsableIndexForReplicaIdentityFull(Relation localrel, AttrMap *attrmap) FindUsableIndexForReplicaIdentityFull(Relation localrel, AttrMap *attrmap)
{ {
List *idxlist = RelationGetIndexList(localrel); List *idxlist = RelationGetIndexList(localrel);
ListCell *lc;
foreach(lc, idxlist) foreach_oid(idxoid, idxlist)
{ {
Oid idxoid = lfirst_oid(lc);
bool isUsableIdx; bool isUsableIdx;
Relation idxRel; Relation idxRel;
IndexInfo *idxInfo; IndexInfo *idxInfo;

View File

@ -1036,11 +1036,11 @@ fetch_remote_table_info(char *nspname, char *relname,
/* Build the pubname list. */ /* Build the pubname list. */
initStringInfo(&pub_names); 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, ", ");
appendStringInfoString(&pub_names, quote_literal_cstr(pubname)); appendStringInfoString(&pub_names, quote_literal_cstr(pubname));

View File

@ -2234,7 +2234,6 @@ cleanup_rel_sync_cache(TransactionId xid, bool is_commit)
{ {
HASH_SEQ_STATUS hash_seq; HASH_SEQ_STATUS hash_seq;
RelationSyncEntry *entry; RelationSyncEntry *entry;
ListCell *lc;
Assert(RelationSyncCache != NULL); 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 * corresponding schema and we don't need to send it unless there is
* any invalidation for that relation. * 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) if (is_commit)
entry->schema_sent = true; entry->schema_sent = true;
entry->streamed_txns = entry->streamed_txns =
foreach_delete_current(entry->streamed_txns, lc); foreach_delete_current(entry->streamed_txns, streamed_txn);
break; break;
} }
} }

View File

@ -6340,7 +6340,7 @@ array_unnest_support(PG_FUNCTION_ARGS)
/* We can use estimated argument values here */ /* We can use estimated argument values here */
arg1 = estimate_expression_value(req->root, linitial(args)); 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; ret = (Node *) req;
} }
} }

View File

@ -2128,10 +2128,11 @@ scalararraysel(PlannerInfo *root,
/* /*
* Estimate number of elements in the array yielded by an expression. * 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 double
estimate_array_length(Node *arrayexpr) estimate_array_length(PlannerInfo *root, Node *arrayexpr)
{ {
/* look through any binary-compatible relabeling of arrayexpr */ /* look through any binary-compatible relabeling of arrayexpr */
arrayexpr = strip_array_coercion(arrayexpr); arrayexpr = strip_array_coercion(arrayexpr);
@ -2152,11 +2153,39 @@ estimate_array_length(Node *arrayexpr)
{ {
return list_length(((ArrayExpr *) arrayexpr)->elements); return list_length(((ArrayExpr *) arrayexpr)->elements);
} }
else else if (arrayexpr)
{ {
/* default guess --- see also scalararraysel */ /* See if we can find any statistics about it */
return 10; 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)) if (IsA(rinfo->clause, ScalarArrayOpExpr))
{ {
ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) rinfo->clause; 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) if (alength > 1)
num_sa_scans *= alength; num_sa_scans *= alength;
@ -6820,7 +6849,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
{ {
ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause; ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
Node *other_operand = (Node *) lsecond(saop->args); 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; clause_op = saop->opno;
found_saop = true; found_saop = true;
@ -7414,7 +7443,7 @@ gincost_scalararrayopexpr(PlannerInfo *root,
{ {
counts->exactEntries++; counts->exactEntries++;
counts->searchEntries++; counts->searchEntries++;
counts->arrayScans *= estimate_array_length(rightop); counts->arrayScans *= estimate_array_length(root, rightop);
return true; return true;
} }

View File

@ -2504,7 +2504,7 @@ psql_completion(const char *text, int start, int end)
/* ALTER TABLE ALTER [COLUMN] <foo> SET */ /* ALTER TABLE ALTER [COLUMN] <foo> SET */
else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") || else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
Matches("ALTER", "TABLE", MatchAny, "ALTER", 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 */ /* a subset of ALTER SEQUENCE options */
"INCREMENT", "MINVALUE", "MAXVALUE", "START", "NO", "CACHE", "CYCLE"); "INCREMENT", "MINVALUE", "MAXVALUE", "START", "NO", "CACHE", "CYCLE");
/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */ /* 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") || else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "COMPRESSION") ||
Matches("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "COMPRESSION")) Matches("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "COMPRESSION"))
COMPLETE_WITH("DEFAULT", "PGLZ", "LZ4"); 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 */ /* ALTER TABLE ALTER [COLUMN] <foo> SET GENERATED */
else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "GENERATED") || else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "GENERATED") ||
Matches("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "GENERATED")) Matches("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "GENERATED"))

View File

@ -2192,6 +2192,7 @@ typedef enum AlterTableType
AT_DropNotNull, /* alter column drop not null */ AT_DropNotNull, /* alter column drop not null */
AT_SetNotNull, /* alter column set not null */ AT_SetNotNull, /* alter column set not null */
AT_SetAttNotNull, /* set attnotnull w/o a constraint */ AT_SetAttNotNull, /* set attnotnull w/o a constraint */
AT_SetExpression, /* alter column set expression */
AT_DropExpression, /* alter column drop expression */ AT_DropExpression, /* alter column drop expression */
AT_SetStatistics, /* alter column set statistics */ AT_SetStatistics, /* alter column set statistics */
AT_SetOptions, /* alter column set ( options ) */ AT_SetOptions, /* alter column set ( options ) */

View File

@ -381,26 +381,26 @@ lnext(const List *l, const ListCell *c)
/* /*
* foreach_delete_current - * foreach_delete_current -
* delete the current list element from the List associated with a * 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 * This is similar to list_delete_cell(), but it also adjusts the loop's state
* loop's state so that no list elements will be missed. Do not delete * so that no list elements will be missed. Do not delete elements from an
* elements from an active foreach loop's list in any other way! * active foreach or foreach_* loop's list in any other way!
*/ */
#define foreach_delete_current(lst, cell) \ #define foreach_delete_current(lst, var_or_cell) \
(cell##__state.i--, \ ((List *) (var_or_cell##__state.l = list_delete_nth_cell(lst, var_or_cell##__state.i--)))
(List *) (cell##__state.l = list_delete_cell(lst, cell)))
/* /*
* foreach_current_index - * foreach_current_index -
* get the zero-based list index of a surrounding foreach() loop's * get the zero-based list index of a surrounding foreach() or foreach_*()
* current element; pass the name of the "ListCell *" iterator variable. * loop's current element; pass the name of the iterator variable.
* *
* Beware of using this after foreach_delete_current(); the value will be * 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 * out of sync for the rest of the current loop iteration. Anyway, since
* you just deleted the current element, the value is pretty meaningless. * 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 - * for_each_from -
@ -452,6 +452,57 @@ for_each_cell_setup(const List *lst, const ListCell *initcell)
return r; 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 - * forboth -
* a convenience macro for advancing through two linked lists * a convenience macro for advancing through two linked lists

View File

@ -200,7 +200,7 @@ extern Selectivity scalararraysel(PlannerInfo *root,
ScalarArrayOpExpr *clause, ScalarArrayOpExpr *clause,
bool is_join_clause, bool is_join_clause,
int varRelid, JoinType jointype, SpecialJoinInfo *sjinfo); 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, extern Selectivity rowcomparesel(PlannerInfo *root,
RowCompareExpr *clause, RowCompareExpr *clause,
int varRelid, JoinType jointype, SpecialJoinInfo *sjinfo); int varRelid, JoinType jointype, SpecialJoinInfo *sjinfo);

View File

@ -93,3 +93,82 @@ LINE 1: a.r[1] := 2
^ ^
QUERY: a.r[1] := 2 QUERY: a.r[1] := 2
CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment 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)"}

View File

@ -2208,6 +2208,33 @@ build_datatype(HeapTuple typeTup, int32 typmod,
return typ; 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 * plpgsql_recognize_err_condition
* Check condition name and translate it to SQLSTATE. * Check condition name and translate it to SQLSTATE.

View File

@ -757,8 +757,9 @@ decl_const :
decl_datatype : decl_datatype :
{ {
/* /*
* If there's a lookahead token, read_datatype * If there's a lookahead token, read_datatype() will
* should consume it. * consume it, and then we must tell bison to forget
* it.
*/ */
$$ = read_datatype(yychar); $$ = read_datatype(yychar);
yyclearin; yyclearin;
@ -2783,13 +2784,17 @@ read_sql_construct(int until,
return expr; return expr;
} }
/*
* Read a datatype declaration, consuming the current lookahead token if any.
* Returns a PLpgSQL_type struct.
*/
static PLpgSQL_type * static PLpgSQL_type *
read_datatype(int tok) read_datatype(int tok)
{ {
StringInfoData ds; StringInfoData ds;
char *type_name; char *type_name;
int startlocation; int startlocation;
PLpgSQL_type *result; PLpgSQL_type *result = NULL;
int parenlevel = 0; int parenlevel = 0;
/* Should only be called while parsing DECLARE sections */ /* Should only be called while parsing DECLARE sections */
@ -2799,11 +2804,15 @@ read_datatype(int tok)
if (tok == YYEMPTY) if (tok == YYEMPTY)
tok = yylex(); tok = yylex();
/* The current token is the start of what we'll pass to parse_datatype */
startlocation = yylloc; startlocation = yylloc;
/* /*
* If we have a simple or composite identifier, check for %TYPE * If we have a simple or composite identifier, check for %TYPE and
* and %ROWTYPE constructs. * %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) if (tok == T_WORD)
{ {
@ -2815,18 +2824,10 @@ read_datatype(int tok)
tok = yylex(); tok = yylex();
if (tok_is_keyword(tok, &yylval, if (tok_is_keyword(tok, &yylval,
K_TYPE, "type")) K_TYPE, "type"))
{
result = plpgsql_parse_wordtype(dtname); result = plpgsql_parse_wordtype(dtname);
if (result)
return result;
}
else if (tok_is_keyword(tok, &yylval, else if (tok_is_keyword(tok, &yylval,
K_ROWTYPE, "rowtype")) K_ROWTYPE, "rowtype"))
{
result = plpgsql_parse_wordrowtype(dtname); result = plpgsql_parse_wordrowtype(dtname);
if (result)
return result;
}
} }
} }
else if (plpgsql_token_is_unreserved_keyword(tok)) else if (plpgsql_token_is_unreserved_keyword(tok))
@ -2839,18 +2840,10 @@ read_datatype(int tok)
tok = yylex(); tok = yylex();
if (tok_is_keyword(tok, &yylval, if (tok_is_keyword(tok, &yylval,
K_TYPE, "type")) K_TYPE, "type"))
{
result = plpgsql_parse_wordtype(dtname); result = plpgsql_parse_wordtype(dtname);
if (result)
return result;
}
else if (tok_is_keyword(tok, &yylval, else if (tok_is_keyword(tok, &yylval,
K_ROWTYPE, "rowtype")) K_ROWTYPE, "rowtype"))
{
result = plpgsql_parse_wordrowtype(dtname); result = plpgsql_parse_wordrowtype(dtname);
if (result)
return result;
}
} }
} }
else if (tok == T_CWORD) else if (tok == T_CWORD)
@ -2863,21 +2856,56 @@ read_datatype(int tok)
tok = yylex(); tok = yylex();
if (tok_is_keyword(tok, &yylval, if (tok_is_keyword(tok, &yylval,
K_TYPE, "type")) K_TYPE, "type"))
{
result = plpgsql_parse_cwordtype(dtnames); result = plpgsql_parse_cwordtype(dtnames);
if (result)
return result;
}
else if (tok_is_keyword(tok, &yylval, else if (tok_is_keyword(tok, &yylval,
K_ROWTYPE, "rowtype")) K_ROWTYPE, "rowtype"))
{
result = plpgsql_parse_cwordrowtype(dtnames); 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 != ';') while (tok != ';')
{ {
if (tok == 0) if (tok == 0)

View File

@ -1249,6 +1249,7 @@ extern PLpgSQL_type *plpgsql_parse_cwordrowtype(List *idents);
extern PGDLLEXPORT PLpgSQL_type *plpgsql_build_datatype(Oid typeOid, int32 typmod, extern PGDLLEXPORT PLpgSQL_type *plpgsql_build_datatype(Oid typeOid, int32 typmod,
Oid collation, Oid collation,
TypeName *origtypname); TypeName *origtypname);
extern PLpgSQL_type *plpgsql_build_datatype_arrayof(PLpgSQL_type *dtype);
extern PLpgSQL_variable *plpgsql_build_variable(const char *refname, int lineno, extern PLpgSQL_variable *plpgsql_build_variable(const char *refname, int lineno,
PLpgSQL_type *dtype, PLpgSQL_type *dtype,
bool add2namespace); bool add2namespace);

View File

@ -77,3 +77,74 @@ begin a[1] := 2; raise notice 'a = %', a; end$$;
do $$ declare a complex; do $$ declare a complex;
begin a.r[1] := 2; raise notice 'a = %', a; end$$; 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;
$$;

View File

@ -132,6 +132,9 @@ get_altertable_subcmdinfo(PG_FUNCTION_ARGS)
case AT_SetAttNotNull: case AT_SetAttNotNull:
strtype = "SET ATTNOTNULL"; strtype = "SET ATTNOTNULL";
break; break;
case AT_SetExpression:
strtype = "SET EXPRESSION";
break;
case AT_DropExpression: case AT_DropExpression:
strtype = "DROP EXPRESSION"; strtype = "DROP EXPRESSION";
break; break;

View File

@ -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)"} {"(0,,)","(1,3,foo)","(2,2,bar)","(3,1,baz)"}
(1 row) (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 -- check node I/O via view creation and usage, also deparsing logic
create view agg_view1 as create view agg_view1 as
select aggfns(a,b,c) select aggfns(a,b,c)

View File

@ -578,6 +578,9 @@ INSERT INTO gtest20 (a) VALUES (10); -- ok
INSERT INTO gtest20 (a) VALUES (30); -- violates constraint INSERT INTO gtest20 (a) VALUES (30); -- violates constraint
ERROR: new row for relation "gtest20" violates check constraint "gtest20_b_check" ERROR: new row for relation "gtest20" violates check constraint "gtest20_b_check"
DETAIL: Failing row contains (30, 60). 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); 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 (10);
INSERT INTO gtest20a (a) VALUES (30); INSERT INTO gtest20a (a) VALUES (30);
@ -673,6 +676,47 @@ SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
1 | 2 1 | 2
(1 row) (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_seqscan;
RESET enable_bitmapscan; RESET enable_bitmapscan;
-- foreign keys -- foreign keys
@ -698,6 +742,10 @@ INSERT INTO gtest23b VALUES (1); -- ok
INSERT INTO gtest23b VALUES (5); -- error INSERT INTO gtest23b VALUES (5); -- error
ERROR: insert or update on table "gtest23b" violates foreign key constraint "gtest23b_b_fkey" ERROR: insert or update on table "gtest23b" violates foreign key constraint "gtest23b_b_fkey"
DETAIL: Key (b)=(10) is not present in table "gtest23a". 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 gtest23b;
DROP TABLE gtest23a; DROP TABLE gtest23a;
CREATE TABLE gtest23p (x int, y int GENERATED ALWAYS AS (x * 2) STORED, PRIMARY KEY (y)); 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') 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); INSERT INTO gtest_parent (f1, f2) VALUES ('2016-07-15', 1);
SELECT * FROM gtest_parent; INSERT INTO gtest_parent (f1, f2) VALUES ('2016-07-15', 2);
f1 | f2 | f3 INSERT INTO gtest_parent (f1, f2) VALUES ('2016-08-15', 3);
------------+----+---- SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3;
07-15-2016 | 1 | 2 tableoid | f1 | f2 | f3
(1 row) --------------+------------+----+----
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; UPDATE gtest_parent SET f1 = f1 + 60 WHERE f2 = 1;
f1 | f2 | f3 SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3;
------------+----+---- tableoid | f1 | f2 | f3
07-15-2016 | 1 | 2 --------------+------------+----+----
(1 row) 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; -- alter only parent's and one child's generation expression
SELECT * FROM gtest_parent; ALTER TABLE ONLY gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 4);
f1 | f2 | f3 ALTER TABLE gtest_child ALTER COLUMN f3 SET EXPRESSION AS (f2 * 10);
------------+----+---- \d gtest_parent
09-13-2016 | 1 | 33 Partitioned table "public.gtest_parent"
(1 row) 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; \d gtest_child
f1 | f2 | f3 Table "public.gtest_child"
------------+----+---- Column | Type | Collation | Nullable | Default
09-13-2016 | 1 | 33 --------+--------+-----------+----------+--------------------------------------
(1 row) 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 -- we leave these tables around for purposes of testing dump/reload/upgrade
-- generated columns in partition key (not allowed) -- generated columns in partition key (not allowed)
@ -825,7 +962,7 @@ DETAIL: Column "f3" is a generated column.
-- ALTER TABLE ... ADD COLUMN -- ALTER TABLE ... ADD COLUMN
CREATE TABLE gtest25 (a int PRIMARY KEY); CREATE TABLE gtest25 (a int PRIMARY KEY);
INSERT INTO gtest25 VALUES (3), (4); 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; SELECT * FROM gtest25 ORDER BY a;
a | b a | b
---+---- ---+----
@ -935,18 +1072,50 @@ CREATE TABLE gtest29 (
b int GENERATED ALWAYS AS (a * 2) STORED b int GENERATED ALWAYS AS (a * 2) STORED
); );
INSERT INTO gtest29 (a) VALUES (3), (4); 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 ALTER TABLE gtest29 ALTER COLUMN a DROP EXPRESSION; -- error
ERROR: column "a" of relation "gtest29" is not a stored generated column ERROR: column "a" of relation "gtest29" is not a stored generated column
ALTER TABLE gtest29 ALTER COLUMN a DROP EXPRESSION IF EXISTS; -- notice ALTER TABLE gtest29 ALTER COLUMN a DROP EXPRESSION IF EXISTS; -- notice
NOTICE: column "a" of relation "gtest29" is not a stored generated column, skipping 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; ALTER TABLE gtest29 ALTER COLUMN b DROP EXPRESSION;
INSERT INTO gtest29 (a) VALUES (5); INSERT INTO gtest29 (a) VALUES (5);
INSERT INTO gtest29 (a, b) VALUES (6, 66); INSERT INTO gtest29 (a, b) VALUES (6, 66);
SELECT * FROM gtest29; SELECT * FROM gtest29;
a | b a | b
---+---- ---+----
3 | 6 3 | 9
4 | 8 4 | 12
5 | 5 |
6 | 66 6 | 66
(4 rows) (4 rows)

View File

@ -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), from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
generate_series(1,2) i; 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 -- check node I/O via view creation and usage, also deparsing logic
create view agg_view1 as create view agg_view1 as

View File

@ -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 (10); -- ok
INSERT INTO gtest20 (a) VALUES (30); -- violates constraint 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); 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 (10);
INSERT INTO gtest20a (a) VALUES (30); 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; SELECT * FROM gtest22c WHERE b * 3 = 6;
EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0; EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
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_seqscan;
RESET enable_bitmapscan; 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 (1); -- ok
INSERT INTO gtest23b VALUES (5); -- error 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 gtest23b;
DROP TABLE gtest23a; 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_child2
\d gtest_child3 \d gtest_child3
INSERT INTO gtest_parent (f1, f2) VALUES ('2016-07-15', 1); INSERT INTO gtest_parent (f1, f2) VALUES ('2016-07-15', 1);
SELECT * FROM gtest_parent; INSERT INTO gtest_parent (f1, f2) VALUES ('2016-07-15', 2);
SELECT * FROM gtest_child; INSERT INTO gtest_parent (f1, f2) VALUES ('2016-08-15', 3);
UPDATE gtest_parent SET f1 = f1 + 60; SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3;
SELECT * FROM gtest_parent; UPDATE gtest_parent SET f1 = f1 + 60 WHERE f2 = 1;
SELECT * FROM gtest_child3; 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 -- we leave these tables around for purposes of testing dump/reload/upgrade
-- generated columns in partition key (not allowed) -- 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 -- ALTER TABLE ... ADD COLUMN
CREATE TABLE gtest25 (a int PRIMARY KEY); CREATE TABLE gtest25 (a int PRIMARY KEY);
INSERT INTO gtest25 VALUES (3), (4); 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; 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 (b * 4) STORED; -- error
ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (z * 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 b int GENERATED ALWAYS AS (a * 2) STORED
); );
INSERT INTO gtest29 (a) VALUES (3), (4); 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; -- error
ALTER TABLE gtest29 ALTER COLUMN a DROP EXPRESSION IF EXISTS; -- notice 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; ALTER TABLE gtest29 ALTER COLUMN b DROP EXPRESSION;
INSERT INTO gtest29 (a) VALUES (5); INSERT INTO gtest29 (a) VALUES (5);
INSERT INTO gtest29 (a, b) VALUES (6, 66); INSERT INTO gtest29 (a, b) VALUES (6, 66);