@@ -13,6 +13,7 @@
#include "executor/spi.h"
#include "funcapi.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/typcache.h"

@@ -751,14 +752,20 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r
PyDict_SetItemString(pltdata, "level", pltlevel);
Py_DECREF(pltlevel);

/*
* Note: In BEFORE trigger, stored generated columns are not computed yet,
* so don't make them accessible in NEW row.
*/

if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event))
{
pltevent = PyString_FromString("INSERT");

PyDict_SetItemString(pltdata, "old", Py_None);
pytnew = PLy_input_from_tuple(&proc->result_in,
tdata->tg_trigtuple,
rel_descr);
rel_descr,
!TRIGGER_FIRED_BEFORE(tdata->tg_event));
PyDict_SetItemString(pltdata, "new", pytnew);
Py_DECREF(pytnew);
*rv = tdata->tg_trigtuple;
@@ -770,7 +777,8 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r
PyDict_SetItemString(pltdata, "new", Py_None);
pytold = PLy_input_from_tuple(&proc->result_in,
tdata->tg_trigtuple,
rel_descr);
rel_descr,
true);
PyDict_SetItemString(pltdata, "old", pytold);
Py_DECREF(pytold);
*rv = tdata->tg_trigtuple;
@@ -781,12 +789,14 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r

pytnew = PLy_input_from_tuple(&proc->result_in,
tdata->tg_newtuple,
rel_descr);
rel_descr,
!TRIGGER_FIRED_BEFORE(tdata->tg_event));
PyDict_SetItemString(pltdata, "new", pytnew);
Py_DECREF(pytnew);
pytold = PLy_input_from_tuple(&proc->result_in,
tdata->tg_trigtuple,
rel_descr);
rel_descr,
true);
PyDict_SetItemString(pltdata, "old", pytold);
Py_DECREF(pytold);
*rv = tdata->tg_newtuple;
@@ -952,6 +962,11 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot set system attribute \"%s\"",
plattstr)));
if (TupleDescAttr(tupdesc, attn - 1)->attgenerated)
ereport(ERROR,
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
errmsg("cannot set generated column \"%s\"",
plattstr)));

plval = PyDict_GetItem(plntup, platt);
if (plval == NULL)
@@ -419,7 +419,8 @@ PLy_spi_execute_fetch_result(SPITupleTable *tuptable, uint64 rows, int status)
{
PyObject *row = PLy_input_from_tuple(&ininfo,
tuptable->vals[i],
tuptable->tupdesc);
tuptable->tupdesc,
true);

PyList_SetItem(result->rows, i, row);
}
@@ -41,7 +41,7 @@ static PyObject *PLyList_FromArray(PLyDatumToOb *arg, Datum d);
static PyObject *PLyList_FromArray_recurse(PLyDatumToOb *elm, int *dims, int ndim, int dim,
char **dataptr_p, bits8 **bitmap_p, int *bitmask_p);
static PyObject *PLyDict_FromComposite(PLyDatumToOb *arg, Datum d);
static PyObject *PLyDict_FromTuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc);
static PyObject *PLyDict_FromTuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc, bool include_generated);

/* conversion from Python objects to Datums */
static Datum PLyObject_ToBool(PLyObToDatum *arg, PyObject *plrv,
@@ -134,7 +134,7 @@ PLy_output_convert(PLyObToDatum *arg, PyObject *val, bool *isnull)
* but in practice all callers have the right tupdesc available.
*/
PyObject *
PLy_input_from_tuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc)
PLy_input_from_tuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc, bool include_generated)
{
PyObject *dict;
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
@@ -148,7 +148,7 @@ PLy_input_from_tuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc)

oldcontext = MemoryContextSwitchTo(scratch_context);

dict = PLyDict_FromTuple(arg, tuple, desc);
dict = PLyDict_FromTuple(arg, tuple, desc, include_generated);

MemoryContextSwitchTo(oldcontext);

@@ -804,7 +804,7 @@ PLyDict_FromComposite(PLyDatumToOb *arg, Datum d)
tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
tmptup.t_data = td;

dict = PLyDict_FromTuple(arg, &tmptup, tupdesc);
dict = PLyDict_FromTuple(arg, &tmptup, tupdesc, true);

