Skip to content

RestApiHandler: @computed fields from mixin types (with) cause column does not exist error when the model is explicitly included #2540

@lsmith77

Description

@lsmith77

Description and expected behavior

When a model inherits @computed fields via a mixin type (the with keyword) and is fetched as a nested relation via an explicit include, ZenStack generates invalid SQL that references the computed field as a raw column that does not exist in the query, causing PostgreSQL to fail with:

error: column "$$tN.fieldName" does not exist

The bug does not occur when:

  • the @computed field is declared directly on the model (not via a mixin)
  • the model is queried directly without include
  • the relation is not explicitly included by the parent query

Minimal reproduction

Schema:

type ParentRelated {
    parentCode String? @computed
}

model Parent {
    id       Int     @id @default(autoincrement())
    code     String
    children Child[]
}

model Child with ParentRelated {
    id       Int     @id @default(autoincrement())
    name     String
    parentId Int
    parent   Parent  @relation(fields: [parentId], references: [id])
}

computedFields config:

computedFields: {
    Child: {
        parentCode: (eb) =>
            eb.selectFrom('Parent')
              .select('Parent.code')
              .whereRef('Parent.id', '=', 'parentId')
              .limit(1),
    },
}

Query that fails:

db.parent.findFirst({
    where: { id: 1 },
    include: { children: true },
})

Generated SQL (simplified):

SELECT "Parent"."id", "Parent"."code", "$$t1"."$data" AS "children"
FROM "Parent"
LEFT JOIN LATERAL (
  SELECT coalesce(jsonb_agg(jsonb_build_object(
    'parentCode', "$$t2"."parentCode",   -- ❌ column does not exist
    'id',         "$$t2"."id",
    'name',       "$$t2"."name",
    'parentId',   "$$t2"."parentId"
  )), '[]'::jsonb) AS "$data"
  FROM "Child" AS "$$t2"               -- ✅ flat join, no computed column selected
  WHERE "$$t2"."parentId" = "Parent"."id"
) AS "$$t1" ON true

Root cause

canJoinWithoutNestedSelect guards whether the lateral join needs to wrap the child model in a nested subquery (which would include the computed field in its SELECT via buildSelectAllFields). The guard checks modelDef.computedFields, but when @computed fields are inherited from a mixin type, they are stored on fieldDef.originModel rather than directly on the model definition. The guard therefore returns true (no nested subquery needed), the computed field is never selected into the child subquery, and the outer jsonb_build_object references $$tN.parentCode which does not exist.

When the @computed field is declared directly on the model, modelDef.computedFields is populated and the guard correctly returns false, producing a nested subquery that includes the computed field.

Screenshots

N/A

Environment:

  • ZenStack version: 3.4.4
  • Database type: PostgreSQL
  • Node.js version: 23.10.0
  • Package manager: npm 10.9.2

Additional context
Here is a test case
#2539

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions