prisma-client-go icon indicating copy to clipboard operation
prisma-client-go copied to clipboard

Handle relations with the same name

Open steebchen opened this issue 4 years ago • 6 comments

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.

  1. 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 called author.

  2. 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.

  3. 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 or AuthorRelation.

  4. 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.

steebchen avatar May 22 '20 05:05 steebchen

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

RafaelKr avatar Jun 01 '20 15:06 RafaelKr

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.

thebiglabasky avatar Dec 04 '20 10:12 thebiglabasky

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.

steebchen avatar Dec 04 '20 10:12 steebchen

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)

thebiglabasky avatar Dec 04 '20 12:12 thebiglabasky

Missing this feature is preventing us from using golang for a project. :(

deanshelton913 avatar Aug 21 '21 15:08 deanshelton913

@deanshelton913 Can you please elaborate? Are you using introspection with the Go client but run into this problem?

steebchen avatar Aug 21 '21 18:08 steebchen