Skip to content

Commit

Permalink
Merge pull request #94 from hasura/djh/explain-endpoint
Browse files Browse the repository at this point in the history
Add `explain` endpoint
  • Loading branch information
danieljharvey authored Dec 6, 2023
2 parents 3f5eb2e + fde27de commit cc73da1
Show file tree
Hide file tree
Showing 12 changed files with 259 additions and 74 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 7 additions & 4 deletions crates/ndc-sqlserver/src/connector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use ndc_sdk::json_response::JsonResponse;
use ndc_sdk::models;

use super::configuration;
use super::explain;
use super::query;
use super::schema;
use std::sync::Arc;
Expand Down Expand Up @@ -132,11 +133,13 @@ impl connector::Connector for SQLServer {
/// This function implements the [explain endpoint](https://hasura.github.io/ndc-spec/specification/explain.html)
/// from the NDC specification.
async fn explain(
_configuration: &Self::Configuration,
_state: &Self::State,
_query_request: models::QueryRequest,
configuration: &Self::Configuration,
state: &Self::State,
query_request: models::QueryRequest,
) -> Result<JsonResponse<models::ExplainResponse>, connector::ExplainError> {
todo!("explain!")
explain::explain(configuration, state, query_request)
.await
.map(JsonResponse::Value)
}

/// Execute a mutation
Expand Down
94 changes: 94 additions & 0 deletions crates/ndc-sqlserver/src/explain.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//! Implement the `/explain` endpoint to return a query execution plan.
//! See the Hasura
//! [Native Data Connector Specification](https://hasura.github.io/ndc-spec/specification/explain.html)
//! for further details.
use std::collections::BTreeMap;

use tracing::{info_span, Instrument};

use ndc_sdk::connector;
use ndc_sdk::models;
use query_engine_execution::execution;
use query_engine_sql::sql;
use query_engine_translation::translation;

use super::configuration;

/// Explain a query by creating an execution plan
///
/// This function implements the [explain endpoint](https://hasura.github.io/ndc-spec/specification/explain.html)
/// from the NDC specification.
pub async fn explain(
configuration: &configuration::Configuration,
state: &configuration::State,
query_request: models::QueryRequest,
) -> Result<models::ExplainResponse, connector::ExplainError> {
async move {
tracing::info!(
query_request_json = serde_json::to_string(&query_request).unwrap(),
query_request = ?query_request
);

// Compile the query.
let plan = async { plan_query(configuration, state, query_request) }
.instrument(info_span!("Plan query"))
.await?;

// Execute an explain query.
let (query, plan) = execution::explain(&state.mssql_pool, plan)
.instrument(info_span!("Explain query"))
.await
.map_err(|err| match err {
execution::Error::Query(err) => {
tracing::error!("{}", err);

connector::ExplainError::Other(err.to_string().into())
}
execution::Error::ConnectionPool(err) => {
tracing::error!("{}", err);

connector::ExplainError::Other(err.to_string().into())
}
execution::Error::TiberiusError(err) => {
tracing::error!("{}", err);

connector::ExplainError::Other(err.to_string().into())
}
})?;

state.metrics.record_successful_explain();

let details =
BTreeMap::from_iter([("SQL Query".into(), query), ("Execution Plan".into(), plan)]);

let response = models::ExplainResponse { details };

Ok(response)
}
.instrument(info_span!("/explain"))
.await
}

fn plan_query(
configuration: &configuration::Configuration,
state: &configuration::State,
query_request: models::QueryRequest,
) -> Result<sql::execution_plan::ExecutionPlan, connector::ExplainError> {
let timer = state.metrics.time_query_plan();
let result = translation::query::translate(&configuration.config.metadata, query_request)
.map_err(|err| {
tracing::error!("{}", err);
match err {
translation::query::error::Error::CapabilityNotSupported(_) => {
state.metrics.error_metrics.record_unsupported_capability();
connector::ExplainError::UnsupportedOperation(err.to_string())
}
_ => {
state.metrics.error_metrics.record_invalid_request();
connector::ExplainError::InvalidRequest(err.to_string())
}
}
});
timer.complete_with(result)
}
1 change: 1 addition & 0 deletions crates/ndc-sqlserver/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod configuration;
pub mod connector;
pub mod explain;
pub mod query;
pub mod schema;
10 changes: 4 additions & 6 deletions crates/ndc-sqlserver/tests/explain_tests.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
/*
pub mod common;

use crate::common::{is_contained_in_lines, run_explain};

#[tokio::test]
async fn select_by_pk() {
let result = run_explain("select_by_pk").await;
is_contained_in_lines(vec!["Aggregate", "Scan", "35"], result.details.plan);
is_contained_in_lines(vec!["Clustered", "Index", "Seek"], result.details.plan);
insta::assert_snapshot!(result.details.query);
}

#[tokio::test]
async fn select_where_variable() {
let result = run_explain("select_where_variable").await;
is_contained_in_lines(vec!["Aggregate", "Seq Scan", "Filter"], result.details.plan);
is_contained_in_lines(vec!["Clustered", "Index", "Scan"], result.details.plan);
insta::assert_snapshot!(result.details.query);
}

#[tokio::test]
async fn select_where_name_nilike() {
let result = run_explain("select_where_name_nilike").await;
let keywords = vec!["Aggregate", "Subquery Scan", "Limit", "Seq Scan", "Filter"];
let result = run_explain("select_where_name_like").await;
let keywords = vec!["Compute", "Scalar"];
is_contained_in_lines(keywords, result.details.plan);
insta::assert_snapshot!(result.details.query);
}
*/
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,17 @@
source: crates/ndc-sqlserver/tests/explain_tests.rs
expression: result.details.query
---
EXPLAIN
SELECT
row_to_json("universe") AS "universe"
ISNULL([2_rows].[rows], '[]') AS [rows]
FROM
(
SELECT
*
[0_Album].[Title] AS [Title]
FROM
(
SELECT
coalesce(json_agg(row_to_json("rows")), '[]') AS "rows"
FROM
(
SELECT
"Album"."Title" AS "Title"
FROM
"public"."Album" AS "Album"
WHERE
(
true
AND ("Album"."AlbumId" = 35)
)
) AS "rows"
) AS "rows"
) AS "universe"
[dbo].[Album] AS [0_Album]
WHERE
([0_Album].[AlbumId] = 35) FOR JSON PATH,
INCLUDE_NULL_VALUES
) AS [2_rows]([rows]) FOR JSON PATH,
INCLUDE_NULL_VALUES,
WITHOUT_ARRAY_WRAPPER
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,18 @@
source: crates/ndc-sqlserver/tests/explain_tests.rs
expression: result.details.query
---
EXPLAIN
SELECT
row_to_json("universe") AS "universe"
ISNULL([2_rows].[rows], '[]') AS [rows]
FROM
(
SELECT
*
[0_Album].[AlbumId] AS [AlbumId],
[0_Album].[Title] AS [Title]
FROM
(
SELECT
coalesce(json_agg(row_to_json("rows")), '[]') AS "rows"
FROM
(
SELECT
"Album"."Title" AS "Title"
FROM
"public"."Album" AS "Album"
WHERE
(
true
AND ("Album"."Title" NOT ILIKE $1)
)
LIMIT
5
) AS "rows"
) AS "rows"
) AS "universe"
[dbo].[Album] AS [0_Album]
WHERE
([0_Album].[Title] LIKE @P1) FOR JSON PATH,
INCLUDE_NULL_VALUES
) AS [2_rows]([rows]) FOR JSON PATH,
INCLUDE_NULL_VALUES,
WITHOUT_ARRAY_WRAPPER
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,17 @@
source: crates/ndc-sqlserver/tests/explain_tests.rs
expression: result.details.query
---
EXPLAIN
SELECT
row_to_json("universe") AS "universe"
ISNULL([2_rows].[rows], '[]') AS [rows]
FROM
(
SELECT
*
[0_Album].[Title] AS [Title]
FROM
(
SELECT
coalesce(json_agg(row_to_json("rows")), '[]') AS "rows"
FROM
(
SELECT
"Album"."Title" AS "Title"
FROM
"public"."Album" AS "Album"
WHERE
("Album"."Title" LIKE $1)
) AS "rows"
) AS "rows"
) AS "universe"
[dbo].[Album] AS [0_Album]
WHERE
([0_Album].[Title] LIKE cast(@P1 as nvarchar)) FOR JSON PATH,
INCLUDE_NULL_VALUES
) AS [2_rows]([rows]) FOR JSON PATH,
INCLUDE_NULL_VALUES,
WITHOUT_ARRAY_WRAPPER
1 change: 1 addition & 0 deletions crates/query-engine/execution/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ bb8-tiberius = "0.15.0"
bytes = "1.5.0"
prometheus = "0.13.3"
serde_json = "1.0.108"
sqlformat = "0.2.2"
tokio-stream = "0.1.14"
tracing = "0.1.40"
Loading

0 comments on commit cc73da1

Please sign in to comment.