• User Manuals /
  • Denodo GraphQL Custom Wrapper - User Manual

Denodo GraphQL Custom Wrapper - User Manual

Download original document


You can translate the document:

Introduction

The GraphQL custom wrapper is a Virtual DataPort custom wrapper for querying GraphQL services.

What is GraphQL?

GraphQL is an open-source data query and manipulation language for APIs. It enables declarative data fetching where a client can specify exactly what data it needs from an API. A GraphQL server exposes a single endpoint and responds with precisely the data a client asked for. This wrapper is based on the GraphQL specification of October 2021.

Main features

The Denodo GraphQL custom wrapper allows us to create base views on an external GraphQL service, and execute VQL queries that retrieve data from it.

The main functionalities this custom wrapper support are:

  • Creation of base views from a GraphQL service data source using a schema introspection query.
  • Option to specify the maximum nesting level allowed.
  • Support for all types: scalars, enums, objects, union, interfaces, input objects, lists and non-nulls.
  • Support for arguments.
  • They will be treated as fields in the base view in order to be included in the WHERE clause. More information in Argument handling.

  • Execution of VQL read-only queries over these base views. The following aspects are delegated to the source:
  • Simple projections (no nested subfields).
  • Selections over fields that come from arguments.
  • Only equals and AND operators.
  • Selections over subfields of complex input objects.

Architecture

A GraphQL service exposes a root operation type for each kind of operation it supports (query, mutation and subscription). This root operation type will define the set of fields that will be available at the top level of a query operation. The custom wrapper will create a base view from one of these fields. For example if our GraphQL service defines the following query root type:

type Query {

    getStudents: [Student]

}

We would be able to create a base view from “getStudents”, which will contain as columns the fields of the type Student:

Service schema and introspection

The schema of a GraphQL service is the collection of its type system capabilities. In other words, the schema contains all information about the types and operations that the service publishes.

All GraphQL engines offer the possibility to query this schema by means of an introspection query. As the custom wrapper needs to analyze the schema in order to build the base view, it will execute an introspection query.

NOTE: Some services disable introspection queries in order to obscure the information that you can get about the service. Please note that, if this is the case, the base view schema creation will not work.

Here we can see a high-level schema of the base view creation process:

Query execution

Once the base view schema is created, the user can execute VQL queries on it. The wrapper will receive the VQL statement and translate it to a GraphQL query. Once the result is obtained it will use the schema to build the response for VDP. Here is another high-level schema:

Argument handling

Arguments are a big part of GraphQL services. Fields are conceptually functions which return values, and occasionally accept arguments which alter their behavior. These arguments often map directly to function arguments within a GraphQL service’s implementation.

In this wrapper arguments are included in the base view as columns, with the _arg suffix in the name. This is done in order to allow the user to provide values to these arguments through a WHERE statement. This is explained in the following example.

We have a Graphql service with this schema:

type Query {

   getStudents(name: String): [Student]

}

type Student {

    name: String

    grade: Int

}

The base view generated from the getStudents query root field would be:

If we execute a simple SELECT query over it, the GraphQL query generated would be:

studentsByName {

        name

        age

}

And the result:

But if we include the column name_arg in the WHERE statement, the GraphQL query executed would be:

studentsByName(name: "Jimmy") {

        name

        age

}

And the result:

Only the conditions on fields originated from arguments are delegated to the source. If, in this last query, we had included in the WHERE statement name instead of name_arg, the GraphQL query executed would be that without the argument, and VDP would make a standard filter in memory.

This is important to understand, as arguments in GraphQL may not perform a filter, they can be treated in any way the service wants. The results of including name_arg and name in the WHERE statement can be completely different.

NOTE: As stated in Limitations, complex argument types can´t be used in the WHERE clause as they are not delegated to the source. Please use their subfields instead of the entire object.

Mandatory arguments

Arguments can be marked as non-null in a GraphQL service, which means they are mandatory. We treat this in Denodo as input parameters, which if not included in the WHERE clause of the query, will return an error. When we create the base view mandatory arguments can be checked in Options → Search methods.

