# GSoC 2026: Add support for generate\_series in postgres (Draft Proposal Feedback)

**URL:** https://forum.djangoproject.com/t/gsoc-2026-add-support-for-generate-series-in-postgres-draft-proposal-feedback/44360
**Category:** Google Season of Code
**Created:** [March 2, 2026, 9:37pm UTC](https://forum.djangoproject.com/t/gsoc-2026-add-support-for-generate-series-in-postgres-draft-proposal-feedback/44360 "2026-03-02T21:37:19Z")
**Posts on this page:** 2
**Page:** 1

<div class="post-metadata">

### Author: ![Samriddha9619](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/samriddha9619/32/29438_2.png) [@Samriddha9619](https://forum.djangoproject.com/u/Samriddha9619)
#### Post date: [March 2, 2026, 9:37pm UTC](https://forum.djangoproject.com/t/gsoc-2026-add-support-for-generate-series-in-postgres-draft-proposal-feedback/44360/1 "2026-03-02T21:37:19Z")

</div>

Hi,  
I am Samriddha. I am interested in distributed systems and understanding how databases work internally, which is why I enjoy working on ORM-related issues in Django.  
This is my open source work till now (mainly in Django) - [GitHub - Samriddha9619/github-open-source-logs · GitHub](https://github.com/Samriddha9619/github-open-source-logs)  
I’m putting together my GSoC 2026 proposal for the “Add support for  
generate\_series in postgres” project . Before I finalize my proposal, I wanted to get early  
feedback on my implementation approach.

**The Bug**

When `.annotate(val=GenerateSeries(1, 5)).filter(val__gt=3)` is called,  
`build_filter()` calls `solve_lookup_type()` → `refs_expression()`,  
which returns the raw `GenerateSeries` expression as  
`reffed_expression`. The early return path in `build_filter()` then  
compiles `generate_series(1, 5)` directly into the WHERE clause.  
PostgreSQL raises `ProgrammingError` because SRFs are not allowed  
in WHERE.

The root cause: `add_annotation()` doesn’t inspect the  
`set_returning` flag. All annotations are treated as SELECT-clause  
values.

**Proposed Architecture**

The fix point is `Query.add_annotation()`. When `set_returning=True`  
is detected:

1. Route the expression to a new `TableValuedFunction` AST node in  
`alias_map` (alongside `Join` and `BaseTable` in  
`datastructures.py`).
2. Replace the annotation with a `Col` reference to the FROM-clause  
alias.
3. Subsequent `refs_expression` calls return the `Col`, so the WHERE  
clause gets `"val"."value" > 3` instead of the raw function.

```auto
User writes: .annotate(series=GenerateSeries(1, 10)).filter(series__gt=5)

                            │ │
                            ▼ │
              Query.add_annotation() │
              detects set_returning=True │
                            │ │
              ┌─────────────┴──────────────┐ │
              ▼ ▼ │
     alias_map["series"] = annotations["series"] = │
    TableValuedFunction( Col("series", IntegerField) │
       generate_series(1,10), │
       alias="series" ◄───────────────────────────────────┘
    ) resolve_ref("series") returns Col
              │ compiler emits: "series"."value" > 5
              ▼
    get_from_clause() emits:
    , generate_series(1, 10) AS "series"("value")

```

This requires no new QuerySet methods everything goes through  
`.annotate()`. `get_from_clause()` already iterates over `alias_map`  
and calls `compile()` on each entry, so the `TableValuedFunction`  
node integrates with zero changes to the compiler’s `as_sql()`.

I’m scoping this to single-column SRFs (`generate_series`, `unnest`)  
first, leaving multi-column functions like `json_each` for a  
follow-up phase per Lily’s comments on new-features Issue #25  
about alias management for composite return types.

The core routing lives in `django.db.models.sql`, not  
`contrib.postgres`, so the infrastructure is reusable for other  
backends’ SRFs in the future.

**My question:**

`Query.join()` handles deduplication and join-type promotion via  
`JoinPromoter`, but expects a `Join` object with `join_field` and  
`join_cols` attributes that don’t exist for a virtual table source.  
Should I adapt `join()` to accept `TableValuedFunction` nodes, or is  
direct `alias_map` insertion with manual refcount management the  
cleaner path?

Looking forward to any feedback and pointers.

Thanks.

---

<div class="post-metadata">

### Author: ![Samriddha9619](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/samriddha9619/32/29438_2.png) [@Samriddha9619](https://forum.djangoproject.com/u/Samriddha9619)
#### Post date: [March 16, 2026, 9:48am UTC](https://forum.djangoproject.com/t/gsoc-2026-add-support-for-generate-series-in-postgres-draft-proposal-feedback/44360/2 "2026-03-16T09:48:11Z")

</div>

Following recent feedback on this forum post - [Post](https://forum.djangoproject.com/t/gsoc-2026-interest-in-adding-support-for-generate-series-in-postgres/44362/4)  
I’ve restructured my proposal to prioritize the foundational ORM primitives rather than jumping directly to SRF routing.

**Revised phased approach:**

1. **`CompositeField`** — Enabling expressions to declare tuple return types. The existing ColPairs (composite PK support) and `allows_composite_expressions` flag show the ORM is already moving in this direction.
2. **output\_field on `alias_map` members** — Having BaseTable and Join define their own output\_field so that lookup resolution in names\_to\_path() can transition from `self.model` → `self.base_table.output_field`. This delegates SQL generation to expressions instead of hardcoding it in the compiler.
3. **SRF support via LATERAL JOIN** — With the above foundations, a `TableValuedFunction` alias\_map member naturally integrates: add\_annotation() detects `set_returning=True`, routes the expression to `alias_map` with a `CompositeField` output, and replaces the annotation with a Col reference. The compiler emits `LATERAL generate_series(...)` in the FROM clause.

**One specific question:** Once Phase 2 makes `alias_map` members self-describing via

output\_field, would you expect `TableValuedFunction` to integrate through `Query.join()` (with a lightweight adaptation for members that lack `join_cols`), or is direct `alias_map` insertion with manual refcount management the cleaner approach? TVFs don’t participate in join deduplication or promotion, so I lean toward direct insertion — but I’d value your perspective on long-term maintainability.

**Updated AST ( Abstract Syntax Tree )**

```auto
User writes:
.annotate(series=GenerateSeries(1, 10)).filter(series__gt=5)
                               │
                               ▼
                  ┌────────────────────────┐
                  │ Query.add_annotation() │
                  │ (set_returning=True) │
                  └────────────┬───────────┘
                               │
            ┌──────────────────┴──────────────────┐
            ▼ ▼
  [Phase 2: FROM clause] [Phase 1: SELECT/WHERE]
  alias_map["series"] = annotations["series"] =
  TableValuedFunction( Col("series",
    expr=GenerateSeries(1, 10), CompositeField(
    alias="series", IntegerField("value")
    output_field=CompositeField( )
      IntegerField("value") )
    )
  ) │
            │ │
            ▼ ▼
  get_from_clause() iterates resolve_ref("series")
  alias_map, calls as_sql() returns the Col object
            │ │
            ▼ ▼
  LATERAL generate_series(1, 10) "series"."value" > 5
  AS "series"("value")
 
 ─────────────────────────────────────────────────────────────
 FINAL SQL:
 SELECT "myapp_model"."id", "series"."value"
 FROM "myapp_model",
      LATERAL generate_series(1, 10) AS "series"("value")
 WHERE "series"."value" > 5
 ─────────────────────────────────────────────────────────────
```
