Skip to content

entgql: schema-qualify M2M join table in totalCount loader - #636

Merged
a8m merged 3 commits into
ent:masterfrom
michaelcaulley:entgql-schemaconfig-m2m-totalcount
Jun 18, 2026
Merged

entgql: schema-qualify M2M join table in totalCount loader#636
a8m merged 3 commits into
ent:masterfrom
michaelcaulley:entgql-schemaconfig-m2m-totalcount

Conversation

@michaelcaulley

Copy link
Copy Markdown
Contributor

The gql_collection.go totalCount loader for M2M edges builds the join table via sql.Table(...) but never calls joinT.Schema(...). With the sql/schemaconfig feature enabled, this emits an unqualified join table in the generated SQL while the rest of the query is correctly schema- qualified, producing relation does not exist errors at runtime.

Hook the same dialect/sql/query/eagerloading/join/* template extension point that ent core's query.tmpl uses for the loader path, so the join table picks up its schema from schemaConfig. No-op when the feature is disabled (verified: all 6 integration tests regenerate byte-identically).

@michaelcaulley
michaelcaulley force-pushed the entgql-schemaconfig-m2m-totalcount branch 2 times, most recently from 17ccd50 to 31b1a58 Compare June 16, 2026 20:59
@a8m
a8m requested a review from giautm June 17, 2026 13:00
@a8m

a8m commented Jun 17, 2026

Copy link
Copy Markdown
Member

cc @chinhnguyenh for review too.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes schema qualification for M2M join tables in the totalCount loader generated by entgql, ensuring queries work correctly when Ent’s sql/schemaconfig feature is enabled (avoiding runtime relation does not exist errors due to unqualified join tables).

Changes:

  • Hooks the dialect/sql/query/eagerloading/join/* template extension point into the M2M totalCount loader path.
  • Ensures the join-table builder (joinT := sql.Table(...)) can be modified by the same join-schema logic used by Ent core eager-loading queries.
  • Keeps behavior a no-op when no matching join templates are present (e.g., feature disabled).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@chinhnguyenh

Copy link
Copy Markdown
Contributor

Look good to me.
It would be nice if you can add a small example project for it.

@michaelcaulley

michaelcaulley commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

Look good to me. It would be nice if you can add a small example project for it.

Hi, @chinhnguyenh, are you on Discord? If so, can I reach out there? I have a few questions about the best way to add a test. If we want to create a new multischema project, we'll need MySQL to test it. Otherwise, we can limit the test to reading the generated code.

Edit: I've added each option as a separate commit. If you'd like to discuss either option, please feel free to message me on Discord.

@michaelcaulley
michaelcaulley force-pushed the entgql-schemaconfig-m2m-totalcount branch from 31b1a58 to 8defc40 Compare June 17, 2026 15:39
The gql_collection.go totalCount loader for M2M edges builds the join
table via `sql.Table(...)` but never calls `joinT.Schema(...)`. With the
`sql/schemaconfig` feature enabled, this emits an unqualified join table
in the generated SQL while the rest of the query is correctly schema-
qualified, producing `relation does not exist` errors at runtime.

Hook the same `dialect/sql/query/eagerloading/join/*` template extension
point that ent core's query.tmpl uses for the loader path, so the join
table picks up its schema from `schemaConfig`. No-op when the feature is
disabled (verified: all 6 integration tests regenerate byte-identically).

Co-authored-by: Isaac
Add a test (entgql/template_test.go) that renders the collection template
with and without the sql/schemaconfig feature and asserts the M2M
totalCount join table is schema-qualified only when the feature is
enabled. Covers the fix from the preceding commit at the codegen level and
runs in CI against SQLite.
@michaelcaulley
michaelcaulley force-pushed the entgql-schemaconfig-m2m-totalcount branch from 8defc40 to d3a3d97 Compare June 17, 2026 15:42
Add internal/multischema, a minimal Group<->User M2M project with the
sql/schemaconfig feature, plus a MySQL-backed functional test that places
the join table in a separate schema and drives totalCount through the
gqlgen + ent runtime. ent omits schema qualifiers for SQLite (see
(*sql.Builder).writeSchema), so the schema-qualified join is only
observable on a schema-aware database; the test skips when MySQL is
unreachable.

Add a mysql8 service to the CI test job (mirroring ent's integration CI)
so the test runs instead of skipping, and the go-sql-driver/mysql
dependency it requires.

Co-authored-by: Isaac
@michaelcaulley
michaelcaulley force-pushed the entgql-schemaconfig-m2m-totalcount branch from d3a3d97 to 659006e Compare June 17, 2026 15:59
@chinhnguyenh

Copy link
Copy Markdown
Contributor

Look good to me. It would be nice if you can add a small example project for it.

Hi, @chinhnguyenh, are you on Discord? If so, can I reach out there? I have a few questions about the best way to add a test. If we want to create a new multischema project, we'll need MySQL to test it. Otherwise, we can limit the test to reading the generated code.

Edit: I've added each option as a separate commit. If you'd like to discuss either option, please feel free to message me on Discord.

We don't need to test it with MySQL, verifing the generated code is enough.
Please rename the project to todoschema to keep consistency with others.

Comment thread entgql/template_test.go
// TestCollection_M2MTotalCountSchemaConfig verifies that the M2M totalCount
// loader schema-qualifies its join table when the sql/schemaconfig feature is
// enabled, and leaves it unqualified otherwise.
func TestCollection_M2MTotalCountSchemaConfig(t *testing.T) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create gen_test.go in the example project and create tests there instead.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're testing only the generated code, do we need the new project? Can I drop my last commit?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example project is what CI regenerates and diffs. The test only reads the output, but the project is what keeps that output committed, compilable, and in sync with the templates.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyway, it's just a nice-to-have.

@a8m
a8m merged commit 6a207cd into ent:master Jun 18, 2026
4 checks passed
thejensemann added a commit to spread-ai/ent-contrib that referenced this pull request Jul 5, 2026
* entgql: fix field arguments collection (ent#495)

* entgql: collect interafce/union selection fields of unique edges (ent#498)

* entproto: support enum values with special characters (ent#487)

* build(deps): bump golang.org/x/tools from 0.7.0 to 0.9.1 (ent#504)

Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.7.0 to 0.9.1.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](golang/tools@v0.7.0...v0.9.1)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* entgql: support collecting fields of renamed types (ent#505)

* entgql: implement interfaces and collect all node implementors (ent#507)

* entgql: support generating non-optional Map fields (ent#508)

* feat: add SkipGenFile option (ent#499)

* entgql: fixed Paginate query for count (ent#512)

* entgql: fixed Paginate query for count

* Update entgql/template/pagination.tmpl

Co-authored-by: Ariel Mashraki <7413593+a8m@users.noreply.github.com>

* entgql: codegen

---------

Co-authored-by: Ariel Mashraki <7413593+a8m@users.noreply.github.com>

* entgql/internal/todo: add example for json as mutation inputs (ent#513)

* entproto/cmd: fix setting target path (ent#521)

* entproto: add support for int slices (ent#522)

* entproto: support json for integer type slices

* adding int slices to grpc test suite

---------

Co-authored-by: jamslinger <80337165+jamslinger@users.noreply.github.com>

* entgql: privatize columns used in cursors predicates (ent#537)

* privatize columns used in cursors predicates

* add tests

* entgql: avoid naming conflicts in toCursor template (ent#538)

* fix: rename variable when iterating through p.order to prevent conflict with $r that starts with "O" character

* fix: run go generate on entgql

* entgql: upgrade gqlgen and disable getters (ent#544)

* entgql: upgrade gqlgen and disable getters

* entgql: codegen

* chore: fixed go.mod

* add AllowClientUUIDs for client UUIDs in POSTs (ent#541)

* chore: upgrade ent and atlas (ent#554)

* chore: upgrade ent and atlas

* fix: bump go version to 1.20

* .ci: upgrade golangci

* chore: codegen

* entgql: fixed go-fmt (ent#555)

* all: upgrade ent and gqlgen to latest (ent#564)

* entgql: ensure order fields are prefixed with type name (ent#565)

Also when order field is set on an edge

* entgql: add missing directive's argument (ent#567)

* entgql: avoid using cte queries for single node paths (ent#569)

No need to limit per node, when a single node is returned in a path

* entgql: support skipping tx-opening by operation or field name (ent#571)

* entgql: improve var name collision avoidance in toCursor (ent#572)

Improves variable name collision avoidance in the `toCursor` method of
the pager types in the `gql_pagination` template by suffixing all
hard-coded variables names with an underscore.

* fix tests

* fix

* go: update gqlgen (ent#583)

* update gqlgen pkg

* run code gen

* update proto

* gen protobuf

* fix tests

* update protoc version in ci

* update back protoc version

* entgql/template: improve goimports time by adding missing imports (ent#584)

* ci: use node20 actions because node16 actions are deprecated (ent#588)

* all: bump ent and atlas to latest version (ent#596)

* all: bump ent and atlas to latest version

* chore: update test due to msgpack upgrade

* entgql: add annotation Deprecated for deprecated field (ent#595)

* entgql: support SkipMutationCreateInput (and update) on entity (ent#598)

* entgql/internal/examples: fix template rendering for pulid example (ent#599)

* entgql: fixed zero value on `first`/`last` when receive `null` input (ent#601)

* entgql: use dynamic Value/GetValue method name in pagination with edge order fields (ent#603)

Updates to use the `ValueName` method to get the name of the dynamic
`Value`/`GetValue` method name for a type instead of using the
hard-coded name `Value` in the `pagination.tmpl` file.

* go: use go-viper/mapstructure/v2 over archived mitchellh/mapstructure (ent#606)

* entgql/templates: ability to add extra fields and methods to mutation inputs (ent#608)

* ability to add additonal fields to mutation input

* Update mutation_input.tmpl removed the model keyword

* chore: bump go to 1.23.4 and gqlgen to v0.17.51 (ent#607)

* chore: bump go to 1.23.4 and gqlgen to v0.17.51

gqlgen v0.17.52 breaks field collection
see 99designs/gqlgen@f81239e

* chore: bump golangci and go version

* entgql: upgrade to 99designs/gqlgen@v0.17.62 and vektah/gqlparser/v2@v2.5.21 (ent#609)

* Update github.com/99designs/gqlgen@v0.17.61 and github.com/vektah/gqlparser/v2@v2.5.21

Signed-off-by: Steve Coffman <steve@khanacademy.org>

* Re-generate

Signed-off-by: Steve Coffman <steve@khanacademy.org>

* Re-generate with correct protobuf versions

Signed-off-by: Steve Coffman <steve@khanacademy.org>

* Try again with protoc v3.19.4

Signed-off-by: Steve Coffman <steve@khanacademy.org>

* minor tweak to go.mod and avoid pager getting stuck

Signed-off-by: Steve Coffman <steve@khanacademy.org>

* Update gqlgen to v0.17.62

Signed-off-by: Steve Coffman <steve@khanacademy.org>

* regenerate?

Signed-off-by: Steve Coffman <steve@khanacademy.org>

* Minor tweak

Signed-off-by: Steve Coffman <steve@khanacademy.org>

* re-re-generate

Signed-off-by: Steve Coffman <steve@khanacademy.org>

* Add tools.go and run go mod tidy

Signed-off-by: Steve Coffman <steve@khanacademy.org>

* Generate using nektos/act -j generate

Signed-off-by: Steve Coffman <steve@khanacademy.org>

* Go generate again

Signed-off-by: Steve Coffman <steve@khanacademy.org>

* Generate using nektos/act -j generate

Signed-off-by: Steve Coffman <steve@khanacademy.org>

* go clean --modcache and then go mod tidy

Signed-off-by: Steve Coffman <steve@khanacademy.org>

---------

Signed-off-by: Steve Coffman <steve@khanacademy.org>

* entgql: bump gqlgen version (ent#610)

* fix: regenerate resolvers on codegen

regeneration is required so that new ent fields and schemas are included

* chore: bump gqlgen to v0.17.63 and regenerate code

* entgql: support global id feature from ent (ent#611)

* entgql: bump entgo and update template for receiver logic (ent#617)

* entgql: bump entgo and update template for receiver logic

* entgql: codegen

* entoas: codegen

* entproto: codegen

* schemast: codegen

* .github/workflows: update ci.yml workflow

* entgql: bump gqlgen to latest version (ent#618)

* entgql: add the CollectedFor annotation (ent#623)

* fix: remove duplicate graphql import in generated files (ent#629)

* entgql: prevent panic in case of a missing operation context (ent#630)

* entgql: expose TableName for sql/global is feature (ent#632)

* entgql: support collecting multiple ent fields for GraphQL field (ent#633)

* entgql: replace golang.org/x/exp/slices import with standard slices package (ent#634)

* entgql: fix relay connection eager-load on union/interface fragments (ent#635)

Walk nested connection selections from the connection field with the current type condition instead of from the resolver field context with nil satisfies, which merged inline fragments and caused wrong eager loads.

* entgql: schema-qualify M2M join table in totalCount loader (ent#636)

* entgql: schema-qualify M2M join table in totalCount loader

The gql_collection.go totalCount loader for M2M edges builds the join
table via `sql.Table(...)` but never calls `joinT.Schema(...)`. With the
`sql/schemaconfig` feature enabled, this emits an unqualified join table
in the generated SQL while the rest of the query is correctly schema-
qualified, producing `relation does not exist` errors at runtime.

Hook the same `dialect/sql/query/eagerloading/join/*` template extension
point that ent core's query.tmpl uses for the loader path, so the join
table picks up its schema from `schemaConfig`. No-op when the feature is
disabled (verified: all 6 integration tests regenerate byte-identically).

Co-authored-by: Isaac

* entgql: add codegen test for M2M totalCount schema qualification

Add a test (entgql/template_test.go) that renders the collection template
with and without the sql/schemaconfig feature and asserts the M2M
totalCount join table is schema-qualified only when the feature is
enabled. Covers the fix from the preceding commit at the codegen level and
runs in CI against SQLite.

* entgql: add multischema project with MySQL test for M2M totalCount

Add internal/multischema, a minimal Group<->User M2M project with the
sql/schemaconfig feature, plus a MySQL-backed functional test that places
the join table in a separate schema and drives totalCount through the
gqlgen + ent runtime. ent omits schema qualifiers for SQLite (see
(*sql.Builder).writeSchema), so the schema-qualified join is only
observable on a schema-aware database; the test skips when MySQL is
unreachable.

Add a mysql8 service to the CI test job (mirroring ent's integration CI)
so the test runs instead of skipping, and the go-sql-driver/mysql
dependency it requires.

Co-authored-by: Isaac

* entgql: rename multischema project to todoschema, drop MySQL test (ent#637)

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Steve Coffman <steve@khanacademy.org>
Co-authored-by: Ariel Mashraki <7413593+a8m@users.noreply.github.com>
Co-authored-by: jamslinger <80337165+jamslinger@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Zxilly <zxilly@outlook.com>
Co-authored-by: Giau. Tran Minh <giau.tmg@gmail.com>
Co-authored-by: Rotem Tamir <rotemtamir@gmail.com>
Co-authored-by: Amr Tamimi <amrnt@users.noreply.github.com>
Co-authored-by: Tantatorn Suksangwarn <timefear007@gmail.com>
Co-authored-by: Giau. Tran Minh <12751435+giautm@users.noreply.github.com>
Co-authored-by: A B <bartke@users.noreply.github.com>
Co-authored-by: Henry Woody <github@henrywoody.com>
Co-authored-by: Ronen Lubin <63970571+ronenlu@users.noreply.github.com>
Co-authored-by: ASAKURA Kazuki <32762324+Arthur1@users.noreply.github.com>
Co-authored-by: Tran Minh Luan <tranminhluan1206@gmail.com>
Co-authored-by: Jeffrey Stiles <jbstiles@gmail.com>
Co-authored-by: Kai Dinghofer <10863868+kdevo@users.noreply.github.com>
Co-authored-by: Henry Woody <git@henrywoody.com>
Co-authored-by: Justin Cummins <sul3n3t@gmail.com>
Co-authored-by: Aman Bansal <khanakia@gmail.com>
Co-authored-by: Michael Caulley <me@mikecaulley.com>
Co-authored-by: Steve Coffman <StevenACoffman@users.noreply.github.com>
Co-authored-by: Michael Caulley <michael.caulley@databricks.com>
Co-authored-by: Jannik Clausen <12862103+masseelch@users.noreply.github.com>
Co-authored-by: Noam Cattan <62568565+noamcattan@users.noreply.github.com>
Co-authored-by: Chinh Nguyen <monkeynhucci@gmail.com>
Co-authored-by: Claude Fable 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.

4 participants