GraphQL - Return the Size of an Array Field?

I have a GraphQL schema that looks something like this:

type Guest {
  id: ID!
  guest_name: String
  guest_visit_dates: [DateTime]
}

Is there a way in GraphQL to return the guest id and a total count of guest_visit_dates?

Thanks in advance!

Hey @MattH,

Unfortunately dgraph only generates the aggregate<Field> queries for lists of Types or Interfaces (https://dgraph.io/docs/v21.03/graphql/queries/aggregate/#advanced-aggregate-queries-at-root).

However you can easily achieve what you need with Lambda resolvers. Have a look at this document: https://dgraph.io/docs/v21.03/graphql/lambda/query/

Let me know if you need help.

1 Like

And FYI, it is a Set not an Array even though everything makes you think it is an array that preserves order and can contain duplicates… it is NOT!

2 Likes

@matthewmcneely @amaster507

Thank you both for your input!

I’ve stood up a Lambda server using the dgraph-lambda container image and I think that I have the custom Lambda field defined correctly.

However, when I try to run a query using the lambda predicate, I receive the error message:

Evaluation of custom field failed because external request returned an error: Post \"http://<lambda-server-ip:8686/graphql-worker\": dial tcp <lambda-server-ip>:8686: connect: no route to host for field: guest_visit_count within type: Guest."

I’m running on AWS and originally forgot to open the 8686 port in the security group and initially got a timeout when trying to run a query using the custom field.

After opening up the port, I received the error above. I’ve also tested opening the port up to 0.0.0.0/0 and still received the same error, so I don’t think it’s a networking issue.

Any suggestions on how to go about validating my lambda-server setup?

Thanks!
Matt

Hey Matt,

Can you send your docker-compose file (or other artifact)?

Or… have a look at this one I have up on github: https://github.com/matthewmcneely/dgraph-v23.01-sandbox/blob/master/docker-compose.yml

1 Like

Thanks @matthewmcneely - I actually just made it past the first error - I needed to open the 8686 port in the local firewall on the EC2 running the dgraph-lambda container.

My current error is:

Evaluation of custom field failed because external request returned an error: unexpected error with: 400 for field: guest_visit_count within type: Guest

Is there way to validate the Javascript definition?

On the dgraph-lambda server itself, I tried running:
curl localhost:8686/graphql-worker -H "Content-Type: application/json" -d '{"resolver":"Guest.guest_visit_count","parent":[{"guest_visit_count":"Dgraph Labs"}]}'

but I don’t get any response at all.

I have added the --graphql superflag and the lambda-url to the alpha startup command.

I have the Javascript defined as:

async function visitCount( { parent } ) {
           let guest_visit_count = guest_visit_dates.size;
           return guest_visit_count;
}
self.addGraphQLResolvers({
          "Guest.guest_visit_count": visitCount,
});

The 400 error usually is an indication of an error in the JS. I think the problem is that guest_visit_dates.size. The lambda server logs errors to stderr. Maybe have a look.

If you can see the logs, put a console.log(parent) in there. If parent.guest_visit_dates is present, then you should be able to simply return parent.guest_visit_dates.length. If it’s not present (I can’t recall if lists of scalars are present in Lambda invocations) I can help you with a graphql query in there.

1 Like

Thanks again @matthewmcneely, I appreciate your help with this.

I do have access to the docker logs. I simplified the code and added in the console.log per your suggestion, so it looks like:

async function visitCount( { parent } ) {
           console.log(parent);
           return guest_visit_dates.size;
}
self.addGraphQLResolvers({
          "Guest.guest_visit_count": visitCount
})

I don’t see anything showing up in the Docker log at all though. Is it possible that the .js is not getting loaded into the container?

I’m starting the container using the command:
docker run --detach --rm -p 8686:8686 -v /path/to/visitcount.js:/app/script/visitcount.js -e DGRAPH_URL=:8080/ dgraph-lambda:1.3.0

Thanks,
Matt

Hey Matt,

Line 3 should read

return parent.guest_visit_dates.length;

If it’s returning a 400 error code, then I’m pretty sure the lambda’s getting invoked. Regarding access to the logs, can you see the logs for your alpha and zero containers?

1 Like

Thanks for catch! Unfortunately, it didn’t change anything.

Yes, I have access to the Zero and Alpha logs (not running in a containers). I didnt see anything query-related in there.

Hmm, so if you docker ps and then do a docker logs <containerid> you’re not seeing anything?

1 Like

@matthewmcneely - I got it working, thank you very much for your help!

Here’s what it took -

Since there was no indication of the script attempting to execute in the docker logs, I logged into the container to verify that the script was mounting correctly.

I noticed that the script permissions were initially:

-rwx------ 1 root root 198 Aug 5 22:07 visitcount.js
-rw-rw-r-- 1 app app 688 Mar 10 2021 script.js

I opened up the permissions on visitcount.js but it still didn’t work.

I then mounted my “visitcount.js” named as “script.js” and made sure the permissions were opened up - then I saw activity in the docker logs and my query returned correctly!

I didn’t realize that the name of the mounted script actually had to be named “script.js”, I presumed that was just a placeholder value on the documentation page - lesson learned.

I appreciate you sticking with me and pointing me in the right direction. Thanks again!!

-Matt

1 Like

@matthewmcneely - follow up question -

Do you know if it’s possible to filter or otherwise query on a Lambda predicate? I just tried a query using a filter on the visit_count predicate and get the message:

Field \"visit_count\" is not defined by type Guest.

I’m thinking this is going to require a Lambda Query but just wanted to confirm that this is expected behavior.

Thank you

If you introspect the API you will see that filter input is not available. Furthermore, you cannot filter parent objects on lambda resolved fields.

Thanks @amaster507 for confirming.

Are you aware of any way to accomplish selecting on the size of the set field? For example, return all guests who have more than 10 guest_visit_dates entries?

Yes, Custom DQL Resolvers to the rescue:

Add this to your GraphQL schema:

type Query {
  queryGuestsByMinVisits(visits: Int!): [Guest] @custom(dql: """
    query q($visits: int) {
        var(func: type(Guest)) {
            {
                visitCount as count(Guest.guest_visit_dates)
            }
        }
    
        queryGuestsByMinVisits(func: uid(visitCount)) @filter(ge(val(visitCount), $visits)) {
            id: uid
            guest_name: Guest.guest_name
            guest_visit_dates: Guest.guest_visit_dates
            guest_visit_count: val(visitCount)
        }
    }
	"""
    )
}

Then query like this:

query {
  queryGuestsByMinVisits(visits: 2) {
    id
    guest_name
    guest_visit_dates
    guest_visit_count
  }
}

I’ve pushed all this up to my sandbox: https://github.com/matthewmcneely/dgraph-v21.03-sandbox/tree/explanations/lamda-query-resolver

2 Likes

Thank you @matthewmcneely!

I was able to get this tested successfully today. I appreciate your help!

@matthewmcneely

I’m trying to expand upon what was done above and running into an issue. I’ve added a location code and trying to add a second parameter “in_location_code” to the function. Do you happen to see what I have wrong here:

Updated schema:

type Guest {
  id: ID!
  guest_name: String
  location_code: !String
  guest_visit_dates: [DateTime]
}

Updated custom DQL resolver:

type Query {
  queryGuestsByMinVisits(visits: Int!, in_location_code: String!): [Guest] @custom(dql: """
    query q($visits: int, $in_location_code: String) {
        var(func: type(Guest)) {
            {
                visitCount as count(Guest.visit_dates)
            }
        }

        queryGuestsByMinVisits(func: uid(visitCount)) @filter(eq(Guest.location_code, $in_location_code)) @filter(ge(val(visitCount), $visits))  {
             id: uid
	         guest_name: Guest.guest_name
		     guest_visit_dates: Guest.guest_visit_dates
		     guest_visit_count: val(visitcount)
        }
    }
        """
    )
}

When I look at the function in GraphiQL, it looks like it’s defined properly, but when I run the query, I receive:

resolving queryGuestsByMinVisits failed because Dgraph query failed because Dgraph execution failed because Type \"String\" not supported.

Thank you

I believe the issue is the capitalized String. In DQL that type is defined lowercase.

1 Like

Yes, you were correct - thank you. Now I have a lexing issue to resolve…