Prisma1: Go Client: Implement Relay Connections

Created on 10 Feb 2019  Â·  24Comments  Â·  Source: prisma/prisma1

Describe the bug
Connections are currently not supported in the Go client. Some types already exist (Edge, PageInfo...), but the functions are not implemented yet.

To Reproduce
Create a single-to-many relation between User and Post:

type User {
    id: ID! @unique
    email: String! @unique
    name: String!
    posts: [Post!]!
}

type Post {
    id: ID! @unique
    user: User!
    title: String!
    content: String!
}

Generate the Prisma client and access it:

func (r *queryResolver) Feed(ctx context.Context) ([]prisma.Post, error) {
    conn, err := r.Prisma.PostsConnection(&prisma.PostsConnectionParams{}).Exec(ctx)
    // conn is a struct with no further methods and PostsConnections panics
}

Next steps

An actual implementation is missing for the connection methods. Also, some structs are empty.

func (client *Client) PostsConnection(params *PostsConnectionParams) PostConnectionExec {
    panic("not implemented")
}

...

type PostConnection struct {
}

related: https://github.com/prisma/prisma/blob/214389359b94de9abec44e43110b70df46d2046a/cli/packages/prisma-client-lib/src/codegen/generators/go-client.ts#L588

kinfeature arecliengo

Most helpful comment

Sorry for the long wait – it's now finally published! Please update the prisma cli with npm or yarn, try it out and let me know if it works as intended. Please note that you probably just want to use the .Connection method (vs. Edges, PageInfo etc.).

All 24 comments

I would consider this a bug since types are not properly generated, too:

type UserEdge struct {
    Cursor string `json:"cursor"`
}

type UserConnection struct {
}

instead of

type UserEdge struct {
  Cursor string `json:"cursor"`
  Node   User `json:"node"`
}

type UserConnection struct {
  Edges     []UserEdge `json:"edges"`
  PageInfo  PageInfo       `json:"pageInfo"`
  Aggregate Aggregate      `json:"aggregate"`
}

Also, I believe

func (instance *FooEdgeExec) Node() *FooExec {

should be Nodes() and the final result should return an array.

Temporary Solution

Use manual http connection,
I haven't tested this with production endpoint tho...

type UserConnection struct {
    Data struct {
        UsersConnection struct {
            Aggregate struct {
                Count int `json:"count,omitempty"`
            } `json:"aggregate,omitempty"`
        } `json:"usersConnection,omitempty"`
    } `json:"data,omitempty"`
}

func (r *queryResolver) UsersCount(ctx context.Context) (*ConnectionPayload, error) {
    query := `
    query{
        usersConnection{
          aggregate{
            count
          }
        }
      }
    `
    body, _ := json.Marshal(map[string]string{
        "query": query,
    })

    resp, err := http.Post("http://localhost:4466/my-service/dev", "application/json", bytes.NewBuffer(body))
    if err != nil {
        return nil, err
    }

    defer resp.Body.Close()

    data, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }

    res := UserConnection{}

    _ = json.Unmarshal(data, &res)

    return &ConnectionPayload{
        Count: res.Data.UsersConnection.Aggregate.Count,
    }, nil
}

I would like to implement this, but since there is no spec (or is there?) I'm not sure how to implement it. After quickly scanning through the Go client, I could fix the Edges to return both the cursor and the Node. So you could do the following:

// let's drop the .Exec for now as this should fetch everything
conn := prisma.UsersConnection(&prisma.UsersConnectionParams{
    Where: &prisma.UserWhereInput{
        Name: prisma.Str("John"),
    },
    First: prisma.Int32(5),
})

pageInfo, err := conn.PageInfo(ctx).Exec(ctx)
if err != nil {
    return nil, err
}
edges, err := conn.Edges(ctx).Exec(ctx)
if err != nil {
    return nil, err
}

// return pageInfo & edges

This solution is flexible, but it results in two SQL queries, one for the page info and one for the edges.

However, I don't know how to create a query which fetches both PageInfo and Edges.
For example, the .Exec() method on the Connection could fetch pageInfo & edges per default:

conn, err := prisma.UsersConnection(&prisma.UsersConnectionParams{
    Where: &prisma.UserWhereInput{
        Name: prisma.Str("John"),
    },
    First: prisma.Int32(5),
}).Exec(ctx)
if err != nil {
    return nil, err
}

log.Printf("%+v", conn.Edges)
log.Printf("%+v", conn.PageInfo)

I'd still call PageInfo() and Edges() which would still result in two queries:

