feat : improve the generated introspection schema and avoid the chirino/graphql api leaking through the core api. (#53)
This commit is contained in:
parent
0a02bde219
commit
2a32c179ba
28
core/api.go
28
core/api.go
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue