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
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:
- Route the expression to a new
TableValuedFunctionAST node in
alias_map(alongsideJoinandBaseTablein
datastructures.py). - Replace the annotation with a
Colreference to the FROM-clause
alias. - Subsequent
refs_expressioncalls return theCol, so the WHERE
clause gets"val"."value" > 3instead of the raw function.
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.