Skip to content

entc/gen: a field named after a method every mutation has compiles - #21

Merged
lesomnus merged 1 commit into
masterfrom
mutation-names
Oct 24, 2026
Merged

lesomnus merged 1 commit into
masterfrom
mutation-names

Conversation

@lesomnus

@lesomnus lesomnus commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

Fixes #18.

What was wrong

A field named field generated a mutation that did not compile. Its own methods would be AddField, AddedField, ClearField and FieldCleared, which every mutation has already (ent.Mutation), and the two halves of the template disagreed about what to do about that.

  • The namers renamed, and the callers did not ask them. MutationAdd and MutationAdded answer AddFieldField and AddedFieldField, but the dispatchers that take a field by its name called the field's methods by the plain name -- m.Added{{ $f.StructField }}(), m.Add{{ $f.StructField }}(v), m.Clear{{ $f.StructField }}() -- which reached the generic methods with the wrong arguments.
  • Two namers never asked mutMethods. MutationClear and MutationCleared always answered Clear<F> and <F>Cleared, which redeclared the interface's.
  • Three more callers spelled a name. The update builders' sqlSave and update defaults called {{ $f.StructField }}Cleared(), and an update's check of a required unique edge {{ $e.StructField }}Cleared(), though the edge's own namer renames it. An edge named edge broke there.
  • mutMethods was not every method a mutation has. It was ent.Mutation's and four others (Client, Tx, Where, SetOp). It missed Predicates, WhereP, the expression methods (SetFieldExpr, FieldExpr, ExprFields, entc/gen, sqlgraph: a mutation assigns a field a SQL expression #12), and Ids: the ent package's mutation has Ids(ctx), which is upstream's IDs spelled by this fork's rule, and a field named ids got a getter Ids() that it shadowed.

What broke, generated from master: field (the five errors in the issue), fields (AddedFields), edge (ClearEdge, EdgeCleared, and the edge check), ids (AddedIds and the getter).

What changed

  • MutationClear and MutationCleared ask mutMethods as the others do: ClearFieldField, and FieldFieldCleared, which puts the word where an edge's EdgeEdgeCleared puts its own.
  • Every call of a field's method goes through its namer: the dispatchers AddedField, AddField and ClearField; the update builders' Clear<F>, which keeps its own name as Set<F> does, sqlSave and defaults; and the edge check uses $e.MutationCleared.
  • mutMethods lists every method a generated mutation has beside ent.Mutation's.
  • UPSTREAM.md has a Behaviour row. Upstream has the same code, but for Ids.

Nothing that compiled is named differently. Every method renamed is one whose old name did not compile. The generate gate regenerates every example and integration schema, and nothing moves, so an app regenerates nothing it already had.

Tests

  • entc/integration/fieldnames. Item has a numeric, optional, nillable field for each of field, fields, edge, ids, where, op and type; Owner has an update default on field and a required unique edge named edge. Generated with entql, privacy, intercept, sql/upsert, sql/modifier and sql/returning. It compiles, which it did not. On every engine:
    • each field is written, read, filtered and ordered by;
    • an update adds to each, and an update of many clears each;
    • a hook adds to, clears and sets each through AddField, ClearField and SetField, by name, and reads AddedField and FieldCleared back;
    • Owner's update default is filled unless the update clears the field, and its edge moves and refuses to be cleared.
  • entc/gen.
    • TestField_MutationNames names a field after every method in mutMethods, and after what is left of one without a namer's prefix or suffix (field for AddField, edge for EdgeCleared), and fails a namer that answers a method every mutation has.
    • TestMutationMethods generates a type with nothing of its own, reads the methods of its two mutations, and fails one mutMethods does not list.
    • On master's type.go they fail with MutationClear of a field named "edge" is ClearEdge and every mutation has SetFieldExpr.
  • Run here, green:
    • go test -race in entc/gen and examples;
    • the generate gate;
    • .github/lint.sh over the files changed;
    • entc/integration/fieldnames on SQLite, MySQL 8.0 and 8.4, MariaDB 10.11 and 11.4, and PostgreSQL 14 and 17;
    • the whole entc/integration with -race -count=2 on all six engines. Twenty packages passed together; the root package, enttime and migrate ran into go test's ten minutes on this machine, which was loaded throughout (load average 14 to 20 on 16 cores), and passed run one at a time (-p 1 -timeout 60m). CI ran the same in about four minutes.

Found, not changed

