From 2a32c179bacfec26a53c726f2f8e665f9229e5ce Mon Sep 17 00:00:00 2001 From: Hiram Chirino Date: Tue, 21 Apr 2020 10:03:05 -0400 Subject: [PATCH] feat : improve the generated introspection schema and avoid the chirino/graphql api leaking through the core api. (#53) --- core/api.go | 28 +- core/graph-schema.go | 407 +++++++++++++++--- .../integration_tests/integration_tests.go | 63 +-- .../integration_tests/introspection.graphql | 319 ++++++++++++++ 4 files changed, 704 insertions(+), 113 deletions(-) create mode 100644 core/internal/integration_tests/introspection.graphql diff --git a/core/api.go b/core/api.go index c660c08..17125ab 100644 --- a/core/api.go +++ b/core/api.go @@ -93,7 +93,6 @@ type SuperGraph struct { anonExists bool qc *qcode.Compiler pc *psql.Compiler - Engine *graphql.Engine } // 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 } - if err := sg.initGraphQLEgine(); err != nil { - return nil, err - } - if len(conf.SecretKey) != 0 { sk := sha256.Sum256([]byte(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 // disabled when allow list is enforced - if !sg.conf.UseAllowList && - res.op == qcode.QTQuery && - res.name == "IntrospectionQuery" { + if !sg.conf.UseAllowList && res.name == "IntrospectionQuery" { + engine, err := sg.createGraphQLEgine() + 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 - + if r.Error() != nil { + res.Error = r.Error().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 } + +func (sg *SuperGraph) GraphQLSchema() (string, error) { + engine, err := sg.createGraphQLEgine() + if err != nil { + return "", err + } + return engine.Schema.String(), nil +} diff --git a/core/graph-schema.go b/core/graph-schema.go index 0f87e49..f6261de 100644 --- a/core/graph-schema.go +++ b/core/graph-schema.go @@ -1,6 +1,8 @@ package core import ( + "errors" + "regexp" "strings" "github.com/chirino/graphql" @@ -24,15 +26,17 @@ var typeMap map[string]string = map[string]string{ "boolean": "Boolean", } -func (sg *SuperGraph) initGraphQLEgine() error { - sg.Engine = graphql.New() - engineSchema := sg.Engine.Schema +func (sg *SuperGraph) createGraphQLEgine() (*graphql.Engine, error) { + engine := graphql.New() + engineSchema := engine.Schema dbSchema := sg.schema - sanitizeForGraphQLSchema := func(value string) string { - // TODO: - return value - } + engineSchema.Parse(` +enum OrderDirection { + asc + desc +} +`) gqltype := func(col psql.DBColumn) schema.Type { typeName := typeMap[strings.ToLower(col.Type)] @@ -59,106 +63,409 @@ func (sg *SuperGraph) initGraphQLEgine() error { engineSchema.EntryPoints[schema.Query] = query engineSchema.EntryPoints[schema.Mutation] = mutation + validGraphQLIdentifierRegex := regexp.MustCompile(`^[A-Za-z_][A-Za-z_0-9]*$`) + + scalarExpressionTypesNeeded := map[string]bool{} tableNames := dbSchema.GetTableNames() for _, table := range tableNames { ti, err := dbSchema.GetTable(table) if err != nil { - return err + return nil, err } if !ti.IsSingular { continue } - pti, err := dbSchema.GetTable(ti.Plural) - if err != nil { - return err + singularName := ti.Singular + if !validGraphQLIdentifierRegex.MatchString(singularName) { + 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{ - Name: sanitizeForGraphQLSchema(ti.Singular) + "Output", + Name: singularName + "Output", Fields: schema.FieldList{}, } + engineSchema.Types[outputType.Name] = outputType inputType := &schema.InputObject{ - Name: sanitizeForGraphQLSchema(ti.Singular) + "Input", + Name: singularName + "Input", 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 { + 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{ - Name: sanitizeForGraphQLSchema(col.Name), - Type: gqltype(col), + Name: colName, + 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{ - Name: schema.Ident{Text: sanitizeForGraphQLSchema(col.Name)}, - Type: gqltype(col), + Name: schema.Ident{Text: colName}, + 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}} 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}}}}} 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 { t := gqltype(*ti.PrimaryCol) if _, ok := t.(*schema.NonNull); !ok { 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"}, 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{ - Name: sanitizeForGraphQLSchema(ti.Singular), + Desc: &schema.Description{Text: ""}, + Name: singularName, Type: outputTypeName, Args: args, }) query.Fields = append(query.Fields, &schema.Field{ - Name: sanitizeForGraphQLSchema(pti.Plural), + Desc: &schema.Description{Text: ""}, + Name: pluralName, Type: pluralOutputTypeName, 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{ - Name: sanitizeForGraphQLSchema(ti.Singular), - Args: append(args, schema.InputValueList{ - &schema.InputValue{ - Name: schema.Ident{Text: "insert"}, - Type: inputTypeName, - }, - &schema.InputValue{ - 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, - }, - }...), + Name: singularName, + Args: append(args, mutationArgs...), + Type: outputType, + }) + mutation.Fields = append(mutation.Fields, &schema.Field{ + Name: pluralName, + Args: append(args, mutationArgs...), Type: outputType, }) } - err := engineSchema.ResolveTypes() - if err != nil { - return err + + for typeName, _ := range scalarExpressionTypesNeeded { + 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) if resolver != nil { return resolver @@ -170,5 +477,5 @@ func (sg *SuperGraph) initGraphQLEgine() error { return nil }) - return nil + return engine, nil } diff --git a/core/internal/integration_tests/integration_tests.go b/core/internal/integration_tests/integration_tests.go index b7595e2..f175e19 100644 --- a/core/internal/integration_tests/integration_tests.go +++ b/core/internal/integration_tests/integration_tests.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "encoding/json" + "io/ioutil" "testing" "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) { before(t) - // fmt.Println(sg.Engine.Schema.String()) - assert.Equal(t, `type Mutation { - line_item(id:Int!, insert:line_itemInput, inserts:[line_itemInput!]!, update:line_itemInput, updates:[line_itemInput!]!):line_itemOutput - product(id:Int!, insert:productInput, inserts:[productInput!]!, update:productInput, updates:[productInput!]!):productOutput - user(id:Int!, insert:userInput, inserts:[userInput!]!, update:userInput, updates:[userInput!]!):userOutput -} -type Query { - 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()) + schema, err := sg.GraphQLSchema() + require.NoError(t, err) + // Uncomment the following line if you need to regenerate the expected schema. + // ioutil.WriteFile("../introspection.graphql", []byte(schema), 0644) + expected, err := ioutil.ReadFile("../introspection.graphql") + require.NoError(t, err) + assert.Equal(t, string(expected), schema) }) - res, err := sg.GraphQL(ctx,introspectionQuery, json.RawMessage(``)) + res, err := sg.GraphQL(ctx, introspectionQuery, json.RawMessage(``)) assert.NoError(t, err) assert.Contains(t, string(res.Data), `{"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 = ` - query { + query IntrospectionQuery { __schema { queryType { name } mutationType { name } diff --git a/core/internal/integration_tests/introspection.graphql b/core/internal/integration_tests/introspection.graphql new file mode 100644 index 0000000..313deeb --- /dev/null +++ b/core/internal/integration_tests/introspection.graphql @@ -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 +}