[Django] #36792: Add support for virtual generated columns (PostgreSQL 18+) in GeneratedField

2 views
Skip to first unread message

Django

unread,
Dec 11, 2025, 4:20:05 AM12/11/25
to django-...@googlegroups.com
#36792: Add support for virtual generated columns (PostgreSQL 18+) in
GeneratedField
-------------------------------------+-------------------------------------
Reporter: Paolo Melchiorre | Type: New
| feature
Status: new | Component: Database
| layer (models, ORM)
Version: dev | Severity: Normal
Keywords: postgresql | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
=== Background ===
PostgreSQL 18 introduces virtual generated columns: columns declared with
GENERATED ALWAYS AS ( <generation_expr> ) VIRTUAL are computed at query
time
and do not occupy storage. PostgreSQL 18 also makes VIRTUAL the default
for
generated columns while continuing to support STORED (materialized)
generated
columns. See the PostgreSQL 18 release notes:
[https://www.postgresql.org/about/news/postgresql-18-released-3142/
PostgreSQL 18 release notes].

=== Motivation ===
Django's GeneratedField support currently targets databases that support
STORED
(materialized) generated columns. With PostgreSQL 18, users should be able
to
declare GeneratedFields that are VIRTUAL (computed at query time) and have
Django generate the correct DDL and perform correct introspection and
migration autogeneration.

=== Goals ===
* Add support in Django's PostgreSQL backend to emit the VIRTUAL keyword
in
CREATE TABLE and ALTER TABLE statements for generated columns when
targeting
PostgreSQL 18 or newer.
* Extend PostgreSQL introspection to detect whether an existing generated
column is STORED or VIRTUAL and populate migration state accordingly so
autogeneration can produce correct AddField / AlterField operations.
* Ensure migration operations handle switching between STORED and VIRTUAL
where supported by the server, and emit clear errors or warnings when
not
supported by the connected server version.
* Add tests and documentation; skip/guard tests on PostgreSQL servers
older
than 18.

=== Implementation notes and suggestions ===
* Server gating: use the existing {{{server_version_int}}} checks (treat
PostgreSQL 18 as the cutoff, e.g. {{{180000}}}) so that DDL including
VIRTUAL is only emitted on servers that support it.
* DDL generation: update the PostgreSQL schema editor (schema
creation/alter) to emit either "VIRTUAL" or "STORED" based on field
metadata and server version. If the field model exposes a {{{stored}}}
boolean, emit {{{STORED}}} when True and {{{VIRTUAL}}} when False (and
server >= 18).
* Introspection: extend postgresql introspection to read the generation
expression and the storage type (stored vs virtual) from
{{{information_schema}}} and/or {{{pg_catalog}}} and expose those details
in the field_info consumed by the migration autogenerator.
* Migrations: ensure {{{AlterField}}} and {{{AddField}}} include the
storage attribute and that migration autogeneration treats changes in
stored/virtual as schema changes. If the connected server does not support
VIRTUAL, raise a clear error during migration planning/execution.
* Tests: add backend tests that:
* create a model with a GeneratedField stored=True (STORED) and
stored=False (VIRTUAL),
* introspect an existing table with VIRTUAL columns,
* autogenerate migrations for switching between STORED and VIRTUAL,
* ensure tests are skipped on PostgreSQL < 18.
* Documentation: update GeneratedField docs to describe the stored vs
virtual option and PostgreSQL version requirements (PG 18+ for VIRTUAL).
Recommend keeping DB versions consistent across environments.

=== Examples ===
SQL (PG18+):
{{{
CREATE TABLE example (
a integer,
b integer GENERATED ALWAYS AS (a + 1) VIRTUAL
);
}}}

Stored column:
{{{
CREATE TABLE example (
a integer,
b integer GENERATED ALWAYS AS (a + 1) STORED
);
}}}

Django (draft API):
{{{
class MyModel(models.Model):
width = models.IntegerField()
area = models.GeneratedField(models.F('width') * 2, stored=False) #
VIRTUAL on PG18+
}}}

=== Backward-compatibility and migration considerations ===
* Emit {{{VIRTUAL}}} only when connected to PG 18+; otherwise use
{{{STORED}}} or raise a clear error.
* Autogenerated migrations created on PG18 that include {{{VIRTUAL}}}
should be documented or guarded to avoid failures on older servers.
* Recommend teams keep DB versions consistent across dev/test/prod if they
plan to use {{{VIRTUAL}}} columns.

=== Related ===
* Similar work for Oracle persisted generated fields:
[https://code.djangoproject.com/ticket/36765 #36765]
* PostgreSQL 18 release notes:
[https://www.postgresql.org/about/news/postgresql-18-released-3142/]
--
Ticket URL: <https://code.djangoproject.com/ticket/36792>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
Dec 11, 2025, 5:15:54 AM12/11/25
to django-...@googlegroups.com
#36792: Add support for virtual generated columns (PostgreSQL 18+) in
GeneratedField
-------------------------------------+-------------------------------------
Reporter: Paolo Melchiorre | Owner: (none)
Type: New feature | Status: closed
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution: duplicate
Keywords: postgresql | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by David Sanders):

* resolution: => duplicate
* status: new => closed

Comment:

Hi Paolo 👋

Was previously raised by Adam, it just missed out on the 6.0 release
however 😭

Duplicate of #36277
--
Ticket URL: <https://code.djangoproject.com/ticket/36792#comment:1>

Django

unread,
Dec 11, 2025, 6:07:01 AM12/11/25
to django-...@googlegroups.com
#36792: Add support for virtual generated columns (PostgreSQL 18+) in
GeneratedField
-------------------------------------+-------------------------------------
Reporter: Paolo Melchiorre | Owner: (none)
Type: New feature | Status: closed
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution: duplicate
Keywords: postgresql | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Paolo Melchiorre):

I totally missed it :)
Thanks
--
Ticket URL: <https://code.djangoproject.com/ticket/36792#comment:2>
Reply all
Reply to author
Forward
0 new messages