feat : improve the generated introspection schema and avoid the chirino/graphql api leaking through the core api. (#53)

This commit is contained in:
Hiram Chirino 2020-04-21 10:03:05 -04:00 committed by GitHub
parent 0a02bde219
commit 2a32c179ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 704 additions and 113 deletions

View File

@ -93,7 +93,6 @@ type SuperGraph struct {
anonExists bool anonExists bool
qc *qcode.Compiler qc *qcode.Compiler
pc *psql.Compiler pc *psql.Compiler
Engine *graphql.Engine
} }
// NewSuperGraph creates the SuperGraph struct, this involves querying the database to learn its // NewSuperGraph creates the SuperGraph struct, this involves querying the database to learn its
@ -125,10 +124,6 @@ func NewSuperGraph(conf *Config, db *sql.DB) (*SuperGraph, error) {
return nil, err return nil, err
} }
if err := sg.initGraphQLEgine(); err != nil {
return nil, err
}
if len(conf.SecretKey) != 0 { if len(conf.SecretKey) != 0 {
sk := sha256.Sum256([]byte(conf.SecretKey)) sk := sha256.Sum256([]byte(conf.SecretKey))
conf.SecretKey = "" conf.SecretKey = ""
@ -167,13 +162,18 @@ func (sg *SuperGraph) GraphQL(c context.Context, query string, vars json.RawMess
// use the chirino/graphql library for introspection queries // use the chirino/graphql library for introspection queries
// disabled when allow list is enforced // disabled when allow list is enforced
if !sg.conf.UseAllowList && if !sg.conf.UseAllowList && res.name == "IntrospectionQuery" {
res.op == qcode.QTQuery && engine, err := sg.createGraphQLEgine()
res.name == "IntrospectionQuery" { if err != nil {
res.Error = err.Error()
return &res, err
}
r := sg.Engine.ExecuteOne(&graphql.EngineRequest{Query: query}) r := engine.ExecuteOne(&graphql.EngineRequest{Query: query})
res.Data = r.Data res.Data = r.Data
if r.Error() != nil {
res.Error = r.Error().Error()
}
return &res, r.Error() return &res, r.Error()
} }
@ -198,3 +198,11 @@ func (sg *SuperGraph) GraphQL(c context.Context, query string, vars json.RawMess
return &ct.res, nil return &ct.res, nil
} }
func (sg *SuperGraph) GraphQLSchema() (string, error) {
engine, err := sg.createGraphQLEgine()
if err != nil {
return "", err
}
return engine.Schema.String(), nil
}

View File

@ -1,6 +1,8 @@
package core package core
import ( import (
"errors"
"regexp"
"strings" "strings"
"github.com/chirino/graphql" "github.com/chirino/graphql"
@ -24,15 +26,17 @@ var typeMap map[string]string = map[string]string{
"boolean": "Boolean", "boolean": "Boolean",
} }
func (sg *SuperGraph) initGraphQLEgine() error { func (sg *SuperGraph) createGraphQLEgine() (*graphql.Engine, error) {
sg.Engine = graphql.New() engine := graphql.New()
engineSchema := sg.Engine.Schema engineSchema := engine.Schema
dbSchema := sg.schema dbSchema := sg.schema
sanitizeForGraphQLSchema := func(value string) string { engineSchema.Parse(`
// TODO: enum OrderDirection {
return value asc
} desc
}
`)
gqltype := func(col psql.DBColumn) schema.Type { gqltype := func(col psql.DBColumn) schema.Type {
typeName := typeMap[strings.ToLower(col.Type)] typeName := typeMap[strings.ToLower(col.Type)]
@ -59,106 +63,409 @@ func (sg *SuperGraph) initGraphQLEgine() error {
engineSchema.EntryPoints[schema.Query] = query engineSchema.EntryPoints[schema.Query] = query
engineSchema.EntryPoints[schema.Mutation] = mutation engineSchema.EntryPoints[schema.Mutation] = mutation
validGraphQLIdentifierRegex := regexp.MustCompile(`^[A-Za-z_][A-Za-z_0-9]*$`)
scalarExpressionTypesNeeded := map[string]bool{}
tableNames := dbSchema.GetTableNames() tableNames := dbSchema.GetTableNames()
for _, table := range tableNames { for _, table := range tableNames {
ti, err := dbSchema.GetTable(table) ti, err := dbSchema.GetTable(table)
if err != nil { if err != nil {
return err return nil, err
} }
if !ti.IsSingular { if !ti.IsSingular {
continue continue
} }
pti, err := dbSchema.GetTable(ti.Plural) singularName := ti.Singular
if err != nil { if !validGraphQLIdentifierRegex.MatchString(singularName) {
return err return nil, errors.New("table name is not a valid GraphQL identifier: " + singularName)
}
pluralName := ti.Plural
if !validGraphQLIdentifierRegex.MatchString(pluralName) {
return nil, errors.New("table name is not a valid GraphQL identifier: " + pluralName)
} }
outputType := &schema.Object{ outputType := &schema.Object{
Name: sanitizeForGraphQLSchema(ti.Singular) + "Output", Name: singularName + "Output",
Fields: schema.FieldList{}, Fields: schema.FieldList{},
} }
engineSchema.Types[outputType.Name] = outputType
inputType := &schema.InputObject{ inputType := &schema.InputObject{
Name: sanitizeForGraphQLSchema(ti.Singular) + "Input", Name: singularName + "Input",
Fields: schema.InputValueList{}, Fields: schema.InputValueList{},
} }
engineSchema.Types[inputType.Name] = inputType
orderByType := &schema.InputObject{
Name: singularName + "OrderBy",
Fields: schema.InputValueList{},
}
engineSchema.Types[orderByType.Name] = orderByType
expressionTypeName := singularName + "Expression"
expressionType := &schema.InputObject{
Name: expressionTypeName,
Fields: schema.InputValueList{
&schema.InputValue{
Name: schema.Ident{Text: "and"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: expressionTypeName}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "or"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: expressionTypeName}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "not"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: expressionTypeName}}},
},
},
}
engineSchema.Types[expressionType.Name] = expressionType
for _, col := range ti.Columns { for _, col := range ti.Columns {
colName := col.Name
if !validGraphQLIdentifierRegex.MatchString(colName) {
return nil, errors.New("column name is not a valid GraphQL identifier: " + colName)
}
colType := gqltype(col)
nullableColType := ""
if x, ok := colType.(*schema.NonNull); ok {
nullableColType = x.OfType.(*schema.TypeName).Ident.Text
} else {
nullableColType = colType.(*schema.TypeName).Ident.Text
}
outputType.Fields = append(outputType.Fields, &schema.Field{ outputType.Fields = append(outputType.Fields, &schema.Field{
Name: sanitizeForGraphQLSchema(col.Name), Name: colName,
Type: gqltype(col), Type: colType,
}) })
// If it's a numeric type...
if nullableColType == "Float" || nullableColType == "Int" {
outputType.Fields = append(outputType.Fields, &schema.Field{
Name: "avg_" + colName,
Type: colType,
})
outputType.Fields = append(outputType.Fields, &schema.Field{
Name: "count_" + colName,
Type: colType,
})
outputType.Fields = append(outputType.Fields, &schema.Field{
Name: "max_" + colName,
Type: colType,
})
outputType.Fields = append(outputType.Fields, &schema.Field{
Name: "min_" + colName,
Type: colType,
})
outputType.Fields = append(outputType.Fields, &schema.Field{
Name: "stddev_" + colName,
Type: colType,
})
outputType.Fields = append(outputType.Fields, &schema.Field{
Name: "stddev_pop_" + colName,
Type: colType,
})
outputType.Fields = append(outputType.Fields, &schema.Field{
Name: "stddev_samp_" + colName,
Type: colType,
})
outputType.Fields = append(outputType.Fields, &schema.Field{
Name: "variance_" + colName,
Type: colType,
})
outputType.Fields = append(outputType.Fields, &schema.Field{
Name: "var_pop_" + colName,
Type: colType,
})
outputType.Fields = append(outputType.Fields, &schema.Field{
Name: "var_samp_" + colName,
Type: colType,
})
}
inputType.Fields = append(inputType.Fields, &schema.InputValue{ inputType.Fields = append(inputType.Fields, &schema.InputValue{
Name: schema.Ident{Text: sanitizeForGraphQLSchema(col.Name)}, Name: schema.Ident{Text: colName},
Type: gqltype(col), Type: colType,
})
orderByType.Fields = append(orderByType.Fields, &schema.InputValue{
Name: schema.Ident{Text: colName},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "OrderDirection"}}},
})
scalarExpressionTypesNeeded[nullableColType] = true
expressionType.Fields = append(expressionType.Fields, &schema.InputValue{
Name: schema.Ident{Text: colName},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: nullableColType + "Expression"}}},
}) })
} }
engineSchema.Types[outputType.Name] = outputType
engineSchema.Types[inputType.Name] = inputType
outputTypeName := &schema.TypeName{Ident: schema.Ident{Text: outputType.Name}} outputTypeName := &schema.TypeName{Ident: schema.Ident{Text: outputType.Name}}
inputTypeName := &schema.TypeName{Ident: schema.Ident{Text: inputType.Name}} inputTypeName := &schema.TypeName{Ident: schema.Ident{Text: inputType.Name}}
pluralOutputTypeName := &schema.NonNull{OfType: &schema.List{OfType: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: outputType.Name}}}}} pluralOutputTypeName := &schema.NonNull{OfType: &schema.List{OfType: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: outputType.Name}}}}}
pluralInputTypeName := &schema.NonNull{OfType: &schema.List{OfType: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: inputType.Name}}}}} pluralInputTypeName := &schema.NonNull{OfType: &schema.List{OfType: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: inputType.Name}}}}}
args := schema.InputValueList{} args := schema.InputValueList{
&schema.InputValue{
Desc: &schema.Description{Text: "To sort or ordering results just use the order_by argument. This can be combined with where, search, etc to build complex queries to fit you needs."},
Name: schema.Ident{Text: "order_by"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: orderByType.Name}}},
},
&schema.InputValue{
Desc: &schema.Description{Text: ""},
Name: schema.Ident{Text: "where"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: expressionType.Name}}},
},
&schema.InputValue{
Desc: &schema.Description{Text: ""},
Name: schema.Ident{Text: "limit"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "Int"}}},
},
&schema.InputValue{
Desc: &schema.Description{Text: ""},
Name: schema.Ident{Text: "offset"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "Int"}}},
},
&schema.InputValue{
Desc: &schema.Description{Text: ""},
Name: schema.Ident{Text: "first"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "Int"}}},
},
&schema.InputValue{
Desc: &schema.Description{Text: ""},
Name: schema.Ident{Text: "last"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "Int"}}},
},
&schema.InputValue{
Desc: &schema.Description{Text: ""},
Name: schema.Ident{Text: "before"},
Type: &schema.TypeName{Ident: schema.Ident{Text: "String"}},
},
&schema.InputValue{
Desc: &schema.Description{Text: ""},
Name: schema.Ident{Text: "after"},
Type: &schema.TypeName{Ident: schema.Ident{Text: "String"}},
},
}
if ti.PrimaryCol != nil { if ti.PrimaryCol != nil {
t := gqltype(*ti.PrimaryCol) t := gqltype(*ti.PrimaryCol)
if _, ok := t.(*schema.NonNull); !ok { if _, ok := t.(*schema.NonNull); !ok {
t = &schema.NonNull{OfType: t} t = &schema.NonNull{OfType: t}
} }
arg := &schema.InputValue{ args = append(args, &schema.InputValue{
Desc: &schema.Description{Text: "Finds the record by the primary key"},
Name: schema.Ident{Text: "id"}, Name: schema.Ident{Text: "id"},
Type: t, Type: t,
} })
args = append(args, arg) }
if ti.TSVCol != nil {
args = append(args, &schema.InputValue{
Desc: &schema.Description{Text: "Performs full text search using a TSV index"},
Name: schema.Ident{Text: "search"},
Type: &schema.TypeName{Ident: schema.Ident{Text: "String!"}},
})
} }
query.Fields = append(query.Fields, &schema.Field{ query.Fields = append(query.Fields, &schema.Field{
Name: sanitizeForGraphQLSchema(ti.Singular), Desc: &schema.Description{Text: ""},
Name: singularName,
Type: outputTypeName, Type: outputTypeName,
Args: args, Args: args,
}) })
query.Fields = append(query.Fields, &schema.Field{ query.Fields = append(query.Fields, &schema.Field{
Name: sanitizeForGraphQLSchema(pti.Plural), Desc: &schema.Description{Text: ""},
Name: pluralName,
Type: pluralOutputTypeName, Type: pluralOutputTypeName,
Args: args, Args: args,
}) })
mutationArgs := schema.InputValueList{
&schema.InputValue{
Desc: &schema.Description{Text: ""},
Name: schema.Ident{Text: "insert"},
Type: inputTypeName,
},
&schema.InputValue{
Desc: &schema.Description{Text: ""},
Name: schema.Ident{Text: "inserts"},
Type: pluralInputTypeName,
},
&schema.InputValue{
Desc: &schema.Description{Text: ""},
Name: schema.Ident{Text: "update"},
Type: inputTypeName,
},
&schema.InputValue{
Desc: &schema.Description{Text: ""},
Name: schema.Ident{Text: "updates"},
Type: pluralInputTypeName,
},
&schema.InputValue{
Desc: &schema.Description{Text: ""},
Name: schema.Ident{Text: "upsert"},
Type: inputTypeName,
},
&schema.InputValue{
Desc: &schema.Description{Text: ""},
Name: schema.Ident{Text: "upserts"},
Type: pluralInputTypeName,
},
}
mutation.Fields = append(mutation.Fields, &schema.Field{ mutation.Fields = append(mutation.Fields, &schema.Field{
Name: sanitizeForGraphQLSchema(ti.Singular), Name: singularName,
Args: append(args, schema.InputValueList{ Args: append(args, mutationArgs...),
&schema.InputValue{ Type: outputType,
Name: schema.Ident{Text: "insert"}, })
Type: inputTypeName, mutation.Fields = append(mutation.Fields, &schema.Field{
}, Name: pluralName,
&schema.InputValue{ Args: append(args, mutationArgs...),
Name: schema.Ident{Text: "inserts"},
Type: pluralInputTypeName,
},
&schema.InputValue{
Name: schema.Ident{Text: "update"},
Type: inputTypeName,
},
&schema.InputValue{
Name: schema.Ident{Text: "updates"},
Type: pluralInputTypeName,
},
}...),
Type: outputType, Type: outputType,
}) })
} }
err := engineSchema.ResolveTypes()
if err != nil { for typeName, _ := range scalarExpressionTypesNeeded {
return err expressionType := &schema.InputObject{
Name: typeName + "Expression",
Fields: schema.InputValueList{
&schema.InputValue{
Name: schema.Ident{Text: "eq"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "equals"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "neq"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "not_equals"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "gt"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "greater_than"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "lt"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "lesser_than"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "gte"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "greater_or_equals"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "lte"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "lesser_or_equals"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "in"},
Type: &schema.NonNull{OfType: &schema.List{OfType: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "nin"},
Type: &schema.NonNull{OfType: &schema.List{OfType: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "not_in"},
Type: &schema.NonNull{OfType: &schema.List{OfType: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "like"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "String"}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "nlike"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "String"}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "not_like"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "String"}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "ilike"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "String"}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "nilike"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "String"}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "not_ilike"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "String"}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "similar"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "String"}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "nsimilar"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "String"}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "not_similar"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "String"}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "has_key"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "has_key_any"},
Type: &schema.NonNull{OfType: &schema.List{OfType: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "has_key_all"},
Type: &schema.NonNull{OfType: &schema.List{OfType: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "contains"},
Type: &schema.NonNull{OfType: &schema.List{OfType: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "contained_in"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "String"}}},
},
&schema.InputValue{
Name: schema.Ident{Text: "is_null"},
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "Boolean"}}},
},
},
}
engineSchema.Types[expressionType.Name] = expressionType
} }
sg.Engine.Resolver = resolvers.Func(func(request *resolvers.ResolveRequest, next resolvers.Resolution) resolvers.Resolution { err := engineSchema.ResolveTypes()
if err != nil {
return nil, err
}
engine.Resolver = resolvers.Func(func(request *resolvers.ResolveRequest, next resolvers.Resolution) resolvers.Resolution {
resolver := resolvers.MetadataResolver.Resolve(request, next) resolver := resolvers.MetadataResolver.Resolve(request, next)
if resolver != nil { if resolver != nil {
return resolver return resolver
@ -170,5 +477,5 @@ func (sg *SuperGraph) initGraphQLEgine() error {
return nil return nil
}) })
return nil return engine, nil
} }

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"io/ioutil"
"testing" "testing"
"github.com/dosco/super-graph/core" "github.com/dosco/super-graph/core"
@ -152,67 +153,23 @@ func TestSuperGraph(t *testing.T, db *sql.DB, before func(t *testing.T)) {
t.Run("schema introspection", func(t *testing.T) { t.Run("schema introspection", func(t *testing.T) {
before(t) before(t)
// fmt.Println(sg.Engine.Schema.String()) schema, err := sg.GraphQLSchema()
assert.Equal(t, `type Mutation { require.NoError(t, err)
line_item(id:Int!, insert:line_itemInput, inserts:[line_itemInput!]!, update:line_itemInput, updates:[line_itemInput!]!):line_itemOutput // Uncomment the following line if you need to regenerate the expected schema.
product(id:Int!, insert:productInput, inserts:[productInput!]!, update:productInput, updates:[productInput!]!):productOutput // ioutil.WriteFile("../introspection.graphql", []byte(schema), 0644)
user(id:Int!, insert:userInput, inserts:[userInput!]!, update:userInput, updates:[userInput!]!):userOutput expected, err := ioutil.ReadFile("../introspection.graphql")
} require.NoError(t, err)
type Query { assert.Equal(t, string(expected), schema)
line_item(id:Int!):line_itemOutput
line_items(id:Int!):[line_itemOutput!]!
product(id:Int!):productOutput
products(id:Int!):[productOutput!]!
user(id:Int!):userOutput
users(id:Int!):[userOutput!]!
}
input line_itemInput {
id:Int!
price:Float
product:Int
quantity:Int
}
type line_itemOutput {
id:Int!
price:Float
product:Int
quantity:Int
}
input productInput {
id:Int!
name:String
weight:Float
}
type productOutput {
id:Int!
name:String
weight:Float
}
input userInput {
full_name:String
id:Int!
}
type userOutput {
full_name:String
id:Int!
}
schema {
mutation: Mutation
query: Query
}
`, sg.Engine.Schema.String())
}) })
res, err := sg.GraphQL(ctx,introspectionQuery, json.RawMessage(``)) res, err := sg.GraphQL(ctx, introspectionQuery, json.RawMessage(``))
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, string(res.Data), assert.Contains(t, string(res.Data),
`{"queryType":{"name":"Query"},"mutationType":{"name":"Mutation"},"subscriptionType":null,"types":`) `{"queryType":{"name":"Query"},"mutationType":{"name":"Mutation"},"subscriptionType":null,"types":`)
assert.Contains(t, string(res.Data),
`{"kind":"OBJECT","name":"Mutation","description":null,"fields":[{"name":"line_item","description":null`)
} }
const introspectionQuery = ` const introspectionQuery = `
query { query IntrospectionQuery {
__schema { __schema {
queryType { name } queryType { name }
mutationType { name } mutationType { name }

View File

@ -0,0 +1,319 @@
input FloatExpression {
contained_in:String!
contains:[Float!]!
eq:Float!
equals:Float!
greater_or_equals:Float!
greater_than:Float!
gt:Float!
gte:Float!
has_key:Float!
has_key_all:[Float!]!
has_key_any:[Float!]!
ilike:String!
in:[Float!]!
is_null:Boolean!
lesser_or_equals:Float!
lesser_than:Float!
like:String!
lt:Float!
lte:Float!
neq:Float!
nilike:String!
nin:[Float!]!
nlike:String!
not_equals:Float!
not_ilike:String!
not_in:[Float!]!
not_like:String!
not_similar:String!
nsimilar:String!
similar:String!
}
input IntExpression {
contained_in:String!
contains:[Int!]!
eq:Int!
equals:Int!
greater_or_equals:Int!
greater_than:Int!
gt:Int!
gte:Int!
has_key:Int!
has_key_all:[Int!]!
has_key_any:[Int!]!
ilike:String!
in:[Int!]!
is_null:Boolean!
lesser_or_equals:Int!
lesser_than:Int!
like:String!
lt:Int!
lte:Int!
neq:Int!
nilike:String!
nin:[Int!]!
nlike:String!
not_equals:Int!
not_ilike:String!
not_in:[Int!]!
not_like:String!
not_similar:String!
nsimilar:String!
similar:String!
}
type Mutation {
line_item(
"To sort or ordering results just use the order_by argument. This can be combined with where, search, etc to build complex queries to fit you needs."
order_by:line_itemOrderBy!, where:line_itemExpression!, limit:Int!, offset:Int!, first:Int!, last:Int!, before:String, after:String,
"Finds the record by the primary key"
id:Int!, insert:line_itemInput, inserts:[line_itemInput!]!, update:line_itemInput, updates:[line_itemInput!]!, upsert:line_itemInput, upserts:[line_itemInput!]!
):line_itemOutput
line_items(
"To sort or ordering results just use the order_by argument. This can be combined with where, search, etc to build complex queries to fit you needs."
order_by:line_itemOrderBy!, where:line_itemExpression!, limit:Int!, offset:Int!, first:Int!, last:Int!, before:String, after:String,
"Finds the record by the primary key"
id:Int!, insert:line_itemInput, inserts:[line_itemInput!]!, update:line_itemInput, updates:[line_itemInput!]!, upsert:line_itemInput, upserts:[line_itemInput!]!
):line_itemOutput
product(
"To sort or ordering results just use the order_by argument. This can be combined with where, search, etc to build complex queries to fit you needs."
order_by:productOrderBy!, where:productExpression!, limit:Int!, offset:Int!, first:Int!, last:Int!, before:String, after:String,
"Finds the record by the primary key"
id:Int!, insert:productInput, inserts:[productInput!]!, update:productInput, updates:[productInput!]!, upsert:productInput, upserts:[productInput!]!
):productOutput
products(
"To sort or ordering results just use the order_by argument. This can be combined with where, search, etc to build complex queries to fit you needs."
order_by:productOrderBy!, where:productExpression!, limit:Int!, offset:Int!, first:Int!, last:Int!, before:String, after:String,
"Finds the record by the primary key"
id:Int!, insert:productInput, inserts:[productInput!]!, update:productInput, updates:[productInput!]!, upsert:productInput, upserts:[productInput!]!
):productOutput
user(
"To sort or ordering results just use the order_by argument. This can be combined with where, search, etc to build complex queries to fit you needs."
order_by:userOrderBy!, where:userExpression!, limit:Int!, offset:Int!, first:Int!, last:Int!, before:String, after:String,
"Finds the record by the primary key"
id:Int!, insert:userInput, inserts:[userInput!]!, update:userInput, updates:[userInput!]!, upsert:userInput, upserts:[userInput!]!
):userOutput
users(
"To sort or ordering results just use the order_by argument. This can be combined with where, search, etc to build complex queries to fit you needs."
order_by:userOrderBy!, where:userExpression!, limit:Int!, offset:Int!, first:Int!, last:Int!, before:String, after:String,
"Finds the record by the primary key"
id:Int!, insert:userInput, inserts:[userInput!]!, update:userInput, updates:[userInput!]!, upsert:userInput, upserts:[userInput!]!
):userOutput
}
enum OrderDirection {
asc
desc
}
type Query {
line_item(
"To sort or ordering results just use the order_by argument. This can be combined with where, search, etc to build complex queries to fit you needs."
order_by:line_itemOrderBy!, where:line_itemExpression!, limit:Int!, offset:Int!, first:Int!, last:Int!, before:String, after:String,
"Finds the record by the primary key"
id:Int!
):line_itemOutput
line_items(
"To sort or ordering results just use the order_by argument. This can be combined with where, search, etc to build complex queries to fit you needs."
order_by:line_itemOrderBy!, where:line_itemExpression!, limit:Int!, offset:Int!, first:Int!, last:Int!, before:String, after:String,
"Finds the record by the primary key"
id:Int!
):[line_itemOutput!]!
product(
"To sort or ordering results just use the order_by argument. This can be combined with where, search, etc to build complex queries to fit you needs."
order_by:productOrderBy!, where:productExpression!, limit:Int!, offset:Int!, first:Int!, last:Int!, before:String, after:String,
"Finds the record by the primary key"
id:Int!
):productOutput
products(
"To sort or ordering results just use the order_by argument. This can be combined with where, search, etc to build complex queries to fit you needs."
order_by:productOrderBy!, where:productExpression!, limit:Int!, offset:Int!, first:Int!, last:Int!, before:String, after:String,
"Finds the record by the primary key"
id:Int!
):[productOutput!]!
user(
"To sort or ordering results just use the order_by argument. This can be combined with where, search, etc to build complex queries to fit you needs."
order_by:userOrderBy!, where:userExpression!, limit:Int!, offset:Int!, first:Int!, last:Int!, before:String, after:String,
"Finds the record by the primary key"
id:Int!
):userOutput
users(
"To sort or ordering results just use the order_by argument. This can be combined with where, search, etc to build complex queries to fit you needs."
order_by:userOrderBy!, where:userExpression!, limit:Int!, offset:Int!, first:Int!, last:Int!, before:String, after:String,
"Finds the record by the primary key"
id:Int!
):[userOutput!]!
}
input StringExpression {
contained_in:String!
contains:[String!]!
eq:String!
equals:String!
greater_or_equals:String!
greater_than:String!
gt:String!
gte:String!
has_key:String!
has_key_all:[String!]!
has_key_any:[String!]!
ilike:String!
in:[String!]!
is_null:Boolean!
lesser_or_equals:String!
lesser_than:String!
like:String!
lt:String!
lte:String!
neq:String!
nilike:String!
nin:[String!]!
nlike:String!
not_equals:String!
not_ilike:String!
not_in:[String!]!
not_like:String!
not_similar:String!
nsimilar:String!
similar:String!
}
input line_itemExpression {
and:line_itemExpression!
id:IntExpression!
not:line_itemExpression!
or:line_itemExpression!
price:FloatExpression!
product:IntExpression!
quantity:IntExpression!
}
input line_itemInput {
id:Int!
price:Float
product:Int
quantity:Int
}
input line_itemOrderBy {
id:OrderDirection!
price:OrderDirection!
product:OrderDirection!
quantity:OrderDirection!
}
type line_itemOutput {
avg_id:Int!
avg_price:Float
avg_product:Int
avg_quantity:Int
count_id:Int!
count_price:Float
count_product:Int
count_quantity:Int
id:Int!
max_id:Int!
max_price:Float
max_product:Int
max_quantity:Int
min_id:Int!
min_price:Float
min_product:Int
min_quantity:Int
price:Float
product:Int
quantity:Int
stddev_id:Int!
stddev_pop_id:Int!
stddev_pop_price:Float
stddev_pop_product:Int
stddev_pop_quantity:Int
stddev_price:Float
stddev_product:Int
stddev_quantity:Int
stddev_samp_id:Int!
stddev_samp_price:Float
stddev_samp_product:Int
stddev_samp_quantity:Int
var_pop_id:Int!
var_pop_price:Float
var_pop_product:Int
var_pop_quantity:Int
var_samp_id:Int!
var_samp_price:Float
var_samp_product:Int
var_samp_quantity:Int
variance_id:Int!
variance_price:Float
variance_product:Int
variance_quantity:Int
}
input productExpression {
and:productExpression!
id:IntExpression!
name:StringExpression!
not:productExpression!
or:productExpression!
weight:FloatExpression!
}
input productInput {
id:Int!
name:String
weight:Float
}
input productOrderBy {
id:OrderDirection!
name:OrderDirection!
weight:OrderDirection!
}
type productOutput {
avg_id:Int!
avg_weight:Float
count_id:Int!
count_weight:Float
id:Int!
max_id:Int!
max_weight:Float
min_id:Int!
min_weight:Float
name:String
stddev_id:Int!
stddev_pop_id:Int!
stddev_pop_weight:Float
stddev_samp_id:Int!
stddev_samp_weight:Float
stddev_weight:Float
var_pop_id:Int!
var_pop_weight:Float
var_samp_id:Int!
var_samp_weight:Float
variance_id:Int!
variance_weight:Float
weight:Float
}
input userExpression {
and:userExpression!
full_name:StringExpression!
id:IntExpression!
not:userExpression!
or:userExpression!
}
input userInput {
full_name:String
id:Int!
}
input userOrderBy {
full_name:OrderDirection!
id:OrderDirection!
}
type userOutput {
avg_id:Int!
count_id:Int!
full_name:String
id:Int!
max_id:Int!
min_id:Int!
stddev_id:Int!
stddev_pop_id:Int!
stddev_samp_id:Int!
var_pop_id:Int!
var_samp_id:Int!
variance_id:Int!
}
schema {
mutation: Mutation
query: Query
}