Skip to content
58 changes: 54 additions & 4 deletions graph/src/schema/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,24 @@ use std::sync::Arc;
use anyhow::Context;
use graphql_parser::Pos;
use lazy_static::lazy_static;
use thiserror::Error;

use crate::cheap_clone::CheapClone;
use crate::data::graphql::{ObjectOrInterface, ObjectTypeExt, TypeExt};
use crate::data::store::IdType;
use crate::env::ENV_VARS;
use crate::schema::{ast, META_FIELD_NAME, META_FIELD_TYPE, SCHEMA_TYPE_NAME};
use crate::schema::{
ast, META_FIELD_NAME, META_FIELD_TYPE, SCHEMA_TYPE_NAME, SQL_FIELD_NAME, SQL_FIELD_TYPE,
SQL_INPUT_TYPE,
};

use crate::data::graphql::ext::{
camel_cased_names, DefinitionExt, DirectiveExt, DocumentExt, ValueExt,
};
use crate::prelude::s::*;
use crate::prelude::*;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please avoid * imports - they become a huge headache later in some refactorings. Just list everything you actually need.

Also, the single letter crates, like s, q and r should always be used as such, i.e., code should always say s::Document instead of just Document

use thiserror::Error;

use crate::cheap_clone::CheapClone;
use crate::env::ENV_VARS;

use crate::derive::CheapClone;
use crate::prelude::{q, r, s, DeploymentHash};

Expand Down Expand Up @@ -356,6 +363,7 @@ pub(in crate::schema) fn api_schema(
// Refactor: Don't clone the schema.
let mut api = init_api_schema(input_schema)?;
add_meta_field_type(&mut api.document);
add_sql_field_type(&mut api.document);
add_types_for_object_types(&mut api, input_schema)?;
add_types_for_interface_types(&mut api, input_schema)?;
add_types_for_aggregation_types(&mut api, input_schema)?;
Expand Down Expand Up @@ -465,6 +473,21 @@ fn add_meta_field_type(api: &mut s::Document) {
.extend(META_FIELD_SCHEMA.definitions.iter().cloned());
}

// Adds a global `SqlOutput` type to the schema. The `sql` field
// accepts values of this type
fn add_sql_field_type(schema: &mut Document) {
lazy_static! {
static ref SQL_FIELD_SCHEMA: Document = {
let schema = include_str!("sql.graphql");
parse_schema(schema).expect("the schema `sql.graphql` is invalid")
};
}

schema
.definitions
.extend(SQL_FIELD_SCHEMA.definitions.iter().cloned());
}

fn add_types_for_object_types(
api: &mut Schema,
schema: &InputSchema,
Expand Down Expand Up @@ -1061,6 +1084,7 @@ fn add_query_type(api: &mut s::Document, input_schema: &InputSchema) -> Result<(
fields.append(&mut agg_fields);
fields.append(&mut fulltext_fields);
fields.push(meta_field());
fields.push(sql_field());

let typedef = s::TypeDefinition::Object(s::ObjectType {
position: Pos::default(),
Expand Down Expand Up @@ -1159,6 +1183,7 @@ fn add_subscription_type(
.collect::<Vec<s::Field>>();
fields.append(&mut agg_fields);
fields.push(meta_field());
fields.push(sql_field());

let typedef = s::TypeDefinition::Object(s::ObjectType {
position: Pos::default(),
Expand Down Expand Up @@ -1304,6 +1329,31 @@ fn meta_field() -> s::Field {
META_FIELD.clone()
}

fn sql_field() -> s::Field {
lazy_static! {
static ref SQL_FIELD: s::Field = s::Field {
position: Pos::default(),
description: Some("Access to SQL queries".to_string()),
name: SQL_FIELD_NAME.to_string(),
arguments: vec![
InputValue {
position: Pos::default(),
description: None,
name: String::from("input"),
value_type: Type::NonNullType(Box::new(Type::NamedType(SQL_INPUT_TYPE.to_string()))),
default_value: None,
directives: vec![],

}
],
field_type: Type::NamedType(SQL_FIELD_TYPE.to_string()),
directives: vec![],
};
}

SQL_FIELD.clone()
}

#[cfg(test)]
mod tests {
use crate::{
Expand Down
6 changes: 6 additions & 0 deletions graph/src/schema/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ pub const INTROSPECTION_SCHEMA_FIELD_NAME: &str = "__schema";
pub const META_FIELD_TYPE: &str = "_Meta_";
pub const META_FIELD_NAME: &str = "_meta";

pub const SQL_FIELD_TYPE: &str = "SqlOutput";
pub const SQL_JSON_FIELD_TYPE: &str = "SqlJSONOutput";
pub const SQL_CSV_FIELD_TYPE: &str = "SqlCSVOutput";
pub const SQL_INPUT_TYPE: &str = "SqlInput";
pub const SQL_FIELD_NAME: &str = "sql";

pub const INTROSPECTION_TYPE_FIELD_NAME: &str = "__type";

pub const BLOCK_FIELD_TYPE: &str = "_Block_";
Expand Down
67 changes: 67 additions & 0 deletions graph/src/schema/sql.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
scalar String
scalar JSONObject
scalar SqlVariable


enum SqlFormat {
JSON
CSV
}

input SqlInput {
"""
The SQL query to execute. The query may contain positional parameters
that are passed in the `parameters` field.
"""
query: String!
"""
The SQL query parameters. The parameters are passed to the SQL query
as positional parameters. The parameters are converted to the SQL
types based on the GraphQL types of the parameters.
"""
parameters: [SqlVariable]

"""
The format of the SQL query result. The default format is JSON.
"""
format: SqlFormat = JSON
}


# The type names are purposely awkward to minimize the risk of them
# colliding with user-supplied types
"The type for the top-level sql field"
type SqlJSONOutput {
"The columns returned by the query"
columns: [String!]!

"""
The SQL query result row count.
"""
rowCount: Int!

"""
The SQL query result rows. Each row is represented as a list of values.
The values are represented as JSON values.
"""
rows: [JSONObject!]!
}


type SqlCSVOutput {

"The columns returned by the query"
columns: [String!]!

"""
The SQL query result rows. Represented as a CSV string
"""
result: String
"""
Number of SQL rows being returned
"""
rowCount: Int!
}

union SqlOutput = SqlJSONOutput | SqlCSVOutput