ReleaseTupleDesc(tupdesc);

@@ -815,7 +815,7 @@ PLyDict_FromComposite(PLyDatumToOb *arg, Datum d)
* Transform a tuple into a Python dict object.
*/
static PyObject *
PLyDict_FromTuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc)
PLyDict_FromTuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc, bool include_generated)
{
PyObject *volatile dict;

@@ -842,6 +842,13 @@ PLyDict_FromTuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc)
if (attr->attisdropped)
continue;

if (attr->attgenerated)
{
/* don't include unless requested */
if (!include_generated)
continue;
}

key = NameStr(attr->attname);
vattr = heap_getattr(tuple, (i + 1), desc, &is_null);

@@ -151,7 +151,7 @@ extern Datum PLy_output_convert(PLyObToDatum *arg, PyObject *val,
bool *isnull);

extern PyObject *PLy_input_from_tuple(PLyDatumToOb *arg, HeapTuple tuple,
TupleDesc desc);
TupleDesc desc, bool include_generated);

extern void PLy_input_setup_func(PLyDatumToOb *arg, MemoryContext arg_mcxt,
Oid typeOid, int32 typmod,
@@ -67,6 +67,11 @@ SELECT * FROM users;
CREATE TABLE trigger_test
(i int, v text );

CREATE TABLE trigger_test_generated (
i int,
j int GENERATED ALWAYS AS (i * 2) STORED
);

CREATE FUNCTION trigger_data() RETURNS trigger LANGUAGE plpythonu AS $$

if 'relid' in TD:
@@ -109,6 +114,21 @@ DROP TRIGGER show_trigger_data_trig_stmt on trigger_test;
DROP TRIGGER show_trigger_data_trig_before on trigger_test;
DROP TRIGGER show_trigger_data_trig_after on trigger_test;

CREATE TRIGGER show_trigger_data_trig_before
BEFORE INSERT OR UPDATE OR DELETE ON trigger_test_generated
FOR EACH ROW EXECUTE PROCEDURE trigger_data();

CREATE TRIGGER show_trigger_data_trig_after
AFTER INSERT OR UPDATE OR DELETE ON trigger_test_generated
FOR EACH ROW EXECUTE PROCEDURE trigger_data();

insert into trigger_test_generated (i) values (1);
update trigger_test_generated set i = 11 where i = 1;
delete from trigger_test_generated;

DROP TRIGGER show_trigger_data_trig_before ON trigger_test_generated;
DROP TRIGGER show_trigger_data_trig_after ON trigger_test_generated;

insert into trigger_test values(1,'insert');
CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test;

@@ -430,3 +450,20 @@ UPDATE transition_table_test SET name = 'b';

DROP TABLE transition_table_test;
DROP FUNCTION transition_table_test_f();


-- dealing with generated columns

CREATE FUNCTION generated_test_func1() RETURNS trigger
LANGUAGE plpythonu
AS $$
TD['new']['j'] = 5 # not allowed
return 'MODIFY'
$$;

CREATE TRIGGER generated_test_trigger1 BEFORE INSERT ON trigger_test_generated
FOR EACH ROW EXECUTE PROCEDURE generated_test_func1();

TRUNCATE trigger_test_generated;
INSERT INTO trigger_test_generated (i) VALUES (1);
SELECT * FROM trigger_test_generated;
@@ -61,6 +61,10 @@ CREATE TABLE trigger_test (
);
-- Make certain dropped attributes are handled correctly
ALTER TABLE trigger_test DROP dropme;
CREATE TABLE trigger_test_generated (
i int,
j int GENERATED ALWAYS AS (i * 2) STORED
);
CREATE VIEW trigger_test_view AS SELECT i, v FROM trigger_test;
CREATE FUNCTION trigger_data() returns trigger language pltcl as $_$
if {$TG_table_name eq "trigger_test" && $TG_level eq "ROW" && $TG_op ne "DELETE"} {
@@ -112,6 +116,12 @@ FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
CREATE TRIGGER statement_trigger
BEFORE INSERT OR UPDATE OR DELETE OR TRUNCATE ON trigger_test
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_data(42,'statement trigger');
CREATE TRIGGER show_trigger_data_trig_before
BEFORE INSERT OR UPDATE OR DELETE ON trigger_test_generated
FOR EACH ROW EXECUTE PROCEDURE trigger_data();
CREATE TRIGGER show_trigger_data_trig_after
AFTER INSERT OR UPDATE OR DELETE ON trigger_test_generated
FOR EACH ROW EXECUTE PROCEDURE trigger_data();
CREATE TRIGGER show_trigger_data_view_trig
INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view
FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view');
@@ -631,6 +641,75 @@ NOTICE: TG_table_name: trigger_test
NOTICE: TG_table_schema: public
NOTICE: TG_when: BEFORE
NOTICE: args: {23 skidoo}
insert into trigger_test_generated (i) values (1);
NOTICE: NEW: {i: 1}
NOTICE: OLD: {}
NOTICE: TG_level: ROW
NOTICE: TG_name: show_trigger_data_trig_before
NOTICE: TG_op: INSERT
NOTICE: TG_relatts: {{} i j}
NOTICE: TG_relid: bogus:12345
NOTICE: TG_table_name: trigger_test_generated
NOTICE: TG_table_schema: public
NOTICE: TG_when: BEFORE
NOTICE: args: {}
NOTICE: NEW: {i: 1, j: 2}
NOTICE: OLD: {}
NOTICE: TG_level: ROW
NOTICE: TG_name: show_trigger_data_trig_after
NOTICE: TG_op: INSERT
NOTICE: TG_relatts: {{} i j}
NOTICE: TG_relid: bogus:12345
NOTICE: TG_table_name: trigger_test_generated
NOTICE: TG_table_schema: public
NOTICE: TG_when: AFTER
NOTICE: args: {}
update trigger_test_generated set i = 11 where i = 1;
NOTICE: NEW: {i: 11}
NOTICE: OLD: {i: 1, j: 2}
NOTICE: TG_level: ROW
NOTICE: TG_name: show_trigger_data_trig_before
NOTICE: TG_op: UPDATE
NOTICE: TG_relatts: {{} i j}
NOTICE: TG_relid: bogus:12345
NOTICE: TG_table_name: trigger_test_generated
NOTICE: TG_table_schema: public
NOTICE: TG_when: BEFORE
NOTICE: args: {}
NOTICE: NEW: {i: 11, j: 22}
NOTICE: OLD: {i: 1, j: 2}
NOTICE: TG_level: ROW
NOTICE: TG_name: show_trigger_data_trig_after
NOTICE: TG_op: UPDATE
NOTICE: TG_relatts: {{} i j}
NOTICE: TG_relid: bogus:12345
NOTICE: TG_table_name: trigger_test_generated
NOTICE: TG_table_schema: public
NOTICE: TG_when: AFTER
NOTICE: args: {}
delete from trigger_test_generated;
NOTICE: NEW: {}
NOTICE: OLD: {i: 11, j: 22}
NOTICE: TG_level: ROW
NOTICE: TG_name: show_trigger_data_trig_before
NOTICE: TG_op: DELETE
NOTICE: TG_relatts: {{} i j}
NOTICE: TG_relid: bogus:12345
NOTICE: TG_table_name: trigger_test_generated
NOTICE: TG_table_schema: public
NOTICE: TG_when: BEFORE
NOTICE: args: {}
NOTICE: NEW: {}
NOTICE: OLD: {i: 11, j: 22}
NOTICE: TG_level: ROW
NOTICE: TG_name: show_trigger_data_trig_after
NOTICE: TG_op: DELETE
NOTICE: TG_relatts: {{} i j}
NOTICE: TG_relid: bogus:12345
NOTICE: TG_table_name: trigger_test_generated
NOTICE: TG_table_schema: public
NOTICE: TG_when: AFTER
NOTICE: args: {}
insert into trigger_test_view values(2,'insert');
NOTICE: NEW: {i: 2, v: insert}
NOTICE: OLD: {}
@@ -738,6 +817,8 @@ NOTICE: TG_table_name: trigger_test
NOTICE: TG_table_schema: public
NOTICE: TG_when: BEFORE
NOTICE: args: {42 {statement trigger}}
DROP TRIGGER show_trigger_data_trig_before ON trigger_test_generated;
DROP TRIGGER show_trigger_data_trig_after ON trigger_test_generated;
-- should error
insert into trigger_test(test_argisnull) values(true);
NOTICE: NEW: {}
@@ -787,3 +868,21 @@ INFO: old: 1 -> a
INFO: new: 1 -> b
drop table transition_table_test;
drop function transition_table_test_f();
-- dealing with generated columns
CREATE FUNCTION generated_test_func1() RETURNS trigger
LANGUAGE pltcl
AS $$
# not allowed
set NEW(j) 5
return [array get NEW]
$$;
CREATE TRIGGER generated_test_trigger1 BEFORE INSERT ON trigger_test_generated
FOR EACH ROW EXECUTE PROCEDURE generated_test_func1();
TRUNCATE trigger_test_generated;
INSERT INTO trigger_test_generated (i) VALUES (1);
ERROR: cannot set generated column "j"
SELECT * FROM trigger_test_generated;
i | j
---+---
(0 rows)

@@ -324,7 +324,7 @@ static void pltcl_subtrans_abort(Tcl_Interp *interp,

static void pltcl_set_tuple_values(Tcl_Interp *interp, const char *arrayname,
uint64 tupno, HeapTuple tuple, TupleDesc tupdesc);
static Tcl_Obj *pltcl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc);
static Tcl_Obj *pltcl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc, bool include_generated);
static HeapTuple pltcl_build_tuple_result(Tcl_Interp *interp,
Tcl_Obj **kvObjv, int kvObjc,
pltcl_call_state *call_state);
@@ -889,7 +889,7 @@ pltcl_func_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state,
tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
tmptup.t_data = td;

list_tmp = pltcl_build_tuple_argument(&tmptup, tupdesc);
list_tmp = pltcl_build_tuple_argument(&tmptup, tupdesc, true);
Tcl_ListObjAppendElement(NULL, tcl_cmd, list_tmp);

ReleaseTupleDesc(tupdesc);
@@ -1060,7 +1060,6 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state,
volatile HeapTuple rettup;
Tcl_Obj *tcl_cmd;
Tcl_Obj *tcl_trigtup;
Tcl_Obj *tcl_newtup;
int tcl_rc;
int i;
const char *result;
@@ -1162,20 +1161,22 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state,
Tcl_ListObjAppendElement(NULL, tcl_cmd,
Tcl_NewStringObj("ROW", -1));

/* Build the data list for the trigtuple */
tcl_trigtup = pltcl_build_tuple_argument(trigdata->tg_trigtuple,
tupdesc);

/*
* Now the command part of the event for TG_op and data for NEW
* and OLD
*
* Note: In BEFORE trigger, stored generated columns are not computed yet,
* so don't make them accessible in NEW row.
*/
if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
{
Tcl_ListObjAppendElement(NULL, tcl_cmd,
Tcl_NewStringObj("INSERT", -1));

Tcl_ListObjAppendElement(NULL, tcl_cmd, tcl_trigtup);
Tcl_ListObjAppendElement(NULL, tcl_cmd,
pltcl_build_tuple_argument(trigdata->tg_trigtuple,
tupdesc,
!TRIGGER_FIRED_BEFORE(trigdata->tg_event)));
Tcl_ListObjAppendElement(NULL, tcl_cmd, Tcl_NewObj());

rettup = trigdata->tg_trigtuple;
@@ -1186,7 +1187,10 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state,
Tcl_NewStringObj("DELETE", -1));

Tcl_ListObjAppendElement(NULL, tcl_cmd, Tcl_NewObj());
Tcl_ListObjAppendElement(NULL, tcl_cmd, tcl_trigtup);
Tcl_ListObjAppendElement(NULL, tcl_cmd,
pltcl_build_tuple_argument(trigdata->tg_trigtuple,
tupdesc,
true));

rettup = trigdata->tg_trigtuple;
}
@@ -1195,11 +1199,14 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state,
Tcl_ListObjAppendElement(NULL, tcl_cmd,
Tcl_NewStringObj("UPDATE", -1));

