I tried out a little bit the Custom DQL feature which came out with the 20.07.0 .
It apparently gives a little bit of integration between GraphQL and GraphQL± but I found a quite big limitation:
given the schema
interface Metadata {
id: String! @id @search(by: [hash])
}
interface WithName {
names: [TextWrapper!]!
}
type TextWrapper implements Metadata {
text: Text!
localization: String!
isName: [WithName!]! @hasInverse(field: names)
}
type Text implements Metadata {
string: String! @search(by: [trigram, term, fulltext])
}
type Person implements Metadata & WithName {
age: Int!
parents: [Person!]!
}
type Company implements Metadata & WithName {
address: String!
}
extend type Query {
queryByName(search: String!): [WithName] @custom(dql: """
query q($search: string) {
var(func: anyoftext(Text.string, $search)) {
~TextWrapper.text {
~WithName.names {
u as uid
}
}
}
queryByName(func: uid(u)) {
id: Metadata.id
names : WithName.names
age: Person.age
parents: Person.parents
address: Company.address
}
}
""")
}
, updated the relation needed from queryByName to traverse reverse edges:
<TextWrapper.text>: uid @reverse .
<WithName.names>: [uid] @reverse .
and populaed the db with some data
mutation {
addText(input:[
{id: "cccc", string: "Mario"},
{id: "eeee", string: "マリオ"},
{id: "hhhh", string: "Leonardo"},
{id: "jjjjj", string: "レオナルド"},
{id: "qwer", string: "Shueisha"},
]) {
numUids
}
addPerson(input:[{
id: "aaaa",
age: 23,
names: [
{
id: "bbbb",
text: {id: "cccc"},
localization: "it",
},
{
id: "dddd",
text: {id: "eeee"},
localization: "ja",
}
],
parents:[{
id: "ffff",
age: 50,
names: [{
id: "gggg",
text: {id: "hhhh"},
localization: "it",
},
{
id: "iiiii",
text: {id: "jjjjj"},
localization: "ja",
}],
parents:[],
}]
}]
) {
numUids
}
addCompany(input:[{
names: [{
id: "dfsdf",
text: {id: "qwer"},
localization: "it"
}],
id: "shu",
address: "via Roma"
}]) {
numUids
}
}
I tried to use the DQL query:
queryByName(search:"Mario") {
__typename
... on Person {
id
age
parents {
id
}
}
}
result:
{
"data": {
"queryByName": [
{
"__typename": "WithName",
"id": "aaaa",
"age": 23,
"parents": []
}
]
},
"extensions": {
"touched_uids": 8,
"tracing": {
"version": 1,
"startTime": "2020-08-10T22:17:08.93566995Z",
"endTime": "2020-08-10T22:17:08.940677751Z",
"duration": 5007701,
"execution": {
"resolvers": [
{
"path": [
"queryByName"
],
"parentType": "Query",
"fieldName": "queryByName",
"returnType": "[WithName]",
"startOffset": 91300,
"duration": 4900601,
"dgraph": [
{
"label": "query",
"startOffset": 95200,
"duration": 4867001
}
]
}
]
}
}
}
}
As specified in the Documentation, it’s possible to query only data already retrieved from the queryByName itself and therefore parents is empty.
What I want to point out is that the __typename returned is WithName
, which makes sense to some degree.
The real problem is when I tried to put an Apollo server in front of my dgraph. Using this repo I generated everything needed to call the gateway’s graphql and delegate to the dgraph GraphQL the execution of the queries.
In order to resolve the interface type i also added this custom resolver to the Apollo Server
WithName: {
__resolveType: (obj, context, info) => {
console.log(obj)
if (obj.age) {
return 'Person'
}
if (obj.address) {
return 'Company'
}
return obj.__typename
},
},
in order to get the underlying type of the interface returned
queryByName(search:"Mario") {
__typename
... on Person {
id
age
parents {
id
}
}
}
calling this query, which is the same, I got a super bad error
{
"errors": [
{
"message": "Abstract type SearchResult must resolve to an Object type at runtime for field Query.queryByName with value {}, received \"undefined\". Either the SearchResult type should provide a \"resolveType\" function or each possible type should provide an \"isTypeOf\" function.",
"locations": [
{
"line": 16,
"column": 3
}
],
"path": [
"queryByName",
0
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"message": "Abstract type SearchResult must resolve to an Object type at runtime for field Query.queryByName with value {}, received \"undefined\". Either the SearchResult type should provide a \"resolveType\" function or each possible type should provide an \"isTypeOf\" function.",
"locations": [
{
"line": 16,
"column": 3
}
],
"stacktrace": [
"GraphQLError: Abstract type SearchResult must resolve to an Object type at runtime for field Query.queryByName with value {}, received \"undefined\". Either the SearchResult type should provide a \"resolveType\" function or each possible type should provide an \"isTypeOf\" function.",
" at ensureValidRuntimeType (A:\\Windows\\Users\\Luscha\\Desktop\\GIT\\Personal\\esperiments\\graphql-gateway-test\\node_modules\\graphql\\execution\\execute.js:667:11)",
" at completeAbstractValue (A:\\Windows\\Users\\Luscha\\Desktop\\GIT\\Personal\\esperiments\\graphql-gateway-test\\node_modules\\graphql\\execution\\execute.js:660:42)",
" at completeValue (A:\\Windows\\Users\\Luscha\\Desktop\\GIT\\Personal\\esperiments\\graphql-gateway-test\\node_modules\\graphql\\execution\\execute.js:585:12)",
" at completeValueCatchingError (A:\\Windows\\Users\\Luscha\\Desktop\\GIT\\Personal\\esperiments\\graphql-gateway-test\\node_modules\\graphql\\execution\\execute.js:495:19)",
" at A:\\Windows\\Users\\Luscha\\Desktop\\GIT\\Personal\\esperiments\\graphql-gateway-test\\node_modules\\graphql\\execution\\execute.js:618:25",
" at Array.forEach (<anonymous>)",
" at forEach (A:\\Windows\\Users\\Luscha\\Desktop\\GIT\\Personal\\esperiments\\graphql-gateway-test\\node_modules\\iterall\\index.js:83:25)",
" at completeListValue (A:\\Windows\\Users\\Luscha\\Desktop\\GIT\\Personal\\esperiments\\graphql-gateway-test\\node_modules\\graphql\\execution\\execute.js:614:24)",
" at completeValue (A:\\Windows\\Users\\Luscha\\Desktop\\GIT\\Personal\\esperiments\\graphql-gateway-test\\node_modules\\graphql\\execution\\execute.js:573:12)",
" at A:\\Windows\\Users\\Luscha\\Desktop\\GIT\\Personal\\esperiments\\graphql-gateway-test\\node_modules\\graphql\\execution\\execute.js:492:16"
]
}
}
}
],
"data": {
"queryByName": [
null
]
}
}
which do not make any sense, given the fact I specified a resolver for the __typename
Digging further i put some logs int he query resolver:
export function buildQuery(schema: GraphQLSchema): QueryResolvers {
return {
queryByName: async (root, args, context, info) => {
console.log("here")
console.log(args)
var a = await delegateToSchema({
schema,
operation : "query",
fieldName: "queryByName",
args,
context,
info
});
console.log("here 2")
a.forEach(element => {
console.log(element)
});
return a
}
....
and the result is
here
{ search: 'mario' }
here 2
{
[Symbol(subSchemaErrors)]: [
{
message: '__typename did not match an object type: WithName',
locations: [],
path: []
}
]
}
which makes totally sense. Dgraph returns an object with __typename WithName, as reported some queries ago, but apollo expects a result which implements WithName, in our case Person or Company.
I could not figure out how should behave dgraph in those case, either not allowing the return of an inteface in a @custom query, or giving the possibility to resolve / harcode a default __typename internally.
Anyway at the moment the feature do not behave with compliance to Apollo and i guess the GrapghQL spec in general.