// prisma.go
func (instance *UserConnectionExec) Exec(ctx context.Context) (*UserConnection, error) {
    edges, err := instance.Edges().Exec(ctx)
    if err != nil {
        return nil, err
    }

    pageInfo, err := instance.PageInfo().Exec(ctx)
    if err != nil {
        return nil, err
    }

    return &UserConnection{
        Edges:    edges,
        PageInfo: pageInfo,
    }, nil
}
// prisma.go
func (instance *UserConnectionExec) Exec(ctx context.Context) (*UserConnection, error) {
    // first query
    pageInfo := instance.exec.Client.GetOne(
        instance.exec,
        nil,
        [2]string{"", "PageInfo"},
        "pageInfo",
        []string{"hasNextPage", "hasPreviousPage", "startCursor", "endCursor"})

    // second query
    edges := instance.exec.Client.GetMany(
        instance.exec,
        nil,
        [3]string{"UserWhereInput", "UserOrderByInput", "UserEdge"},
        "edges",
        []string{"cursor"})

    // re-use second query and fill nodes
    edgesWithNodes := edges.Client.GetMany(
        edges,
        nil,
        [3]string{"", "", "User"},
        "node",
        []string{"id", "createdAt", "updatedAt", "name", "desc"})

Can anyone help me to do this in a single query? Also I'm not sure if I should include Aggregate.count in Connection.Exec().

You might need to implement a query builder if you want to support connections query fully.

Generate a proper GQL syntax based on user inputs and send request to Prisma..

Would the maintainers of prisma merge a PR for a partial implemention to allow querying for Connection().PageInfo().Exec() and Connection().Edges().Exec() where the latter would include both cursors and nodes? Instead of panicking in Connection(), I would suggest to panic in Connection().Exec() while it's not implemented, so one could at least query for Edges & PageInfo separately.

When using GraphQL you could also easily wrap both edges and pageInfo into a separate resolver, which means they would get executed in parallel so it would not be much slower.

@darmie @chris-rock Can you take a look at #4709 and let me know if this implementation would work for you?

Thank you @steebchen Your PR is a great step into the right direction. As you pointed out in https://github.com/prisma/prisma/pull/4709/files#r308206748, the current merged implementation is not really usable due to an error in the first implementation. Seems like @steebchen's fix https://github.com/prisma/prisma/pull/4730/files may solves the issue. Until then I propose to reopen this issue since its still non-functional.

any update for this issue? Current implementation for relay connection is broken. Hope https://github.com/prisma/prisma/pull/4730/files could be merged soon.

@soqt I can merge this soon. Can you please post feedback on what you think under the pull request? Basically if you want nodes or edges :)

@soqt I can merge this soon. Can you please post feedback on what you think under the pull request? Basically if you want nodes or edges :)

Do you mean if query subfield "edges" or "node"? In this case, I hope to get { edges { node } } for sure! I think this is also what the official relay spec requires.

The thing is that Prisma uses node IDs as cursors, which means you could just query for nodes and use their IDs as cursors. The field "nodes" does not exist in the relay spec, yes, but it's a common practice if you don't need edge cursors (for example, the Github GraphQL API allows querying both edges and nodes).

Got it, I thought you mean node inside edges. I've been following Shopify GraphQL style, so I personally still prefer edges over nodes. And maybe it's good to stick with what Prisma server returns in order to avoid confusions.

Alright, then I think I will add both nodes and edges methods so the user can choose what he wants. The only question left is what to return when you invoke Connection() to get both pageInfo and either nodes/edges; although I could also provide both nodes and edges there (it would not be optimal, but irrelevant for small queries (<100 items at once)

How about fetch data and then calculate nodes/edges both in memory when you invoke Connection()? or pass a second parameter(although doesn't make much sense).

Yes, that is the current plan for the .Connection() method, fetch edges, calc nodes internally, and return a struct with pageInfo, nodes, and edges.

Sounds good! Otherwise, if we really consider the performance overhead here, and option to let user to decide if convert to nodes manually based on the edges could be an option (which means only return edges). I feel nodes is largely for frontend convenience. Either way works, you can decide it :)

It seems it is not so easy to implement an extra nodes field because how this works internally. However, it would be easy to add a .Nodes() method to the Connection struct, which would also mean the user can optionally convert edges to nodes but only if needed. It would look like this:

conn, err := x.UsersConnection(...).Exec(ctx)

// conn is UserConnection{Edges: []UserEdge, PageInfo: PageInfo}
log.Printf("%+v", conn.PageInfo)
log.Printf("%+v", conn.Edges)

// here I want to return nodes, so I can do
return conn.Nodes()

The only thing Nodes() would do is range through Edges and return Nodes instead.

I think this is actually a good solution. What do you think @soqt? also @chris-rock

I think this is great! I'm perfectly agree with this implementation. Thanks for your hard work @steebchen

This is fixed by #4730. Just waiting for a release now.

Published under 1.34.9. Please let me know if there are any issues!

EDIT: actually it's not released on npm yet due to a CI error. We will look into it next monday.

Published under 1.34.9. Please let me know if there are any issues!

EDIT: actually it's not released on npm yet due to a CI error. We will look into it next monday.

Any updates for the CI error?

Still pending, sorry... We plan to fix it tomorrow. I'll let you know here immediately when it's fixed.

Sorry for the long wait – it's now finally published! Please update the prisma cli with npm or yarn, try it out and let me know if it works as intended. Please note that you probably just want to use the .Connection method (vs. Edges, PageInfo etc.).

Was this page helpful?
0 / 5 - 0 ratings

Related issues

sorenbs picture sorenbs  Â·  3Comments

jannone picture jannone  Â·  3Comments

ragnorc picture ragnorc  Â·  3Comments

schickling picture schickling  Â·  3Comments

tbrannam picture tbrannam  Â·  3Comments