tcl_newtup = pltcl_build_tuple_argument(trigdata->tg_newtuple,
tupdesc);

Tcl_ListObjAppendElement(NULL, tcl_cmd, tcl_newtup);
Tcl_ListObjAppendElement(NULL, tcl_cmd, tcl_trigtup);
Tcl_ListObjAppendElement(NULL, tcl_cmd,
pltcl_build_tuple_argument(trigdata->tg_newtuple,
tupdesc,
!TRIGGER_FIRED_BEFORE(trigdata->tg_event)));
Tcl_ListObjAppendElement(NULL, tcl_cmd,
pltcl_build_tuple_argument(trigdata->tg_trigtuple,
tupdesc,
true));

rettup = trigdata->tg_newtuple;
}
@@ -3091,7 +3098,7 @@ pltcl_set_tuple_values(Tcl_Interp *interp, const char *arrayname,
* from all attributes of a given tuple
**********************************************************************/
static Tcl_Obj *
pltcl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc)
pltcl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc, bool include_generated)
{
Tcl_Obj *retobj = Tcl_NewObj();
int i;
@@ -3110,6 +3117,13 @@ pltcl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc)
if (att->attisdropped)
continue;

if (att->attgenerated)
{
/* don't include unless requested */
if (!include_generated)
continue;
}

