Thread

Commits

Same data as JSON: GET /api/v1/messages/:b64id/commits the thread's linked commits as JSON, with link sources. API reference →
  1. Cope with indicator arrays that do not have the correct length.

  1. [PATCH v3] Generated columns

    Peter Eisentraut <peter_e@gmx.net> — 2018-01-16T14:42:28Z

    This is an SQL-standard feature that allows creating columns that are
    computed from expressions rather than assigned, similar to a view or
    materialized view but on a column basis.
    
    The plan to is implement two kinds of generated columns:
    virtual (computed on read) and stored (computed on write).  This patch
    only implements the virtual kind, leaving stubs to implement the stored
    kind later.
    ---
     doc/src/sgml/catalogs.sgml                      |  11 +
     doc/src/sgml/information_schema.sgml            |  10 +-
     doc/src/sgml/ref/copy.sgml                      |   3 +-
     doc/src/sgml/ref/create_table.sgml              |  46 ++-
     src/backend/access/common/tupdesc.c             |   7 +
     src/backend/catalog/heap.c                      |  88 ++++-
     src/backend/catalog/index.c                     |   1 +
     src/backend/catalog/information_schema.sql      |   8 +-
     src/backend/commands/copy.c                     |  10 +-
     src/backend/commands/indexcmds.c                |  24 +-
     src/backend/commands/tablecmds.c                | 143 +++++++-
     src/backend/commands/trigger.c                  |  36 +++
     src/backend/commands/typecmds.c                 |   6 +-
     src/backend/executor/execMain.c                 |   7 +-
     src/backend/nodes/copyfuncs.c                   |   2 +
     src/backend/nodes/equalfuncs.c                  |   2 +
     src/backend/nodes/outfuncs.c                    |   9 +
     src/backend/parser/gram.y                       |  26 +-
     src/backend/parser/parse_agg.c                  |  11 +
     src/backend/parser/parse_expr.c                 |   5 +
     src/backend/parser/parse_func.c                 |  12 +
     src/backend/parser/parse_utilcmd.c              |  87 ++++-
     src/backend/rewrite/rewriteHandler.c            | 149 ++++++++-
     src/backend/utils/cache/lsyscache.c             |  32 ++
     src/backend/utils/cache/relcache.c              |   1 +
     src/bin/pg_dump/pg_dump.c                       |  39 ++-
     src/bin/pg_dump/pg_dump.h                       |   1 +
     src/bin/pg_dump/pg_dump_sort.c                  |  10 +
     src/bin/pg_dump/t/002_pg_dump.pl                |  39 +++
     src/bin/psql/describe.c                         |  28 +-
     src/include/catalog/heap.h                      |   3 +-
     src/include/catalog/pg_attribute.h              |  23 +-
     src/include/catalog/pg_class.h                  |   2 +-
     src/include/nodes/parsenodes.h                  |  12 +-
     src/include/parser/kwlist.h                     |   2 +
     src/include/parser/parse_node.h                 |   3 +-
     src/include/rewrite/rewriteHandler.h            |   1 +
     src/include/utils/lsyscache.h                   |   1 +
     src/test/regress/expected/create_table_like.out |  46 +++
     src/test/regress/expected/generated.out         | 413 ++++++++++++++++++++++++
     src/test/regress/parallel_schedule              |   2 +-
     src/test/regress/serial_schedule                |   1 +
     src/test/regress/sql/create_table_like.sql      |  14 +
     src/test/regress/sql/generated.sql              | 237 ++++++++++++++
     44 files changed, 1525 insertions(+), 88 deletions(-)
     create mode 100644 src/test/regress/expected/generated.out
     create mode 100644 src/test/regress/sql/generated.sql
    
    diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
    index 3f02202caf..76237d9eb8 100644
    --- a/doc/src/sgml/catalogs.sgml
    +++ b/doc/src/sgml/catalogs.sgml
    @@ -1160,6 +1160,17 @@ <title><structname>pg_attribute</structname> Columns</title>
           </entry>
          </row>
     
    +     <row>
    +      <entry><structfield>attgenerated</structfield></entry>
    +      <entry><type>char</type></entry>
    +      <entry></entry>
    +      <entry>
    +       If a zero byte (<literal>''</literal>), then not a generated column.
    +       Otherwise, <literal>s</literal> = stored, <literal>v</literal> =
    +       virtual.
    +      </entry>
    +     </row>
    +
          <row>
           <entry><structfield>attisdropped</structfield></entry>
           <entry><type>bool</type></entry>
    diff --git a/doc/src/sgml/information_schema.sgml b/doc/src/sgml/information_schema.sgml
    index 0faa72f1d3..6edf04cc69 100644
    --- a/doc/src/sgml/information_schema.sgml
    +++ b/doc/src/sgml/information_schema.sgml
    @@ -1648,13 +1648,19 @@ <title><literal>columns</literal> Columns</title>
          <row>
           <entry><literal>is_generated</literal></entry>
           <entry><type>character_data</type></entry>
    -      <entry>Applies to a feature not available in <productname>PostgreSQL</productname></entry>
    +      <entry>
    +       If the column is a generated column, then <literal>ALWAYS</literal>,
    +       else <literal>NEVER</literal>.
    +      </entry>
          </row>
     
          <row>
           <entry><literal>generation_expression</literal></entry>
           <entry><type>character_data</type></entry>
    -      <entry>Applies to a feature not available in <productname>PostgreSQL</productname></entry>
    +      <entry>
    +       If the column is a generated column, then the generation expression,
    +       else null.
    +      </entry>
          </row>
     
          <row>
    diff --git a/doc/src/sgml/ref/copy.sgml b/doc/src/sgml/ref/copy.sgml
    index af2a0e91b9..1ee0304888 100644
    --- a/doc/src/sgml/ref/copy.sgml
    +++ b/doc/src/sgml/ref/copy.sgml
    @@ -103,7 +103,8 @@ <title>Parameters</title>
          <listitem>
          <para>
           An optional list of columns to be copied.  If no column list is
    -      specified, all columns of the table will be copied.
    +      specified, all columns of the table except generated columns will be
    +      copied.
          </para>
         </listitem>
        </varlistentry>
    diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
    index a0c9a6d257..5dd8a5e5b1 100644
    --- a/doc/src/sgml/ref/create_table.sgml
    +++ b/doc/src/sgml/ref/create_table.sgml
    @@ -62,6 +62,7 @@
       NULL |
       CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ] |
       DEFAULT <replaceable>default_expr</replaceable> |
    +  GENERATED ALWAYS AS ( <replaceable>generation_expr</replaceable> ) [ VIRTUAL | STORED ] |
       GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ] |
       UNIQUE <replaceable class="parameter">index_parameters</replaceable> |
       PRIMARY KEY <replaceable class="parameter">index_parameters</replaceable> |
    @@ -82,7 +83,7 @@
     
     <phrase>and <replaceable class="parameter">like_option</replaceable> is:</phrase>
     
    -{ INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | IDENTITY | INDEXES | STORAGE | COMMENTS | ALL }
    +{ INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | IDENTITY | GENERATED | INDEXES | STORAGE | COMMENTS | ALL }
     
     <phrase>and <replaceable class="parameter">partition_bound_spec</replaceable> is:</phrase>
     
    @@ -585,6 +586,12 @@ <title>Parameters</title>
           sequence is created for each identity column of the new table, separate
           from the sequences associated with the old table.
          </para>
    +     <para>
    +      Generated columns will only become generated columns in the new table
    +      if <literal>INCLUDING GENERATED</literal> is specified, which will copy
    +      the generation expression and the virtual/stored choice.  Otherwise, the
    +      new column will be a regular base column.
    +     </para>
          <para>
           Not-null constraints are always copied to the new table.
           <literal>CHECK</literal> constraints will be copied only if
    @@ -617,7 +624,7 @@ <title>Parameters</title>
          </para>
          <para>
           <literal>INCLUDING ALL</literal> is an abbreviated form of
    -      <literal>INCLUDING DEFAULTS INCLUDING IDENTITY INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS</literal>.
    +      <literal>INCLUDING DEFAULTS INCLUDING IDENTITY INCLUDING GENERATED INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS</literal>.
          </para>
          <para>
           Note that unlike <literal>INHERITS</literal>, columns and
    @@ -731,6 +738,31 @@ <title>Parameters</title>
         </listitem>
        </varlistentry>
     
    +   <varlistentry>
    +    <term><literal>GENERATED ALWAYS AS ( <replaceable>generation_expr</replaceable> ) [ VIRTUAL | STORED ]</literal><indexterm><primary>generated column</primary></indexterm></term>
    +    <listitem>
    +     <para>
    +      This clause creates the column as a <firstterm>generated
    +      column</firstterm>.  The column cannot be written to, and when read it
    +      will be computed from the specified expression.
    +     </para>
    +
    +     <para>
    +      When <literal>VIRTUAL</literal> is specified, the column will be
    +      computed when it is read, and it will not occupy any storage.
    +      When <literal>STORED</literal> is specified, the column will be computed
    +      on write and will be stored on disk.  <literal>VIRTUAL</literal> is the
    +      default.
    +     </para>
    +
    +     <para>
    +      The generation expression can refer to other columns in the table, but
    +      not other generated columns.  Any functions and operators used must be
    +      immutable.  References to other tables are not allowed.
    +     </para>
    +    </listitem>
    +   </varlistentry>
    +
        <varlistentry>
         <term><literal>GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ]</literal></term>
         <listitem>
    @@ -1938,6 +1970,16 @@ <title>Multiple Identity Columns</title>
        </para>
       </refsect2>
     
    +  <refsect2>
    +   <title>Generated Columns</title>
    +
    +   <para>
    +    The options <literal>VIRTUAL</literal> and <literal>STORED</literal> are
    +    not standard but are also used by other SQL implementations.  The SQL
    +    standard does not specify the storage of generated columns.
    +   </para>
    +  </refsect2>
    +
       <refsect2>
        <title><literal>LIKE</literal> Clause</title>
     
    diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
    index f1f44230cd..31146eadd3 100644
    --- a/src/backend/access/common/tupdesc.c
    +++ b/src/backend/access/common/tupdesc.c
    @@ -130,6 +130,7 @@ CreateTupleDescCopy(TupleDesc tupdesc)
     		att->attnotnull = false;
     		att->atthasdef = false;
     		att->attidentity = '\0';
    +		att->attgenerated = '\0';
     	}
     
     	/* We can copy the tuple type identification, too */
    @@ -228,6 +229,7 @@ TupleDescCopy(TupleDesc dst, TupleDesc src)
     		att->attnotnull = false;
     		att->atthasdef = false;
     		att->attidentity = '\0';
    +		att->attgenerated = '\0';
     	}
     	dst->constr = NULL;
     
    @@ -280,6 +282,7 @@ TupleDescCopyEntry(TupleDesc dst, AttrNumber dstAttno,
     	dstAtt->attnotnull = false;
     	dstAtt->atthasdef = false;
     	dstAtt->attidentity = '\0';
    +	dstAtt->attgenerated = '\0';
     }
     
     /*
    @@ -426,6 +429,8 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
     			return false;
     		if (attr1->attidentity != attr2->attidentity)
     			return false;
    +		if (attr1->attgenerated != attr2->attgenerated)
    +			return false;
     		if (attr1->attisdropped != attr2->attisdropped)
     			return false;
     		if (attr1->attislocal != attr2->attislocal)
    @@ -585,6 +590,7 @@ TupleDescInitEntry(TupleDesc desc,
     	att->attnotnull = false;
     	att->atthasdef = false;
     	att->attidentity = '\0';
    +	att->attgenerated = '\0';
     	att->attisdropped = false;
     	att->attislocal = true;
     	att->attinhcount = 0;
    @@ -643,6 +649,7 @@ TupleDescInitBuiltinEntry(TupleDesc desc,
     	att->attnotnull = false;
     	att->atthasdef = false;
     	att->attidentity = '\0';
    +	att->attgenerated = '\0';
     	att->attisdropped = false;
     	att->attislocal = true;
     	att->attinhcount = 0;
    diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
    index 089b7965f2..4bd728c154 100644
    --- a/src/backend/catalog/heap.c
    +++ b/src/backend/catalog/heap.c
    @@ -67,6 +67,7 @@
     #include "parser/parse_collate.h"
     #include "parser/parse_expr.h"
     #include "parser/parse_relation.h"
    +#include "parser/parsetree.h"
     #include "storage/lmgr.h"
     #include "storage/predicate.h"
     #include "storage/smgr.h"
    @@ -144,37 +145,37 @@ static List *insert_ordered_unique_oid(List *list, Oid datum);
     static FormData_pg_attribute a1 = {
     	0, {"ctid"}, TIDOID, 0, sizeof(ItemPointerData),
     	SelfItemPointerAttributeNumber, 0, -1, -1,
    -	false, 'p', 's', true, false, '\0', false, true, 0
    +	false, 'p', 's', true, false, '\0', '\0', false, true, 0
     };
     
     static FormData_pg_attribute a2 = {
     	0, {"oid"}, OIDOID, 0, sizeof(Oid),
     	ObjectIdAttributeNumber, 0, -1, -1,
    -	true, 'p', 'i', true, false, '\0', false, true, 0
    +	true, 'p', 'i', true, false, '\0', '\0', false, true, 0
     };
     
     static FormData_pg_attribute a3 = {
     	0, {"xmin"}, XIDOID, 0, sizeof(TransactionId),
     	MinTransactionIdAttributeNumber, 0, -1, -1,
    -	true, 'p', 'i', true, false, '\0', false, true, 0
    +	true, 'p', 'i', true, false, '\0', '\0', false, true, 0
     };
     
     static FormData_pg_attribute a4 = {
     	0, {"cmin"}, CIDOID, 0, sizeof(CommandId),
     	MinCommandIdAttributeNumber, 0, -1, -1,
    -	true, 'p', 'i', true, false, '\0', false, true, 0
    +	true, 'p', 'i', true, false, '\0', '\0', false, true, 0
     };
     
     static FormData_pg_attribute a5 = {
     	0, {"xmax"}, XIDOID, 0, sizeof(TransactionId),
     	MaxTransactionIdAttributeNumber, 0, -1, -1,
    -	true, 'p', 'i', true, false, '\0', false, true, 0
    +	true, 'p', 'i', true, false, '\0', '\0', false, true, 0
     };
     
     static FormData_pg_attribute a6 = {
     	0, {"cmax"}, CIDOID, 0, sizeof(CommandId),
     	MaxCommandIdAttributeNumber, 0, -1, -1,
    -	true, 'p', 'i', true, false, '\0', false, true, 0
    +	true, 'p', 'i', true, false, '\0', '\0', false, true, 0
     };
     
     /*
    @@ -186,7 +187,7 @@ static FormData_pg_attribute a6 = {
     static FormData_pg_attribute a7 = {
     	0, {"tableoid"}, OIDOID, 0, sizeof(Oid),
     	TableOidAttributeNumber, 0, -1, -1,
    -	true, 'p', 'i', true, false, '\0', false, true, 0
    +	true, 'p', 'i', true, false, '\0', '\0', false, true, 0
     };
     
     static const Form_pg_attribute SysAtt[] = {&a1, &a2, &a3, &a4, &a5, &a6, &a7};
    @@ -624,6 +625,7 @@ InsertPgAttributeTuple(Relation pg_attribute_rel,
     	values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(new_attribute->attnotnull);
     	values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(new_attribute->atthasdef);
     	values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(new_attribute->attidentity);
    +	values[Anum_pg_attribute_attgenerated - 1] = CharGetDatum(new_attribute->attgenerated);
     	values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attribute->attisdropped);
     	values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attribute->attislocal);
     	values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attribute->attinhcount);
    @@ -1938,6 +1940,7 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
     	Relation	attrrel;
     	HeapTuple	atttup;
     	Form_pg_attribute attStruct;
    +	char		attgenerated;
     	Oid			attrdefOid;
     	ObjectAddress colobject,
     				defobject;
    @@ -1993,6 +1996,7 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
     		elog(ERROR, "cache lookup failed for attribute %d of relation %u",
     			 attnum, RelationGetRelid(rel));
     	attStruct = (Form_pg_attribute) GETSTRUCT(atttup);
    +	attgenerated = attStruct->attgenerated;
     	if (!attStruct->atthasdef)
     	{
     		attStruct->atthasdef = true;
    @@ -2014,7 +2018,22 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
     	/*
     	 * Record dependencies on objects used in the expression, too.
     	 */
    -	recordDependencyOnExpr(&defobject, expr, NIL, DEPENDENCY_NORMAL);
    +	if (attgenerated)
    +		/*
    +		 * Generated column: Dropping anything that the generation expression
    +		 * refers to automatically drops the generated column.
    +		 */
    +		recordDependencyOnSingleRelExpr(&colobject, expr, RelationGetRelid(rel),
    +										DEPENDENCY_AUTO,
    +										DEPENDENCY_AUTO, false);
    +	else
    +		/*
    +		 * Normal default: Dropping anything that the default refers to
    +		 * requires CASCADE and drops the default only.
    +		 */
    +		recordDependencyOnSingleRelExpr(&defobject, expr, RelationGetRelid(rel),
    +										DEPENDENCY_NORMAL,
    +										DEPENDENCY_NORMAL, false);
     
     	/*
     	 * Post creation hook for attribute defaults.
    @@ -2278,7 +2297,8 @@ AddRelationNewConstraints(Relation rel,
     
     		expr = cookDefault(pstate, colDef->raw_default,
     						   atp->atttypid, atp->atttypmod,
    -						   NameStr(atp->attname));
    +						   NameStr(atp->attname),
    +						   atp->attgenerated);
     
     		/*
     		 * If the expression is just a NULL constant, we do not bother to make
    @@ -2643,6 +2663,46 @@ SetRelationNumChecks(Relation rel, int numchecks)
     	heap_close(relrel, RowExclusiveLock);
     }
     
    +/*
    + * Check for references to generated columns
    + */
    +static bool
    +check_nested_generated_walker(Node *node, void *context)
    +{
    +	ParseState *pstate = context;
    +
    +	if (node == NULL)
    +		return false;
    +	else if (IsA(node, Var))
    +	{
    +		Var		   *var = (Var *) node;
    +		Oid			relid;
    +		AttrNumber	attnum;
    +
    +		relid = getrelid(var->varno, pstate->p_rtable);
    +		attnum = var->varattno;
    +
    +		if (relid && attnum && get_attgenerated(relid, attnum))
    +			ereport(ERROR,
    +					(errcode(ERRCODE_SYNTAX_ERROR),
    +					 errmsg("cannot use generated column \"%s\" in column generation expression",
    +							get_attname(relid, attnum)),
    +					 errdetail("A generated column cannot reference another generated column."),
    +					 parser_errposition(pstate, var->location)));
    +
    +		return false;
    +	}
    +	else
    +		return expression_tree_walker(node, check_nested_generated_walker,
    +									  (void *) context);
    +}
    +
    +static void
    +check_nested_generated(ParseState *pstate, Node *node)
    +{
    +	check_nested_generated_walker(node, pstate);
    +}
    +
     /*
      * Take a raw default and convert it to a cooked format ready for
      * storage.
    @@ -2660,7 +2720,8 @@ cookDefault(ParseState *pstate,
     			Node *raw_default,
     			Oid atttypid,
     			int32 atttypmod,
    -			const char *attname)
    +			const char *attname,
    +			char attgenerated)
     {
     	Node	   *expr;
     
    @@ -2669,17 +2730,20 @@ cookDefault(ParseState *pstate,
     	/*
     	 * Transform raw parsetree to executable expression.
     	 */
    -	expr = transformExpr(pstate, raw_default, EXPR_KIND_COLUMN_DEFAULT);
    +	expr = transformExpr(pstate, raw_default, attgenerated ? EXPR_KIND_GENERATED_COLUMN : EXPR_KIND_COLUMN_DEFAULT);
     
     	/*
     	 * Make sure default expr does not refer to any vars (we need this check
     	 * since the pstate includes the target table).
     	 */
    -	if (contain_var_clause(expr))
    +	if (!attgenerated && contain_var_clause(expr))
     		ereport(ERROR,
     				(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
     				 errmsg("cannot use column references in default expression")));
     
    +	if (attgenerated)
    +		check_nested_generated(pstate, expr);
    +
     	/*
     	 * transformExpr() should have already rejected subqueries, aggregates,
     	 * window functions, and SRFs, based on the EXPR_KIND_ for a default
    diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
    index 330488b96f..27f8480225 100644
    --- a/src/backend/catalog/index.c
    +++ b/src/backend/catalog/index.c
    @@ -354,6 +354,7 @@ ConstructTupleDescriptor(Relation heapRelation,
     			to->attnotnull = false;
     			to->atthasdef = false;
     			to->attidentity = '\0';
    +			to->attgenerated = '\0';
     			to->attislocal = true;
     			to->attinhcount = 0;
     			to->attcollation = collationObjectId[i];
    diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
    index 6fb1a1bc1c..5a33c9ca23 100644
    --- a/src/backend/catalog/information_schema.sql
    +++ b/src/backend/catalog/information_schema.sql
    @@ -299,7 +299,7 @@ CREATE VIEW attributes AS
                CAST(c.relname AS sql_identifier) AS udt_name,
                CAST(a.attname AS sql_identifier) AS attribute_name,
                CAST(a.attnum AS cardinal_number) AS ordinal_position,
    -           CAST(pg_get_expr(ad.adbin, ad.adrelid) AS character_data) AS attribute_default,
    +           CAST(pg_get_expr(ad.adbin, ad.adrelid, true) AS character_data) AS attribute_default,
                CAST(CASE WHEN a.attnotnull OR (t.typtype = 'd' AND t.typnotnull) THEN 'NO' ELSE 'YES' END
                  AS yes_or_no)
                  AS is_nullable, -- This column was apparently removed between SQL:2003 and SQL:2008.
    @@ -656,7 +656,7 @@ CREATE VIEW columns AS
                CAST(c.relname AS sql_identifier) AS table_name,
                CAST(a.attname AS sql_identifier) AS column_name,
                CAST(a.attnum AS cardinal_number) AS ordinal_position,
    -           CAST(pg_get_expr(ad.adbin, ad.adrelid) AS character_data) AS column_default,
    +           CAST(CASE WHEN a.attgenerated = '' THEN pg_get_expr(ad.adbin, ad.adrelid, true) END AS character_data) AS column_default,
                CAST(CASE WHEN a.attnotnull OR (t.typtype = 'd' AND t.typnotnull) THEN 'NO' ELSE 'YES' END
                  AS yes_or_no)
                  AS is_nullable,
    @@ -745,8 +745,8 @@ CREATE VIEW columns AS
                CAST(seq.seqmin AS character_data) AS identity_minimum,
                CAST(CASE WHEN seq.seqcycle THEN 'YES' ELSE 'NO' END AS yes_or_no) AS identity_cycle,
     
    -           CAST('NEVER' AS character_data) AS is_generated,
    -           CAST(null AS character_data) AS generation_expression,
    +           CAST(CASE WHEN a.attgenerated <> '' THEN 'ALWAYS' ELSE 'NEVER' END AS character_data) AS is_generated,
    +           CAST(CASE WHEN a.attgenerated <> '' THEN pg_get_expr(ad.adbin, ad.adrelid, true) END AS character_data) AS generation_expression,
     
                CAST(CASE WHEN c.relkind IN ('r', 'p') OR
                               (c.relkind IN ('v', 'f') AND
    diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
    index 6bfca2a4af..f4c2d89b42 100644
    --- a/src/backend/commands/copy.c
    +++ b/src/backend/commands/copy.c
    @@ -3018,7 +3018,7 @@ BeginCopyFrom(ParseState *pstate,
     		fmgr_info(in_func_oid, &in_functions[attnum - 1]);
     
     		/* Get default info if needed */
    -		if (!list_member_int(cstate->attnumlist, attnum))
    +		if (!list_member_int(cstate->attnumlist, attnum) && !att->attgenerated)
     		{
     			/* attribute is NOT to be copied from input */
     			/* use default value if one exists */
    @@ -4709,6 +4709,8 @@ CopyGetAttnums(TupleDesc tupDesc, Relation rel, List *attnamelist)
     		{
     			if (TupleDescAttr(tupDesc, i)->attisdropped)
     				continue;
    +			if (TupleDescAttr(tupDesc, i)->attgenerated)
    +				continue;		/* TODO: could be a COPY option */
     			attnums = lappend_int(attnums, i + 1);
     		}
     	}
    @@ -4733,6 +4735,12 @@ CopyGetAttnums(TupleDesc tupDesc, Relation rel, List *attnamelist)
     					continue;
     				if (namestrcmp(&(att->attname), name) == 0)
     				{
    +					if (att->attgenerated)
    +						ereport(ERROR,
    +								(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
    +								 errmsg("column \"%s\" is a generated column",
    +										name),
    +								 errdetail("Generated columns cannot be used in COPY.")));
     					attnum = att->attnum;
     					break;
     				}
    diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
    index 9e6ba92008..b912e41ca0 100644
    --- a/src/backend/commands/indexcmds.c
    +++ b/src/backend/commands/indexcmds.c
    @@ -597,6 +597,8 @@ DefineIndex(Oid relationId,
     	/*
     	 * We disallow indexes on system columns other than OID.  They would not
     	 * necessarily get updated correctly, and they don't seem useful anyway.
    +	 *
    +	 * Also disallow generated columns in indexes. (could be implemented)
     	 */
     	for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
     	{
    @@ -606,10 +608,16 @@ DefineIndex(Oid relationId,
     			ereport(ERROR,
     					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     					 errmsg("index creation on system columns is not supported")));
    +
    +		if (get_attgenerated(relationId, attno))
    +			ereport(ERROR,
    +					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
    +					 errmsg("index creation on generated columns is not supported")));
     	}
     
     	/*
    -	 * Also check for system columns used in expressions or predicates.
    +	 * Also check for system and generated columns used in expressions or
    +	 * predicates.
     	 */
     	if (indexInfo->ii_Expressions || indexInfo->ii_Predicate)
     	{
    @@ -618,14 +626,20 @@ DefineIndex(Oid relationId,
     		pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexattrs);
     		pull_varattnos((Node *) indexInfo->ii_Predicate, 1, &indexattrs);
     
    -		for (i = FirstLowInvalidHeapAttributeNumber + 1; i < 0; i++)
    +		i = -1;
    +		while ((i = bms_next_member(indexattrs, i)) >= 0)
     		{
    -			if (i != ObjectIdAttributeNumber &&
    -				bms_is_member(i - FirstLowInvalidHeapAttributeNumber,
    -							  indexattrs))
    +			AttrNumber	attno = i + FirstLowInvalidHeapAttributeNumber;
    +
    +			if (attno < 0 && attno != ObjectIdAttributeNumber)
     				ereport(ERROR,
     						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     						 errmsg("index creation on system columns is not supported")));
    +
    +			if (get_attgenerated(relationId, attno))
    +				ereport(ERROR,
    +						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
    +						 errmsg("index creation on generated columns is not supported")));
     		}
     	}
     
    diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
    index f2a928b823..37ce44cef0 100644
    --- a/src/backend/commands/tablecmds.c
    +++ b/src/backend/commands/tablecmds.c
    @@ -723,6 +723,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
     
     		if (colDef->identity)
     			attr->attidentity = colDef->identity;
    +
    +		if (colDef->generated)
    +			attr->attgenerated = colDef->generated;
     	}
     
     	/*
    @@ -1948,6 +1951,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
     				def->is_not_null |= attribute->attnotnull;
     				/* Default and other constraints are handled below */
     				newattno[parent_attno - 1] = exist_attno;
    +
    +				/* Check for GENERATED conflicts */
    +				if (def->generated != attribute->attgenerated)
    +					ereport(ERROR,
    +							(errcode(ERRCODE_DATATYPE_MISMATCH),
    +							 errmsg("inherited column \"%s\" has a generation conflict",
    +									attributeName)));
     			}
     			else
     			{
    @@ -1966,6 +1976,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
     				def->storage = attribute->attstorage;
     				def->raw_default = NULL;
     				def->cooked_default = NULL;
    +				def->generated = attribute->attgenerated;
     				def->collClause = NULL;
     				def->collOid = attribute->attcollation;
     				def->constraints = NIL;
    @@ -4437,7 +4448,9 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
     		{
     			case CONSTR_CHECK:
     				needscan = true;
    -				con->qualstate = ExecPrepareExpr((Expr *) con->qual, estate);
    +				con->qualstate = ExecPrepareExpr(expand_generated_columns_in_expr((Expr *) con->qual,
    +																				  newrel ? newrel : oldrel),
    +												 estate);
     				break;
     			case CONSTR_FOREIGN:
     				/* Nothing to do here */
    @@ -5333,6 +5346,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
     	attribute.attnotnull = colDef->is_not_null;
     	attribute.atthasdef = false;
     	attribute.attidentity = colDef->identity;
    +	attribute.attgenerated = colDef->generated;
     	attribute.attisdropped = false;
     	attribute.attislocal = colDef->is_local;
     	attribute.attinhcount = colDef->inhcount;
    @@ -5706,6 +5720,18 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
     				 errmsg("cannot alter system column \"%s\"",
     						colName)));
     
    +	/*
    +	 * Generated columns don't use the attnotnull field but use a full CHECK
    +	 * constraint instead.  We could implement here that it finds that CHECK
    +	 * constraint and drops it, which is kind of what the SQL standard would
    +	 * require anyway, but that would be quite a bit more work.
    +	 */
    +	if (((Form_pg_attribute) GETSTRUCT(tuple))->attgenerated)
    +		ereport(ERROR,
    +				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
    +				 errmsg("cannot use DROP NOT NULL on generated column \"%s\"",
    +						colName)));
    +
     	if (get_attidentity(RelationGetRelid(rel), attnum))
     		ereport(ERROR,
     				(errcode(ERRCODE_SYNTAX_ERROR),
    @@ -5797,9 +5823,6 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
     
     /*
      * ALTER TABLE ALTER COLUMN SET NOT NULL
    - *
    - * Return the address of the modified column.  If the column was already NOT
    - * NULL, InvalidObjectAddress is returned.
      */
     
     static void
    @@ -5822,6 +5845,10 @@ ATPrepSetNotNull(Relation rel, bool recurse, bool recursing)
     	}
     }
     
    +/*
    + * Return the address of the modified column.  If the column was already NOT
    + * NULL, InvalidObjectAddress is returned.
    + */
     static ObjectAddress
     ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
     				 const char *colName, LOCKMODE lockmode)
    @@ -5853,6 +5880,17 @@ ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
     				 errmsg("cannot alter system column \"%s\"",
     						colName)));
     
    +	/*
    +	 * XXX We might want to convert this to a CHECK constraint like we do in
    +	 * transformColumnDefinition().
    +	 */
    +	if (((Form_pg_attribute) GETSTRUCT(tuple))->attgenerated)
    +		ereport(ERROR,
    +				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
    +				 errmsg("cannot use SET NOT NULL on generated column \"%s\"",
    +						colName),
    +				 errhint("Add a CHECK constraint instead.")));
    +
     	/*
     	 * Okay, actually perform the catalog change ... if needed
     	 */
    @@ -5915,6 +5953,12 @@ ATExecColumnDefault(Relation rel, const char *colName,
     						colName, RelationGetRelationName(rel)),
     				 newDefault ? 0 : errhint("Use ALTER TABLE ... ALTER COLUMN ... DROP IDENTITY instead.")));
     
    +	if (get_attgenerated(RelationGetRelid(rel), attnum))
    +		ereport(ERROR,
    +				(errcode(ERRCODE_SYNTAX_ERROR),
    +				 errmsg("column \"%s\" of relation \"%s\" is a generated column",
    +						colName, RelationGetRelationName(rel))));
    +
     	/*
     	 * Remove any old default for the column.  We use RESTRICT here for
     	 * safety, but at present we do not expect anything to depend on the
    @@ -7184,6 +7228,41 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
     	 */
     	checkFkeyPermissions(pkrel, pkattnum, numpks);
     
    +	/*
    +	 * Foreign keys on generated columns are not yet implemented.
    +	 */
    +	for (i = 0; i < numpks; i++)
    +	{
    +		if (get_attgenerated(RelationGetRelid(pkrel), pkattnum[i]))
    +			ereport(ERROR,
    +					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
    +					 errmsg("foreign key constraints referencing generated columns are not supported")));
    +	}
    +	for (i = 0; i < numfks; i++)
    +	{
    +		if (get_attgenerated(RelationGetRelid(rel), fkattnum[i]))
    +		{
    +			/*
    +			 * Check restrictions on UPDATE/DELETE actions, per SQL standard
    +			 */
    +			if (fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETNULL ||
    +				fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETDEFAULT ||
    +				fkconstraint->fk_upd_action == FKCONSTR_ACTION_CASCADE)
    +				ereport(ERROR,
    +						(errcode(ERRCODE_SYNTAX_ERROR),
    +						 errmsg("invalid ON UPDATE action for foreign key constraint containing generated column")));
    +			if (fkconstraint->fk_del_action == FKCONSTR_ACTION_SETNULL ||
    +				fkconstraint->fk_del_action == FKCONSTR_ACTION_SETDEFAULT)
    +				ereport(ERROR,
    +						(errcode(ERRCODE_SYNTAX_ERROR),
    +						 errmsg("invalid ON DELETE action for foreign key constraint containing generated column")));
    +
    +			ereport(ERROR,
    +					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
    +					 errmsg("foreign key constraints on generated columns are not supported")));
    +		}
    +	}
    +
     	/*
     	 * Look up the equality operators to use in the constraint.
     	 *
    @@ -8805,8 +8884,9 @@ ATPrepAlterColumnType(List **wqueue,
     					   list_make1_oid(rel->rd_rel->reltype),
     					   false);
     
    -	if (tab->relkind == RELKIND_RELATION ||
    -		tab->relkind == RELKIND_PARTITIONED_TABLE)
    +	if ((tab->relkind == RELKIND_RELATION ||
    +		 tab->relkind == RELKIND_PARTITIONED_TABLE) &&
    +		get_attgenerated(RelationGetRelid(rel), attnum) != ATTRIBUTE_GENERATED_VIRTUAL)
     	{
     		/*
     		 * Set up an expression to transform the old data value to the new
    @@ -9080,10 +9160,18 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
     											COERCE_IMPLICIT_CAST,
     											-1);
     		if (defaultexpr == NULL)
    -			ereport(ERROR,
    -					(errcode(ERRCODE_DATATYPE_MISMATCH),
    -					 errmsg("default for column \"%s\" cannot be cast automatically to type %s",
    -							colName, format_type_be(targettype))));
    +		{
    +			if (get_attgenerated(RelationGetRelid(rel), attnum))
    +				ereport(ERROR,
    +						(errcode(ERRCODE_DATATYPE_MISMATCH),
    +						 errmsg("generation expression for column \"%s\" cannot be cast automatically to type %s",
    +								colName, format_type_be(targettype))));
    +			else
    +				ereport(ERROR,
    +						(errcode(ERRCODE_DATATYPE_MISMATCH),
    +						 errmsg("default for column \"%s\" cannot be cast automatically to type %s",
    +								colName, format_type_be(targettype))));
    +		}
     	}
     	else
     		defaultexpr = NULL;
    @@ -9158,6 +9246,21 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
     						 */
     						Assert(foundObject.objectSubId == 0);
     					}
    +					else if (relKind == RELKIND_RELATION &&
    +							 foundObject.objectSubId != 0 &&
    +							 get_attgenerated(foundObject.objectId, foundObject.objectSubId))
    +					{
    +						/*
    +						 * Changing the type of a column that is used by a
    +						 * generated column is not allowed by SQL standard.
    +						 * It might be doable with some thinking and effort.
    +						 */
    +						ereport(ERROR,
    +								(errcode(ERRCODE_SYNTAX_ERROR),
    +								 errmsg("cannot alter type of a column used by a generated column"),
    +								 errdetail("Column \"%s\" is used by generated column \"%s\".",
    +										   colName, get_attname(foundObject.objectId, foundObject.objectSubId))));
    +					}
     					else
     					{
     						/* Not expecting any other direct dependencies... */
    @@ -9323,7 +9426,8 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
     	/*
     	 * Now scan for dependencies of this column on other things.  The only
     	 * thing we should find is the dependency on the column datatype, which we
    -	 * want to remove, and possibly a collation dependency.
    +	 * want to remove, possibly a collation dependency, and dependencies on
    +	 * other columns if it is a generated column.
     	 */
     	ScanKeyInit(&key[0],
     				Anum_pg_depend_classid,
    @@ -9344,15 +9448,26 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
     	while (HeapTupleIsValid(depTup = systable_getnext(scan)))
     	{
     		Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(depTup);
    +		ObjectAddress foundObject;
    +
    +		foundObject.classId = foundDep->refclassid;
    +		foundObject.objectId = foundDep->refobjid;
    +		foundObject.objectSubId = foundDep->refobjsubid;
     
    -		if (foundDep->deptype != DEPENDENCY_NORMAL)
    +		if (foundDep->deptype != DEPENDENCY_NORMAL &&
    +			foundDep->deptype != DEPENDENCY_AUTO)
     			elog(ERROR, "found unexpected dependency type '%c'",
     				 foundDep->deptype);
     		if (!(foundDep->refclassid == TypeRelationId &&
     			  foundDep->refobjid == attTup->atttypid) &&
     			!(foundDep->refclassid == CollationRelationId &&
    -			  foundDep->refobjid == attTup->attcollation))
    -			elog(ERROR, "found unexpected dependency for column");
    +			  foundDep->refobjid == attTup->attcollation) &&
    +			!(foundDep->refclassid == RelationRelationId &&
    +			  foundDep->refobjid == RelationGetRelid(rel) &&
    +			  foundDep->refobjsubid != 0)
    +			)
    +			elog(ERROR, "found unexpected dependency for column: %s",
    +				 getObjectDescription(&foundObject));
     
     		CatalogTupleDelete(depRel, &depTup->t_self);
     	}
    diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
    index 1c488c338a..ff44dcaa26 100644
    --- a/src/backend/commands/trigger.c
    +++ b/src/backend/commands/trigger.c
    @@ -43,6 +43,7 @@
     #include "parser/parse_relation.h"
     #include "parser/parsetree.h"
     #include "pgstat.h"
    +#include "rewrite/rewriteHandler.h"
     #include "rewrite/rewriteManip.h"
     #include "storage/bufmgr.h"
     #include "storage/lmgr.h"
    @@ -101,6 +102,7 @@ static void AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
     					  TransitionCaptureState *transition_capture);
     static void AfterTriggerEnlargeQueryState(void);
     static bool before_stmt_triggers_fired(Oid relid, CmdType cmdType);
    +static void check_modified_virtual_generated(TupleDesc tupdesc, HeapTuple tuple);
     
     
     /*
    @@ -562,6 +564,11 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
     								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     								 errmsg("BEFORE trigger's WHEN condition cannot reference NEW system columns"),
     								 parser_errposition(pstate, var->location)));
    +					if (get_attgenerated(RelationGetRelid(rel), var->varattno) && TRIGGER_FOR_BEFORE(tgtype))
    +						ereport(ERROR,
    +								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
    +								 errmsg("BEFORE trigger's WHEN condition cannot reference NEW generated columns"),
    +								 parser_errposition(pstate, var->location)));
     					break;
     				default:
     					/* can't happen without add_missing_from, so just elog */
    @@ -2342,6 +2349,8 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
     		TupleTableSlot *newslot = estate->es_trig_tuple_slot;
     		TupleDesc	tupdesc = RelationGetDescr(relinfo->ri_RelationDesc);
     
    +		check_modified_virtual_generated(tupdesc, newtuple);
    +
     		if (newslot->tts_tupleDescriptor != tupdesc)
     			ExecSetSlotDescriptor(newslot, tupdesc);
     		ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
    @@ -2829,6 +2838,8 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
     		TupleTableSlot *newslot = estate->es_trig_tuple_slot;
     		TupleDesc	tupdesc = RelationGetDescr(relinfo->ri_RelationDesc);
     
    +		check_modified_virtual_generated(tupdesc, newtuple);
    +
     		if (newslot->tts_tupleDescriptor != tupdesc)
     			ExecSetSlotDescriptor(newslot, tupdesc);
     		ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
    @@ -3221,6 +3232,7 @@ TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
     
     			oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
     			tgqual = stringToNode(trigger->tgqual);
    +			tgqual = (Node *) expand_generated_columns_in_expr((Expr *) tgqual, relinfo->ri_RelationDesc);
     			/* Change references to OLD and NEW to INNER_VAR and OUTER_VAR */
     			ChangeVarNodes(tgqual, PRS2_OLD_VARNO, INNER_VAR, 0);
     			ChangeVarNodes(tgqual, PRS2_NEW_VARNO, OUTER_VAR, 0);
    @@ -5839,3 +5851,27 @@ pg_trigger_depth(PG_FUNCTION_ARGS)
     {
     	PG_RETURN_INT32(MyTriggerDepth);
     }
    +
    +/*
    + * Check whether a trigger modified a virtual generated column and error if
    + * so.
    + */
    +static void
    +check_modified_virtual_generated(TupleDesc tupdesc, HeapTuple tuple)
    +{
    +	int			i;
    +
    +	for (i = 0; i < tupdesc->natts; i++)
    +	{
    +		if (TupleDescAttr(tupdesc, i)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
    +		{
    +			bool		isnull;
    +
    +			fastgetattr(tuple, i + 1, tupdesc, &isnull);
    +			if (!isnull)
    +				ereport(ERROR,
    +						(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
    +						 errmsg("trigger modified virtual generated column value")));
    +		}
    +	}
    +}
    diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
    index a40b3cf752..11c088711b 100644
    --- a/src/backend/commands/typecmds.c
    +++ b/src/backend/commands/typecmds.c
    @@ -917,7 +917,8 @@ DefineDomain(CreateDomainStmt *stmt)
     					defaultExpr = cookDefault(pstate, constr->raw_expr,
     											  basetypeoid,
     											  basetypeMod,
    -											  domainName);
    +											  domainName,
    +											  0);
     
     					/*
     					 * If the expression is just a NULL constant, we treat it
    @@ -2244,7 +2245,8 @@ AlterDomainDefault(List *names, Node *defaultRaw)
     		defaultExpr = cookDefault(pstate, defaultRaw,
     								  typTup->typbasetype,
     								  typTup->typtypmod,
    -								  NameStr(typTup->typname));
    +								  NameStr(typTup->typname),
    +								  0);
     
     		/*
     		 * If the expression is just a NULL constant, we treat the command
    diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
    index 16822e962a..177afc69f5 100644
    --- a/src/backend/executor/execMain.c
    +++ b/src/backend/executor/execMain.c
    @@ -52,7 +52,7 @@
     #include "miscadmin.h"
     #include "optimizer/clauses.h"
     #include "parser/parsetree.h"
    -#include "rewrite/rewriteManip.h"
    +#include "rewrite/rewriteHandler.h"
     #include "storage/bufmgr.h"
     #include "storage/lmgr.h"
     #include "tcop/utility.h"
    @@ -1812,6 +1812,7 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
     			Expr	   *checkconstr;
     
     			checkconstr = stringToNode(check[i].ccbin);
    +			checkconstr = expand_generated_columns_in_expr(checkconstr, rel);
     			resultRelInfo->ri_ConstraintExprs[i] =
     				ExecPrepareExpr(checkconstr, estate);
     		}
    @@ -2290,6 +2291,10 @@ ExecBuildSlotValueDescription(Oid reloid,
     		if (att->attisdropped)
     			continue;
     
    +		/* ignore virtual generated columns; they are always null here */
    +		if (att->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
    +			continue;
    +
     		if (!table_perm)
     		{
     			/*
    diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
    index ddbbc79823..deb0265243 100644
    --- a/src/backend/nodes/copyfuncs.c
    +++ b/src/backend/nodes/copyfuncs.c
    @@ -2817,6 +2817,7 @@ _copyColumnDef(const ColumnDef *from)
     	COPY_NODE_FIELD(raw_default);
     	COPY_NODE_FIELD(cooked_default);
     	COPY_SCALAR_FIELD(identity);
    +	COPY_SCALAR_FIELD(generated);
     	COPY_NODE_FIELD(collClause);
     	COPY_SCALAR_FIELD(collOid);
     	COPY_NODE_FIELD(constraints);
    @@ -2840,6 +2841,7 @@ _copyConstraint(const Constraint *from)
     	COPY_NODE_FIELD(raw_expr);
     	COPY_STRING_FIELD(cooked_expr);
     	COPY_SCALAR_FIELD(generated_when);
    +	COPY_SCALAR_FIELD(generated_kind);
     	COPY_NODE_FIELD(keys);
     	COPY_NODE_FIELD(exclusions);
     	COPY_NODE_FIELD(options);
    diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
    index 30ccc9c5ae..fdb05aa035 100644
    --- a/src/backend/nodes/equalfuncs.c
    +++ b/src/backend/nodes/equalfuncs.c
    @@ -2563,6 +2563,7 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
     	COMPARE_NODE_FIELD(raw_default);
     	COMPARE_NODE_FIELD(cooked_default);
     	COMPARE_SCALAR_FIELD(identity);
    +	COMPARE_SCALAR_FIELD(generated);
     	COMPARE_NODE_FIELD(collClause);
     	COMPARE_SCALAR_FIELD(collOid);
     	COMPARE_NODE_FIELD(constraints);
    @@ -2584,6 +2585,7 @@ _equalConstraint(const Constraint *a, const Constraint *b)
     	COMPARE_NODE_FIELD(raw_expr);
     	COMPARE_STRING_FIELD(cooked_expr);
     	COMPARE_SCALAR_FIELD(generated_when);
    +	COMPARE_SCALAR_FIELD(generated_kind);
     	COMPARE_NODE_FIELD(keys);
     	COMPARE_NODE_FIELD(exclusions);
     	COMPARE_NODE_FIELD(options);
    diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
    index 5e72df137e..65ca5ba1be 100644
    --- a/src/backend/nodes/outfuncs.c
    +++ b/src/backend/nodes/outfuncs.c
    @@ -2810,6 +2810,7 @@ _outColumnDef(StringInfo str, const ColumnDef *node)
     	WRITE_NODE_FIELD(raw_default);
     	WRITE_NODE_FIELD(cooked_default);
     	WRITE_CHAR_FIELD(identity);
    +	WRITE_CHAR_FIELD(generated);
     	WRITE_NODE_FIELD(collClause);
     	WRITE_OID_FIELD(collOid);
     	WRITE_NODE_FIELD(constraints);
    @@ -3461,6 +3462,14 @@ _outConstraint(StringInfo str, const Constraint *node)
     			WRITE_CHAR_FIELD(generated_when);
     			break;
     
    +		case CONSTR_GENERATED:
    +			appendStringInfoString(str, "GENERATED");
    +			WRITE_NODE_FIELD(raw_expr);
    +			WRITE_STRING_FIELD(cooked_expr);
    +			WRITE_CHAR_FIELD(generated_when);
    +			WRITE_CHAR_FIELD(generated_kind);
    +			break;
    +
     		case CONSTR_CHECK:
     			appendStringInfoString(str, "CHECK");
     			WRITE_BOOL_FIELD(is_no_inherit);
    diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
    index e42b7caff6..40d79cae01 100644
    --- a/src/backend/parser/gram.y
    +++ b/src/backend/parser/gram.y
    @@ -572,7 +572,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
     				opt_frame_clause frame_extent frame_bound
     %type <str>		opt_existing_window_name
     %type <boolean> opt_if_not_exists
    -%type <ival>	generated_when override_kind
    +%type <ival>	generated_when override_kind opt_virtual_or_stored
     %type <partspec>	PartitionSpec OptPartitionSpec
     %type <str>			part_strategy
     %type <partelem>	part_elem
    @@ -672,7 +672,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
     	SAVEPOINT SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
     	SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
     	SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
    -	START STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P
    +	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRIP_P
     	SUBSCRIPTION SUBSTRING SYMMETRIC SYSID SYSTEM_P
     
     	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
    @@ -683,7 +683,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
     	UNTIL UPDATE USER USING
     
     	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
    -	VERBOSE VERSION_P VIEW VIEWS VOLATILE
    +	VERBOSE VERSION_P VIEW VIEWS VIRTUAL VOLATILE
     
     	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
     
    @@ -3521,6 +3521,17 @@ ColConstraintElem:
     					n->location = @1;
     					$$ = (Node *)n;
     				}
    +			| GENERATED generated_when AS '(' a_expr ')' opt_virtual_or_stored
    +				{
    +					Constraint *n = makeNode(Constraint);
    +					n->contype = CONSTR_GENERATED;
    +					n->generated_when = $2;
    +					n->raw_expr = $5;
    +					n->cooked_expr = NULL;
    +					n->generated_kind = $7;
    +					n->location = @1;
    +					$$ = (Node *)n;
    +				}
     			| REFERENCES qualified_name opt_column_list key_match key_actions
     				{
     					Constraint *n = makeNode(Constraint);
    @@ -3543,6 +3554,12 @@ generated_when:
     			| BY DEFAULT	{ $$ = ATTRIBUTE_IDENTITY_BY_DEFAULT; }
     		;
     
    +opt_virtual_or_stored:
    +			STORED			{ $$ = ATTRIBUTE_GENERATED_STORED; }
    +			| VIRTUAL		{ $$ = ATTRIBUTE_GENERATED_VIRTUAL; }
    +			| /*EMPTY*/		{ $$ = ATTRIBUTE_GENERATED_VIRTUAL; }
    +		;
    +
     /*
      * ConstraintAttr represents constraint attributes, which we parse as if
      * they were independent constraint clauses, in order to avoid shift/reduce
    @@ -3610,6 +3627,7 @@ TableLikeOption:
     				DEFAULTS			{ $$ = CREATE_TABLE_LIKE_DEFAULTS; }
     				| CONSTRAINTS		{ $$ = CREATE_TABLE_LIKE_CONSTRAINTS; }
     				| IDENTITY_P		{ $$ = CREATE_TABLE_LIKE_IDENTITY; }
    +				| GENERATED			{ $$ = CREATE_TABLE_LIKE_GENERATED; }
     				| INDEXES			{ $$ = CREATE_TABLE_LIKE_INDEXES; }
     				| STORAGE			{ $$ = CREATE_TABLE_LIKE_STORAGE; }
     				| COMMENTS			{ $$ = CREATE_TABLE_LIKE_COMMENTS; }
    @@ -15144,6 +15162,7 @@ unreserved_keyword:
     			| STDIN
     			| STDOUT
     			| STORAGE
    +			| STORED
     			| STRICT_P
     			| STRIP_P
     			| SUBSCRIPTION
    @@ -15179,6 +15198,7 @@ unreserved_keyword:
     			| VERSION_P
     			| VIEW
     			| VIEWS
    +			| VIRTUAL
     			| VOLATILE
     			| WHITESPACE_P
     			| WITHIN
    diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
    index 6a9f1b0217..c82feb82ba 100644
    --- a/src/backend/parser/parse_agg.c
    +++ b/src/backend/parser/parse_agg.c
    @@ -507,6 +507,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
     				err = _("grouping operations are not allowed in partition key expression");
     
     			break;
    +		case EXPR_KIND_GENERATED_COLUMN:
    +
    +			if (isAgg)
    +				err = _("aggregate functions are not allowed in column generation expressions");
    +			else
    +				err = _("grouping operations are not allowed in column generation expressions");
    +
    +			break;
     
     		case EXPR_KIND_CALL:
     			if (isAgg)
    @@ -894,6 +902,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
     		case EXPR_KIND_CALL:
     			err = _("window functions are not allowed in CALL arguments");
     			break;
    +		case EXPR_KIND_GENERATED_COLUMN:
    +			err = _("window functions are not allowed in column generation expressions");
    +			break;
     
     			/*
     			 * There is intentionally no default: case here, so that the
    diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
    index b2f5e46e3b..78826b1dee 100644
    --- a/src/backend/parser/parse_expr.c
    +++ b/src/backend/parser/parse_expr.c
    @@ -1846,6 +1846,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
     		case EXPR_KIND_PARTITION_EXPRESSION:
     			err = _("cannot use subquery in partition key expression");
     			break;
    +		case EXPR_KIND_GENERATED_COLUMN:
    +			err = _("cannot use subquery in column generation expression");
    +			break;
     
     			/*
     			 * There is intentionally no default: case here, so that the
    @@ -3470,6 +3473,8 @@ ParseExprKindName(ParseExprKind exprKind)
     			return "PARTITION BY";
     		case EXPR_KIND_CALL:
     			return "CALL";
    +		case EXPR_KIND_GENERATED_COLUMN:
    +			return "GENERATED AS";
     
     			/*
     			 * There is intentionally no default: case here, so that the
    diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
    index ffae0f3cf3..b2f0414f38 100644
    --- a/src/backend/parser/parse_func.c
    +++ b/src/backend/parser/parse_func.c
    @@ -557,6 +557,15 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
     					 parser_errposition(pstate, location)));
     	}
     
    +	if (pstate->p_expr_kind == EXPR_KIND_GENERATED_COLUMN &&
    +		func_volatile(funcid) != PROVOLATILE_IMMUTABLE)
    +		ereport(ERROR,
    +				(errcode(ERRCODE_SYNTAX_ERROR),
    +				 errmsg("cannot use function %s in column generation expression",
    +						func_signature_string(funcname, nargs, argnames, actual_arg_types)),
    +				 errdetail("Functions used in a column generation expression must be immutable."),
    +				 parser_errposition(pstate, location)));
    +
     	/*
     	 * If there are default arguments, we have to include their types in
     	 * actual_arg_types for the purpose of checking generic type consistency.
    @@ -2292,6 +2301,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
     		case EXPR_KIND_CALL:
     			err = _("set-returning functions are not allowed in CALL arguments");
     			break;
    +		case EXPR_KIND_GENERATED_COLUMN:
    +			err = _("set-returning functions are not allowed in column generation expressions");
    +			break;
     
     			/*
     			 * There is intentionally no default: case here, so that the
    diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
    index 128f1679c6..126470156c 100644
    --- a/src/backend/parser/parse_utilcmd.c
    +++ b/src/backend/parser/parse_utilcmd.c
    @@ -509,6 +509,7 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
     	bool		saw_nullable;
     	bool		saw_default;
     	bool		saw_identity;
    +	bool		saw_generated;
     	ListCell   *clist;
     
     	cxt->columns = lappend(cxt->columns, column);
    @@ -616,6 +617,7 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
     	saw_nullable = false;
     	saw_default = false;
     	saw_identity = false;
    +	saw_generated = false;
     
     	foreach(clist, column->constraints)
     	{
    @@ -696,6 +698,41 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
     					break;
     				}
     
    +			case CONSTR_GENERATED:
    +				if (saw_generated)
    +					ereport(ERROR,
    +							(errcode(ERRCODE_SYNTAX_ERROR),
    +							 errmsg("multiple generation clauses specified for column \"%s\" of table \"%s\"",
    +									column->colname, cxt->relation->relname),
    +							 parser_errposition(cxt->pstate,
    +												constraint->location)));
    +				column->generated = constraint->generated_kind;
    +				column->raw_default = constraint->raw_expr;
    +				Assert(constraint->cooked_expr == NULL);
    +				saw_generated = true;
    +
    +				/*
    +				 * Prevent virtual generated columns from having a domain
    +				 * type.  We would have to enforce domain constraints when
    +				 * columns underlying the generated column change.  This could
    +				 * possibly be implemented, but it's not.
    +				 */
    +				if (column->generated == ATTRIBUTE_GENERATED_VIRTUAL)
    +				{
    +					Type		ctype;
    +
    +					ctype = typenameType(cxt->pstate, column->typeName, NULL);
    +					if (((Form_pg_type) GETSTRUCT(ctype))->typtype == TYPTYPE_DOMAIN)
    +						ereport(ERROR,
    +								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
    +								 errmsg("virtual generated column \"%s\" cannot have a domain type",
    +										column->colname),
    +								 parser_errposition(cxt->pstate,
    +													column->location)));
    +					ReleaseSysCache(ctype);
    +				}
    +				break;
    +
     			case CONSTR_CHECK:
     				cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
     				break;
    @@ -780,6 +817,50 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
     							column->colname, cxt->relation->relname),
     					 parser_errposition(cxt->pstate,
     										constraint->location)));
    +
    +		if (saw_default && saw_generated)
    +			ereport(ERROR,
    +					(errcode(ERRCODE_SYNTAX_ERROR),
    +					 errmsg("both default and generation expression specified for column \"%s\" of table \"%s\"",
    +							column->colname, cxt->relation->relname),
    +					 parser_errposition(cxt->pstate,
    +										constraint->location)));
    +
    +		if (saw_identity && saw_generated)
    +			ereport(ERROR,
    +					(errcode(ERRCODE_SYNTAX_ERROR),
    +					 errmsg("both identity and generation expression specified for column \"%s\" of table \"%s\"",
    +							column->colname, cxt->relation->relname),
    +					 parser_errposition(cxt->pstate,
    +										constraint->location)));
    +
    +		/*
    +		 * For a generated column, convert the not-null constraint into a full
    +		 * check constraint, so that the generation expression can be expanded
    +		 * at check time.
    +		 */
    +		if (column->is_not_null && column->generated)
    +		{
    +			Constraint *chk = makeNode(Constraint);
    +			NullTest *nt = makeNode(NullTest);
    +			ColumnRef *cr = makeNode(ColumnRef);
    +
    +			cr->location = -1;
    +			cr->fields = list_make1(makeString(column->colname));
    +
    +			nt->arg = (Expr *) cr;
    +			nt->nulltesttype = IS_NOT_NULL;
    +			nt->location = -1;
    +
    +			chk->contype = CONSTR_CHECK;
    +			chk->location = -1;
    +			chk->initially_valid = true;
    +			chk->raw_expr = (Node *) nt;
    +
    +			cxt->ckconstraints = lappend(cxt->ckconstraints, chk);
    +
    +			column->is_not_null = false;
    +		}
     	}
     
     	/*
    @@ -1027,7 +1108,8 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
     		 * Copy default, if present and the default has been requested
     		 */
     		if (attribute->atthasdef &&
    -			(table_like_clause->options & CREATE_TABLE_LIKE_DEFAULTS))
    +			(table_like_clause->options & CREATE_TABLE_LIKE_DEFAULTS ||
    +			 table_like_clause->options & CREATE_TABLE_LIKE_GENERATED))
     		{
     			Node	   *this_default = NULL;
     			AttrDefault *attrdef;
    @@ -1052,6 +1134,9 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
     			 */
     
     			def->cooked_default = this_default;
    +			if (attribute->attgenerated &&
    +				(table_like_clause->options & CREATE_TABLE_LIKE_GENERATED))
    +				def->generated = attribute->attgenerated;
     		}
     
     		/*
    diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
    index 32e3798972..bbaf7b54f4 100644
    --- a/src/backend/rewrite/rewriteHandler.c
    +++ b/src/backend/rewrite/rewriteHandler.c
    @@ -20,6 +20,7 @@
      */
     #include "postgres.h"
     
    +#include "access/htup_details.h"
     #include "access/sysattr.h"
     #include "catalog/dependency.h"
     #include "catalog/pg_type.h"
    @@ -37,6 +38,7 @@
     #include "utils/builtins.h"
     #include "utils/lsyscache.h"
     #include "utils/rel.h"
    +#include "utils/syscache.h"
     
     
     /* We use a list of these to detect recursion in RewriteQuery */
    @@ -828,6 +830,13 @@ rewriteTargetListIU(List *targetList,
     
     			if (att_tup->attidentity == ATTRIBUTE_IDENTITY_BY_DEFAULT && override == OVERRIDING_USER_VALUE)
     				apply_default = true;
    +
    +			if (att_tup->attgenerated && !apply_default)
    +				ereport(ERROR,
    +						(errcode(ERRCODE_SYNTAX_ERROR),
    +						 errmsg("cannot insert into column \"%s\"", NameStr(att_tup->attname)),
    +						 errdetail("Column \"%s\" is a generated column.",
    +								   NameStr(att_tup->attname))));
     		}
     
     		if (commandType == CMD_UPDATE)
    @@ -838,9 +847,28 @@ rewriteTargetListIU(List *targetList,
     						 errmsg("column \"%s\" can only be updated to DEFAULT", NameStr(att_tup->attname)),
     						 errdetail("Column \"%s\" is an identity column defined as GENERATED ALWAYS.",
     								   NameStr(att_tup->attname))));
    +
    +			if (att_tup->attgenerated && new_tle && !apply_default)
    +				ereport(ERROR,
    +						(errcode(ERRCODE_SYNTAX_ERROR),
    +						 errmsg("column \"%s\" can only be updated to DEFAULT", NameStr(att_tup->attname)),
    +						 errdetail("Column \"%s\" is a generated column.",
    +								   NameStr(att_tup->attname))));
     		}
     
    -		if (apply_default)
    +		if (att_tup->attgenerated)
    +		{
    +			/*
    +			 * virtual generated column stores a null value
    +			 */
    +			new_tle = NULL;
    +
    +			if (att_tup->attgenerated == ATTRIBUTE_GENERATED_STORED)
    +				ereport(ERROR,
    +						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
    +						 errmsg("stored generated columns are not yet implemented")));
    +		}
    +		else if (apply_default)
     		{
     			Node	   *new_expr;
     
    @@ -1144,13 +1172,12 @@ build_column_default(Relation rel, int attrno)
     		}
     	}
     
    -	if (expr == NULL)
    -	{
    -		/*
    -		 * No per-column default, so look for a default for the type itself.
    -		 */
    +	/*
    +	 * No per-column default, so look for a default for the type itself.  But
    +	 * not for generated columns.
    +	 */
    +	if (expr == NULL && !att_tup->attgenerated)
     		expr = get_typdefault(atttype);
    -	}
     
     	if (expr == NULL)
     		return NULL;			/* No default anywhere */
    @@ -3551,6 +3578,96 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
     }
     
     
    +static Node *
    +expand_generated_columns_in_expr_mutator(Node *node, Relation rel)
    +{
    +	if (node == NULL)
    +		return NULL;
    +
    +	if (IsA(node, Var))
    +	{
    +		Var *v = (Var *) node;
    +		Oid			relid = RelationGetRelid(rel);
    +		AttrNumber	attnum = v->varattno;
    +
    +		if (relid && attnum && get_attgenerated(relid, attnum) == ATTRIBUTE_GENERATED_VIRTUAL)
    +		{
    +			node = build_column_default(rel, attnum);
    +			ChangeVarNodes(node, 1, v->varno, 0);
    +		}
    +
    +		return node;
    +	}
    +	else
    +		return expression_tree_mutator(node, expand_generated_columns_in_expr_mutator, rel);
    +}
    +
    +
    +Expr *
    +expand_generated_columns_in_expr(Expr *expr, Relation rel)
    +{
    +	return (Expr *) expression_tree_mutator((Node *) expr,
    +											expand_generated_columns_in_expr_mutator,
    +											rel);
    +}
    +
    +typedef struct
    +{
    +	/* list of range tables, innermost last */
    +	List       *rtables;
    +} expand_generated_context;
    +
    +static Node *
    +expand_generated_columns_in_query_mutator(Node *node, expand_generated_context *context)
    +{
    +	if (node == NULL)
    +		return NULL;
    +
    +	if (IsA(node, Var))
    +	{
    +		Var *v = (Var *) node;
    +		Oid			relid;
    +		AttrNumber	attnum;
    +		List	   *rtable = list_nth_node(List,
    +										   context->rtables,
    +										   list_length(context->rtables) - v->varlevelsup - 1);
    +
    +		relid = getrelid(v->varno, rtable);
    +		attnum = v->varattno;
    +
    +		if (!relid || !attnum)
    +			return node;
    +
    +		if (get_attgenerated(relid, attnum) == ATTRIBUTE_GENERATED_VIRTUAL)
    +		{
    +			Relation rt_entry_relation = heap_open(relid, NoLock);
    +
    +			node = build_column_default(rt_entry_relation, attnum);
    +			ChangeVarNodes(node, 1, v->varno, v->varlevelsup);
    +
    +			heap_close(rt_entry_relation, NoLock);
    +		}
    +
    +		return node;
    +	}
    +	else if (IsA(node, Query))
    +	{
    +		Query	   *query = (Query *) node;
    +		Node	   *result;
    +
    +		context->rtables = lappend(context->rtables, query->rtable);
    +		result = (Node *) query_tree_mutator(query,
    +											 expand_generated_columns_in_query_mutator,
    +											 context,
    +											 QTW_DONT_COPY_QUERY);
    +		context->rtables = list_truncate(context->rtables, list_length(context->rtables) - 1);
    +		return result;
    +	}
    +	else
    +		return expression_tree_mutator(node, expand_generated_columns_in_query_mutator, context);
    +}
    +
    +
     /*
      * QueryRewrite -
      *	  Primary entry point to the query rewriter.
    @@ -3606,6 +3723,24 @@ QueryRewrite(Query *parsetree)
     	/*
     	 * Step 3
     	 *
    +	 * Expand generated columns.
    +	 */
    +	foreach(l, querylist)
    +	{
    +		Query	   *query = (Query *) lfirst(l);
    +		expand_generated_context context;
    +
    +		context.rtables = list_make1(query->rtable);
    +
    +		query = query_tree_mutator(query,
    +								   expand_generated_columns_in_query_mutator,
    +								   &context,
    +								   QTW_DONT_COPY_QUERY);
    +	}
    +
    +	/*
    +	 * Step 4
    +	 *
     	 * Determine which, if any, of the resulting queries is supposed to set
     	 * the command-result tag; and update the canSetTag fields accordingly.
     	 *
    diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
    index e8aa179347..b5dd12122e 100644
    --- a/src/backend/utils/cache/lsyscache.c
    +++ b/src/backend/utils/cache/lsyscache.c
    @@ -836,6 +836,38 @@ get_attnum(Oid relid, const char *attname)
     		return InvalidAttrNumber;
     }
     
    +/*
    + * get_attgenerated
    + *
    + *		Given the relation id and the attribute name,
    + *		return the "attgenerated" field from the attribute relation.
    + *
    + *		Returns '\0' if not found.
    + *
    + *		Since not generated is represented by '\0', this can also be used as a
    + *		Boolean test.
    + */
    +char
    +get_attgenerated(Oid relid, AttrNumber attnum)
    +{
    +	HeapTuple	tp;
    +
    +	tp = SearchSysCache2(ATTNUM,
    +						 ObjectIdGetDatum(relid),
    +						 Int16GetDatum(attnum));
    +	if (HeapTupleIsValid(tp))
    +	{
    +		Form_pg_attribute att_tup = (Form_pg_attribute) GETSTRUCT(tp);
    +		char		result;
    +
    +		result = att_tup->attgenerated;
    +		ReleaseSysCache(tp);
    +		return result;
    +	}
    +	else
    +		return '\0';
    +}
    +
     /*
      * get_attidentity
      *
    diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
    index 00ba33bfb4..91d797ba0d 100644
    --- a/src/backend/utils/cache/relcache.c
    +++ b/src/backend/utils/cache/relcache.c
    @@ -3238,6 +3238,7 @@ RelationBuildLocalRelation(const char *relname,
     		Form_pg_attribute datt = TupleDescAttr(rel->rd_att, i);
     
     		datt->attidentity = satt->attidentity;
    +		datt->attgenerated = satt->attgenerated;
     		datt->attnotnull = satt->attnotnull;
     		has_not_null |= satt->attnotnull;
     	}
    diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
    index 27628a397c..7bda2330ab 100644
    --- a/src/bin/pg_dump/pg_dump.c
    +++ b/src/bin/pg_dump/pg_dump.c
    @@ -1973,6 +1973,11 @@ dumpTableData_insert(Archive *fout, void *dcontext)
     			{
     				if (field > 0)
     					archputs(", ", fout);
    +				if (tbinfo->attgenerated[field])
    +				{
    +					archputs("DEFAULT", fout);
    +					continue;
    +				}
     				if (PQgetisnull(res, tuple, field))
     				{
     					archputs("NULL", fout);
    @@ -7888,6 +7893,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
     	int			i_attnotnull;
     	int			i_atthasdef;
     	int			i_attidentity;
    +	int			i_attgenerated;
     	int			i_attisdropped;
     	int			i_attlen;
     	int			i_attalign;
    @@ -7944,6 +7950,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
     							  "CASE WHEN a.attcollation <> t.typcollation "
     							  "THEN a.attcollation ELSE 0 END AS attcollation, "
     							  "a.attidentity, "
    +							  "a.attgenerated, "
     							  "pg_catalog.array_to_string(ARRAY("
     							  "SELECT pg_catalog.quote_ident(option_name) || "
     							  "' ' || pg_catalog.quote_literal(option_value) "
    @@ -8057,6 +8064,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
     		i_attnotnull = PQfnumber(res, "attnotnull");
     		i_atthasdef = PQfnumber(res, "atthasdef");
     		i_attidentity = PQfnumber(res, "attidentity");
    +		i_attgenerated = PQfnumber(res, "attgenerated");
     		i_attisdropped = PQfnumber(res, "attisdropped");
     		i_attlen = PQfnumber(res, "attlen");
     		i_attalign = PQfnumber(res, "attalign");
    @@ -8073,6 +8081,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
     		tbinfo->attstorage = (char *) pg_malloc(ntups * sizeof(char));
     		tbinfo->typstorage = (char *) pg_malloc(ntups * sizeof(char));
     		tbinfo->attidentity = (char *) pg_malloc(ntups * sizeof(char));
    +		tbinfo->attgenerated = (char *) pg_malloc(ntups * sizeof(char));
     		tbinfo->attisdropped = (bool *) pg_malloc(ntups * sizeof(bool));
     		tbinfo->attlen = (int *) pg_malloc(ntups * sizeof(int));
     		tbinfo->attalign = (char *) pg_malloc(ntups * sizeof(char));
    @@ -8098,6 +8107,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
     			tbinfo->attstorage[j] = *(PQgetvalue(res, j, i_attstorage));
     			tbinfo->typstorage[j] = *(PQgetvalue(res, j, i_typstorage));
     			tbinfo->attidentity[j] = (i_attidentity >= 0 ? *(PQgetvalue(res, j, i_attidentity)) : '\0');
    +			tbinfo->attgenerated[j] = (i_attgenerated >= 0 ? *(PQgetvalue(res, j, i_attgenerated)) : '\0');
     			tbinfo->needs_override = tbinfo->needs_override || (tbinfo->attidentity[j] == ATTRIBUTE_IDENTITY_ALWAYS);
     			tbinfo->attisdropped[j] = (PQgetvalue(res, j, i_attisdropped)[0] == 't');
     			tbinfo->attlen[j] = atoi(PQgetvalue(res, j, i_attlen));
    @@ -8130,7 +8140,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
     						  tbinfo->dobj.name);
     
     			printfPQExpBuffer(q, "SELECT tableoid, oid, adnum, "
    -							  "pg_catalog.pg_get_expr(adbin, adrelid) AS adsrc "
    +							  "pg_catalog.pg_get_expr(adbin, adrelid, true) AS adsrc "
     							  "FROM pg_catalog.pg_attrdef "
     							  "WHERE adrelid = '%u'::pg_catalog.oid",
     							  tbinfo->dobj.catId.oid);
    @@ -15512,6 +15522,23 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
     										  tbinfo->atttypnames[j]);
     					}
     
    +					if (has_default)
    +					{
    +						if (tbinfo->attgenerated[j] == ATTRIBUTE_GENERATED_STORED)
    +							appendPQExpBuffer(q, " GENERATED ALWAYS AS (%s) STORED",
    +											  tbinfo->attrdefs[j]->adef_expr);
    +						else if (tbinfo->attgenerated[j] == ATTRIBUTE_GENERATED_VIRTUAL)
    +							appendPQExpBuffer(q, " GENERATED ALWAYS AS (%s)",
    +											  tbinfo->attrdefs[j]->adef_expr);
    +						else
    +							appendPQExpBuffer(q, " DEFAULT %s",
    +											  tbinfo->attrdefs[j]->adef_expr);
    +					}
    +
    +
    +					if (has_notnull)
    +						appendPQExpBufferStr(q, " NOT NULL");
    +
     					/* Add collation if not default for the type */
     					if (OidIsValid(tbinfo->attcollation[j]))
     					{
    @@ -15526,13 +15553,6 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
     							appendPQExpBufferStr(q, fmtId(coll->dobj.name));
     						}
     					}
    -
    -					if (has_default)
    -						appendPQExpBuffer(q, " DEFAULT %s",
    -										  tbinfo->attrdefs[j]->adef_expr);
    -
    -					if (has_notnull)
    -						appendPQExpBufferStr(q, " NOT NULL");
     				}
     			}
     
    @@ -18043,6 +18063,7 @@ fmtCopyColumnList(const TableInfo *ti, PQExpBuffer buffer)
     	int			numatts = ti->numatts;
     	char	  **attnames = ti->attnames;
     	bool	   *attisdropped = ti->attisdropped;
    +	char	   *attgenerated = ti->attgenerated;
     	bool		needComma;
     	int			i;
     
    @@ -18052,6 +18073,8 @@ fmtCopyColumnList(const TableInfo *ti, PQExpBuffer buffer)
     	{
     		if (attisdropped[i])
     			continue;
    +		if (attgenerated[i])
    +			continue;
     		if (needComma)
     			appendPQExpBufferStr(buffer, ", ");
     		appendPQExpBufferStr(buffer, fmtId(attnames[i]));
    diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
    index 49a02b4fa8..fcc65894bc 100644
    --- a/src/bin/pg_dump/pg_dump.h
    +++ b/src/bin/pg_dump/pg_dump.h
    @@ -309,6 +309,7 @@ typedef struct _tableInfo
     	char	   *typstorage;		/* type storage scheme */
     	bool	   *attisdropped;	/* true if attr is dropped; don't dump it */
     	char	   *attidentity;
    +	char	   *attgenerated;
     	int		   *attlen;			/* attribute length, used by binary_upgrade */
     	char	   *attalign;		/* attribute align, used by binary_upgrade */
     	bool	   *attislocal;		/* true if attr has local definition */
    diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
    index 6da1c35a42..7532411daf 100644
    --- a/src/bin/pg_dump/pg_dump_sort.c
    +++ b/src/bin/pg_dump/pg_dump_sort.c
    @@ -1160,6 +1160,16 @@ repairDependencyLoop(DumpableObject **loop,
     		}
     	}
     
    +	/* Loop of table with itself, happens with generated columns */
    +	if (nLoop == 1)
    +	{
    +		if (loop[0]->objType == DO_TABLE)
    +		{
    +			removeObjectDependency(loop[0], loop[0]->dumpId);
    +			return;
    +		}
    +	}
    +
     	/*
     	 * If all the objects are TABLE_DATA items, what we must have is a
     	 * circular set of foreign key constraints (or a single self-referential
    diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
    index 7cf9bdadb2..8477480474 100644
    --- a/src/bin/pg_dump/t/002_pg_dump.pl
    +++ b/src/bin/pg_dump/t/002_pg_dump.pl
    @@ -5026,6 +5026,45 @@
     			role                     => 1,
     			section_post_data        => 1, }, },
     
    +	'CREATE TABLE test_table_generated' => {
    +		all_runs     => 1,
    +		catch_all    => 'CREATE ... commands',
    +		create_order => 3,
    +		create_sql   => 'CREATE TABLE dump_test.test_table_generated (
    +						   col1 int primary key,
    +						   col2 int generated always as (col1 * 2)
    +					   );',
    +		regexp => qr/^
    +			\QCREATE TABLE test_table_generated (\E\n
    +			\s+\Qcol1 integer NOT NULL,\E\n
    +			\s+\Qcol2 integer GENERATED ALWAYS AS (col1 * 2)\E\n
    +			\);
    +			/xms,
    +		like => {
    +			binary_upgrade          => 1,
    +			clean                   => 1,
    +			clean_if_exists         => 1,
    +			createdb                => 1,
    +			defaults                => 1,
    +			exclude_test_table      => 1,
    +			exclude_test_table_data => 1,
    +			no_blobs                => 1,
    +			no_privs                => 1,
    +			no_owner                => 1,
    +			only_dump_test_schema   => 1,
    +			pg_dumpall_dbprivs      => 1,
    +			schema_only             => 1,
    +			section_pre_data        => 1,
    +			test_schema_plus_blobs  => 1,
    +			with_oids               => 1, },
    +		unlike => {
    +			exclude_dump_test_schema => 1,
    +			only_dump_test_table     => 1,
    +			pg_dumpall_globals       => 1,
    +			pg_dumpall_globals_clean => 1,
    +			role                     => 1,
    +			section_post_data        => 1, }, },
    +
     	'CREATE STATISTICS extended_stats_no_options' => {
     		all_runs     => 1,
     		catch_all    => 'CREATE ... commands',
    diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
    index d2787ab41b..791f30a6fa 100644
    --- a/src/bin/psql/describe.c
    +++ b/src/bin/psql/describe.c
    @@ -1692,7 +1692,7 @@ describeOneTableDetails(const char *schemaname,
     	 */
     	printfPQExpBuffer(&buf, "SELECT a.attname,");
     	appendPQExpBufferStr(&buf, "\n  pg_catalog.format_type(a.atttypid, a.atttypmod),"
    -						 "\n  (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid) for 128)"
    +						 "\n  (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid, true) for 128)"
     						 "\n   FROM pg_catalog.pg_attrdef d"
     						 "\n   WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef),"
     						 "\n  a.attnotnull, a.attnum,");
    @@ -1705,6 +1705,10 @@ describeOneTableDetails(const char *schemaname,
     		appendPQExpBufferStr(&buf, ",\n  a.attidentity");
     	else
     		appendPQExpBufferStr(&buf, ",\n  ''::pg_catalog.char AS attidentity");
    +	if (pset.sversion >= 110000)
    +		appendPQExpBufferStr(&buf, ",\n  a.attgenerated");
    +	else
    +		appendPQExpBufferStr(&buf, ",\n  ''::pg_catalog.char AS attgenerated");
     	if (tableinfo.relkind == RELKIND_INDEX)
     		appendPQExpBufferStr(&buf, ",\n  pg_catalog.pg_get_indexdef(a.attrelid, a.attnum, TRUE) AS indexdef");
     	else
    @@ -1886,6 +1890,7 @@ describeOneTableDetails(const char *schemaname,
     		if (show_column_details)
     		{
     			char	   *identity;
    +			char	   *generated;
     			char	   *default_str = "";
     
     			printTableAddCell(&cont, PQgetvalue(res, i, 5), false, false);
    @@ -1893,30 +1898,35 @@ describeOneTableDetails(const char *schemaname,
     			printTableAddCell(&cont, strcmp(PQgetvalue(res, i, 3), "t") == 0 ? "not null" : "", false, false);
     
     			identity = PQgetvalue(res, i, 6);
    +			generated = PQgetvalue(res, i, 7);
     
    -			if (!identity[0])
    -				/* (note: above we cut off the 'default' string at 128) */
    -				default_str = PQgetvalue(res, i, 2);
    -			else if (identity[0] == ATTRIBUTE_IDENTITY_ALWAYS)
    +			if (identity[0] == ATTRIBUTE_IDENTITY_ALWAYS)
     				default_str = "generated always as identity";
     			else if (identity[0] == ATTRIBUTE_IDENTITY_BY_DEFAULT)
     				default_str = "generated by default as identity";
    +			else if (generated[0] == ATTRIBUTE_GENERATED_STORED)
    +				default_str = psprintf("generated always as (%s) stored", PQgetvalue(res, i, 2));
    +			else if (generated[0] == ATTRIBUTE_GENERATED_VIRTUAL)
    +				default_str = psprintf("generated always as (%s)", PQgetvalue(res, i, 2));
    +			else
    +				/* (note: above we cut off the 'default' string at 128) */
    +				default_str = PQgetvalue(res, i, 2);
     
    -			printTableAddCell(&cont, default_str, false, false);
    +			printTableAddCell(&cont, default_str, false, generated[0] ? true : false);
     		}
     
     		/* Expression for index column */
     		if (tableinfo.relkind == RELKIND_INDEX)
    -			printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
    +			printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
     
     		/* FDW options for foreign table column, only for 9.2 or later */
     		if (tableinfo.relkind == RELKIND_FOREIGN_TABLE && pset.sversion >= 90200)
    -			printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
    +			printTableAddCell(&cont, PQgetvalue(res, i, 9), false, false);
     
     		/* Storage and Description */
     		if (verbose)
     		{
    -			int			firstvcol = 9;
    +			int			firstvcol = 10;
     			char	   *storage = PQgetvalue(res, i, firstvcol);
     
     			/* these strings are literal in our syntax, so not translated. */
    diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
    index 9bdc63ceb5..39bdbf3d10 100644
    --- a/src/include/catalog/heap.h
    +++ b/src/include/catalog/heap.h
    @@ -109,7 +109,8 @@ extern Node *cookDefault(ParseState *pstate,
     			Node *raw_default,
     			Oid atttypid,
     			int32 atttypmod,
    -			const char *attname);
    +			const char *attname,
    +			char attgenerated);
     
     extern void DeleteRelationTuple(Oid relid);
     extern void DeleteAttributeTuples(Oid relid);
    diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
    index 8159383834..cb88d55b96 100644
    --- a/src/include/catalog/pg_attribute.h
    +++ b/src/include/catalog/pg_attribute.h
    @@ -136,6 +136,9 @@ CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BK
     	/* One of the ATTRIBUTE_IDENTITY_* constants below, or '\0' */
     	char		attidentity BKI_DEFAULT("");
     
    +	/* One of the ATTRIBUTE_GENERATED_* constants below, or '\0' */
    +	char		attgenerated BKI_DEFAULT("");
    +
     	/* Is dropped (ie, logically invisible) or not */
     	bool		attisdropped BKI_DEFAULT(f);
     
    @@ -191,7 +194,7 @@ typedef FormData_pg_attribute *Form_pg_attribute;
      * ----------------
      */
     
    -#define Natts_pg_attribute				22
    +#define Natts_pg_attribute				23
     #define Anum_pg_attribute_attrelid		1
     #define Anum_pg_attribute_attname		2
     #define Anum_pg_attribute_atttypid		3
    @@ -207,13 +210,14 @@ typedef FormData_pg_attribute *Form_pg_attribute;
     #define Anum_pg_attribute_attnotnull	13
     #define Anum_pg_attribute_atthasdef		14
     #define Anum_pg_attribute_attidentity	15
    -#define Anum_pg_attribute_attisdropped	16
    -#define Anum_pg_attribute_attislocal	17
    -#define Anum_pg_attribute_attinhcount	18
    -#define Anum_pg_attribute_attcollation	19
    -#define Anum_pg_attribute_attacl		20
    -#define Anum_pg_attribute_attoptions	21
    -#define Anum_pg_attribute_attfdwoptions 22
    +#define Anum_pg_attribute_attgenerated	16
    +#define Anum_pg_attribute_attisdropped	17
    +#define Anum_pg_attribute_attislocal	18
    +#define Anum_pg_attribute_attinhcount	19
    +#define Anum_pg_attribute_attcollation	20
    +#define Anum_pg_attribute_attacl		21
    +#define Anum_pg_attribute_attoptions	22
    +#define Anum_pg_attribute_attfdwoptions 23
     
     
     /* ----------------
    @@ -228,4 +232,7 @@ typedef FormData_pg_attribute *Form_pg_attribute;
     #define		  ATTRIBUTE_IDENTITY_ALWAYS		'a'
     #define		  ATTRIBUTE_IDENTITY_BY_DEFAULT 'd'
     
    +#define		  ATTRIBUTE_GENERATED_STORED	's'
    +#define		  ATTRIBUTE_GENERATED_VIRTUAL	'v'
    +
     #endif							/* PG_ATTRIBUTE_H */
    diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
    index e7049438eb..3ae9c5fe56 100644
    --- a/src/include/catalog/pg_class.h
    +++ b/src/include/catalog/pg_class.h
    @@ -149,7 +149,7 @@ typedef FormData_pg_class *Form_pg_class;
      */
     DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
     DESCR("");
    -DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 22 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
    +DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 23 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
     DESCR("");
     DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
     DESCR("");
    diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
    index b72178efd1..46c6c05e1a 100644
    --- a/src/include/nodes/parsenodes.h
    +++ b/src/include/nodes/parsenodes.h
    @@ -647,6 +647,7 @@ typedef struct ColumnDef
     	Node	   *raw_default;	/* default value (untransformed parse tree) */
     	Node	   *cooked_default; /* default value (transformed expr tree) */
     	char		identity;		/* attidentity setting */
    +	char		generated;		/* attgenerated setting */
     	CollateClause *collClause;	/* untransformed COLLATE spec, if any */
     	Oid			collOid;		/* collation OID (InvalidOid if not set) */
     	List	   *constraints;	/* other constraints on column */
    @@ -669,9 +670,10 @@ typedef enum TableLikeOption
     	CREATE_TABLE_LIKE_DEFAULTS = 1 << 0,
     	CREATE_TABLE_LIKE_CONSTRAINTS = 1 << 1,
     	CREATE_TABLE_LIKE_IDENTITY = 1 << 2,
    -	CREATE_TABLE_LIKE_INDEXES = 1 << 3,
    -	CREATE_TABLE_LIKE_STORAGE = 1 << 4,
    -	CREATE_TABLE_LIKE_COMMENTS = 1 << 5,
    +	CREATE_TABLE_LIKE_GENERATED = 1 << 3,
    +	CREATE_TABLE_LIKE_INDEXES = 1 << 4,
    +	CREATE_TABLE_LIKE_STORAGE = 1 << 5,
    +	CREATE_TABLE_LIKE_COMMENTS = 1 << 6,
     	CREATE_TABLE_LIKE_ALL = PG_INT32_MAX
     } TableLikeOption;
     
    @@ -2063,6 +2065,7 @@ typedef enum ConstrType			/* types of constraints */
     	CONSTR_NOTNULL,
     	CONSTR_DEFAULT,
     	CONSTR_IDENTITY,
    +	CONSTR_GENERATED,
     	CONSTR_CHECK,
     	CONSTR_PRIMARY,
     	CONSTR_UNIQUE,
    @@ -2101,7 +2104,8 @@ typedef struct Constraint
     	bool		is_no_inherit;	/* is constraint non-inheritable? */
     	Node	   *raw_expr;		/* expr, as untransformed parse tree */
     	char	   *cooked_expr;	/* expr, as nodeToString representation */
    -	char		generated_when;
    +	char		generated_when;	/* ALWAYS or BY DEFAULT */
    +	char		generated_kind; /* STORED or VIRTUAL */
     
     	/* Fields used for unique constraints (UNIQUE and PRIMARY KEY): */
     	List	   *keys;			/* String nodes naming referenced column(s) */
    diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
    index 26af944e03..3b4f15d162 100644
    --- a/src/include/parser/kwlist.h
    +++ b/src/include/parser/kwlist.h
    @@ -381,6 +381,7 @@ PG_KEYWORD("statistics", STATISTICS, UNRESERVED_KEYWORD)
     PG_KEYWORD("stdin", STDIN, UNRESERVED_KEYWORD)
     PG_KEYWORD("stdout", STDOUT, UNRESERVED_KEYWORD)
     PG_KEYWORD("storage", STORAGE, UNRESERVED_KEYWORD)
    +PG_KEYWORD("stored", STORED, UNRESERVED_KEYWORD)
     PG_KEYWORD("strict", STRICT_P, UNRESERVED_KEYWORD)
     PG_KEYWORD("strip", STRIP_P, UNRESERVED_KEYWORD)
     PG_KEYWORD("subscription", SUBSCRIPTION, UNRESERVED_KEYWORD)
    @@ -436,6 +437,7 @@ PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD)
     PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD)
     PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD)
     PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD)
    +PG_KEYWORD("virtual", VIRTUAL, UNRESERVED_KEYWORD)
     PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD)
     PG_KEYWORD("when", WHEN, RESERVED_KEYWORD)
     PG_KEYWORD("where", WHERE, RESERVED_KEYWORD)
    diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
    index 4e96fa7907..ee4c447f1f 100644
    --- a/src/include/parser/parse_node.h
    +++ b/src/include/parser/parse_node.h
    @@ -68,7 +68,8 @@ typedef enum ParseExprKind
     	EXPR_KIND_TRIGGER_WHEN,		/* WHEN condition in CREATE TRIGGER */
     	EXPR_KIND_POLICY,			/* USING or WITH CHECK expr in policy */
     	EXPR_KIND_PARTITION_EXPRESSION,	/* PARTITION BY expression */
    -	EXPR_KIND_CALL				/* CALL argument */
    +	EXPR_KIND_CALL,				/* CALL argument */
    +	EXPR_KIND_GENERATED_COLUMN	/* generation expression for a column */
     } ParseExprKind;
     
     
    diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
    index 8128199fc3..30093d7eb3 100644
    --- a/src/include/rewrite/rewriteHandler.h
    +++ b/src/include/rewrite/rewriteHandler.h
    @@ -32,5 +32,6 @@ extern const char *view_query_is_auto_updatable(Query *viewquery,
     extern int relation_is_updatable(Oid reloid,
     					  bool include_triggers,
     					  Bitmapset *include_cols);
    +extern Expr *expand_generated_columns_in_expr(Expr *expr, Relation rel);
     
     #endif							/* REWRITEHANDLER_H */
    diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
    index 9731e6f7ae..7be4438a8e 100644
    --- a/src/include/utils/lsyscache.h
    +++ b/src/include/utils/lsyscache.h
    @@ -86,6 +86,7 @@ extern Oid get_opfamily_proc(Oid opfamily, Oid lefttype, Oid righttype,
     extern char *get_attname(Oid relid, AttrNumber attnum);
     extern char *get_relid_attribute_name(Oid relid, AttrNumber attnum);
     extern AttrNumber get_attnum(Oid relid, const char *attname);
    +extern char get_attgenerated(Oid relid, AttrNumber attnum);
     extern char get_attidentity(Oid relid, AttrNumber attnum);
     extern Oid	get_atttype(Oid relid, AttrNumber attnum);
     extern int32 get_atttypmod(Oid relid, AttrNumber attnum);
    diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out
    index 3f405c94ce..5d47e22981 100644
    --- a/src/test/regress/expected/create_table_like.out
    +++ b/src/test/regress/expected/create_table_like.out
    @@ -113,6 +113,52 @@ SELECT * FROM test_like_id_3;  -- identity was copied and applied
     (1 row)
     
     DROP TABLE test_like_id_1, test_like_id_2, test_like_id_3;
    +CREATE TABLE test_like_gen_1 (a int, b int GENERATED ALWAYS AS (a * 2));
    +\d test_like_gen_1
    +                    Table "public.test_like_gen_1"
    + Column |  Type   | Collation | Nullable |           Default           
    +--------+---------+-----------+----------+-----------------------------
    + a      | integer |           |          | 
    + b      | integer |           |          | generated always as (a * 2)
    +
    +INSERT INTO test_like_gen_1 (a) VALUES (1);
    +SELECT * FROM test_like_gen_1;
    + a | b 
    +---+---
    + 1 | 2
    +(1 row)
    +
    +CREATE TABLE test_like_gen_2 (LIKE test_like_gen_1);
    +\d test_like_gen_2
    +          Table "public.test_like_gen_2"
    + Column |  Type   | Collation | Nullable | Default 
    +--------+---------+-----------+----------+---------
    + a      | integer |           |          | 
    + b      | integer |           |          | 
    +
    +INSERT INTO test_like_gen_2 (a) VALUES (1);
    +SELECT * FROM test_like_gen_2;
    + a | b 
    +---+---
    + 1 |  
    +(1 row)
    +
    +CREATE TABLE test_like_gen_3 (LIKE test_like_gen_1 INCLUDING GENERATED);
    +\d test_like_gen_3
    +                    Table "public.test_like_gen_3"
    + Column |  Type   | Collation | Nullable |           Default           
    +--------+---------+-----------+----------+-----------------------------
    + a      | integer |           |          | 
    + b      | integer |           |          | generated always as (a * 2)
    +
    +INSERT INTO test_like_gen_3 (a) VALUES (1);
    +SELECT * FROM test_like_gen_3;
    + a | b 
    +---+---
    + 1 | 2
    +(1 row)
    +
    +DROP TABLE test_like_gen_1, test_like_gen_2, test_like_gen_3;
     CREATE TABLE inhg (x text, LIKE inhx INCLUDING INDEXES, y text); /* copies indexes */
     INSERT INTO inhg VALUES (5, 10);
     INSERT INTO inhg VALUES (20, 10); -- should fail
    diff --git a/src/test/regress/expected/generated.out b/src/test/regress/expected/generated.out
    new file mode 100644
    index 0000000000..084b39964e
    --- /dev/null
    +++ b/src/test/regress/expected/generated.out
    @@ -0,0 +1,413 @@
    +-- sanity check of system catalog
    +SELECT attrelid, attname, attgenerated FROM pg_attribute WHERE attgenerated NOT IN ('', 's', 'v');
    + attrelid | attname | attgenerated 
    +----------+---------+--------------
    +(0 rows)
    +
    +CREATE TABLE gtest0 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (55));
    +CREATE TABLE gtest1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2));
    +SELECT table_name, column_name, column_default, is_nullable, is_generated, generation_expression FROM information_schema.columns WHERE table_name LIKE 'gtest_' ORDER BY 1, 2;
    + table_name | column_name | column_default | is_nullable | is_generated | generation_expression 
    +------------+-------------+----------------+-------------+--------------+-----------------------
    + gtest0     | a           |                | NO          | NEVER        | 
    + gtest0     | b           |                | YES         | ALWAYS       | 55
    + gtest1     | a           |                | NO          | NEVER        | 
    + gtest1     | b           |                | YES         | ALWAYS       | a * 2
    +(4 rows)
    +
    +\d gtest1
    +                         Table "public.gtest1"
    + Column |  Type   | Collation | Nullable |           Default           
    +--------+---------+-----------+----------+-----------------------------
    + a      | integer |           | not null | 
    + b      | integer |           |          | generated always as (a * 2)
    +Indexes:
    +    "gtest1_pkey" PRIMARY KEY, btree (a)
    +
    +-- duplicate generated
    +CREATE TABLE gtest_err_1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) GENERATED ALWAYS AS (a * 3));
    +ERROR:  multiple generation clauses specified for column "b" of table "gtest_err_1"
    +LINE 1: ...nt PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) GENERATED ...
    +                                                             ^
    +-- references to other generated columns, including self-references
    +CREATE TABLE gtest_err_2a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (b * 2));
    +ERROR:  cannot use generated column "b" in column generation expression
    +DETAIL:  A generated column cannot reference another generated column.
    +CREATE TABLE gtest_err_2b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2), c int GENERATED ALWAYS AS (b * 3));
    +ERROR:  cannot use generated column "b" in column generation expression
    +DETAIL:  A generated column cannot reference another generated column.
    +-- invalid reference
    +CREATE TABLE gtest_err_3 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (c * 2));
    +ERROR:  column "c" does not exist
    +-- functions must be immutable
    +CREATE TABLE gtest_err_4 (a int PRIMARY KEY, b double precision GENERATED ALWAYS AS (random()));
    +ERROR:  cannot use function random() in column generation expression
    +DETAIL:  Functions used in a column generation expression must be immutable.
    +-- cannot have default/identity and generated
    +CREATE TABLE gtest_err_5a (a int PRIMARY KEY, b int DEFAULT 5 GENERATED ALWAYS AS (a * 2));
    +ERROR:  both default and generation expression specified for column "b" of table "gtest_err_5a"
    +LINE 1: ... gtest_err_5a (a int PRIMARY KEY, b int DEFAULT 5 GENERATED ...
    +                                                             ^
    +CREATE TABLE gtest_err_5b (a int PRIMARY KEY, b int GENERATED ALWAYS AS identity GENERATED ALWAYS AS (a * 2));
    +ERROR:  both identity and generation expression specified for column "b" of table "gtest_err_5b"
    +LINE 1: ...t PRIMARY KEY, b int GENERATED ALWAYS AS identity GENERATED ...
    +                                                             ^
    +INSERT INTO gtest1 VALUES (1);
    +INSERT INTO gtest1 VALUES (2, DEFAULT);
    +INSERT INTO gtest1 VALUES (3, 33);  -- error
    +ERROR:  cannot insert into column "b"
    +DETAIL:  Column "b" is a generated column.
    +SELECT * FROM gtest1 ORDER BY a;
    + a | b 
    +---+---
    + 1 | 2
    + 2 | 4
    +(2 rows)
    +
    +UPDATE gtest1 SET b = DEFAULT WHERE a = 1;
    +UPDATE gtest1 SET b = 11 WHERE a = 1;  -- error
    +ERROR:  column "b" can only be updated to DEFAULT
    +DETAIL:  Column "b" is a generated column.
    +SELECT * FROM gtest1 ORDER BY a;
    + a | b 
    +---+---
    + 1 | 2
    + 2 | 4
    +(2 rows)
    +
    +SELECT a, b, b * 2 AS b2 FROM gtest1 ORDER BY a;
    + a | b | b2 
    +---+---+----
    + 1 | 2 |  4
    + 2 | 4 |  8
    +(2 rows)
    +
    +SELECT a, b FROM gtest1 WHERE b = 4 ORDER BY a;
    + a | b 
    +---+---
    + 2 | 4
    +(1 row)
    +
    +-- test with joins
    +CREATE TABLE gtestx (x int, y int);
    +INSERT INTO gtestx VALUES (11, 1), (22, 2), (33, 3);
    +SELECT * FROM gtestx, gtest1 WHERE gtestx.y = gtest1.a;
    + x  | y | a | b 
    +----+---+---+---
    + 11 | 1 | 1 | 2
    + 22 | 2 | 2 | 4
    +(2 rows)
    +
    +DROP TABLE gtestx;
    +-- test UPDATE/DELETE quals
    +SELECT * FROM gtest1 ORDER BY a;
    + a | b 
    +---+---
    + 1 | 2
    + 2 | 4
    +(2 rows)
    +
    +UPDATE gtest1 SET a = 3 WHERE b = 4;
    +SELECT * FROM gtest1 ORDER BY a;
    + a | b 
    +---+---
    + 1 | 2
    + 3 | 6
    +(2 rows)
    +
    +DELETE FROM gtest1 WHERE b = 2;
    +SELECT * FROM gtest1 ORDER BY a;
    + a | b 
    +---+---
    + 3 | 6
    +(1 row)
    +
    +-- views
    +CREATE VIEW gtest1v AS SELECT * FROM gtest1;
    +SELECT * FROM gtest1v;
    + a | b 
    +---+---
    + 3 | 6
    +(1 row)
    +
    +INSERT INTO gtest1v VALUES (4, 8);  -- fails
    +ERROR:  cannot insert into column "b"
    +DETAIL:  Column "b" is a generated column.
    +-- inheritance
    +CREATE TABLE gtest1_1 () INHERITS (gtest1);
    +SELECT * FROM gtest1_1;
    + a | b 
    +---+---
    +(0 rows)
    +
    +\d gtest1_1
    +                        Table "public.gtest1_1"
    + Column |  Type   | Collation | Nullable |           Default           
    +--------+---------+-----------+----------+-----------------------------
    + a      | integer |           | not null | 
    + b      | integer |           |          | generated always as (a * 2)
    +Inherits: gtest1
    +
    +INSERT INTO gtest1_1 VALUES (4);
    +SELECT * FROM gtest1_1;
    + a | b 
    +---+---
    + 4 | 8
    +(1 row)
    +
    +SELECT * FROM gtest1;
    + a | b 
    +---+---
    + 3 | 6
    + 4 | 8
    +(2 rows)
    +
    +-- test inheritance mismatch
    +CREATE TABLE gtesty (x int, b int);
    +CREATE TABLE gtest1_2 () INHERITS (gtest1, gtesty);  -- error
    +NOTICE:  merging multiple inherited definitions of column "b"
    +ERROR:  inherited column "b" has a generation conflict
    +DROP TABLE gtesty;
    +-- stored
    +CREATE TABLE gtest3 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 3) STORED);
    +INSERT INTO gtest3 (a) VALUES (1), (2), (3);
    +ERROR:  stored generated columns are not yet implemented
    +SELECT * FROM gtest3 ORDER BY a;
    + a | b 
    +---+---
    +(0 rows)
    +
    +UPDATE gtest3 SET a = 22 WHERE a = 2;
    +ERROR:  stored generated columns are not yet implemented
    +SELECT * FROM gtest3 ORDER BY a;
    + a | b 
    +---+---
    +(0 rows)
    +
    +-- COPY
    +TRUNCATE gtest1;
    +INSERT INTO gtest1 (a) VALUES (1), (2);
    +COPY gtest1 TO stdout;
    +1
    +2
    +COPY gtest1 (a, b) TO stdout;
    +ERROR:  column "b" is a generated column
    +DETAIL:  Generated columns cannot be used in COPY.
    +COPY gtest1 FROM stdin;
    +COPY gtest1 (a, b) FROM stdin;
    +ERROR:  column "b" is a generated column
    +DETAIL:  Generated columns cannot be used in COPY.
    +SELECT * FROM gtest1 ORDER BY a;
    + a | b 
    +---+---
    + 1 | 2
    + 2 | 4
    + 3 | 6
    +(3 rows)
    +
    +-- drop column behavior
    +CREATE TABLE gtest10 (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (b * 2));
    +ALTER TABLE gtest10 DROP COLUMN b;
    +\d gtest10
    +              Table "public.gtest10"
    + Column |  Type   | Collation | Nullable | Default 
    +--------+---------+-----------+----------+---------
    + a      | integer |           | not null | 
    +Indexes:
    +    "gtest10_pkey" PRIMARY KEY, btree (a)
    +
    +-- privileges
    +CREATE USER regress_user11;
    +CREATE TABLE gtest11 (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (b * 2));
    +INSERT INTO gtest11 VALUES (1, 10), (2, 20);
    +GRANT SELECT (a, c) ON gtest11 TO regress_user11;
    +CREATE FUNCTION gf1(a int) RETURNS int AS $$ SELECT a * 3 $$ IMMUTABLE LANGUAGE SQL;
    +REVOKE ALL ON FUNCTION gf1(int) FROM PUBLIC;
    +CREATE TABLE gtest12 (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (gf1(b)));
    +INSERT INTO gtest12 VALUES (1, 10), (2, 20);
    +GRANT SELECT (a, c) ON gtest12 TO regress_user11;
    +SET ROLE regress_user11;
    +SELECT a, b FROM gtest11;  -- not allowed
    +ERROR:  permission denied for relation gtest11
    +SELECT a, c FROM gtest11;  -- allowed
    + a | c  
    +---+----
    + 1 | 20
    + 2 | 40
    +(2 rows)
    +
    +SELECT gf1(10);  -- not allowed
    +ERROR:  permission denied for function gf1
    +SELECT a, c FROM gtest12;  -- FIXME: should be allowed
    +ERROR:  permission denied for function gf1
    +RESET ROLE;
    +DROP TABLE gtest11, gtest12;
    +DROP FUNCTION gf1(int);
    +DROP USER regress_user11;
    +-- check constraints
    +CREATE TABLE gtest20 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) CHECK (b < 50));
    +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).
    +CREATE TABLE gtest20a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2));
    +INSERT INTO gtest20a (a) VALUES (10);
    +INSERT INTO gtest20a (a) VALUES (30);
    +ALTER TABLE gtest20a ADD CHECK (b < 50);  -- fails on existing row
    +ERROR:  check constraint "gtest20a_b_check" is violated by some row
    +-- not-null constraints
    +CREATE TABLE gtest21 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) not null);
    +INSERT INTO gtest21 (a) VALUES (1);  -- ok
    +INSERT INTO gtest21 (a) VALUES (0);  -- violates constraint
    +ERROR:  new row for relation "gtest21" violates check constraint "gtest21_b_check"
    +DETAIL:  Failing row contains (0).
    +CREATE TABLE gtest21a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)));
    +ALTER TABLE gtest21a ALTER COLUMN b SET NOT NULL;  -- error
    +ERROR:  cannot use SET NOT NULL on generated column "b"
    +HINT:  Add a CHECK constraint instead.
    +ALTER TABLE gtest21a ALTER COLUMN b DROP NOT NULL;  -- error
    +ERROR:  cannot use DROP NOT NULL on generated column "b"
    +-- index constraints
    +CREATE TABLE gtest22a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) unique);
    +ERROR:  index creation on generated columns is not supported
    +CREATE TABLE gtest22b (a int, b int GENERATED ALWAYS AS (a * 2), PRIMARY KEY (a, b));
    +ERROR:  index creation on generated columns is not supported
    +CREATE TABLE gtest22c (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2));
    +CREATE INDEX ON gtest22c (b);
    +ERROR:  index creation on generated columns is not supported
    +CREATE INDEX ON gtest22c ((b * 2));
    +ERROR:  index creation on generated columns is not supported
    +CREATE INDEX ON gtest22c (a) WHERE b > 0;
    +ERROR:  index creation on generated columns is not supported
    +-- foreign keys
    +CREATE TABLE gtest23a (x int PRIMARY KEY, y int);
    +CREATE TABLE gtest23b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) REFERENCES gtest23a (x) ON UPDATE CASCADE);
    +ERROR:  invalid ON UPDATE action for foreign key constraint containing generated column
    +CREATE TABLE gtest23b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) REFERENCES gtest23a (x) ON DELETE SET NULL);
    +ERROR:  invalid ON DELETE action for foreign key constraint containing generated column
    +CREATE TABLE gtest23b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) REFERENCES gtest23a (x));
    +ERROR:  foreign key constraints on generated columns are not supported
    +DROP TABLE gtest23a;
    +-- domains
    +CREATE DOMAIN gtestdomain1 AS int CHECK (VALUE < 10);
    +CREATE TABLE gtest24 (a int PRIMARY KEY, b gtestdomain1 GENERATED ALWAYS AS (a * 2));  -- prohibited
    +ERROR:  virtual generated column "b" cannot have a domain type
    +LINE 1: CREATE TABLE gtest24 (a int PRIMARY KEY, b gtestdomain1 GENE...
    +                                                 ^
    +-- 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);
    +SELECT * FROM gtest25 ORDER BY a;
    + a | b  
    +---+----
    + 3 |  9
    + 4 | 12
    +(2 rows)
    +
    +ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (b * 4);  -- error
    +ERROR:  cannot use generated column "b" in column generation expression
    +DETAIL:  A generated column cannot reference another generated column.
    +ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (z * 4);  -- error
    +ERROR:  column "z" does not exist
    +-- ALTER TABLE ... ALTER COLUMN
    +CREATE TABLE gtest27 (a int, b int GENERATED ALWAYS AS (a * 2));
    +INSERT INTO gtest27 (a) VALUES (3), (4);
    +ALTER TABLE gtest27 ALTER COLUMN a TYPE text;  -- error
    +ERROR:  cannot alter type of a column used by a generated column
    +DETAIL:  Column "a" is used by generated column "b".
    +ALTER TABLE gtest27 ALTER COLUMN b TYPE numeric;
    +\d gtest27
    +                         Table "public.gtest27"
    + Column |  Type   | Collation | Nullable |            Default            
    +--------+---------+-----------+----------+-------------------------------
    + a      | integer |           |          | 
    + b      | numeric |           |          | generated always as ((a * 2))
    +
    +SELECT * FROM gtest27;
    + a | b 
    +---+---
    + 3 | 6
    + 4 | 8
    +(2 rows)
    +
    +ALTER TABLE gtest27 ALTER COLUMN b TYPE boolean;  -- error
    +ERROR:  generation expression for column "b" cannot be cast automatically to type boolean
    +ALTER TABLE gtest27 ALTER COLUMN b DROP DEFAULT;  -- FIXME
    +ERROR:  column "b" of relation "gtest27" is a generated column
    +\d gtest27
    +                         Table "public.gtest27"
    + Column |  Type   | Collation | Nullable |            Default            
    +--------+---------+-----------+----------+-------------------------------
    + a      | integer |           |          | 
    + b      | numeric |           |          | generated always as ((a * 2))
    +
    +-- triggers
    +CREATE TABLE gtest26 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2));
    +CREATE FUNCTION gtest_trigger_func() RETURNS trigger
    +  LANGUAGE plpgsql
    +AS $$
    +BEGIN
    +  RAISE INFO '%: old = %', TG_NAME, OLD;
    +  RAISE INFO '%: new = %', TG_NAME, NEW;
    +  RETURN NEW;
    +END
    +$$;
    +CREATE TRIGGER gtest1 BEFORE UPDATE ON gtest26
    +  FOR EACH ROW
    +  WHEN (OLD.b < 0)  -- ok
    +  EXECUTE PROCEDURE gtest_trigger_func();
    +CREATE TRIGGER gtest2 BEFORE UPDATE ON gtest26
    +  FOR EACH ROW
    +  WHEN (NEW.b < 0)  -- error
    +  EXECUTE PROCEDURE gtest_trigger_func();
    +ERROR:  BEFORE trigger's WHEN condition cannot reference NEW generated columns
    +LINE 3:   WHEN (NEW.b < 0)  
    +                ^
    +CREATE TRIGGER gtest3 AFTER UPDATE ON gtest26
    +  FOR EACH ROW
    +  WHEN (OLD.b < 0)  -- ok
    +  EXECUTE PROCEDURE gtest_trigger_func();
    +CREATE TRIGGER gtest4 AFTER UPDATE ON gtest26
    +  FOR EACH ROW
    +  WHEN (NEW.b < 0)  -- ok
    +  EXECUTE PROCEDURE gtest_trigger_func();
    +INSERT INTO gtest26 (a) VALUES (-2), (0), (3);
    +SELECT * FROM gtest26 ORDER BY a;
    + a  | b  
    +----+----
    + -2 | -4
    +  0 |  0
    +  3 |  6
    +(3 rows)
    +
    +UPDATE gtest26 SET a = a * -2;
    +INFO:  gtest1: old = (-2,)
    +INFO:  gtest1: new = (4,)
    +INFO:  gtest3: old = (-2,)
    +INFO:  gtest3: new = (4,)
    +INFO:  gtest4: old = (3,)
    +INFO:  gtest4: new = (-6,)
    +SELECT * FROM gtest26 ORDER BY a;
    + a  |  b  
    +----+-----
    + -6 | -12
    +  0 |   0
    +  4 |   8
    +(3 rows)
    +
    +CREATE FUNCTION gtest_trigger_func2() RETURNS trigger
    +  LANGUAGE plpgsql
    +AS $$
    +BEGIN
    +  NEW.b = 5;
    +  RETURN NEW;
    +END
    +$$;
    +CREATE TRIGGER gtest10 BEFORE INSERT OR UPDATE ON gtest26
    +  FOR EACH ROW
    +  EXECUTE PROCEDURE gtest_trigger_func2();
    +INSERT INTO gtest26 (a) VALUES (10);
    +ERROR:  trigger modified virtual generated column value
    +UPDATE gtest26 SET a = 1 WHERE a = 0;
    +ERROR:  trigger modified virtual generated column value
    diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
    index e224977791..30ce38dccf 100644
    --- a/src/test/regress/parallel_schedule
    +++ b/src/test/regress/parallel_schedule
    @@ -116,7 +116,7 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid c
     # ----------
     # Another group of parallel tests
     # ----------
    -test: identity partition_join partition_prune reloptions hash_part
    +test: identity generated partition_join partition_prune reloptions hash_part
     
     # event triggers cannot run concurrently with any test that runs DDL
     test: event_trigger
    diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
    index 9fc5f1a268..f0a71485bd 100644
    --- a/src/test/regress/serial_schedule
    +++ b/src/test/regress/serial_schedule
    @@ -180,6 +180,7 @@ test: largeobject
     test: with
     test: xml
     test: identity
    +test: generated
     test: partition_join
     test: partition_prune
     test: reloptions
    diff --git a/src/test/regress/sql/create_table_like.sql b/src/test/regress/sql/create_table_like.sql
    index 557040bbe7..2ae96e3d68 100644
    --- a/src/test/regress/sql/create_table_like.sql
    +++ b/src/test/regress/sql/create_table_like.sql
    @@ -51,6 +51,20 @@ CREATE TABLE test_like_id_3 (LIKE test_like_id_1 INCLUDING IDENTITY);
     SELECT * FROM test_like_id_3;  -- identity was copied and applied
     DROP TABLE test_like_id_1, test_like_id_2, test_like_id_3;
     
    +CREATE TABLE test_like_gen_1 (a int, b int GENERATED ALWAYS AS (a * 2));
    +\d test_like_gen_1
    +INSERT INTO test_like_gen_1 (a) VALUES (1);
    +SELECT * FROM test_like_gen_1;
    +CREATE TABLE test_like_gen_2 (LIKE test_like_gen_1);
    +\d test_like_gen_2
    +INSERT INTO test_like_gen_2 (a) VALUES (1);
    +SELECT * FROM test_like_gen_2;
    +CREATE TABLE test_like_gen_3 (LIKE test_like_gen_1 INCLUDING GENERATED);
    +\d test_like_gen_3
    +INSERT INTO test_like_gen_3 (a) VALUES (1);
    +SELECT * FROM test_like_gen_3;
    +DROP TABLE test_like_gen_1, test_like_gen_2, test_like_gen_3;
    +
     CREATE TABLE inhg (x text, LIKE inhx INCLUDING INDEXES, y text); /* copies indexes */
     INSERT INTO inhg VALUES (5, 10);
     INSERT INTO inhg VALUES (20, 10); -- should fail
    diff --git a/src/test/regress/sql/generated.sql b/src/test/regress/sql/generated.sql
    new file mode 100644
    index 0000000000..dd770c8dee
    --- /dev/null
    +++ b/src/test/regress/sql/generated.sql
    @@ -0,0 +1,237 @@
    +-- sanity check of system catalog
    +SELECT attrelid, attname, attgenerated FROM pg_attribute WHERE attgenerated NOT IN ('', 's', 'v');
    +
    +
    +CREATE TABLE gtest0 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (55));
    +CREATE TABLE gtest1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2));
    +
    +SELECT table_name, column_name, column_default, is_nullable, is_generated, generation_expression FROM information_schema.columns WHERE table_name LIKE 'gtest_' ORDER BY 1, 2;
    +
    +\d gtest1
    +
    +-- duplicate generated
    +CREATE TABLE gtest_err_1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) GENERATED ALWAYS AS (a * 3));
    +
    +-- references to other generated columns, including self-references
    +CREATE TABLE gtest_err_2a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (b * 2));
    +CREATE TABLE gtest_err_2b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2), c int GENERATED ALWAYS AS (b * 3));
    +
    +-- invalid reference
    +CREATE TABLE gtest_err_3 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (c * 2));
    +
    +-- functions must be immutable
    +CREATE TABLE gtest_err_4 (a int PRIMARY KEY, b double precision GENERATED ALWAYS AS (random()));
    +
    +-- cannot have default/identity and generated
    +CREATE TABLE gtest_err_5a (a int PRIMARY KEY, b int DEFAULT 5 GENERATED ALWAYS AS (a * 2));
    +CREATE TABLE gtest_err_5b (a int PRIMARY KEY, b int GENERATED ALWAYS AS identity GENERATED ALWAYS AS (a * 2));
    +
    +INSERT INTO gtest1 VALUES (1);
    +INSERT INTO gtest1 VALUES (2, DEFAULT);
    +INSERT INTO gtest1 VALUES (3, 33);  -- error
    +
    +SELECT * FROM gtest1 ORDER BY a;
    +
    +UPDATE gtest1 SET b = DEFAULT WHERE a = 1;
    +UPDATE gtest1 SET b = 11 WHERE a = 1;  -- error
    +
    +SELECT * FROM gtest1 ORDER BY a;
    +
    +SELECT a, b, b * 2 AS b2 FROM gtest1 ORDER BY a;
    +SELECT a, b FROM gtest1 WHERE b = 4 ORDER BY a;
    +
    +-- test with joins
    +CREATE TABLE gtestx (x int, y int);
    +INSERT INTO gtestx VALUES (11, 1), (22, 2), (33, 3);
    +SELECT * FROM gtestx, gtest1 WHERE gtestx.y = gtest1.a;
    +DROP TABLE gtestx;
    +
    +-- test UPDATE/DELETE quals
    +SELECT * FROM gtest1 ORDER BY a;
    +UPDATE gtest1 SET a = 3 WHERE b = 4;
    +SELECT * FROM gtest1 ORDER BY a;
    +DELETE FROM gtest1 WHERE b = 2;
    +SELECT * FROM gtest1 ORDER BY a;
    +
    +-- views
    +CREATE VIEW gtest1v AS SELECT * FROM gtest1;
    +SELECT * FROM gtest1v;
    +INSERT INTO gtest1v VALUES (4, 8);  -- fails
    +
    +-- inheritance
    +CREATE TABLE gtest1_1 () INHERITS (gtest1);
    +SELECT * FROM gtest1_1;
    +\d gtest1_1
    +INSERT INTO gtest1_1 VALUES (4);
    +SELECT * FROM gtest1_1;
    +SELECT * FROM gtest1;
    +
    +-- test inheritance mismatch
    +CREATE TABLE gtesty (x int, b int);
    +CREATE TABLE gtest1_2 () INHERITS (gtest1, gtesty);  -- error
    +DROP TABLE gtesty;
    +
    +-- stored
    +CREATE TABLE gtest3 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 3) STORED);
    +INSERT INTO gtest3 (a) VALUES (1), (2), (3);
    +SELECT * FROM gtest3 ORDER BY a;
    +UPDATE gtest3 SET a = 22 WHERE a = 2;
    +SELECT * FROM gtest3 ORDER BY a;
    +
    +-- COPY
    +TRUNCATE gtest1;
    +INSERT INTO gtest1 (a) VALUES (1), (2);
    +
    +COPY gtest1 TO stdout;
    +
    +COPY gtest1 (a, b) TO stdout;
    +
    +COPY gtest1 FROM stdin;
    +3
    +\.
    +
    +COPY gtest1 (a, b) FROM stdin;
    +
    +SELECT * FROM gtest1 ORDER BY a;
    +
    +-- drop column behavior
    +CREATE TABLE gtest10 (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (b * 2));
    +ALTER TABLE gtest10 DROP COLUMN b;
    +
    +\d gtest10
    +
    +-- privileges
    +CREATE USER regress_user11;
    +CREATE TABLE gtest11 (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (b * 2));
    +INSERT INTO gtest11 VALUES (1, 10), (2, 20);
    +GRANT SELECT (a, c) ON gtest11 TO regress_user11;
    +
    +CREATE FUNCTION gf1(a int) RETURNS int AS $$ SELECT a * 3 $$ IMMUTABLE LANGUAGE SQL;
    +REVOKE ALL ON FUNCTION gf1(int) FROM PUBLIC;
    +CREATE TABLE gtest12 (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (gf1(b)));
    +INSERT INTO gtest12 VALUES (1, 10), (2, 20);
    +GRANT SELECT (a, c) ON gtest12 TO regress_user11;
    +
    +SET ROLE regress_user11;
    +SELECT a, b FROM gtest11;  -- not allowed
    +SELECT a, c FROM gtest11;  -- allowed
    +SELECT gf1(10);  -- not allowed
    +SELECT a, c FROM gtest12;  -- FIXME: should be allowed
    +RESET ROLE;
    +
    +DROP TABLE gtest11, gtest12;
    +DROP FUNCTION gf1(int);
    +DROP USER regress_user11;
    +
    +-- check constraints
    +CREATE TABLE gtest20 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) CHECK (b < 50));
    +INSERT INTO gtest20 (a) VALUES (10);  -- ok
    +INSERT INTO gtest20 (a) VALUES (30);  -- violates constraint
    +
    +CREATE TABLE gtest20a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2));
    +INSERT INTO gtest20a (a) VALUES (10);
    +INSERT INTO gtest20a (a) VALUES (30);
    +ALTER TABLE gtest20a ADD CHECK (b < 50);  -- fails on existing row
    +
    +-- not-null constraints
    +CREATE TABLE gtest21 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) not null);
    +INSERT INTO gtest21 (a) VALUES (1);  -- ok
    +INSERT INTO gtest21 (a) VALUES (0);  -- violates constraint
    +
    +CREATE TABLE gtest21a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)));
    +ALTER TABLE gtest21a ALTER COLUMN b SET NOT NULL;  -- error
    +ALTER TABLE gtest21a ALTER COLUMN b DROP NOT NULL;  -- error
    +
    +-- index constraints
    +CREATE TABLE gtest22a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) unique);
    +CREATE TABLE gtest22b (a int, b int GENERATED ALWAYS AS (a * 2), PRIMARY KEY (a, b));
    +CREATE TABLE gtest22c (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2));
    +CREATE INDEX ON gtest22c (b);
    +CREATE INDEX ON gtest22c ((b * 2));
    +CREATE INDEX ON gtest22c (a) WHERE b > 0;
    +
    +-- foreign keys
    +CREATE TABLE gtest23a (x int PRIMARY KEY, y int);
    +CREATE TABLE gtest23b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) REFERENCES gtest23a (x) ON UPDATE CASCADE);
    +CREATE TABLE gtest23b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) REFERENCES gtest23a (x) ON DELETE SET NULL);
    +CREATE TABLE gtest23b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) REFERENCES gtest23a (x));
    +DROP TABLE gtest23a;
    +
    +-- domains
    +CREATE DOMAIN gtestdomain1 AS int CHECK (VALUE < 10);
    +CREATE TABLE gtest24 (a int PRIMARY KEY, b gtestdomain1 GENERATED ALWAYS AS (a * 2));  -- prohibited
    +
    +-- 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);
    +SELECT * FROM gtest25 ORDER BY a;
    +ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (b * 4);  -- error
    +ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (z * 4);  -- error
    +
    +-- ALTER TABLE ... ALTER COLUMN
    +CREATE TABLE gtest27 (a int, b int GENERATED ALWAYS AS (a * 2));
    +INSERT INTO gtest27 (a) VALUES (3), (4);
    +ALTER TABLE gtest27 ALTER COLUMN a TYPE text;  -- error
    +ALTER TABLE gtest27 ALTER COLUMN b TYPE numeric;
    +\d gtest27
    +SELECT * FROM gtest27;
    +ALTER TABLE gtest27 ALTER COLUMN b TYPE boolean;  -- error
    +
    +ALTER TABLE gtest27 ALTER COLUMN b DROP DEFAULT;  -- FIXME
    +\d gtest27
    +
    +-- triggers
    +CREATE TABLE gtest26 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2));
    +
    +CREATE FUNCTION gtest_trigger_func() RETURNS trigger
    +  LANGUAGE plpgsql
    +AS $$
    +BEGIN
    +  RAISE INFO '%: old = %', TG_NAME, OLD;
    +  RAISE INFO '%: new = %', TG_NAME, NEW;
    +  RETURN NEW;
    +END
    +$$;
    +
    +CREATE TRIGGER gtest1 BEFORE UPDATE ON gtest26
    +  FOR EACH ROW
    +  WHEN (OLD.b < 0)  -- ok
    +  EXECUTE PROCEDURE gtest_trigger_func();
    +
    +CREATE TRIGGER gtest2 BEFORE UPDATE ON gtest26
    +  FOR EACH ROW
    +  WHEN (NEW.b < 0)  -- error
    +  EXECUTE PROCEDURE gtest_trigger_func();
    +
    +CREATE TRIGGER gtest3 AFTER UPDATE ON gtest26
    +  FOR EACH ROW
    +  WHEN (OLD.b < 0)  -- ok
    +  EXECUTE PROCEDURE gtest_trigger_func();
    +
    +CREATE TRIGGER gtest4 AFTER UPDATE ON gtest26
    +  FOR EACH ROW
    +  WHEN (NEW.b < 0)  -- ok
    +  EXECUTE PROCEDURE gtest_trigger_func();
    +
    +INSERT INTO gtest26 (a) VALUES (-2), (0), (3);
    +SELECT * FROM gtest26 ORDER BY a;
    +UPDATE gtest26 SET a = a * -2;
    +SELECT * FROM gtest26 ORDER BY a;
    +
    +
    +CREATE FUNCTION gtest_trigger_func2() RETURNS trigger
    +  LANGUAGE plpgsql
    +AS $$
    +BEGIN
    +  NEW.b = 5;
    +  RETURN NEW;
    +END
    +$$;
    +
    +CREATE TRIGGER gtest10 BEFORE INSERT OR UPDATE ON gtest26
    +  FOR EACH ROW
    +  EXECUTE PROCEDURE gtest_trigger_func2();
    +
    +INSERT INTO gtest26 (a) VALUES (10);
    +UPDATE gtest26 SET a = 1 WHERE a = 0;
    
    base-commit: 649aeb123f73e69cf78c52b534c15c51a229d63d
    -- 
    2.15.1
    
    
    --------------224BFE51C635CE1B0117CF0D--