Skip to main content
Version: v3.x beta

Explain API Reference

Introduction

The Explain API is an endpoint for analyzing GraphQL queries. Given a query and authorization, it will return the execution plan for the engine and data connector, if supported.

Endpoint

All requests are POST requests to the /v1/explain endpoint.

API Spec

Request

The request expects the exact same payload as the GraphQL API (including authentication related headers).

  • query: the GraphQL query to be analyzed
  • variables (optional): the variables used in the GraphQL query
  • operationName (optional): the name of the GraphQL operation
POST /v1/explain HTTP/1.1
Content-Type: application/json

{
"query": "<GraphQL query>",
"variables": {
"var1" : "...",
"var2" : "..."
}
}

Sample request

POST /v1/explain HTTP/1.1
Content-Type: application/json

{
"query": "query GetAlbumTracks($AlbumId: Int) {\n AlbumByID(AlbumId: $AlbumId) {\n Title\n Tracks {\n Name\n }\n }\n}",
"variables": {
"AlbumId": 1
},
"operationName": "GetAlbumTracks"
}

Response

The response for a query is the engine' plans for executing the GraphQL query:

{
"explain": "<ExplainStep>",
"errors": [<GraphQLError>]
}

ExplainStep

The ExplainStep can be one of the following:

  1. ModelSelect: A select on the data connector's model
  2. CommandSelect: A select on the data connector's command
  3. ForEach: A for-each loop on the data returned by the parent step
  4. HashJoin: A hash join of the data returned by the steps to construct valid response
  5. Sequence: A sequential execution of steps
  6. Parallel: A parallel execution of steps

ModelSelect

A ModelSelect represents a select on the data connector's model. It has the following structure:

{
"type": "modelSelect",
"value": {
"modelName": "<ModelName>",
"queryRequest": <NDCQueryRequest>,
"ndcExplain": <NDCExplain>
}
}

The fields in the ModelSelect are:

KeySchemaDescription
modelNameStringThe name of the model being selected
queryRequestNDCQueryRequestThe query request sent to the data connector
ndcExplainNDCExplainThe explain response from the data connector

CommandSelect

A CommandSelect represents a select on the data connector's command. It has the following structure:

{
"type": "commandSelect",
"value": {
"commandName": "<CommandName>",
"queryRequest": <NDCQueryRequest>,
"ndcExplain": <NDCExplain>
}
}

The fields in the CommandSelect are:

KeySchemaDescription
commandNameStringThe name of the command being selected
queryRequestNDCQueryRequestThe query request sent to the data connector
ndcExplainNDCExplainThe explain response from the data connector

ForEach

A ForEach represents a for-each loop on the data returned by the parent step. This is present if the engine is going to perform remote joins. It has the following structure:

{
"type": "forEach",
"value": <ForEachStep>
}

The value of the ForEach step can be either a ModelSelect or a CommandSelect.

HashJoin

A HashJoin represents a hash join of the data returned by the steps to construct valid response. This is present if the engine is going to perform remote joins. It has the following structure:

{
"type": "hashJoin"
}

Sequence

A Sequence represents a sequential execution of steps. It has the following structure:

{
"type": "sequence",
"value": [<ExplainStep>]
}

Parallel

A Parallel represents a parallel execution of steps. It has the following structure:

{
"type": "parallel",
"value": [<ExplainStep>]
}

NDCExplain

The NDCExplain contains the explanation of the query execution from the DataConnector point of view. It has the following structure:

{
"type": "response" || "error" || "notSupported"
"value": <data connector specific response>
}

The NDCExplain can be of the following types:

  1. response: The data-connector supports explaining query and has given a valid response.
  2. error: The data-connector has raised some error while explaining the query.
  3. notSupported: The data-connector doesn't support explaining ndc queries.

Example

Let's try to understand the meaning of various nodes in ExplainStep through examples.

Using the following query:

query {
Album {
AlbumId
ArtistId
Artist {
# a remote relationship
ArtistId
}
Tracks {
# a local relationship
TrackId
Album {
# a remote relationship
AlbumId
}
}
}
}

We get the following response:

{
"explain": {
"type": "sequence",
"value": [
{
"type": "modelSelect",
"value": {
"modelName": "Album",
"queryRequest": {
"collection": "Album",
"query": {
"fields": {
"AlbumId": {
"type": "column",
"column": "AlbumId"
},
"ArtistId": {
"type": "column",
"column": "ArtistId"
},
"__hasura_phantom_field__ArtistId": {
"type": "column",
"column": "ArtistId"
},
"Tracks": {
"type": "relationship",
"query": {
"fields": {
"TrackId": {
"type": "column",
"column": "TrackId"
},
"__hasura_phantom_field__AlbumId": {
"type": "column",
"column": "AlbumId"
}
}
},
"relationship": "[{\"subgraph\":\"connector_2\",\"name\":\"Album\"},\"Tracks\"]",
"arguments": {}
}
}
},
"arguments": {},
"collection_relationships": {
"[{\"subgraph\":\"connector_2\",\"name\":\"Album\"},\"Tracks\"]": {
"column_mapping": {
"AlbumId": "AlbumId"
},
"relationship_type": "array",
"target_collection": "Track",
"arguments": {}
}
}
},
"ndcExplain": {
"type": "response",
"value": {
"details": {
"Execution Plan": "Aggregate (cost=2342.72..2342.73 rows=1 width=32)\n -> Aggregate (cost=2342.70..2342.71 rows=1 width=32)\n -> Nested Loop Left Join (cost=11.06..2341.65 rows=210 width=40)\n -> Seq Scan on \"Album\" \"%0_Album\" (cost=0.00..12.10 rows=210 width=8)\n -> Subquery Scan on \"%3_rows\" (cost=11.06..11.08 rows=1 width=32)\n -> Aggregate (cost=11.06..11.07 rows=1 width=32)\n -> Bitmap Heap Scan on \"Track\" \"%2_Track\" (cost=4.29..11.05 rows=2 width=8)\n Recheck Cond: (\"%0_Album\".\"AlbumId\" = \"AlbumId\")\n -> Bitmap Index Scan on \"IFK_TrackAlbumId\" (cost=0.00..4.29 rows=2 width=0)\n Index Cond: (\"AlbumId\" = \"%0_Album\".\"AlbumId\")",
"SQL Query": "EXPLAIN\nSELECT\n coalesce(json_agg(row_to_json(\"%5_universe\")), '[]') AS \"universe\"\nFROM\n (\n SELECT\n *\n FROM\n (\n SELECT\n coalesce(json_agg(row_to_json(\"%6_rows\")), '[]') AS \"rows\"\n FROM\n (\n SELECT\n \"%0_Album\".\"AlbumId\" AS \"AlbumId\",\n \"%0_Album\".\"ArtistId\" AS \"ArtistId\",\n \"%0_Album\".\"ArtistId\" AS \"__hasura_phantom_field__ArtistId\",\n \"%1_RELATIONSHIP_Tracks\".\"Tracks\" AS \"Tracks\"\n FROM\n \"public\".\"Album\" AS \"%0_Album\"\n LEFT OUTER JOIN LATERAL (\n SELECT\n row_to_json(\"%1_RELATIONSHIP_Tracks\") AS \"Tracks\"\n FROM\n (\n SELECT\n *\n FROM\n (\n SELECT\n coalesce(json_agg(row_to_json(\"%3_rows\")), '[]') AS \"rows\"\n FROM\n (\n SELECT\n \"%2_Track\".\"TrackId\" AS \"TrackId\",\n \"%2_Track\".\"AlbumId\" AS \"__hasura_phantom_field__AlbumId\"\n FROM\n \"public\".\"Track\" AS \"%2_Track\"\n WHERE\n (\"%0_Album\".\"AlbumId\" = \"%2_Track\".\"AlbumId\")\n ) AS \"%3_rows\"\n ) AS \"%3_rows\"\n ) AS \"%1_RELATIONSHIP_Tracks\"\n ) AS \"%1_RELATIONSHIP_Tracks\" ON ('true')\n ) AS \"%6_rows\"\n ) AS \"%6_rows\"\n ) AS \"%5_universe\""
}
}
}
}
},
{
"type": "parallel",
"value": [
{
"type": "forEach",
"value": {
"type": "modelSelect",
"value": {
"modelName": "Artist",
"queryRequest": {
"collection": "Artist",
"query": {
"fields": {
"ArtistId": {
"type": "column",
"column": "ArtistId"
}
},
"where": {
"type": "binary_comparison_operator",
"column": {
"type": "column",
"name": "ArtistId",
"path": []
},
"operator": {
"type": "equal"
},
"value": {
"type": "variable",
"name": "$ArtistId"
}
}
},
"arguments": {},
"collection_relationships": {},
"variables": []
},
"ndcExplain": {
"type": "response",
"value": {
"details": {
"Execution Plan": "",
"SQL Query": "EXPLAIN\nSELECT\n coalesce(json_agg(\"%5_universe_agg\".\"universe\"), '[]') AS \"universe\"\nFROM\n (\n SELECT\n row_to_json(\"%2_universe\") AS \"universe\"\n FROM\n json_to_recordset(cast($1 as json)) AS \"%0_%variables_table\"(\"%variable_order\" int)\n CROSS JOIN LATERAL (\n SELECT\n *\n FROM\n (\n SELECT\n coalesce(json_agg(row_to_json(\"%3_rows\")), '[]') AS \"rows\"\n FROM\n (\n SELECT\n \"%1_Artist\".\"ArtistId\" AS \"ArtistId\"\n FROM\n \"public\".\"Artist\" AS \"%1_Artist\"\n WHERE\n (\n \"%1_Artist\".\"ArtistId\" = cast(\"%0_%variables_table\".\"$ArtistId\" as int4)\n )\n ) AS \"%3_rows\"\n ) AS \"%3_rows\"\n ) AS \"%2_universe\"\n ORDER BY\n \"%0_%variables_table\".\"%variable_order\" ASC\n ) AS \"%5_universe_agg\""
}
}
}
}
}
},
{
"type": "sequence",
"value": [
{
"type": "forEach",
"value": {
"type": "modelSelect",
"value": {
"modelName": "Album",
"queryRequest": {
"collection": "Album",
"query": {
"fields": {
"AlbumId": {
"type": "column",
"column": "AlbumId"
}
},
"where": {
"type": "binary_comparison_operator",
"column": {
"type": "column",
"name": "AlbumId",
"path": []
},
"operator": {
"type": "equal"
},
"value": {
"type": "variable",
"name": "$AlbumId"
}
}
},
"arguments": {},
"collection_relationships": {},
"variables": []
},
"ndcExplain": {
"type": "response",
"value": {
"details": {
"Execution Plan": "",
"SQL Query": "EXPLAIN\nSELECT\n coalesce(json_agg(\"%5_universe_agg\".\"universe\"), '[]') AS \"universe\"\nFROM\n (\n SELECT\n row_to_json(\"%2_universe\") AS \"universe\"\n FROM\n json_to_recordset(cast($1 as json)) AS \"%0_%variables_table\"(\"%variable_order\" int)\n CROSS JOIN LATERAL (\n SELECT\n *\n FROM\n (\n SELECT\n coalesce(json_agg(row_to_json(\"%3_rows\")), '[]') AS \"rows\"\n FROM\n (\n SELECT\n \"%1_Album\".\"AlbumId\" AS \"AlbumId\"\n FROM\n \"public\".\"Album\" AS \"%1_Album\"\n WHERE\n (\n \"%1_Album\".\"AlbumId\" = cast(\"%0_%variables_table\".\"$AlbumId\" as int4)\n )\n ) AS \"%3_rows\"\n ) AS \"%3_rows\"\n ) AS \"%2_universe\"\n ORDER BY\n \"%0_%variables_table\".\"%variable_order\" ASC\n ) AS \"%5_universe_agg\""
}
}
}
}
}
},
{
"type": "hashJoin"
}
]
}
]
},
{
"type": "hashJoin"
}
]
}
}

The JSON above represents the execution plan for the query. The query is broken down into multiple steps, each step is a ModelSelect or a ForEach step. The ModelSelect step represents a select on the data connector's model, and the ForEach step represents a for-each loop on the data returned by the parent step.

This allows you to understand how the query is executed and how the data is fetched from the data connector.

Loading...