/************************************************************
* Get the attribute name
************************************************************/
@@ -3219,6 +3233,12 @@ pltcl_build_tuple_result(Tcl_Interp *interp, Tcl_Obj **kvObjv, int kvObjc,
errmsg("cannot set system attribute \"%s\"",
fieldName)));

if (TupleDescAttr(tupdesc, attn - 1)->attgenerated)
ereport(ERROR,
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
errmsg("cannot set generated column \"%s\"",
fieldName)));

values[attn - 1] = utf_u2e(Tcl_GetString(kvObjv[i + 1]));
}

@@ -71,6 +71,11 @@ CREATE TABLE trigger_test (
-- Make certain dropped attributes are handled correctly
ALTER TABLE trigger_test DROP dropme;

CREATE TABLE trigger_test_generated (
i int,
j int GENERATED ALWAYS AS (i * 2) STORED
);

CREATE VIEW trigger_test_view AS SELECT i, v FROM trigger_test;

CREATE FUNCTION trigger_data() returns trigger language pltcl as $_$
@@ -125,6 +130,13 @@ CREATE TRIGGER statement_trigger
BEFORE INSERT OR UPDATE OR DELETE OR TRUNCATE ON trigger_test
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_data(42,'statement trigger');

CREATE TRIGGER show_trigger_data_trig_before
BEFORE INSERT OR UPDATE OR DELETE ON trigger_test_generated
FOR EACH ROW EXECUTE PROCEDURE trigger_data();
CREATE TRIGGER show_trigger_data_trig_after
AFTER INSERT OR UPDATE OR DELETE ON trigger_test_generated
FOR EACH ROW EXECUTE PROCEDURE trigger_data();

CREATE TRIGGER show_trigger_data_view_trig
INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view
FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view');
@@ -531,6 +543,10 @@ select * from T_pkey2 order by key1 using @<, key2 collate "C";
-- show dump of trigger data
insert into trigger_test values(1,'insert');

insert into trigger_test_generated (i) values (1);
update trigger_test_generated set i = 11 where i = 1;
delete from trigger_test_generated;

insert into trigger_test_view values(2,'insert');
update trigger_test_view set v = 'update' where i=1;
delete from trigger_test_view;
@@ -540,6 +556,9 @@ update trigger_test set v = 'update' where i = 1;
delete from trigger_test;
truncate trigger_test;

DROP TRIGGER show_trigger_data_trig_before ON trigger_test_generated;
DROP TRIGGER show_trigger_data_trig_after ON trigger_test_generated;

-- should error
insert into trigger_test(test_argisnull) values(true);

@@ -565,3 +584,20 @@ CREATE TRIGGER a_t AFTER UPDATE ON transition_table_test
update transition_table_test set name = 'b';
drop table transition_table_test;
drop function transition_table_test_f();

-- dealing with generated columns

CREATE FUNCTION generated_test_func1() RETURNS trigger
LANGUAGE pltcl
AS $$
# not allowed
set NEW(j) 5
return [array get NEW]
$$;

CREATE TRIGGER generated_test_trigger1 BEFORE INSERT ON trigger_test_generated
FOR EACH ROW EXECUTE PROCEDURE generated_test_func1();

TRUNCATE trigger_test_generated;
INSERT INTO trigger_test_generated (i) VALUES (1);
SELECT * FROM trigger_test_generated;
@@ -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) STORED);
\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) stored

INSERT INTO test_like_gen_1 (a) VALUES (1);
SELECT * FROM test_like_gen_1;
a | b
---+---
1 | 2
(1 row)

CREATE TABLE test_like_gen_2 (LIKE test_like_gen_1);
\d test_like_gen_2
Table "public.test_like_gen_2"
Column | Type | 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) stored

INSERT INTO test_like_gen_3 (a) VALUES (1);
SELECT * FROM test_like_gen_3;
a | b
---+---
1 | 2
(1 row)

DROP TABLE test_like_gen_1, test_like_gen_2, test_like_gen_3;
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

Large diffs are not rendered by default.

@@ -84,7 +84,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
# ----------
# Another group of parallel tests
# ----------
test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password identity
test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password identity generated

# ----------
# Another group of parallel tests
@@ -122,6 +122,7 @@ test: groupingsets
test: drop_operator
test: password
test: identity
test: generated
test: create_table_like
test: alter_generic
test: alter_operator
@@ -51,6 +51,20 @@ INSERT INTO test_like_id_3 (b) VALUES ('b3');
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) STORED);
\d test_like_gen_1
INSERT INTO test_like_gen_1 (a) VALUES (1);
SELECT * FROM test_like_gen_1;
CREATE TABLE test_like_gen_2 (LIKE test_like_gen_1);
\d test_like_gen_2
INSERT INTO test_like_gen_2 (a) VALUES (1);
SELECT * FROM test_like_gen_2;
CREATE TABLE test_like_gen_3 (LIKE test_like_gen_1 INCLUDING GENERATED);
\d test_like_gen_3
INSERT INTO test_like_gen_3 (a) VALUES (1);
SELECT * FROM test_like_gen_3;
DROP TABLE test_like_gen_1, test_like_gen_2, test_like_gen_3;

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

