Conversation
…reSQL schemas (#2603) Implicit many-to-many join tables (e.g. _PostToTag) were always queried under the 'public' schema. The ORM now derives their schema from the alphabetically-first model in the relation, matching Prisma's convention. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughPostgreSQL implicit many-to-many join tables now have their schemas properly resolved during query execution. A join-table-to-schema mapping is precomputed in the QueryNameMapper constructor based on model/field relation metadata, and the getTableSchema method extended to resolve schemas for join tables that lack direct model entries, ensuring correct schema assignment regardless of custom schema configurations. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Claude finished @ymc9's task —— View job Code Review Complete ✅Todo List
SummaryThis is a well-implemented fix that addresses the PostgreSQL schema resolution bug for implicit M2M join tables. The solution is architecturally sound and follows established patterns. ✅ Strengths
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/orm/src/client/executor/name-mapper.ts (1)
567-585:⚠️ Potential issue | 🟡 MinorUnknown-table fallback to
'public'ignoresdefaultSchema.For a non-join table that happens to be missing from
schema.models(shouldn't normally happen, but it's the defensive branch), the new early return hardcodes'public'rather thandefaultSchema ?? 'public':if (!this.schema.models[model]) { return this.joinTableSchemaMap.get(model) ?? 'public'; }If the join table map lookup misses for any reason (e.g., a join table encountered before the map was fully built, or a relation metadata edge case), returning
'public'under a non-publicdefaultSchemawould resurface the exact symptom this PR is fixing (42P01) but silently. Consider?? (this.schema.provider.defaultSchema ?? 'public')to stay consistent with the normal-model branch below.Proposed tweak
if (!this.schema.models[model]) { - return this.joinTableSchemaMap.get(model) ?? 'public'; + return this.joinTableSchemaMap.get(model) ?? this.schema.provider.defaultSchema ?? 'public'; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/orm/src/client/executor/name-mapper.ts` around lines 567 - 585, The fallback in getTableSchema currently returns joinTableSchemaMap.get(model) ?? 'public', which ignores a non-default provider defaultSchema; update the unknown-model branch in getTableSchema so the final fallback uses the provider defaultSchema (i.e. use joinTableSchemaMap.get(model) ?? (this.schema.provider.defaultSchema ?? 'public')) to match the logic used for known models and avoid hardcoding 'public'; reference getTableSchema, this.schema.models, joinTableSchemaMap and this.schema.provider.defaultSchema when making the change.
🧹 Nitpick comments (2)
tests/regression/test/issue-2603.test.ts (1)
93-128: Minor: strengthen assertions for the cross-schema case.This is the scenario most likely to regress (join table placed in alphabetically-first model's schema), yet it only creates a single tag and asserts the name list. Consider adding a
findFirst({ include: { tags: true } })round-trip plus asserting tag count — that exercises the SELECT-side schema resolution for the join table, which is a separate code path from the nested-create side.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/regression/test/issue-2603.test.ts` around lines 93 - 128, The test implicit m2m with models in different custom schemas only asserts the created tag name and should also exercise SELECT-side join-table resolution: after calling db.post.create (db.post.create in this test), add a db.post.findFirst({ include: { tags: true } }) round-trip and assert that the returned post has tags.length === 1 and the tag name matches (use expect(post.tags.length).toBe(1) and expect(post.tags.map(t => t.name)).toEqual(['foo']) or equivalent), so both nested-create and subsequent SELECT path for the join table are validated.packages/orm/src/client/executor/name-mapper.ts (1)
86-102: Consider usingrealModel(originModel) when deriving the owning model for delegate-inherited m2m fields.
getManyToManyRelationusesrealModel = fieldDef.originModel ?? modelfor sorting and naming the join table (seequery-utils.tslines 262–310). Here the owning-model selection usesmodelNamedirectly:const owningModel = [modelName, m2m.otherModel].sort()[0]!;If
Postis a delegate child ofBasePost, the join table will be named_BasePostTo...(sorted usingBasePost), but schema resolution will look upPost. In most setups both resolve to the same schema so this is benign, but it's an inconsistency that could bite in configurations where delegate base and child carry different@@schemavalues. Consider deriving the owning model from the same realModel + otherModel pair used for the join table name.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/orm/src/client/executor/name-mapper.ts` around lines 86 - 102, The owning-model selection uses the raw modelName instead of the field's origin model, causing inconsistent schema lookup for delegate-inherited m2m fields; update the loop that builds joinTableSchemaMap to compute realModel = fieldDef.originModel ?? modelName (or similar) and then derive owningModel from [realModel, m2m.otherModel].sort()[0] before calling this.getTableSchema(owningModel) so the schema resolution matches the same realModel used when naming the join table; reference getManyToManyRelation, fieldDef.originModel, modelName, joinTableSchemaMap, and getTableSchema to locate and change the logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@packages/orm/src/client/executor/name-mapper.ts`:
- Around line 567-585: The fallback in getTableSchema currently returns
joinTableSchemaMap.get(model) ?? 'public', which ignores a non-default provider
defaultSchema; update the unknown-model branch in getTableSchema so the final
fallback uses the provider defaultSchema (i.e. use joinTableSchemaMap.get(model)
?? (this.schema.provider.defaultSchema ?? 'public')) to match the logic used for
known models and avoid hardcoding 'public'; reference getTableSchema,
this.schema.models, joinTableSchemaMap and this.schema.provider.defaultSchema
when making the change.
---
Nitpick comments:
In `@packages/orm/src/client/executor/name-mapper.ts`:
- Around line 86-102: The owning-model selection uses the raw modelName instead
of the field's origin model, causing inconsistent schema lookup for
delegate-inherited m2m fields; update the loop that builds joinTableSchemaMap to
compute realModel = fieldDef.originModel ?? modelName (or similar) and then
derive owningModel from [realModel, m2m.otherModel].sort()[0] before calling
this.getTableSchema(owningModel) so the schema resolution matches the same
realModel used when naming the join table; reference getManyToManyRelation,
fieldDef.originModel, modelName, joinTableSchemaMap, and getTableSchema to
locate and change the logic.
In `@tests/regression/test/issue-2603.test.ts`:
- Around line 93-128: The test implicit m2m with models in different custom
schemas only asserts the created tag name and should also exercise SELECT-side
join-table resolution: after calling db.post.create (db.post.create in this
test), add a db.post.findFirst({ include: { tags: true } }) round-trip and
assert that the returned post has tags.length === 1 and the tag name matches
(use expect(post.tags.length).toBe(1) and expect(post.tags.map(t =>
t.name)).toEqual(['foo']) or equivalent), so both nested-create and subsequent
SELECT path for the join table are validated.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 7430f94b-9158-4aa3-b7b2-ff5b263336ce
📒 Files selected for processing (2)
packages/orm/src/client/executor/name-mapper.tstests/regression/test/issue-2603.test.ts
Summary
_PostToTag) were always queried under thepublicschema in PostgreSQL multi-schema setups, causing a "relation does not exist" errorChanges
joinTableSchemaMapin the constructor; use it ingetTableSchemawhen the model is not found inschema.models@@schema, and models in different schemasTest plan
implicit m2m with defaultSchema set to non-public schema— creates/fetches posts with tags inmySchemaimplicit m2m with explicit @@schema on models— both models pinned tomySchemaimplicit m2m with models in different custom schemas— join table lands inschema1(alphabetically first)🤖 Generated with Claude Code
Summary by CodeRabbit