These are of the same family. Nothing here needs them.

  • An edge's own getters are spelled, not named. <Edge>Ids(), Removed<Edge>Ids() and <Edge>Id() have no namer, so an edge named added or removed gets an AddedIds() or RemovedIds() beside the interface's.
  • entql gives the ent package's mutation a Filter(). It shadows the getter of a field named filter. It is not in mutMethods, since the list is not per graph: putting it there would rename that getter in every graph without entql.
  • Two fields can meet each other. The ent package's mutation names the old value of name OldName(ctx), which shadows the getter of a field named old_name. A schema with both does not compile.

Stacked

#22 (#19) is on top of this branch.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KxtnpRW533bdErnW8QPhsk

A field named `field` generated a mutation that did not compile (#18).
Its own methods would be AddField, AddedField, ClearField and
FieldCleared, which every mutation already has, and the two halves of
the template disagreed about what to do about that. The namers renamed
Add and Added -- AddFieldField, AddedFieldField -- but the dispatchers
that take a field by its name called the field's methods by the plain
name, which reached the generic ones with the wrong arguments; and
Clear and Cleared were never renamed, so they were declared a second
time. `fields` (AddedFields), `edge` (ClearEdge, EdgeCleared) and `ids`
(AddedIds) broke the same way.

The namers decide now, and everything calls through them.
MutationClear and MutationCleared ask mutMethods as the others do:
ClearFieldField, and FieldFieldCleared, which puts the word where an
edge's EdgeEdgeCleared puts its own. The dispatchers, the update
builders' Clear, sqlSave and update defaults, and an update's check of
a required unique edge call the namer's name instead of spelling one.

mutMethods was ent.Mutation's methods and four others. It lists every
method a generated mutation has beside the interface's now. One it
missed was Ids, upstream's IDs spelled by this fork's rule: that is
also the getter of a field named `ids`, which the ent package's
mutation shadowed.

Nothing that compiled is named differently. Every method renamed is one
whose old name did not compile, and the generated trees here are
unchanged.

entc/integration/fieldnames has an entity with a numeric, optional
field of each of those names, and one with an update default on
`field` and a required unique edge named `edge`. It compiles, which it
did not, and on every engine each field is written, filtered and
ordered by, added to and cleared -- by the builders, and by a hook
through the methods that take a field's name. In entc/gen,
TestField_MutationNames names a field after every method in mutMethods,
and after what is left of one without a namer's prefix or suffix, and
fails a namer that answers a method every mutation has;
TestMutationMethods reads the methods of a generated mutation and fails
one mutMethods does not list. Both fail without this change.

Upstream has the same code, but for Ids.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KxtnpRW533bdErnW8QPhsk
@lesomnus
lesomnus merged commit 739a809 into master Oct 24, 2026
4 checks passed
@lesomnus
lesomnus deleted the mutation-names branch October 24, 2026 20:54
lesomnus added a commit to lesomnus/roster that referenced this pull request Oct 24, 2026
…, and an ent package is imported under a name of payday's

Moves payday 34ab64a -> 53eabb7 (payday#173, payday#174) with
`go tool pd upgrade --payday 53eabb7`, after `GOPROXY=direct go get` of
the same commit, which raises protobuf-orm/ent 2248bed -> 0dd7dad by
payday's go.mod: its master with protobuf-orm/ent#21 and #22 merged.
`buf dep update` leaves the BSR pin at d10eaae1, which payday's `dev`
still is: nothing under payday's proto/ changed. `pd upgrade` moved
nothing on the page.

payday's docs/migrating.md, the three entries since 34ab64a:

- An ent package is imported under a name of payday's (payday#174).
  Applies to every app, which regenerates: server/pd/pd.g.go imports
  each entity's ent package as `ent<Entity>` -- `entHolder` where it
  was `holder` -- and payday's own `audit` and `access` lose the `1`
  protogen gave them. Nothing else in the file moves, and nothing
  outside server/pd names those imports.
- The trail and the access record have an index by time (payday#157).
  Applies to every app: `Audit` and `Access` declare `by_time` on
  (date_created, id), which regenerating copies into
  proto/roster/payday, internal/ent, rstr and ts/gen, and which a
  migration makes as `audit_by_time` and `access_by_time`. pd.g.go's
  `IndexRenames` names each under ent's default name, as it does every
  declared index. wasm/schema/schema.sql, which
  TestTheScriptIsThisSchema writes, has the two lines.
- A deep page of a list starts at its cursor (protobuf-orm/ent#21 and
  #22). Applies to every app, which regenerates: the one file is the
  ent stamp in internal/ent/runtime/runtime.go.

What the index changes here is what payday#174 corrected the second
entry to say: roster's sweep is not a pass over the whole deployment.
`serve` hands `trail.Sweep` the policy `forget.Held` returns, whose
`Tenants` answers roster's holds (#105), so a pass goes tenant by
tenant, as it did; so does `roster trail prune` with no window. What
reads the trail across the deployment here is `roster trail prune
--older-than` (or `--before`) with no `--kind`: `trail.Archive` into
`--to` or `audit.archive`, `Policy.Collect` with `--discard`, and the
count `--dry-run` makes. Those are what the index is for.

A migration on a trail with rows builds each index as it goes, and on
PostgreSQL the table takes no writes from its index's build until the
migration commits. `serve` with `db.migrate: true`, as
deploy/config.yaml has it, migrates before it answers; with it off,
`serve` refuses the database and prints the two statements. A
deployment that cannot pause its writes for that long makes the two
first, `CREATE INDEX CONCURRENTLY IF NOT EXISTS` under the declared
names, and the migration finds them and leaves them.

The pin moves so that kamino, khala, rove and shale can link a roster
generated with this payday: a `pd.g.go` stamped by another is refused
when it runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KxtnpRW533bdErnW8QPhsk
lesomnus added a commit to lesomnus/roster that referenced this pull request Oct 24, 2026
…, and an ent package is imported under a name of payday's (#199)

Moves payday 34ab64a -> 53eabb7 (payday#173, payday#174) with
`go tool pd upgrade --payday 53eabb7`, after `GOPROXY=direct go get` of
the same commit, which raises protobuf-orm/ent 2248bed -> 0dd7dad by
payday's go.mod: its master with protobuf-orm/ent#21 and #22 merged.
`buf dep update` leaves the BSR pin at d10eaae1, which payday's `dev`
still is: nothing under payday's proto/ changed. `pd upgrade` moved
nothing on the page.

payday's docs/migrating.md, the three entries since 34ab64a:

- An ent package is imported under a name of payday's (payday#174).
  Applies to every app, which regenerates: server/pd/pd.g.go imports
  each entity's ent package as `ent<Entity>` -- `entHolder` where it
  was `holder` -- and payday's own `audit` and `access` lose the `1`
  protogen gave them. Nothing else in the file moves, and nothing
  outside server/pd names those imports.
- The trail and the access record have an index by time (payday#157).
  Applies to every app: `Audit` and `Access` declare `by_time` on
  (date_created, id), which regenerating copies into
  proto/roster/payday, internal/ent, rstr and ts/gen, and which a
  migration makes as `audit_by_time` and `access_by_time`. pd.g.go's
  `IndexRenames` names each under ent's default name, as it does every
  declared index. wasm/schema/schema.sql, which
  TestTheScriptIsThisSchema writes, has the two lines.
- A deep page of a list starts at its cursor (protobuf-orm/ent#21 and
  #22). Applies to every app, which regenerates: the one file is the
  ent stamp in internal/ent/runtime/runtime.go.

What the index changes here is what payday#174 corrected the second
entry to say: roster's sweep is not a pass over the whole deployment.
`serve` hands `trail.Sweep` the policy `forget.Held` returns, whose
`Tenants` answers roster's holds (#105), so a pass goes tenant by
tenant, as it did; so does `roster trail prune` with no window. What
reads the trail across the deployment here is `roster trail prune
--older-than` (or `--before`) with no `--kind`: `trail.Archive` into
`--to` or `audit.archive`, `Policy.Collect` with `--discard`, and the
count `--dry-run` makes. Those are what the index is for.

A migration on a trail with rows builds each index as it goes, and on
PostgreSQL the table takes no writes from its index's build until the
migration commits. `serve` with `db.migrate: true`, as
deploy/config.yaml has it, migrates before it answers; with it off,
`serve` refuses the database and prints the two statements. A
deployment that cannot pause its writes for that long makes the two
first, `CREATE INDEX CONCURRENTLY IF NOT EXISTS` under the declared
names, and the migration finds them and leaves them.

The pin moves so that kamino, khala, rove and shale can link a roster
generated with this payday: a `pd.g.go` stamped by another is refused
when it runs.


Claude-Session: https://claude.ai/code/session_01KxtnpRW533bdErnW8QPhsk

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A field named field generates a mutation that does not compile

1 participant