Re: Virtual generated columns
jian he <jian.universality@gmail.com>
Commits
GET /api/v1/messages/:b64id/commits
the thread's linked commits as JSON, with link sources.
API reference →
-
Expand virtual generated columns for ALTER COLUMN TYPE
- 5069fef1cfae 18.0 landed
-
Eliminate code duplication in replace_rte_variables callbacks
- 363a6e8c6fcf 18.0 landed
-
Expand virtual generated columns in the planner
- 1e4351af329f 18.0 landed
-
Virtual generated columns
- 83ea6c54025b 18.0 landed
-
Additional tests for stored generated columns
- 41084409f635 18.0 landed
-
Improve generated_stored test
- 44b61efb7928 18.0 landed
- 86749ea3b766 18.0 landed
-
Fix handling of CREATE DOMAIN with GENERATED constraint syntax
- 84a67725cd11 18.0 landed
-
Add pg_constraint rows for not-null constraints
- 14e87ffa5c54 18.0 cited
-
Put generated_stored test objects in a schema
- 894be11adfa6 18.0 landed
-
Rename regress test generated to generated_stored
- b9ed4969250d 18.0 landed
-
Small code simplification
- 7ff9afbbd1df 18.0 landed
-
Remove useless code
- e26d313bad92 18.0 landed
-
Remove useless initializations
- da2aeba8f533 18.0 landed
-
doc: Clarify that pg_attrdef also stores generation expressions
- da486d360103 18.0 landed
-
Clean out column-level pg_init_privs entries when dropping tables.
- 76618097a6c0 17.0 cited
-
Re-implement the ereport() macro using __VA_ARGS__.
- e3a87b4991cc 13.0 cited
On Thu, Aug 8, 2024 at 2:23 PM Peter Eisentraut <peter@eisentraut.org> wrote:
>
> Thank you for your extensive testing. Here is a new patch set that has
> fixed all the issues you have reported (MERGE, sublinks, statistics,
> ANALYZE).
if (coldef->generated && restdef->generated &&
coldef->generated != restdef->generated)
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
errmsg("column \"%s\" inherits from
generated column of different kind",
restdef->colname)));
the error message is not informal. maybe add errhint that
"column \"%s\" should be same as parent table's generated column kind:
%s", "virtual"|"stored"
.../regress/expected/create_table_like.out | 23 +-
.../regress/expected/generated_stored.out | 27 +-
...rated_stored.out => generated_virtual.out} | 835 +++++++++---------
src/test/regress/parallel_schedule | 3 +
src/test/regress/sql/create_table_like.sql | 2 +-
src/test/regress/sql/generated_stored.sql | 10 +-
...rated_stored.sql => generated_virtual.sql} | 301 ++++---
src/test/subscription/t/011_generated.pl | 38 +-
55 files changed, 1280 insertions(+), 711 deletions(-)
copy src/test/regress/expected/{generated_stored.out
generated_virtual.out} (69%)
copy src/test/regress/sql/{generated_stored.sql => generated_virtual.sql} (72%)
I don't understand the "copy =>" part, I guess related to copy content
from stored to virtual.
anyway. some minor issue:
-- alter generation expression of parent and all its children altogether
ALTER TABLE gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 2);
\d gtest_parent
\d gtest_child
\d gtest_child2
\d gtest_child3
SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3;
The first line ALTER TABLE will fail for
src/test/regress/sql/generated_virtual.sql.
so no need
"""
\d gtest_parent
\d gtest_child
\d gtest_child2
\d gtest_child3
SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3;
"""
Similarly the following tests for gtest29 may aslo need change
-- ALTER TABLE ... ALTER COLUMN ... DROP EXPRESSION
since we cannot do ALTER TABLE SET EXPRESSION for virtual generated columns.
-- ALTER TABLE ... ALTER COLUMN
CREATE TABLE gtest27 (
a int,
b int,
x int GENERATED ALWAYS AS ((a + b) * 2) VIRTUAL
);
INSERT INTO gtest27 (a, b) VALUES (3, 7), (4, 11);
ALTER TABLE gtest27 ALTER COLUMN a TYPE text; -- error
ALTER TABLE gtest27 ALTER COLUMN x TYPE numeric;
will
ALTER TABLE gtest27 ALTER COLUMN a TYPE int4;
be a no-op?
do we need a document that virtual generated columns will use the
expression's collation.
see:
drop table if exists t5;
CREATE TABLE t5 (
a text collate "C",
b text collate "C" GENERATED ALWAYS AS (a collate case_insensitive) ,
d int DEFAULT 22
);
INSERT INTO t5(a,d) values ('d1',28), ('D2',27), ('D1',26);
select * from t5 order by b asc, d asc;
+ /*
+ * TODO: 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.
+ *
+ * XXX If column->typeName is not set, then this column
+ * definition is probably a partition definition and will
+ * presumably get its pre-vetted type from elsewhere. If that
+ * doesn't hold, maybe this check needs to be moved elsewhere.
+ */
+ if (column->generated == ATTRIBUTE_GENERATED_VIRTUAL && column->typeName)
+ {
+ 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);
+ }
create domain mydomain as int4;
create type mydomainrange as range(subtype=mydomain);
CREATE TABLE t3( b bigint, c mydomain GENERATED ALWAYS AS ('11') VIRTUAL);
CREATE TABLE t3( b bigint, c mydomainrange GENERATED ALWAYS AS
('[4,50)') VIRTUAL);
domain will error out, domain over range is ok, is this fine?
+ 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.
drop table if exists t5;
CREATE TABLE t5 (
a int,
b text storage extended collate "C" GENERATED ALWAYS AS (a::text
collate case_insensitive) ,
d int DEFAULT 22
);
select reltoastrelid <> 0 as has_toast_table from pg_class where oid =
't5'::regclass;
if really no storage, should table t5 have an associated toast table or not?
also check ALTER TABLE variant:
alter table t5 alter b set storage extended;
Do we need to do something in ATExecSetStatistics for cases like:
ALTER TABLE t5 ALTER b SET STATISTICS 2000;
(b is a generated virtual column).
because of
examine_attribute
if (attr->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
return NULL;
i guess, this won't have a big impact.
There are some issues with changing virtual generated column type.
like:
drop table if exists another;
create table another (f4 int, f2 text, f3 text, f1 int GENERATED
ALWAYS AS (f4));
insert into another values(1, 'one', 'uno'), (2, 'two', 'due'),(3,
'three', 'tre');
alter table another
alter f1 type text using f2 || ' and ' || f3 || ' more';
table another;
or
alter table another
alter f1 type text using f2 || ' and ' || f3 || ' more',
drop column f1;
ERROR: column "f1" of relation "another" does not exist
These two command outputs seem not right.
the stored generated column which works as expected.
in src/test/regress/sql/alter_table.sql
-- We disallow changing table's row type if it's used for storage
create table at_tab1 (a int, b text);
create table at_tab2 (x int, y at_tab1);
alter table at_tab1 alter column b type varchar; -- fails
drop table at_tab2;
I think the above restriction should apply to virtual generated columns too.
given in ATPrepAlterColumnType, not storage we still call
find_composite_type_dependencies
if (!RELKIND_HAS_STORAGE(tab->relkind))
{
/*
* For relations without storage, do this check now. Regular tables
* will check it later when the table is being rewritten.
*/
find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
}
so i think in ATPrepAlterColumnType, we should do:
if (attTup->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
{
find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
}
else if (tab->relkind == RELKIND_RELATION ||
tab->relkind == RELKIND_PARTITIONED_TABLE)
{
}
else if (transform)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
RelationGetRelationName(rel))));
you may add following tests:
------------------------------------------------------------------------
create table at_tab1 (a int, b text GENERATED ALWAYS AS ('hello'), c text);
create table at_tab2 (x int, y at_tab1);
alter table at_tab1 alter column b type varchar; -- fails
drop table at_tab1, at_tab2;
-- Check it for a partitioned table, too
create table at_tab1 (a int, b text GENERATED ALWAYS AS ('hello'), c
text) partition by list(a);;
create table at_tab2 (x int, y at_tab1);
alter table at_tab1 alter column b type varchar; -- fails
drop table at_tab1, at_tab2;
---------------------------------------------------------------------------------