Large diffs are not rendered by default.

@@ -0,0 +1,65 @@
# Test generated columns
use strict;
use warnings;
use PostgresNode;
use TestLib;
use Test::More tests => 2;

# setup

my $node_publisher = get_new_node('publisher');
$node_publisher->init(allows_streaming => 'logical');
$node_publisher->start;

my $node_subscriber = get_new_node('subscriber');
$node_subscriber->init(allows_streaming => 'logical');
$node_subscriber->start;

my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';

$node_publisher->safe_psql('postgres',
"CREATE TABLE tab1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED)");

$node_subscriber->safe_psql('postgres',
"CREATE TABLE tab1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 22) STORED)");

# data for initial sync

$node_publisher->safe_psql('postgres',
"INSERT INTO tab1 (a) VALUES (1), (2), (3)");

$node_publisher->safe_psql('postgres',
"CREATE PUBLICATION pub1 FOR ALL TABLES");
$node_subscriber->safe_psql('postgres',
"CREATE SUBSCRIPTION sub1 CONNECTION '$publisher_connstr' PUBLICATION pub1"
);

# Wait for initial sync of all subscriptions
my $synced_query =
"SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
$node_subscriber->poll_query_until('postgres', $synced_query)
or die "Timed out while waiting for subscriber to synchronize data";

my $result = $node_subscriber->safe_psql('postgres',
"SELECT a, b FROM tab1");
is($result, qq(1|22
2|44
3|66), 'generated columns initial sync');

# data to replicate

$node_publisher->safe_psql('postgres',
"INSERT INTO tab1 VALUES (4), (5)");

$node_publisher->safe_psql('postgres',
"UPDATE tab1 SET a = 6 WHERE a = 5");

$node_publisher->wait_for_catchup('sub1');

$result = $node_subscriber->safe_psql('postgres',
"SELECT a, b FROM tab1");
is($result, qq(1|22
2|44
3|66
4|88
6|132), 'generated columns replicated');