From 347b0701aec9c57903598570d9554d10c91f1591 Mon Sep 17 00:00:00 2001 From: Radhakrishna Sanka Date: Thu, 7 Dec 2023 23:59:44 +0530 Subject: [PATCH] Refactor: clarity (#467) * Updated the basic documentation example * Improving code clarity I'm starting to make some changes to the code documentation here. I'm including a bit of refactoring to make sure that things are more readable. - The code extensively uses the short variable names to the detriment of the readability. This compounds the problem of not having enough code documentation. - While golang recommends short variable names, the intention is to avoid long nested function calls and to avoid diverting the attention from the main logic of the program. However, even the main logic variables have really short names making it almost impossible to read. * Incremental renaming of variables in intro.go --- README.md | 87 +++++---- core/api.go | 4 + core/core.go | 14 ++ core/init.go | 1 + core/intro.go | 479 +++++++++++++++++++++++++------------------------- 5 files changed, 317 insertions(+), 268 deletions(-) diff --git a/README.md b/README.md index 8312c947..8c1812e3 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,19 @@ query getProducts { } ``` +**Note**: The corresponding SQL for creating the table (POSTGRES) + + ```sql + CREATE TABLE products ( + id SERIAL NOT NULL, + name TEXT NOT NULL, + price INTEGER NOT NULL, + owner_id INTEGER NOT NULL, + category_id INTEGER NOT NULL, + PRIMARY KEY(id) + ); + ``` + ## Secure out of the box In production all queries are always read from locally saved copies not from what the client sends hence clients cannot modify the query. This makes @@ -143,44 +156,52 @@ go get github.com/dosco/graphjin/core/v3 package main import ( - "context" - "database/sql" - "fmt" - "log" - - "github.com/dosco/graphjin/core/v3" - _ "github.com/jackc/pgx/v4/stdlib" + "context" + "database/sql" + "log" + "net/http" + + "github.com/dosco/graphjin/core" + "github.com/go-chi/chi/v5" + _ "github.com/jackc/pgx/v5/stdlib" ) func main() { - db, err := sql.Open("pgx", "postgres://postgres:@localhost:5432/example_db") - if err != nil { - log.Fatal(err) - } - - gj, err := core.NewGraphJin(nil, db) - if err != nil { - log.Fatal(err) - } - - query := ` - query { - posts { - id - title - } - }` - - ctx := context.Background() - ctx = context.WithValue(ctx, core.UserIDKey, 1) - - res, err := gj.GraphQL(ctx, query, nil, nil) - if err != nil { - log.Fatal(err) - } + db, err := sql.Open("pgx", "postgres://postgres:@localhost:5432/exampledb?sslmode=disable") + if err != nil { + log.Fatal(err) + } + + gj, err := core.NewGraphJin(nil, db) + if err != nil { + log.Fatal(err) + } + + query := ` + query getPosts { + posts { + id + title + } + posts_cursor + }` + + router := chi.NewRouter() + router.Get("/", func(w http.ResponseWriter, request *http.Request) { + context := context.WithValue(request.Context(), core.UserIDKey, 1) + res, err := gj.GraphQL(context, query, nil, nil) + if err != nil { + log.Fatal(err) + return + } + w.Write(res.Data) + }) + + log.Println("Go server started on port 3000") + http.ListenAndServe(":3000", router) - fmt.Println(string(res.Data)) } + ``` ### Use GraphJin Service diff --git a/core/api.go b/core/api.go index 8dccd007..dbb5dbdd 100644 --- a/core/api.go +++ b/core/api.go @@ -271,6 +271,7 @@ func (rc *ReqConfig) SetNamespace(ns string) { rc.ns = &ns } +// GetNamespace is used to get the namespace requests within a single instance of GraphJin func (rc *ReqConfig) GetNamespace() (string, bool) { if rc.ns != nil { return *rc.ns, true @@ -445,6 +446,7 @@ func (gj *graphjin) newGraphqlReq(rc *ReqConfig, return } +// Set is used to set the namespace, operation type, name and query for the GraphQL request func (r *graphqlReq) Set(item allow.Item) { r.ns = item.Namespace r.op = qcode.GetQTypeByName(item.Operation) @@ -453,11 +455,13 @@ func (r *graphqlReq) Set(item allow.Item) { r.aschema = item.ActionJSON } +// GraphQL function is our main function it takes a GraphQL query compiles it func (gj *graphjin) queryWithResult(c context.Context, r graphqlReq) (res *Result, err error) { resp, err := gj.query(c, r) return &resp.res, err } +// GraphQL function is our main function it takes a GraphQL query compiles it func (gj *graphjin) query(c context.Context, r graphqlReq) ( resp graphqlResp, err error, ) { diff --git a/core/core.go b/core/core.go index f881930a..3770ba26 100644 --- a/core/core.go +++ b/core/core.go @@ -68,6 +68,7 @@ func (gj *graphjin) getIntroResult() (data json.RawMessage, err error) { return } +// Initializes the database discovery process on graphjin func (gj *graphjin) initDiscover() (err error) { switch gj.conf.DBType { case "": @@ -84,6 +85,7 @@ func (gj *graphjin) initDiscover() (err error) { return } +// Private method that does the actual database discovery for initDiscover func (gj *graphjin) _initDiscover() (err error) { if gj.prod && gj.conf.EnableSchema { b, err := gj.fs.Get("db.graphql") @@ -129,6 +131,7 @@ func (gj *graphjin) _initDiscover() (err error) { return } +// Initializes the database schema on graphjin func (gj *graphjin) initSchema() error { if err := gj._initSchema(); err != nil { return fmt.Errorf("%s: %w", gj.dbtype, err) @@ -186,6 +189,7 @@ func (gj *graphjin) _initSchema() (err error) { return } +// Initializes the qcode compilers func (gj *graphjin) initCompilers() (err error) { qcc := qcode.Config{ TConfig: gj.tmap, @@ -273,6 +277,7 @@ func (gj *graphjin) executeRoleQuery(c context.Context, return } +// Returns the operation type for the query result func (r *Result) Operation() OpType { switch r.op { case qcode.QTQuery: @@ -286,26 +291,32 @@ func (r *Result) Operation() OpType { } } +// Returns the namespace for the query result func (r *Result) Namespace() string { return r.ns } +// Returns the operation name for the query result func (r *Result) OperationName() string { return r.op.String() } +// Returns the query name for the query result func (r *Result) QueryName() string { return r.name } +// Returns the role used to execute the query func (r *Result) Role() string { return r.role } +// Returns the SQL query string for the query result func (r *Result) SQL() string { return r.sql } +// Returns the cache control header value for the query result func (r *Result) CacheControl() string { return r.cacheControl } @@ -370,6 +381,7 @@ func (s *gstate) debugLogStmt() { } } +// Saved the query qcode to the allow list func (gj *graphjin) saveToAllowList(qc *qcode.QCode, ns string) (err error) { if gj.conf.DisableAllowList { return nil @@ -399,10 +411,12 @@ func (gj *graphjin) saveToAllowList(qc *qcode.QCode, ns string) (err error) { return gj.allowList.Set(item) } +// Starts tracing with the given name func (gj *graphjin) spanStart(c context.Context, name string) (context.Context, Spaner) { return gj.trace.Start(c, name) } +// Retry operation with jittered backoff at 50, 100, 200 ms func retryOperation(c context.Context, fn func() error) (err error) { jitter := []int{50, 100, 200} for i := 0; i < 3; i++ { diff --git a/core/init.go b/core/init.go index 1f340154..aa20d408 100644 --- a/core/init.go +++ b/core/init.go @@ -10,6 +10,7 @@ import ( "github.com/dosco/graphjin/core/v3/internal/sdata" ) +// Initializes the graphjin instance with the config func (gj *graphjin) initConfig() error { c := gj.conf diff --git a/core/intro.go b/core/intro.go index dc20034d..f4a975d9 100644 --- a/core/intro.go +++ b/core/intro.go @@ -41,73 +41,73 @@ var ( TYPE_JSON = "JSON" ) -type typeRef struct { +type TypeRef struct { Kind string `json:"kind"` Name *string `json:"name"` - OfType *typeRef `json:"ofType"` + OfType *TypeRef `json:"ofType"` } -type inputValue struct { +type InputValue struct { Name string `json:"name"` Description string `json:"description"` - Type *typeRef `json:"type"` + Type *TypeRef `json:"type"` DefaultValue *string `json:"defaultValue"` } -type fieldObj struct { +type FieldObject struct { Name string `json:"name"` Description string `json:"description"` - Args []inputValue `json:"args"` - Type *typeRef `json:"type"` + Args []InputValue `json:"args"` + Type *TypeRef `json:"type"` IsDeprecated bool `json:"isDeprecated"` DeprecationReason *string `json:"deprecationReason"` } -type enumValue struct { +type EnumValue struct { Name string `json:"name"` Description string `json:"description"` IsDeprecated bool `json:"isDeprecated"` DeprecationReason *string `json:"deprecationReason"` } -type fullType struct { - Kind string `json:"kind"` - Name string `json:"name"` - Description string `json:"description"` - Fields []fieldObj `json:"fields"` - InputFields []inputValue `json:"inputFields"` - EnumValues []enumValue `json:"enumValues"` - Interfaces []typeRef `json:"interfaces"` - PossibleTypes []typeRef `json:"possibleTypes"` +type FullType struct { + Kind string `json:"kind"` + Name string `json:"name"` + Description string `json:"description"` + Fields []FieldObject `json:"fields"` + InputFields []InputValue `json:"inputFields"` + EnumValues []EnumValue `json:"enumValues"` + Interfaces []TypeRef `json:"interfaces"` + PossibleTypes []TypeRef `json:"possibleTypes"` } -type shortFullType struct { +type ShortFullType struct { Name string `json:"name"` } -type directiveType struct { +type DirectiveType struct { Name string `json:"name"` Description string `json:"description"` Locations []string `json:"locations"` - Args []inputValue `json:"args"` + Args []InputValue `json:"args"` IsRepeatable bool `json:"isRepeatable"` } -type introSchema struct { - Types []fullType `json:"types"` - QueryType *shortFullType `json:"queryType"` - MutationType *shortFullType `json:"mutationType"` - SubscriptionType *shortFullType `json:"subscriptionType"` - Directives []directiveType `json:"directives"` +type IntrospectionSchema struct { + Types []FullType `json:"types"` + QueryType *ShortFullType `json:"queryType"` + MutationType *ShortFullType `json:"mutationType"` + SubscriptionType *ShortFullType `json:"subscriptionType"` + Directives []DirectiveType `json:"directives"` } -type introResult struct { - Schema introSchema `json:"__schema"` +type IntroResult struct { + Schema IntrospectionSchema `json:"__schema"` } // const singularSuffix = "ByID" -var stdTypes = []fullType{ +var stdTypes = []FullType{ { Kind: KIND_SCALAR, Name: TYPE_BOOLEAN, @@ -132,22 +132,22 @@ var stdTypes = []fullType{ }, { Kind: KIND_OBJECT, Name: "Query", - Interfaces: []typeRef{}, - Fields: []fieldObj{}, + Interfaces: []TypeRef{}, + Fields: []FieldObject{}, }, { Kind: KIND_OBJECT, Name: "Subscription", - Interfaces: []typeRef{}, - Fields: []fieldObj{}, + Interfaces: []TypeRef{}, + Fields: []FieldObject{}, }, { Kind: KIND_OBJECT, Name: "Mutation", - Interfaces: []typeRef{}, - Fields: []fieldObj{}, + Interfaces: []TypeRef{}, + Fields: []FieldObject{}, }, { Kind: KIND_ENUM, Name: "FindSearchInput", - EnumValues: []enumValue{{ + EnumValues: []EnumValue{{ Name: "children", Description: "Children of parent row", }, { @@ -158,7 +158,7 @@ var stdTypes = []fullType{ Kind: "ENUM", Name: "OrderDirection", Description: "Result ordering types", - EnumValues: []enumValue{{ + EnumValues: []EnumValue{{ Name: "asc", Description: "Ascending order", }, { @@ -189,122 +189,129 @@ var stdTypes = []fullType{ }, } -type intro struct { +type Introspection struct { schema *sdata.DBSchema camelCase bool - types map[string]fullType - enumValues map[string]enumValue - inputValues map[string]inputValue - res introResult + types map[string]FullType + enumValues map[string]EnumValue + inputValues map[string]InputValue + result IntroResult } func (gj *graphjin) introQuery() (result json.RawMessage, err error) { - in := intro{ + + // Initialize the introscpection object + in := Introspection{ schema: gj.schema, camelCase: gj.conf.EnableCamelcase, - types: make(map[string]fullType), - enumValues: make(map[string]enumValue), - inputValues: make(map[string]inputValue), + types: make(map[string]FullType), + enumValues: make(map[string]EnumValue), + inputValues: make(map[string]InputValue), } - in.res.Schema = introSchema{ - QueryType: &shortFullType{Name: "Query"}, - SubscriptionType: &shortFullType{Name: "Subscription"}, - MutationType: &shortFullType{Name: "Mutation"}, + // Initialize the schema + in.result.Schema = IntrospectionSchema{ + QueryType: &ShortFullType{Name: "Query"}, + SubscriptionType: &ShortFullType{Name: "Subscription"}, + MutationType: &ShortFullType{Name: "Mutation"}, } + // Add the standard types for _, v := range stdTypes { in.addType(v) } // Expression types v := append(expAll, expScalar...) - in.addExpTypes(v, "ID", newTR("", "ID", nil)) - in.addExpTypes(v, "String", newTR("", "String", nil)) - in.addExpTypes(v, "Int", newTR("", "Int", nil)) - in.addExpTypes(v, "Boolean", newTR("", "Boolean", nil)) - in.addExpTypes(v, "Float", newTR("", "Float", nil)) + in.addExpTypes(v, "ID", newTypeRef("", "ID", nil)) + in.addExpTypes(v, "String", newTypeRef("", "String", nil)) + in.addExpTypes(v, "Int", newTypeRef("", "Int", nil)) + in.addExpTypes(v, "Boolean", newTypeRef("", "Boolean", nil)) + in.addExpTypes(v, "Float", newTypeRef("", "Float", nil)) // ListExpression Types v = append(expAll, expList...) - in.addExpTypes(v, "StringList", newTR("", "String", nil)) - in.addExpTypes(v, "IntList", newTR("", "Int", nil)) - in.addExpTypes(v, "BooleanList", newTR("", "Boolean", nil)) - in.addExpTypes(v, "FloatList", newTR("", "Float", nil)) + in.addExpTypes(v, "StringList", newTypeRef("", "String", nil)) + in.addExpTypes(v, "IntList", newTypeRef("", "Int", nil)) + in.addExpTypes(v, "BooleanList", newTypeRef("", "Boolean", nil)) + in.addExpTypes(v, "FloatList", newTypeRef("", "Float", nil)) v = append(expAll, expJSON...) - in.addExpTypes(v, "JSON", newTR("", "String", nil)) + in.addExpTypes(v, "JSON", newTypeRef("", "String", nil)) + // Add the roles in.addRolesEnumType(gj.roles) in.addTablesEnumType() + // Get all the alias and add to the schema for alias, t := range in.schema.GetAliases() { if err = in.addTable(t, alias); err != nil { return } } + // Get all the tables and add to the schema for _, t := range in.schema.GetTables() { if err = in.addTable(t, ""); err != nil { return } } + // Add the directives for _, dt := range dirTypes { in.addDirType(dt) } in.addDirValidateType() + // Add the types to the schema for _, v := range in.types { - in.res.Schema.Types = append(in.res.Schema.Types, v) + in.result.Schema.Types = append(in.result.Schema.Types, v) } - result, err = json.Marshal(in.res) + result, err = json.Marshal(in.result) return } -func (in *intro) addTable(t sdata.DBTable, alias string) (err error) { - if t.Blocked || len(t.Columns) == 0 { +func (in *Introspection) addTable(table sdata.DBTable, alias string) (err error) { + if table.Blocked || len(table.Columns) == 0 { return } - var ftQS fullType + var ftQS FullType // add table type to query and subscription - if ftQS, err = in.addTableType(t, alias); err != nil { + if ftQS, err = in.addTableType(table, alias); err != nil { return } in.addTypeTo("Query", ftQS) in.addTypeTo("Subscription", ftQS) - var ftM fullType + var ftM FullType // add table type to mutation - if ftM, err = in.addInputType(t, ftQS); err != nil { + if ftM, err = in.addInputType(table, ftQS); err != nil { return } in.addTypeTo("Mutation", ftM) - // add tableByID type to query and subscription - ftQS.Name += "ByID" - ftQS.addOrReplaceArg("id", newTR(KIND_NONNULL, "", newTR("", "ID", nil))) + ftQS.addOrReplaceArg("id", newTypeRef(KIND_NONNULL, "", newTypeRef("", "ID", nil))) in.addType(ftQS) in.addTypeTo("Query", ftQS) in.addTypeTo("Subscription", ftQS) return } -func (in *intro) addTypeTo(op string, ft fullType) { +func (in *Introspection) addTypeTo(op string, ft FullType) { qt := in.types[op] - qt.Fields = append(qt.Fields, fieldObj{ + qt.Fields = append(qt.Fields, FieldObject{ Name: ft.Name, Description: ft.Description, Args: ft.InputFields, - Type: newTR("", ft.Name, nil), + Type: newTypeRef("", ft.Name, nil), }) in.types[op] = qt } -func (in *intro) getName(name string) string { +func (in *Introspection) getName(name string) string { if in.camelCase { return util.ToCamel(name) } else { @@ -312,20 +319,20 @@ func (in *intro) getName(name string) string { } } -func (in *intro) addExpTypes(exps []exp, name string, rt *typeRef) { - ft := fullType{ +func (in *Introspection) addExpTypes(exps []exp, name string, rt *TypeRef) { + ft := FullType{ Kind: KIND_INPUT_OBJ, Name: (name + SUFFIX_EXP), - InputFields: []inputValue{}, - Interfaces: []typeRef{}, + InputFields: []InputValue{}, + Interfaces: []TypeRef{}, } for _, ex := range exps { rtVal := rt if ex.etype != "" { - rtVal = newTR("", ex.etype, nil) + rtVal = newTypeRef("", ex.etype, nil) } - ft.InputFields = append(ft.InputFields, inputValue{ + ft.InputFields = append(ft.InputFields, InputValue{ Name: ex.name, Description: ex.desc, Type: rtVal, @@ -334,41 +341,41 @@ func (in *intro) addExpTypes(exps []exp, name string, rt *typeRef) { in.addType(ft) } -func (in *intro) addTableType(t sdata.DBTable, alias string) (ft fullType, err error) { +func (in *Introspection) addTableType(t sdata.DBTable, alias string) (ft FullType, err error) { return in.addTableTypeWithDepth(t, alias, 0) } -func (in *intro) addTableTypeWithDepth( - t sdata.DBTable, alias string, depth int, -) (ft fullType, err error) { - ft = fullType{ +func (in *Introspection) addTableTypeWithDepth( + table sdata.DBTable, alias string, depth int, +) (ft FullType, err error) { + ft = FullType{ Kind: KIND_OBJECT, - InputFields: []inputValue{}, - Interfaces: []typeRef{}, + InputFields: []InputValue{}, + Interfaces: []TypeRef{}, } - name := t.Name + name := table.Name if alias != "" { name = alias } name = in.getName(name) ft.Name = name - ft.Description = t.Comment + ft.Description = table.Comment var hasSearch bool var hasRecursive bool - if err = in.addColumnsEnumType(t); err != nil { + if err = in.addColumnsEnumType(table); err != nil { return } for _, fn := range in.schema.GetFunctions() { - ty := in.addArgsType(t, fn) + ty := in.addArgsType(table, fn) in.addType(ty) } - for _, c := range t.Columns { + for _, c := range table.Columns { if c.Blocked { continue } @@ -378,7 +385,7 @@ func (in *intro) addTableTypeWithDepth( if c.FKRecursive { hasRecursive = true } - var f1 fieldObj + var f1 FieldObject f1, err = in.getColumnField(c) if err != nil { return @@ -387,22 +394,22 @@ func (in *intro) addTableTypeWithDepth( } for _, fn := range in.schema.GetFunctions() { - f1 := in.getFunctionField(t, fn) + f1 := in.getFunctionField(table, fn) ft.Fields = append(ft.Fields, f1) } - relNodes1, err := in.schema.GetFirstDegree(t) + relNodes1, err := in.schema.GetFirstDegree(table) if err != nil { return } - relNodes2, err := in.schema.GetSecondDegree(t) + relNodes2, err := in.schema.GetSecondDegree(table) if err != nil { return } for _, relNode := range append(relNodes1, relNodes2...) { - var f fieldObj + var f FieldObject var skip bool f, skip, err = in.getTableField(relNode) if err != nil { @@ -413,43 +420,43 @@ func (in *intro) addTableTypeWithDepth( } } - ft.addArg("id", newTR("", "ID", nil)) - ft.addArg("limit", newTR("", "Int", nil)) - ft.addArg("offset", newTR("", "Int", nil)) - ft.addArg("distinctOn", newTR("LIST", "", newTR("", "String", nil))) - ft.addArg("first", newTR("", "Int", nil)) - ft.addArg("last", newTR("", "Int", nil)) - ft.addArg("after", newTR("", "Cursor", nil)) - ft.addArg("before", newTR("", "Cursor", nil)) + ft.addArg("id", newTypeRef("", "ID", nil)) + ft.addArg("limit", newTypeRef("", "Int", nil)) + ft.addArg("offset", newTypeRef("", "Int", nil)) + ft.addArg("distinctOn", newTypeRef("LIST", "", newTypeRef("", "String", nil))) + ft.addArg("first", newTypeRef("", "Int", nil)) + ft.addArg("last", newTypeRef("", "Int", nil)) + ft.addArg("after", newTypeRef("", "Cursor", nil)) + ft.addArg("before", newTypeRef("", "Cursor", nil)) - in.addOrderByType(t, &ft) - in.addWhereType(t, &ft) - in.addTableArgsType(t, &ft) + in.addOrderByType(table, &ft) + in.addWhereType(table, &ft) + in.addTableArgsType(table, &ft) if hasSearch { - ft.addArg("search", newTR("", "String", nil)) + ft.addArg("search", newTypeRef("", "String", nil)) } if depth > 1 { return } if depth > 0 { - ft.addArg("find", newTR("", "FindSearchInput", nil)) + ft.addArg("find", newTypeRef("", "FindSearchInput", nil)) } in.addType(ft) if hasRecursive { - _, err = in.addTableTypeWithDepth(t, + _, err = in.addTableTypeWithDepth(table, (name + "Recursive"), (depth + 1)) } return } -func (in *intro) addColumnsEnumType(t sdata.DBTable) (err error) { +func (in *Introspection) addColumnsEnumType(t sdata.DBTable) (err error) { tableName := in.getName(t.Name) - ft := fullType{ + ft := FullType{ Kind: KIND_ENUM, Name: (t.Name + "Columns" + SUFFIX_ENUM), Description: fmt.Sprintf("Table columns for '%s'", tableName), @@ -458,7 +465,7 @@ func (in *intro) addColumnsEnumType(t sdata.DBTable) (err error) { if c.Blocked { continue } - ft.EnumValues = append(ft.EnumValues, enumValue{ + ft.EnumValues = append(ft.EnumValues, EnumValue{ Name: in.getName(c.Name), Description: c.Comment, }) @@ -467,8 +474,8 @@ func (in *intro) addColumnsEnumType(t sdata.DBTable) (err error) { return } -func (in *intro) addTablesEnumType() { - ft := fullType{ +func (in *Introspection) addTablesEnumType() { + ft := FullType{ Kind: KIND_ENUM, Name: ("tables" + SUFFIX_ENUM), Description: "All available tables", @@ -477,7 +484,7 @@ func (in *intro) addTablesEnumType() { if t.Blocked { continue } - ft.EnumValues = append(ft.EnumValues, enumValue{ + ft.EnumValues = append(ft.EnumValues, EnumValue{ Name: in.getName(t.Name), Description: t.Comment, }) @@ -485,8 +492,8 @@ func (in *intro) addTablesEnumType() { in.addType(ft) } -func (in *intro) addRolesEnumType(roles map[string]*Role) { - ft := fullType{ +func (in *Introspection) addRolesEnumType(roles map[string]*Role) { + ft := FullType{ Kind: KIND_ENUM, Name: ("roles" + SUFFIX_ENUM), Description: "All available roles", @@ -496,7 +503,7 @@ func (in *intro) addRolesEnumType(roles map[string]*Role) { if ro.Match != "" { cmt = fmt.Sprintf("%s (Match: %s)", cmt, ro.Match) } - ft.EnumValues = append(ft.EnumValues, enumValue{ + ft.EnumValues = append(ft.EnumValues, EnumValue{ Name: name, Description: cmt, }) @@ -504,8 +511,8 @@ func (in *intro) addRolesEnumType(roles map[string]*Role) { in.addType(ft) } -func (in *intro) addOrderByType(t sdata.DBTable, ft *fullType) { - ty := fullType{ +func (in *Introspection) addOrderByType(t sdata.DBTable, ft *FullType) { + ty := FullType{ Kind: KIND_INPUT_OBJ, Name: (t.Name + SUFFIX_ORDER_BY), } @@ -513,28 +520,28 @@ func (in *intro) addOrderByType(t sdata.DBTable, ft *fullType) { if c.Blocked { continue } - ty.InputFields = append(ty.InputFields, inputValue{ + ty.InputFields = append(ty.InputFields, InputValue{ Name: in.getName(c.Name), Description: c.Comment, - Type: newTR("", "OrderDirection", nil), + Type: newTypeRef("", "OrderDirection", nil), }) } in.addType(ty) - ft.addArg("orderBy", newTR("", (t.Name+SUFFIX_ORDER_BY), nil)) + ft.addArg("orderBy", newTypeRef("", (t.Name+SUFFIX_ORDER_BY), nil)) } -func (in *intro) addWhereType(t sdata.DBTable, ft *fullType) { - tn := (t.Name + SUFFIX_WHERE) - ty := fullType{ +func (in *Introspection) addWhereType(table sdata.DBTable, ft *FullType) { + tablename := (table.Name + SUFFIX_WHERE) + ty := FullType{ Kind: "INPUT_OBJECT", - Name: tn, - InputFields: []inputValue{ - {Name: "and", Type: newTR("", tn, nil)}, - {Name: "or", Type: newTR("", tn, nil)}, - {Name: "not", Type: newTR("", tn, nil)}, + Name: tablename, + InputFields: []InputValue{ + {Name: "and", Type: newTypeRef("", tablename, nil)}, + {Name: "or", Type: newTypeRef("", tablename, nil)}, + {Name: "not", Type: newTypeRef("", tablename, nil)}, }, } - for _, c := range t.Columns { + for _, c := range table.Columns { if c.Blocked { continue } @@ -544,50 +551,50 @@ func (in *intro) addWhereType(t sdata.DBTable, ft *fullType) { } else { ft += SUFFIX_EXP } - ty.InputFields = append(ty.InputFields, inputValue{ + ty.InputFields = append(ty.InputFields, InputValue{ Name: in.getName(c.Name), Description: c.Comment, - Type: newTR("", ft, nil), + Type: newTypeRef("", ft, nil), }) } in.addType(ty) - ft.addArg("where", newTR("", ty.Name, nil)) + ft.addArg("where", newTypeRef("", ty.Name, nil)) } -func (in *intro) addInputType(t sdata.DBTable, ft fullType) (retFT fullType, err error) { +func (in *Introspection) addInputType(table sdata.DBTable, ft FullType) (retFT FullType, err error) { // upsert - ty := fullType{ + ty := FullType{ Kind: "INPUT_OBJECT", - Name: ("upsert" + t.Name + SUFFIX_INPUT), - InputFields: []inputValue{}, + Name: ("upsert" + table.Name + SUFFIX_INPUT), + InputFields: []InputValue{}, } - for _, c := range t.Columns { + for _, c := range table.Columns { if c.Blocked { continue } ft1 := getTypeFromColumn(c) - ty.InputFields = append(ty.InputFields, inputValue{ + ty.InputFields = append(ty.InputFields, InputValue{ Name: in.getName(c.Name), Description: c.Comment, - Type: newTR("", ft1, nil), + Type: newTypeRef("", ft1, nil), }) } in.addType(ty) - ft.addArg("upsert", newTR("", ty.Name, nil)) + ft.addArg("upsert", newTypeRef("", ty.Name, nil)) // insert - relNodes1, err := in.schema.GetFirstDegree(t) + relNodes1, err := in.schema.GetFirstDegree(table) if err != nil { return } - relNodes2, err := in.schema.GetSecondDegree(t) + relNodes2, err := in.schema.GetSecondDegree(table) if err != nil { return } allNodes := append(relNodes1, relNodes2...) fieldLen := len(ty.InputFields) - ty.Name = ("insert" + t.Name + SUFFIX_INPUT) + ty.Name = ("insert" + table.Name + SUFFIX_INPUT) for _, relNode := range allNodes { t1 := relNode.Table if relNode.Type == sdata.RelRemote || @@ -595,17 +602,17 @@ func (in *intro) addInputType(t sdata.DBTable, ft fullType) (retFT fullType, err relNode.Type == sdata.RelEmbedded { continue } - ty.InputFields = append(ty.InputFields, inputValue{ + ty.InputFields = append(ty.InputFields, InputValue{ Name: in.getName(t1.Name), Description: t1.Comment, - Type: newTR("", ("insert" + t1.Name + SUFFIX_INPUT), nil), + Type: newTypeRef("", ("insert" + t1.Name + SUFFIX_INPUT), nil), }) } in.addType(ty) - ft.addArg("insert", newTR("", ty.Name, nil)) + ft.addArg("insert", newTypeRef("", ty.Name, nil)) // update - ty.Name = ("update" + t.Name + SUFFIX_INPUT) + ty.Name = ("update" + table.Name + SUFFIX_INPUT) i := 0 for _, relNode := range allNodes { t1 := relNode.Table @@ -614,66 +621,66 @@ func (in *intro) addInputType(t sdata.DBTable, ft fullType) (retFT fullType, err relNode.Type == sdata.RelEmbedded { continue } - ty.InputFields[(fieldLen + i)] = inputValue{ + ty.InputFields[(fieldLen + i)] = InputValue{ Name: in.getName(t1.Name), Description: t1.Comment, - Type: newTR("", ("update" + t1.Name + SUFFIX_INPUT), nil), + Type: newTypeRef("", ("update" + t1.Name + SUFFIX_INPUT), nil), } i++ } - desc1 := fmt.Sprintf("Connect to rows in table '%s' that match the expression", in.getName(t.Name)) - ty.InputFields = append(ty.InputFields, inputValue{ + description1 := fmt.Sprintf("Connect to rows in table '%s' that match the expression", in.getName(table.Name)) + ty.InputFields = append(ty.InputFields, InputValue{ Name: "connect", - Description: desc1, - Type: newTR("", (t.Name + SUFFIX_WHERE), nil), + Description: description1, + Type: newTypeRef("", (table.Name + SUFFIX_WHERE), nil), }) - desc2 := fmt.Sprintf("Disconnect from rows in table '%s' that match the expression", in.getName(t.Name)) - ty.InputFields = append(ty.InputFields, inputValue{ + description2 := fmt.Sprintf("Disconnect from rows in table '%s' that match the expression", in.getName(table.Name)) + ty.InputFields = append(ty.InputFields, InputValue{ Name: "disconnect", - Description: desc2, - Type: newTR("", (t.Name + SUFFIX_WHERE), nil), + Description: description2, + Type: newTypeRef("", (table.Name + SUFFIX_WHERE), nil), }) - desc3 := fmt.Sprintf("Update rows in table '%s' that match the expression", in.getName(t.Name)) - ty.InputFields = append(ty.InputFields, inputValue{ + desciption3 := fmt.Sprintf("Update rows in table '%s' that match the expression", in.getName(table.Name)) + ty.InputFields = append(ty.InputFields, InputValue{ Name: "where", - Description: desc3, - Type: newTR("", (t.Name + SUFFIX_WHERE), nil), + Description: desciption3, + Type: newTypeRef("", (table.Name + SUFFIX_WHERE), nil), }) in.addType(ty) - ft.addArg("update", newTR("", ty.Name, nil)) + ft.addArg("update", newTypeRef("", ty.Name, nil)) // delete - ft.addArg("delete", newTR("", TYPE_BOOLEAN, nil)) + ft.addArg("delete", newTypeRef("", TYPE_BOOLEAN, nil)) retFT = ft return } -func (in *intro) addTableArgsType(t sdata.DBTable, ft *fullType) { - if t.Type != "function" { +func (in *Introspection) addTableArgsType(table sdata.DBTable, ft *FullType) { + if table.Type != "function" { return } - ty := in.addArgsType(t, t.Func) + ty := in.addArgsType(table, table.Func) in.addType(ty) - ft.addArg("args", newTR("", ty.Name, nil)) + ft.addArg("args", newTypeRef("", ty.Name, nil)) } -func (in *intro) addArgsType(t sdata.DBTable, fn sdata.DBFunction) (ft fullType) { - ft = fullType{ +func (in *Introspection) addArgsType(table sdata.DBTable, fn sdata.DBFunction) (ft FullType) { + ft = FullType{ Kind: "INPUT_OBJECT", - Name: (t.Name + fn.Name + SUFFIX_ARGS), + Name: (table.Name + fn.Name + SUFFIX_ARGS), } for _, fi := range fn.Inputs { - var tr *typeRef + var tr *TypeRef if fn.Agg { - tr = newTR("", (t.Name + "Columns" + SUFFIX_ENUM), nil) + tr = newTypeRef("", (table.Name + "Columns" + SUFFIX_ENUM), nil) } else { tn, list := getType(fi.Type) if tn == "" { tn = "String" } - tr = newTR("", tn, nil) + tr = newTypeRef("", tn, nil) if list { - tr = newTR(KIND_LIST, "", tr) + tr = newTypeRef(KIND_LIST, "", tr) } } @@ -681,7 +688,7 @@ func (in *intro) addArgsType(t sdata.DBTable, fn sdata.DBFunction) (ft fullType) if fname == "" { fname = "_" + strconv.Itoa(fi.ID) } - ft.InputFields = append(ft.InputFields, inputValue{ + ft.InputFields = append(ft.InputFields, InputValue{ Name: fname, Type: tr, }) @@ -689,66 +696,66 @@ func (in *intro) addArgsType(t sdata.DBTable, fn sdata.DBFunction) (ft fullType) return } -func (in *intro) getColumnField(c sdata.DBColumn) (f fieldObj, err error) { - f.Args = []inputValue{} - f.Name = in.getName(c.Name) - typeValue := newTR("", "String", nil) +func (in *Introspection) getColumnField(column sdata.DBColumn) (field FieldObject, err error) { + field.Args = []InputValue{} + field.Name = in.getName(column.Name) + typeValue := newTypeRef("", "String", nil) - if v, ok := in.types[getTypeFromColumn(c)]; ok { + if v, ok := in.types[getTypeFromColumn(column)]; ok { typeValue.Name = &v.Name typeValue.Kind = v.Kind } - if c.Array { - typeValue = newTR(KIND_LIST, "", typeValue) + if column.Array { + typeValue = newTypeRef(KIND_LIST, "", typeValue) } - if c.NotNull { - typeValue = newTR(KIND_NONNULL, "", typeValue) + if column.NotNull { + typeValue = newTypeRef(KIND_NONNULL, "", typeValue) } - f.Type = typeValue + field.Type = typeValue - f.Args = append(f.Args, inputValue{ - Name: "includeIf", Type: newTR("", (c.Table + SUFFIX_WHERE), nil), + field.Args = append(field.Args, InputValue{ + Name: "includeIf", Type: newTypeRef("", (column.Table + SUFFIX_WHERE), nil), }) - f.Args = append(f.Args, inputValue{ - Name: "skipIf", Type: newTR("", (c.Table + SUFFIX_WHERE), nil), + field.Args = append(field.Args, InputValue{ + Name: "skipIf", Type: newTypeRef("", (column.Table + SUFFIX_WHERE), nil), }) return } -func (in *intro) getFunctionField(t sdata.DBTable, fn sdata.DBFunction) (f fieldObj) { +func (in *Introspection) getFunctionField(t sdata.DBTable, fn sdata.DBFunction) (f FieldObject) { f.Name = in.getName(fn.Name) - f.Args = []inputValue{} + f.Args = []InputValue{} ty, list := getType(fn.Type) - f.Type = newTR("", ty, nil) + f.Type = newTypeRef("", ty, nil) if list { - f.Type = newTR(KIND_LIST, "", f.Type) + f.Type = newTypeRef(KIND_LIST, "", f.Type) } if len(fn.Inputs) != 0 { typeName := (t.Name + fn.Name + SUFFIX_ARGS) - argsArg := inputValue{Name: "args", Type: newTR("", typeName, nil)} + argsArg := InputValue{Name: "args", Type: newTypeRef("", typeName, nil)} f.Args = append(f.Args, argsArg) } - f.Args = append(f.Args, inputValue{ - Name: "includeIf", Type: newTR("", (t.Name + SUFFIX_WHERE), nil), + f.Args = append(f.Args, InputValue{ + Name: "includeIf", Type: newTypeRef("", (t.Name + SUFFIX_WHERE), nil), }) - f.Args = append(f.Args, inputValue{ - Name: "skipIf", Type: newTR("", (t.Name + SUFFIX_WHERE), nil), + f.Args = append(f.Args, InputValue{ + Name: "skipIf", Type: newTypeRef("", (t.Name + SUFFIX_WHERE), nil), }) return } -func (in *intro) getTableField(relNode sdata.RelNode) ( - f fieldObj, skip bool, err error, +func (in *Introspection) getTableField(relNode sdata.RelNode) ( + f FieldObject, skip bool, err error, ) { - f.Args = []inputValue{} + f.Args = []InputValue{} f.Name = in.getName(relNode.Name) tn := in.getName(relNode.Table.Name) @@ -759,110 +766,111 @@ func (in *intro) getTableField(relNode sdata.RelNode) ( switch relNode.Type { case sdata.RelOneToOne: - f.Type = newTR(KIND_LIST, "", newTR("", tn, nil)) + f.Type = newTypeRef(KIND_LIST, "", newTypeRef("", tn, nil)) case sdata.RelRecursive: tn += "Recursive" - f.Type = newTR(KIND_LIST, "", newTR("", tn, nil)) + f.Type = newTypeRef(KIND_LIST, "", newTypeRef("", tn, nil)) default: - f.Type = newTR("", tn, nil) + f.Type = newTypeRef("", tn, nil) } return } -func (in *intro) addDirType(dt dir) { - d := directiveType{ +func (in *Introspection) addDirType(dt dir) { + d := DirectiveType{ Name: dt.name, Description: dt.desc, Locations: dt.locs, IsRepeatable: dt.repeat, } for _, a := range dt.args { - d.Args = append(d.Args, inputValue{ + d.Args = append(d.Args, InputValue{ Name: a.name, Description: a.desc, - Type: newTR("", a.atype, nil), + Type: newTypeRef("", a.atype, nil), }) } if len(dt.args) == 0 { - d.Args = []inputValue{} + d.Args = []InputValue{} } - in.res.Schema.Directives = append(in.res.Schema.Directives, d) + in.result.Schema.Directives = append(in.result.Schema.Directives, d) } -func (in *intro) addDirValidateType() { - ft := fullType{ +func (in *Introspection) addDirValidateType() { + ft := FullType{ Kind: KIND_ENUM, Name: ("validateFormat" + SUFFIX_ENUM), Description: "Various formats supported by @validate", } for k := range valid.Formats { - ft.EnumValues = append(ft.EnumValues, enumValue{ + ft.EnumValues = append(ft.EnumValues, EnumValue{ Name: k, }) } in.addType(ft) - d := directiveType{ + d := DirectiveType{ Name: "validate", Description: "Add a validation for input variables", Locations: []string{LOC_QUERY, LOC_MUTATION, LOC_SUBSCRIPTION}, IsRepeatable: true, } - d.Args = append(d.Args, inputValue{ + d.Args = append(d.Args, InputValue{ Name: "variable", Description: "Variable to add the validation on", - Type: newTR(KIND_NONNULL, "", newTR("", "String", nil)), + Type: newTypeRef(KIND_NONNULL, "", newTypeRef("", "String", nil)), }) for k, v := range valid.Validators { if v.Type == "" { continue } - var ty *typeRef + var ty *TypeRef if v.List { - ty = newTR(KIND_LIST, "", newTR("", v.Type, nil)) + ty = newTypeRef(KIND_LIST, "", newTypeRef("", v.Type, nil)) } else { - ty = newTR("", v.Type, nil) + ty = newTypeRef("", v.Type, nil) } - d.Args = append(d.Args, inputValue{ + d.Args = append(d.Args, InputValue{ Name: k, Description: v.Description, Type: ty, }) } - in.res.Schema.Directives = append(in.res.Schema.Directives, d) + in.result.Schema.Directives = append(in.result.Schema.Directives, d) } -func (ft *fullType) addArg(name string, tr *typeRef) { - ft.InputFields = append(ft.InputFields, inputValue{ +func (ft *FullType) addArg(name string, tr *TypeRef) { + ft.InputFields = append(ft.InputFields, InputValue{ Name: name, Type: tr, }) } -func (ft *fullType) addOrReplaceArg(name string, tr *typeRef) { +func (ft *FullType) addOrReplaceArg(name string, tr *TypeRef) { for i, a := range ft.InputFields { if a.Name == name { ft.InputFields[i].Type = tr return } } - ft.InputFields = append(ft.InputFields, inputValue{ + ft.InputFields = append(ft.InputFields, InputValue{ Name: name, Type: tr, }) } -func (in *intro) addType(ft fullType) { +func (in *Introspection) addType(ft FullType) { in.types[ft.Name] = ft } -func newTR(kind, name string, tr *typeRef) *typeRef { +func newTypeRef(kind, name string, tr *TypeRef) *TypeRef { if name == "" { - return &typeRef{Kind: kind, Name: nil, OfType: tr} + return &TypeRef{Kind: kind, Name: nil, OfType: tr} } - return &typeRef{Kind: kind, Name: &name, OfType: tr} + return &TypeRef{Kind: kind, Name: &name, OfType: tr} } +// Returns the type of the given column. Returns ID if column is the primary key func getTypeFromColumn(col sdata.DBColumn) (gqlType string) { if col.PrimaryKey { gqlType = "ID" @@ -872,6 +880,7 @@ func getTypeFromColumn(col sdata.DBColumn) (gqlType string) { return } +// Returns the GraphQL type for the given column type func getType(t string) (gqlType string, list bool) { if i := strings.IndexRune(t, '('); i != -1 { t = t[:i]