NOTE: Mandatory complex objects are not supported, because, as stated in the previous section, such arguments cannot be delegated to the source, so the GraphQL service would always give an error on execution. If a mandatory complex argument is present in a data source, the base view creation will give an error.

Abstract types support

Abstract types require some additional handling in VDP.

Unions

Unions represent an object that could be one of a list of object types, but provides no guaranteed fields between those types. So if we have to process a union type when creating a base view, we can’t know beforehand which type will be received.

This is solved by iterating each possible type of the union and merging all of their fields in one artificial type. Suppose the following GraphQL service definition:

type Query {

   getMedias(name: String): [Media]

}

union Media = Book | Film

type Book {

    name: String

    pages: Int

}

type Film {

    name: String

    minutes: Int

}

If we create a base view over the query getMedias we would get the following schema:

As you can see all the fields of all the possible types are merged. The origin type is concatenated to the field name in order to avoid duplicates and easily identify them. When we execute the query, each result row will have the values that apply to the type obtained. For example, if we execute a simple select over this service we would obtain the following:

The first two results were books and the last two films, so each row has the columns that apply to the received type, and the remaining are set to null.

Interfaces

GraphQL interfaces represent a list of named fields and their arguments. GraphQL objects and interfaces can then implement these interfaces which requires that the implementing type will define all fields defined by those interfaces. They are similar to unions, with the caveat that an interface can define common fields between the types.

The way they are handled in VDP is similar, but in this case we have to add to the merged fields the common fields defined in the interface. Here we can see an example, suppose the following service definition:

type Query {

   getMedias(name: String): [Media]

}

interface Media {

    name: String

}

type Book implements Media {

    name: String

    pages: Int

}

type Film implements Media {

    name: String

    minutes: Int

}

If we create a base view over the root query getMedias we would get the following schema:

As you can see the common fields of the interface don’t have a type suffix, because they belong to the interface. If we execute a simple select over this base view we would get the next result:

Usage

Importing the custom wrapper into VDP

In order to use the Denodo GraphQL Custom Wrapper in VDP, first it is necessary to install its JAR file into the Denodo VDP server. For this, in the VDP administration tool or design studio, go to the File > Extensions menu option and install the jar-with-dependencies file that is included in the distribution of the Denodo GraphQL Custom Wrapper.

Creating a GraphQL data source

  1. Go to New > Data Source > Others > Custom
  2. Enable ‘Select jars’ and select the denodo-graphql-customwrapper jar previously uploaded in extensions.
  3. Click ‘Refresh input parameters’

This custom wrapper only has one data source parameter: the GraphQL endpoint. Please select the only option Http client and configure it to access your GraphQL service.

The configuration will differ depending on the service, but in general a POST request will be needed. As authentication, you can choose any authentication supported by VDP. In this example we are using a local service with no authentication:

Creating base views

Once the custom wrapper data source has been created, click on the ‘Create base view’ tab. It will ask for some parameters.

GraphQL query name: This is the name of the field of the query root operation from which the base view will be constructed.

Maximum nesting level: A GraphQL object have fields that can be again objects. This will be represented in VDP as registers. The nesting, in theory, can be infinite, so we offer a parameter in order to limit it. A maximum nesting level of cero means that only the root field columns will be computed. If one, the columns of the columns will be computed, and so on.

Execution

Once you’ve created a base view you can execute VQL queries over it. The VQL sentence will be translated to a GraphQL query, and the response will be used to generate the resulting table. In order to learn more about how different aspects of query execution works, refer to Architecture.

Limitations

This custom wrapper has the following limitations:

  • If the service does not allow introspection queries, the wrapper won’t be able to create the base view.
  • Only query operations are supported. Mutation or subscription operations are not supported..
  • Delegating projections of specific subfields of complex object fields is not supported. When at least one subfield of a complex object is requested, the whole complex object will be retrieved from the service, and the unneeded parts will be discarded before returning the results.
  • Custom scalars (custom scalars specification) are not natively supported. If present in a service, they will be converted to text values.
  • Complex input arguments are not supported due to the way complex arguments are processed throughout the wrapper. Instead, inner, non-complex subfields of those arguments are supported.
  • Field descriptions defined in the schema are not carried over to the base view columns.