Documentation ¶
Overview ¶
Package core provides the primary API to include and use GraphJin with your own code. For detailed documentation visit https://graphjin.com
Example usage:
package main import ( "database/sql" "fmt" "time" "github.com/dosco/graphjin/core" _ "github.com/jackc/pgx/v4/stdlib" ) func main() { db, err := sql.Open("pgx", "postgres://postgrs:@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.WithValue(ctx, core.UserIDKey, 1) res, err := gj.GraphQL(ctx, query, nil) if err != nil { log.Fatal(err) } }
Example (BlockQueryWithRoles) ¶
gql := `query { users { id full_name email } }` conf := &core.Config{DBType: dbType, DisableAllowList: true} conf.RolesQuery = `SELECT * FROM users WHERE id = $user_id` conf.Roles = []core.Role{{Name: "disabled_user", Match: "disabled = true"}} err := conf.AddRoleTable("disabled_user", "users", core.Query{Block: true}) if err != nil { panic(err) } gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } ctx := context.WithValue(context.Background(), core.UserIDKey, 50) res, err := gj.GraphQL(ctx, gql, nil) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"users": null}
Example (BulkInsert) ¶
gql := `mutation { users(insert: $data) { id email } }` vars := json.RawMessage(`{ "data": [{ "id": 1002, "email": "user1002@test.com", "full_name": "User 1002", "stripe_id": "payment_id_1002", "category_counts": [{"category_id": 1, "count": 400},{"category_id": 2, "count": 600}] }, { "id": 1003, "email": "user1003@test.com", "full_name": "User 1003", "stripe_id": "payment_id_1003", "category_counts": [{"category_id": 2, "count": 400},{"category_id": 3, "count": 600}] }] }`) conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } ctx := context.WithValue(context.Background(), core.UserIDKey, 3) res, err := gj.GraphQL(ctx, gql, vars) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"users": [{"id": 1002, "email": "user1002@test.com"}, {"id": 1003, "email": "user1003@test.com"}]}
Example (Insert) ¶
gql := `mutation { user(insert: $data) { id email } }` vars := json.RawMessage(`{ "data": { "id": 1001, "email": "user1001@test.com", "full_name": "User 1001", "stripe_id": "payment_id_1001", "category_counts": [{"category_id": 1, "count": 400},{"category_id": 2, "count": 600}] } }`) conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } ctx := context.WithValue(context.Background(), core.UserIDKey, 3) res, err := gj.GraphQL(ctx, gql, vars) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"user": {"id": 1001, "email": "user1001@test.com"}}
Example (InsertIntoMultipleRelatedTables1) ¶
gql := `mutation { purchase(insert: $data) { quantity customer { id full_name email } product { id name price } } }` vars := json.RawMessage(`{ "data": { "id": 3001, "quantity": 5, "customer": { "id": 1004, "email": "user1004@test.com", "full_name": "User 1004", "stripe_id": "payment_id_1004", "category_counts": [{"category_id": 1, "count": 400},{"category_id": 2, "count": 600}] }, "product": { "id": 2002, "name": "Product 2002", "description": "Description for product 2002", "price": 2012.5, "tags": ["Tag 1", "Tag 2"], "category_ids": [1, 2, 3, 4, 5], "owner_id": 3 } } }`) conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } ctx := context.WithValue(context.Background(), core.UserIDKey, 3) res, err := gj.GraphQL(ctx, gql, vars) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"purchase": {"product": {"id": 2002, "name": "Product 2002", "price": 2012.5}, "customer": {"id": 1004, "email": "user1004@test.com", "full_name": "User 1004"}, "quantity": 5}}
Example (InsertIntoMultipleRelatedTables2) ¶
gql := `mutation { user(insert: $data) { id full_name email product { id name price } } }` vars := json.RawMessage(`{ "data": { "id": 1005, "email": "user1005@test.com", "full_name": "User 1005", "stripe_id": "payment_id_1005", "category_counts": [{"category_id": 1, "count": 400},{"category_id": 2, "count": 600}], "product": { "id": 2003, "name": "Product 2003", "description": "Description for product 2003", "price": 2013.5, "tags": ["Tag 1", "Tag 2"], "category_ids": [1, 2, 3, 4, 5], "owner_id": 3 } } }`) conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } ctx := context.WithValue(context.Background(), core.UserIDKey, 3) res, err := gj.GraphQL(ctx, gql, vars) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"user": {"id": 1005, "email": "user1005@test.com", "product": {"id": 2003, "name": "Product 2003", "price": 2013.5}, "full_name": "User 1005"}}
Example (InsertIntoMultipleRelatedTables3) ¶
gql := `mutation { product(insert: $data) { id name owner { id full_name email } } }` vars := json.RawMessage(`{ "data": { "id": 2004, "name": "Product 2004", "description": "Description for product 2004", "price": 2014.5, "tags": ["Tag 1", "Tag 2"], "category_ids": [1, 2, 3, 4, 5], "owner": { "id": 1006, "email": "user1006@test.com", "full_name": "User 1006", "stripe_id": "payment_id_1006", "category_counts": [{"category_id": 1, "count": 400},{"category_id": 2, "count": 600}] } } }`) conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } ctx := context.WithValue(context.Background(), core.UserIDKey, 3) res, err := gj.GraphQL(ctx, gql, vars) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"product": {"id": 2004, "name": "Product 2004", "owner": {"id": 1006, "email": "user1006@test.com", "full_name": "User 1006"}}}
Example (InsertIntoRecursiveRelationship) ¶
gql := `mutation { comments(insert: $data, where: { id: { in: [5001, 5002] }}) { id reply_to_id } }` vars := json.RawMessage(`{ "data": { "id": 5001, "body": "Comment body 5001", "created_at": "now", "comment": { "find": "children", "id": 5002, "body": "Comment body 5002", "created_at": "now" } } }`) conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } ctx := context.WithValue(context.Background(), core.UserIDKey, 3) res, err := gj.GraphQL(ctx, gql, vars) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"comments": [{"id": 5001, "reply_to_id": null}, {"id": 5002, "reply_to_id": 5001}]}
Example (InsertIntoRecursiveRelationshipAndConnectTable1) ¶
gql := `mutation { comments(insert: $data, where: { id: { in: [5, 5003] }}) { id reply_to_id } }` vars := json.RawMessage(`{ "data": { "id": 5003, "body": "Comment body 5003", "created_at": "now", "comment": { "find": "children", "connect": { "id": 5 } } } }`) conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } ctx := context.WithValue(context.Background(), core.UserIDKey, 3) res, err := gj.GraphQL(ctx, gql, vars) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"comments": [{"id": 5003, "reply_to_id": null}, {"id": 5, "reply_to_id": 5003}]}
Example (InsertIntoRecursiveRelationshipAndConnectTable2) ¶
gql := `mutation { comment(insert: $data) { id product { id } user { id } comments(find: "children") { id } } }` conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } vars := json.RawMessage(`{ "data": { "id": 5004, "body": "Comment body 5004", "created_at": "now", "comment": { "connect": { "id": 6 }, "find": "children" }, "product": { "connect": { "id": 26 } }, "user":{ "connect":{ "id": 3 } } } }`) ctx := context.WithValue(context.Background(), core.UserIDKey, 50) res, err := gj.GraphQL(ctx, gql, vars) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"comment": {"id": 5004, "user": {"id": 3}, "product": {"id": 26}, "comments": [{"id": 6}]}}
Example (InsertIntoTableAndConnectToRelatedTableWithArrayColumn) ¶
gql := `mutation { product(insert: $data) { id name categories { id name } } }` vars := json.RawMessage(`{ "data": { "id": 2006, "name": "Product 2006", "description": "Description for product 2006", "price": 2016.5, "tags": ["Tag 1", "Tag 2"], "categories": { "connect": { "id": [1, 2, 3, 4, 5] } } } }`) conf := &core.Config{DBType: dbType, DisableAllowList: true} conf.Tables = []core.Table{ {Name: "products", Columns: []core.Column{{Name: "category_ids", ForeignKey: "categories.id"}}}, } gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } ctx := context.WithValue(context.Background(), core.UserIDKey, 3) res, err := gj.GraphQL(ctx, gql, vars) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"product": {"id": 2006, "name": "Product 2006", "categories": [{"id": 1, "name": "Category 1"}, {"id": 2, "name": "Category 2"}, {"id": 3, "name": "Category 3"}, {"id": 4, "name": "Category 4"}, {"id": 5, "name": "Category 5"}]}}
Example (InsertIntoTableAndConnectToRelatedTables) ¶
gql := `mutation { product(insert: $data) { id name owner { id full_name email } } }` vars := json.RawMessage(`{ "data": { "id": 2005, "name": "Product 2005", "description": "Description for product 2005", "price": 2015.5, "tags": ["Tag 1", "Tag 2"], "category_ids": [1, 2, 3, 4, 5], "owner": { "connect": { "id": 6 } } } }`) conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } ctx := context.WithValue(context.Background(), core.UserIDKey, 3) res, err := gj.GraphQL(ctx, gql, vars) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"product": {"id": 2005, "name": "Product 2005", "owner": {"id": 6, "email": "user6@test.com", "full_name": "User 6"}}}
Example (InsertWithPresets) ¶
gql := `mutation { product(insert: $data) { id name user { id email } } }` vars := json.RawMessage(`{ "data": { "id": 2001, "name": "Product 2001", "description": "Description for product 2001", "price": 2011.5, "tags": ["Tag 1", "Tag 2"], "category_ids": [1, 2, 3, 4, 5] } }`) conf := &core.Config{DBType: dbType, DisableAllowList: true} err := conf.AddRoleTable("user", "products", core.Insert{ Presets: map[string]string{"owner_id": "$user_id"}, }) if err != nil { panic(err) } gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } ctx := context.WithValue(context.Background(), core.UserIDKey, 3) res, err := gj.GraphQL(ctx, gql, vars) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"product": {"id": 2001, "name": "Product 2001", "user": {"id": 3, "email": "user3@test.com"}}}
Example (Query) ¶
gql := `query { product { id user { id fullName: full_name } } }` conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, nil) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"product": {"id": 1, "user": {"id": 1, "fullName": "User 1"}}}
Example (QueryByID) ¶
gql := `query { product(id: $id) { id name } }` vars := json.RawMessage(`{ "id": 2 }`) conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, vars) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"product": {"id": 2, "name": "Product 2"}}
Example (QueryBySearch) ¶
gql := `query { products(search: $query, limit: 5) { id name } }` vars := json.RawMessage(`{ "query": "\"Product 3\"" }`) conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, vars) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"products": [{"id": 3, "name": "Product 3"}]}
Example (QueryChildrenWithParent) ¶
gql := `query { products(limit: 2) { name price users { email } } }` conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, nil) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"products": [{"name": "Product 1", "price": 11.5, "users": [{"email": "user1@test.com"}]}, {"name": "Product 2", "price": 12.5, "users": [{"email": "user2@test.com"}]}]}
Example (QueryManyToManyViaJoinTable1) ¶
gql := `query { product { name customers { email } owner { email } } }` conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, nil) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"product": {"name": "Product 1", "owner": {"email": "user1@test.com"}, "customers": [{"email": "user2@test.com"}]}}
Example (QueryManyToManyViaJoinTable2) ¶
gql := `query { users { email full_name products { name } } }` conf := &core.Config{DBType: dbType, DisableAllowList: true, DefaultLimit: 2} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, nil) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"users": [{"email": "user1@test.com", "products": [{"name": "Product 1"}], "full_name": "User 1"}, {"email": "user2@test.com", "products": [{"name": "Product 2"}], "full_name": "User 2"}]}
Example (QueryParentAndChildrenViaArrayColumn) ¶
// gql := ` // query { // products { // name // price // categories { // id // name // } // } // categories { // name // product { // name // } // } // }` gql := ` query { products { name price categories { id name } } }` conf := &core.Config{DBType: dbType, DisableAllowList: true, DefaultLimit: 2} conf.Tables = []core.Table{ {Name: "products", Columns: []core.Column{{Name: "category_ids", ForeignKey: "categories.id", Array: true}}}, } gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, nil) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"products": [{"name": "Product 1", "price": 11.5, "categories": [{"id": 1, "name": "Category 1"}, {"id": 2, "name": "Category 2"}]}, {"name": "Product 2", "price": 12.5, "categories": [{"id": 1, "name": "Category 1"}, {"id": 2, "name": "Category 2"}]}]}
Example (QueryParentsWithChildren) ¶
gql := `query { users(order_by: { id: asc }, limit: 2) { email products { name price } } }` conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, nil) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"users": [{"email": "user1@test.com", "products": [{"name": "Product 1", "price": 11.5}]}, {"email": "user2@test.com", "products": [{"name": "Product 2", "price": 12.5}]}]}
Example (QueryWithAggregation) ¶
gql := `query { products(where: { id: { lteq: 100 } }) { count_id } }` conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, nil) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"products": [{"count_id": 100}]}
Example (QueryWithAggregationBlockedColumn) ¶
gql := `query { products { sum_price } }` conf := &core.Config{DBType: dbType, DisableAllowList: true} err := conf.AddRoleTable("anon", "products", core.Query{ Columns: []string{"id", "name"}, }) if err != nil { panic(err) } gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, nil) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: column blocked: sum (anon)
Example (QueryWithAlternateFieldNames) ¶
gql := `query { comments(limit: 2) { id commenter { email } } }` conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, nil) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"comments": [{"id": 1, "commenter": {"email": "user1@test.com"}}, {"id": 2, "commenter": {"email": "user2@test.com"}}]}
Example (QueryWithCursorPagination) ¶
gql := `query { Products( where: { id: { lesser_or_equals: 100 } } first: 3 after: $cursor order_by: { price: desc }) { Name } products_cursor }` vars := json.RawMessage(`{"cursor": null}`) conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, vars) if err != nil { fmt.Println(err) return } type result struct { Products json.RawMessage `json:"products"` Cursor string `json:"products_cursor"` } var val result if err := json.Unmarshal(res.Data, &val); err != nil { fmt.Println(err) return } if val.Cursor == "" { fmt.Println("product_cursor value missing") return } fmt.Println(string(val.Products))
Output: [{"name": "Product 100"}, {"name": "Product 99"}, {"name": "Product 98"}]
Example (QueryWithFragments1) ¶
gql := ` fragment userFields1 on user { id email } query { users { ...userFields2 stripe_id ...userFields1 } } fragment userFields2 on user { full_name }` conf := &core.Config{DBType: dbType, DisableAllowList: true, DefaultLimit: 2} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, nil) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"users": [{"id": 1, "email": "user1@test.com", "full_name": "User 1", "stripe_id": "payment_id_1001"}, {"id": 2, "email": "user2@test.com", "full_name": "User 2", "stripe_id": "payment_id_1002"}]}
Example (QueryWithFragments2) ¶
gql := ` query { users { ...userFields2 stripe_id ...userFields1 } } fragment userFields1 on user { id email } fragment userFields2 on user { full_name }` conf := &core.Config{DBType: dbType, DisableAllowList: true, DefaultLimit: 2} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, nil) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"users": [{"id": 1, "email": "user1@test.com", "full_name": "User 1", "stripe_id": "payment_id_1001"}, {"id": 2, "email": "user2@test.com", "full_name": "User 2", "stripe_id": "payment_id_1002"}]}
Example (QueryWithFragments3) ¶
gql := ` fragment userFields1 on user { id email } fragment userFields2 on user { full_name ...userFields1 } query { users { ...userFields2 stripe_id } }` conf := &core.Config{DBType: dbType, DisableAllowList: true, DefaultLimit: 2} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, nil) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"users": [{"id": 1, "email": "user1@test.com", "full_name": "User 1", "stripe_id": "payment_id_1001"}, {"id": 2, "email": "user2@test.com", "full_name": "User 2", "stripe_id": "payment_id_1002"}]}
Example (QueryWithFunctionsBlocked) ¶
gql := `query { products { sum_price } }` conf := &core.Config{DBType: dbType, DisableAllowList: true} err := conf.AddRoleTable("anon", "products", core.Query{ DisableFunctions: true, }) if err != nil { panic(err) } gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, nil) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: functions blocked: price (anon)
Example (QueryWithFunctionsWithWhere) ¶
gql := `query { products(where: { id: { lesser_or_equals: 100 } }) { max_price } }` conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, nil) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"products": [{"max_price": 110.5}]}
Example (QueryWithJsonColumn) ¶
gql := `query { user { id category_counts { count category { name } } } }` conf := &core.Config{DBType: dbType, DisableAllowList: true} conf.Tables = []core.Table{ { Name: "category_counts", Table: "users", Type: "json", Columns: []core.Column{ {Name: "category_id", Type: "int", ForeignKey: "categories.id"}, {Name: "count", Type: "int"}, }, }, } gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, nil) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"user": {"id": 1, "category_counts": [{"count": 400, "category": {"name": "Category 1"}}, {"count": 600, "category": {"name": "Category 2"}}]}}
Example (QueryWithLimitOffsetOrderByDistinctAndWhere) ¶
gql := `query { proDUcts( # returns only 5 items limit: 5, # starts from item 10, commented out for now # offset: 10, # orders the response items by highest price order_by: { price: desc }, # no duplicate prices returned distinct: [ price ] # only items with an id >= 50 and < 100 are returned where: { id: { and: { greater_or_equals: 50, lt: 100 } } }) { id NAME price } }` conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, nil) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"products": [{"id": 99, "name": "Product 99", "price": 109.5}, {"id": 98, "name": "Product 98", "price": 108.5}, {"id": 97, "name": "Product 97", "price": 107.5}, {"id": 96, "name": "Product 96", "price": 106.5}, {"id": 95, "name": "Product 95", "price": 105.5}]}
Example (QueryWithMultipleTopLevelTables) ¶
gql := `query { product(id: $id) { id name customer { email } } user(id: $id) { id email } purchase(id: $id) { id } }` vars := json.RawMessage(`{ "id": 1 }`) conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, vars) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"user": {"id": 1, "email": "user1@test.com"}, "product": {"id": 1, "name": "Product 1", "customer": {"email": "user2@test.com"}}, "purchase": {"id": 1}}
Example (QueryWithRecursiveRelationship1) ¶
gql := `query { reply : comment(id: $id) { id comments( where: { id: { lt: 50 } }, limit: 5, find: "parents") { id } } }` vars := json.RawMessage(`{"id": 50 }`) conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, vars) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"reply": {"id": 50, "comments": [{"id": 49}, {"id": 48}, {"id": 47}, {"id": 46}, {"id": 45}]}}
Example (QueryWithRecursiveRelationship2) ¶
gql := `query { comment(id: $id) { id replies: comments(find: "children") { id } } }` vars := json.RawMessage(`{"id": 95}`) conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, vars) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"comment": {"id": 95, "replies": [{"id": 96}, {"id": 97}, {"id": 98}, {"id": 99}, {"id": 100}]}}
Example (QueryWithRemoteAPIJoin) ¶
gql := `query { users { email payments { desc } } }` // fake remote api service go func() { http.HandleFunc("/payments/", func(w http.ResponseWriter, r *http.Request) { id := r.URL.Path[10:] fmt.Fprintf(w, `{"data":[{"desc":"Payment 1 for %s"},{"desc": "Payment 2 for %s"}]}`, id, id) }) log.Fatal(http.ListenAndServe(":12345", nil)) }() conf := &core.Config{DBType: dbType, DisableAllowList: true, DefaultLimit: 2} conf.Resolvers = []core.ResolverConfig{{ Name: "payments", Type: "remote_api", Table: "users", Column: "stripe_id", StripPath: "data", Props: core.ResolverProps{"url": "http://localhost:12345/payments/$id"}, }} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, nil) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"users": [{"email": "user1@test.com", "payments":[{"desc":"Payment 1 for payment_id_1001"},{"desc": "Payment 2 for payment_id_1001"}]}, {"email": "user2@test.com", "payments":[{"desc":"Payment 1 for payment_id_1002"},{"desc": "Payment 2 for payment_id_1002"}]}]}
Example (QueryWithSkipAndIncludeDirectives) ¶
gql := ` query { products(limit: 2) @include(if: $test) { id name } users(limit: 3) @skip(if: $test) { id } }` vars := json.RawMessage(`{ "test": true }`) conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, vars) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"users": [], "products": [{"id": 1, "name": "Product 1"}, {"id": 2, "name": "Product 2"}]}
Example (QueryWithSkippingAuthRequiredSelectors) ¶
gql := `query { product { id name user(where: { id: { eq: $user_id } }) { id email } } }` conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, nil) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"product": {"id": 1, "name": "Product 1", "user": null}}
Example (QueryWithSyntheticTables) ¶
gql := `query { me { email } }` conf := &core.Config{DBType: dbType, DisableAllowList: true} conf.Tables = []core.Table{{Name: "me", Table: "user"}} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, nil) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"me": {"email": "user1@test.com"}}
Example (QueryWithUnionForPolymorphicRelationships) ¶
gql := ` fragment userFields on user { email } fragment productFields on product { name } query { notifications { id verb subjects { ...on users { ...userFields } ...on products { ...productFields } } } }` conf := &core.Config{DBType: dbType, DisableAllowList: true, DefaultLimit: 2} conf.Tables = []core.Table{{ Name: "subject", Type: "polymorphic", Columns: []core.Column{{Name: "subject_id", ForeignKey: "subject_type.id"}}, }} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, nil) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"notifications": [{"id": 1, "verb": "Joined", "subjects": [{"email": "user1@test.com"}]}, {"id": 2, "verb": "Bought", "subjects": [{"name": "Product 2"}]}]}
Example (QueryWithUser) ¶
gql := `query { product(where: { owner_id: { eq: $user_id } }) { id user { id } } }` conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } ctx := context.WithValue(context.Background(), core.UserIDKey, 3) res, err := gj.GraphQL(ctx, gql, nil) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"product": {"id": 3, "user": {"id": 3}}}
Example (QueryWithVariables) ¶
gql := `query { product(id: $product_id, where: { price: { gt: $product_price } }) { id name } }` vars := json.RawMessage(`{ "product_id": 70 }`) conf := &core.Config{DBType: dbType, DisableAllowList: true} conf.Vars = map[string]string{"product_price": "50"} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, vars) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"product": {"id": 70, "name": "Product 70"}}
Example (QueryWithWhereGreaterThanOrLesserThan) ¶
gql := `query { products( limit: 3 where: { or: { price: { gt: 20 }, price: { lt: 22 } } } ) { id name price } }` conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, nil) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"products": [{"id": 1, "name": "Product 1", "price": 11.5}, {"id": 2, "name": "Product 2", "price": 12.5}, {"id": 3, "name": "Product 3", "price": 13.5}]}
Example (QueryWithWhereIn) ¶
gql := `query { products(where: { id: { in: $list } }) { id } }` vars := json.RawMessage(`{ "list": [1,2,3] }`) conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, vars) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"products": [{"id": 1}, {"id": 2}, {"id": 3}]}
Example (QueryWithWhereNotIsNullAndGreaterThan) ¶
gql := `query { product( where: { and: [ { not: { id: { is_null: true } } }, { price: { gt: 10 } }, ] } ) { id name price } }` conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, nil) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"product": {"id": 1, "name": "Product 1", "price": 11.5}}
Example (QueryWithWhereOnRelatedTable) ¶
gql := `query { product(where: { comment: { user: { email: { eq: $email } } } }) { id } }` vars := json.RawMessage(`{ "email": "user10@test.com" }`) conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } res, err := gj.GraphQL(context.Background(), gql, vars) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"product": {"id": 10}}
Example (Subscription) ¶
gql := `subscription test { user(id: $id) { id email phone } }` vars := json.RawMessage(`{ "id": 3 }`) conf := &core.Config{DBType: dbType, DisableAllowList: true, PollDuration: 1} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } m, err := gj.Subscribe(context.Background(), gql, vars) if err != nil { fmt.Println(err) return } for i := 0; i < 10; i++ { msg := <-m.Result fmt.Println(string(msg.Data)) // update user phone in database to trigger subscription q := fmt.Sprintf(`UPDATE users SET phone = '650-447-000%d' WHERE id = 3`, i) _, err := db.Exec(q) if err != nil { panic(err) } }
Output: {"user": {"id": 3, "email": "user3@test.com", "phone": null}} {"user": {"id": 3, "email": "user3@test.com", "phone": "650-447-0000"}} {"user": {"id": 3, "email": "user3@test.com", "phone": "650-447-0001"}} {"user": {"id": 3, "email": "user3@test.com", "phone": "650-447-0002"}} {"user": {"id": 3, "email": "user3@test.com", "phone": "650-447-0003"}} {"user": {"id": 3, "email": "user3@test.com", "phone": "650-447-0004"}} {"user": {"id": 3, "email": "user3@test.com", "phone": "650-447-0005"}} {"user": {"id": 3, "email": "user3@test.com", "phone": "650-447-0006"}} {"user": {"id": 3, "email": "user3@test.com", "phone": "650-447-0007"}} {"user": {"id": 3, "email": "user3@test.com", "phone": "650-447-0008"}}
Example (Update) ¶
gql := `mutation { product(id: $id, update: $data) { id name } }` vars := json.RawMessage(`{ "id": 100, "data": { "name": "Updated Product 100", "description": "Description for updated product 100" } }`) conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } ctx := context.WithValue(context.Background(), core.UserIDKey, 3) res, err := gj.GraphQL(ctx, gql, vars) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"product": {"id": 100, "name": "Updated Product 100"}}
Example (UpdateMultipleRelatedTables1) ¶
gql := `mutation { purchase(id: $id, update: $data) { quantity customer { full_name } product { description } } }` vars := json.RawMessage(`{ "id": 100, "data": { "quantity": 6, "customer": { "full_name": "Updated user related to purchase 100" }, "product": { "description": "Updated product related to purchase 100" } } }`) conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } ctx := context.WithValue(context.Background(), core.UserIDKey, 3) res, err := gj.GraphQL(ctx, gql, vars) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"purchase": {"product": {"description": "Updated product related to purchase 100"}, "customer": {"full_name": "Updated user related to purchase 100"}, "quantity": 6}}
Example (UpdateTableAndConnectToRelatedTables) ¶
gql := `mutation { user(id: $id, update: $data) { full_name products { id } } }` vars := json.RawMessage(`{ "id": 100, "data": { "full_name": "Updated user 100", "product": { "connect": { "id": 99 }, "disconnect": { "id": 100 } } } }`) conf := &core.Config{DBType: dbType, DisableAllowList: true} gj, err := core.NewGraphJin(conf, db) if err != nil { panic(err) } ctx := context.WithValue(context.Background(), core.UserIDKey, 3) res, err := gj.GraphQL(ctx, gql, vars) if err != nil { fmt.Println(err) } else { fmt.Println(string(res.Data)) }
Output: {"user": {"products": [{"id": 99}], "full_name": "Updated user 100"}}
Index ¶
- Constants
- type Column
- type Config
- type Delete
- type GraphJin
- func (gj *GraphJin) GraphQL(c context.Context, query string, vars json.RawMessage) (*Result, error)
- func (gj *GraphJin) GraphQLEx(c context.Context, query string, vars json.RawMessage, rc *ReqConfig) (*Result, error)
- func (gj *GraphJin) Subscribe(c context.Context, query string, vars json.RawMessage) (*Member, error)
- func (gj *GraphJin) SubscribeEx(c context.Context, query string, vars json.RawMessage, rc *ReqConfig) (*Member, error)
- type Insert
- type Member
- type OpType
- type Query
- type ReqConfig
- type Resolver
- type ResolverConfig
- type ResolverProps
- type ResolverReq
- type Result
- type Role
- type RoleTable
- type Table
- type Update
- type Upsert
Examples ¶
- Package (BlockQueryWithRoles)
- Package (BulkInsert)
- Package (Insert)
- Package (InsertIntoMultipleRelatedTables1)
- Package (InsertIntoMultipleRelatedTables2)
- Package (InsertIntoMultipleRelatedTables3)
- Package (InsertIntoRecursiveRelationship)
- Package (InsertIntoRecursiveRelationshipAndConnectTable1)
- Package (InsertIntoRecursiveRelationshipAndConnectTable2)
- Package (InsertIntoTableAndConnectToRelatedTableWithArrayColumn)
- Package (InsertIntoTableAndConnectToRelatedTables)
- Package (InsertWithPresets)
- Package (Query)
- Package (QueryByID)
- Package (QueryBySearch)
- Package (QueryChildrenWithParent)
- Package (QueryManyToManyViaJoinTable1)
- Package (QueryManyToManyViaJoinTable2)
- Package (QueryParentAndChildrenViaArrayColumn)
- Package (QueryParentsWithChildren)
- Package (QueryWithAggregation)
- Package (QueryWithAggregationBlockedColumn)
- Package (QueryWithAlternateFieldNames)
- Package (QueryWithCursorPagination)
- Package (QueryWithFragments1)
- Package (QueryWithFragments2)
- Package (QueryWithFragments3)
- Package (QueryWithFunctionsBlocked)
- Package (QueryWithFunctionsWithWhere)
- Package (QueryWithJsonColumn)
- Package (QueryWithLimitOffsetOrderByDistinctAndWhere)
- Package (QueryWithMultipleTopLevelTables)
- Package (QueryWithRecursiveRelationship1)
- Package (QueryWithRecursiveRelationship2)
- Package (QueryWithRemoteAPIJoin)
- Package (QueryWithSkipAndIncludeDirectives)
- Package (QueryWithSkippingAuthRequiredSelectors)
- Package (QueryWithSyntheticTables)
- Package (QueryWithUnionForPolymorphicRelationships)
- Package (QueryWithUser)
- Package (QueryWithVariables)
- Package (QueryWithWhereGreaterThanOrLesserThan)
- Package (QueryWithWhereIn)
- Package (QueryWithWhereNotIsNullAndGreaterThan)
- Package (QueryWithWhereOnRelatedTable)
- Package (Subscription)
- Package (Update)
- Package (UpdateMultipleRelatedTables1)
- Package (UpdateTableAndConnectToRelatedTables)
Constants ¶
const ( // Name of the authentication provider. Eg. google, github, etc UserIDProviderKey contextkey = iota // User ID value for authenticated users UserIDKey // User role if pre-defined UserRoleKey )
Constants to set values on the context passed to the NewGraphJin function
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Column ¶
type Column struct { Name string Type string Primary bool Array bool ForeignKey string `mapstructure:"related_to"` }
Column struct defines a database column
type Config ¶
type Config struct { // SecretKey is used to encrypt opaque values such as // the cursor. Auto-generated if not set SecretKey string `mapstructure:"secret_key"` // DisableAllowList when set to true entirely disables the // allow list workflow and all queries are always compiled // even in production. (Warning possible security concern) DisableAllowList bool `mapstructure:"disable_allow_list"` // EnforceAllowList (aka production mode) when set to true ensures // only queries saved to the allow list folders can be used. EnforceAllowList bool `mapstructure:"enforce_allow_list"` // AllowListFile if the path to allow list file if not set the // path is assumed to be the same as the config path (allow.list) AllowListFile string `mapstructure:"allow_list_file"` // SetUserID forces the database session variable `user.id` to // be set to the user id. This variables can be used by triggers // or other database functions SetUserID bool `mapstructure:"set_user_id"` // DefaultBlock ensures that in anonymous mode (role 'anon') all tables // are blocked from queries and mutations. To open access to tables in // anonymous mode they have to be added to the 'anon' role config. DefaultBlock bool `mapstructure:"default_block"` // Vars is a map of hardcoded variables that can be leveraged in your // queries (eg. variable admin_id will be $admin_id in the query) Vars map[string]string `mapstructure:"variables"` // HeaderVars is a map of dynamic variables that map to http header // values. HeaderVars map[string]string `mapstructure:"header_variables"` // Blocklist is a list of tables and columns that should be filtered // out from any and all queries Blocklist []string // Resolvers contain the configs for custom resolvers. For example the `remote_api` // resolver would join json from a remote API into your query response. Resolvers []ResolverConfig // Tables contains all table specific configuration such as aliased tables // creating relationships between tables, etc Tables []Table // RolesQuery if set enabled attributed based access control. This query // is used to fetch the user attributes that then dynamically define the users // role. RolesQuery string `mapstructure:"roles_query"` // Roles contains all the configuration for all the roles you want to support // `user` and `anon` are two default roles. User role is for when a user ID is // available and Anon when it's not. // // If you're using the RolesQuery config to enable atribute based acess control then // you can add more custom roles. Roles []Role // Inflections is to add additionally singular to plural mappings // to the engine (eg. sheep: sheep) Inflections []string `mapstructure:"inflections"` // Database schema name. Defaults to 'public' DBSchema string `mapstructure:"db_schema"` // Database type name. Defaults to 'postgres' (options: mysql, postgres) DBType string `mapstructure:"db_type"` // Log warnings and other debug information Debug bool // Useful for quickly debugging. Please set to false in production CredsInVars bool `mapstructure:"creds_in_vars"` // Subscriptions poll the database to query for updates // this sets the duration (in seconds) between requests. // Defaults to 5 seconds PollDuration time.Duration `mapstructure:"poll_every_seconds"` // DefaultLimit sets the default max limit (number of rows) when a // limit is not defined in the query or the table role config. // Default to 20 DefaultLimit int `mapstructure:"default_limit"` // contains filtered or unexported fields }
Core struct contains core specific config value
func ReadInConfig ¶
ReadInConfig function reads in the config file for the environment specified in the GO_ENV environment variable. This is the best way to create a new GraphJin config.
func (*Config) AddRoleTable ¶
AddRoleTable function is a helper function to make it easy to add per-table row-level config
func (*Config) SetResolver ¶ added in v0.15.56
type GraphJin ¶
type GraphJin struct {
// contains filtered or unexported fields
}
GraphJin struct is an instance of the GraphJin engine it holds all the required information like datase schemas, relationships, etc that the GraphQL to SQL compiler would need to do it's job.
func NewGraphJin ¶
NewGraphJin creates the GraphJin struct, this involves querying the database to learn its schemas and relationships
func (*GraphJin) GraphQL ¶
GraphQL function is called on the GraphJin struct to convert the provided GraphQL query into an SQL query and execute it on the database. In production mode prepared statements are directly used and no query compiling takes places.
In developer mode all names queries are saved into a file `allow.list` and in production mode only queries from this file can be run.
func (*GraphJin) GraphQLEx ¶
func (gj *GraphJin) GraphQLEx( c context.Context, query string, vars json.RawMessage, rc *ReqConfig) (*Result, error)
GraphQLEx is the extended version of the GraphQL function allowing for request specific config.
type Member ¶
type Member struct { Result chan *Result // contains filtered or unexported fields }
func (*Member) Unsubscribe ¶
func (m *Member) Unsubscribe()
type Query ¶
type Query struct { Limit int Filters []string Columns []string DisableFunctions bool `mapstructure:"disable_functions"` Block bool }
Query struct contains access control values for query operations
type ReqConfig ¶
type ReqConfig struct {
Vars map[string]interface{}
}
ReqConfig is used to pass request specific config values to the GraphQLEx and SubscribeEx functions. Dynamic variables can be set here.
type Resolver ¶ added in v0.15.56
type Resolver interface {
Resolve(ResolverReq) ([]byte, error)
}
Resolver interface is used to create custom resolvers Custom resolvers must return a JSON value to be merged into the response JSON.
Example Redis Resolver:
type Redis struct { Addr string client redis.Client } func newRedis(v map[string]interface{}) (*Redis, error) { re := &Redis{} if err := mapstructure.Decode(v, re); err != nil { return nil, err } re.client := redis.NewClient(&redis.Options{ Addr: re.Addr, Password: "", // no password set DB: 0, // use default DB }) return re, nil } func (r *remoteAPI) Resolve(req ResolverReq) ([]byte, error) { val, err := rdb.Get(ctx, req.ID).Result() if err != nil { return err } return val, nil } func main() { conf := core.Config{ Resolvers: []Resolver{ Name: "cached_profile", Type: "redis", Table: "users", Column: "id", Props: []ResolverProps{ "addr": "localhost:6379", }, }, } gj.conf.SetResolver("redis", func(v ResolverProps) (Resolver, error) { return newRedis(v) }) gj, err := core.NewGraphJin(conf, db) if err != nil { log.Fatal(err) } }
type ResolverConfig ¶ added in v0.15.56
type ResolverConfig struct { Name string Type string Table string Column string StripPath string `mapstructure:"strip_path"` Props ResolverProps `mapstructure:",remain"` }
ResolverConfig struct defines a custom resolver
type ResolverProps ¶ added in v0.15.56
type ResolverProps map[string]interface{}
ResolverProps is a map of properties from the resolver config to be passed to the customer resolver's builder (new) function
type ResolverReq ¶ added in v0.15.56
type Result ¶
type Result struct { Error string `json:"message,omitempty"` Data json.RawMessage `json:"data,omitempty"` Extensions *extensions `json:"extensions,omitempty"` // contains filtered or unexported fields }
Result struct contains the output of the GraphQL function this includes resulting json from the database query and any error information
func (*Result) OperationName ¶
type Role ¶
type Role struct { Name string Match string Tables []RoleTable // contains filtered or unexported fields }
Role struct contains role specific access control values for for all database tables
type RoleTable ¶
type RoleTable struct { Name string ReadOnly bool `mapstructure:"read_only"` Query *Query Insert *Insert Update *Update Upsert *Upsert Delete *Delete }
RoleTable struct contains role specific access control values for a database table