steebchen / prisma-client-go

Prisma Client Go is an auto-generated and fully type-safe database client
https://goprisma.org
Apache License 2.0
2.15k stars 97 forks source link

Discussion: field selection, aggregates, order bys, relations #9

Open steebchen opened 5 years ago

steebchen commented 5 years ago

Note: This is not implemented yet and feedback is highly appreciated. Please comment if you have concerns or different ideas.

In Go, it's impossible to define dynamic return types depending on what arguments were provided to a chain. This means that selecting specific fields of a model, using advanced aggregations or using an order by is not possible without additional mechanisms. It could also be used to fetch relations, e.g. fetch a user with their posts (and the post's comments, and the author of each comment, etc.).

For advanced queries, we could offer a typed query API but the user would have to define the result struct by himself:

var result []struct{
  Category     `json:"category"`
  UserName     `json:"name"`
  LikeCount    `json:"likeCount"`
  AvgPostLikes `json:"avgPostLikes"`
}

err := client.User.Aggregate.GroupBy(
  photon.User.Post.Category.Group(),
  photon.User.Name.Group(),
).Select(
  photon.User.Post.Count(),
  photon.User.Post.Likes.Sum(),
).Into(&result).Exec(ctx)

This works, but the user has to adapt the result struct type each time they update their query.

It would be possible to automatically generate struct types for advanced queries by parsing the Go source code, which could look as follows:

// use the automatically created struct `MyQuery1`
var result MyQuery1
// provide a name for this query
err := client.User.Select.Name("MyQuery1").GroupBy(
  // when you update this query and re-run prisma, the MyQuery1 struct gets adapted accordingly
  photon.User.Post.Category.Group(),
  photon.User.Name.Group(),
).Fields(
  photon.User.Post.Count(),
  photon.User.Post.Likes.Sum(),
).Into(&result).Exec(ctx)

When you change the query, you would have to run prisma generate to reflect the changes in the struct. A prisma dev mode could make this easier to work with when first writing the query you would still have to generate first before using the result, which could break programming flows.

Example video on how this would look in action: https://www.loom.com/share/9b646dea4cc9431ea9796bd2141cd70c (watch in 2x speed)

Prototype (please note that this is very basic prototype and just used to showcase how it could work): https://github.com/steebchen/go-codegen-prototype

alexdor commented 4 years ago

Are all the function calls needed? Couldn't we replace the above query with this:

err := client.User.Select.Name("MyQuery1").GroupBy(
  // when you update this query and re-run prisma, the MyQuery1 struct gets adapted accordingly
  photon.User.Post.Category.Group,
  photon.User.Name.Group,
).Fields(
  photon.User.Post.Count,
  photon.User.Post.Likes.Sum,
).Into(&result).Exec(ctx)

and have all of them pre-generated instead of generating them when they are called?

steebchen commented 4 years ago

They are not necessarily needed, you're right. However, I think it's more logical to use a function call for two reasons: 1) It looks more like an action. It's not something to sort by or some "minor" thing, but instead Group By changes the complete result by just returning a single column. If you write Group() it looks more like an action which does something. 2) It's more consistent. In more complex queries, you may want to pass something to the functions. It's weird if there are some function calls and some fields.