prisma-client-go
prisma-client-go copied to clipboard
Handle relations with the same name
Problem
model Post {
id String @id
user String
User User @relation(fields: [author], references: [id])
}
model User {
id String @id
name String
}
The author
and Author
fields is a problem.
Go uses capital letters to indicate that this method is public, so author
and Author
resolve to the same Author()
method.
Possible Solutions
While it works in the JS client because you just type author / Author depending on what you want, it can’t be mapped to the Go client, since all fields are uppercased. One of the fields has to be renamed, for example author to authorID.
-
We could just forbid these ambiguous fields this when using the Go client with a proper warning. However this may be annoying when re-introspecting or when importing existing schema files. We could recommend to suffix the scalar fields with
ID
, e.g.authorID
, and then the virtual relation could be calledauthor
. -
We accept the schema but forbid querying for IDs directly, then there should be no conflicts. The JS equivalent would look as follows for this solution: forbid this go equivalent query:
const result = await client.comment.findMany({ where: { user: USER_ID, }, })
force this instead:
const result = await client.comment.findMany({ where: { User: { id: USER_ID }, }, })
This solution is probably bad because it results in a different, less efficient SQL query.
-
Rename the fields in the Go client if they conflict? Probably also very confusing. We could also for example underscore conflicting names (e.g.
Author
=>_Author
), or suffix the relation names with a word (e.g.Author
->AuthorRelation
). In both cases, we would need to make sure to make sure there are no additional conflicts, i.e. there is no field already called_Author
orAuthorRelation
. -
Incorporate the scalar fields into the virtual relation attribute; a suggestion by @RafaelKR. This could look as follows:
result, err := client.Post.FindMany( db.Post.Author.ID.Equals("id"), ).Exec(ctx)
This information would need to be extracted from the virtual relation attribute's referenced field name. It might be a bit confusing, since if the field would stay the same you would write
Author.Author
or if it would be just called id (Author.ID
) it might not be clear what field the user is referring to.
I just want to add a little more context to my suggestion (list item 4 in the opening post https://github.com/prisma/prisma-client-go/issues/140#issue-622961289).
First things first: I'm not a Go developer. So please bear with me if I got some syntax or understanding errors in my following code examples.
Example Schemas
All the following explanations are based on these two examples.
Single IDs
model User {
id String @id
firstName String
lastName String
}
model Post {
id String @id
author String
Author User @relation(fields: [author], references: [id])
}
Multi-field IDs
https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-schema/relations#the-relation-attribute
model User {
firstName String
lastName String
@@id([firstName, lastName])
}
model Post {
id String @id
author User @relation(fields: [authorFirstName, authorLastName], references: [firstName, lastName])
authorFirstName String // relation scalar field (used in the `@relation` attribute above)
authorLastName String // relation scalar field (used in the `@relation` attribute above)
}
Detailed suggestion
Relation Fields
My suggestion is to always have an .Relation
attribute on relation fields, regardless of whether there is a collision or not.
e.g. db.Post.Author.Relation
Note:
.Relation
was called.ID
before
Relation Scalar Fields
Scalar fields should be always hidden from the referencing model (Post
).
So this scalar fields wouldn't exist in the Go code:
// Single ID
db.Post.Author // this exists, but not as relation scalar, this is the relation field itself
// Multi-field IDs
db.Post.AuthorFirstName
db.Post.AuthorLastName
This would prevent any collsion.
Instead they are always referenced on the relation field. To also prevent collisions with other fields (.Relation
, .Some
, ...) we could introduce another subfield. I'm using .RelationField
for that.
Now we can access them like this:
Single ID: Post.Author.RelationField.Id
Multi-field IDs: Post.Author.RelationField.FirstName
and Post.Author.RelationField.LastName
As you can see I'm using the fieldnames of the referenced model User
. The other option would be to use the fieldnames on the current model, but due to the naming redundancy this doesn't look that good IMO.
~Single ID: Post.Author.RelationField.Author
Multi-field IDs: Post.Author.RelationField.AuthorFirstName
and Post.Author.RelationField.AuthorLastName
~
Querying
Single IDs
If we only have one item in fields
we can directly call Methods like .Equals
on .Relation
:
result, err := client.Post.FindMany(
db.Post.Author.Relation.Equals("id"),
).Exec(ctx)
Multi-field IDs
If we have multiple items in fields
the .Relation
fields becomes a method where we have to query like this:
result, err := client.Post.FindMany(
db.Post.Author.Relation(
db.Post.Author.RelationField.FirstName.Equals("firstName"),
db.Post.Author.RelationField.LastName.Equals("lastName"),
),
).Exec(ctx)
As said, I'm not a Go developer and I'm not sure if this is working, but after looking at Querying for relations and Create a record with a relation it could be possible.
If there's something like a JavaScript callback function in Go, it would be nice to use that instead to prevent this long chaining (db.Post.Author.RelationField.FirstName):
// !!!! WARNING: IMAGINARY CODE !!!!
result, err := client.Post.FindMany(
db.Post.Author.Relation((Author) => ( // maybe User instead of Author?
Author.FirstName.Equals("firstName"),
Author.LastName.Equals("lastName"),
))),
).Exec(ctx)
Edit: Change attribute naming from .ID
to .Relation
and from .Fields
to .RelationField
Why would one have the same fields in the first place and only distinguish based on the casing? That feels like a good way to create confusion. Maybe I'm lacking context: would that come from a database that has been set up this way and can't be changed?
The model used there doesn't seem to make much sense to me: why not use authorId
? If the author
string is some kind of full name, then one could call it authorFullName
etc.
That doesn't mean casing conflict couldn't happen, but I'd rather have an example that makes the problem clearer as this one feels constructed to me.
The example schema is gendered by Prisma introspection; with the field being lowercase and the relation attribute being uppercase. Sorry for the missing context, I thought I had added that to the actual schema.
Alright, that makes more sense. Then for now I'd say we should incentivize using @map
and rename when this happens, until we offer ways to configure further the introspection behavior (cf. https://github.com/prisma/prisma/issues/1184)
Missing this feature is preventing us from using golang for a project. :(
@deanshelton913 Can you please elaborate? Are you using introspection with the Go client but run into this problem?