core

package
v2.0.30 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 21, 2023 License: Apache-2.0 Imports: 49 Imported by: 0

Documentation

Overview

Package core provides an API to include and use the GraphJin compiler with your own code. For detailed documentation visit https://graphjin.com

Example (Insert)
gql := `mutation {
		users(insert: {
			id: $id,
			email: $email,
			full_name: $fullName,
			stripe_id: $stripeID,
			category_counts: $categoryCounts
		}) {
			id
			email
		}
	}`

vars := json.RawMessage(`{
		"id": 1001,
		"email": "user1001@test.com",
		"fullName": "User 1001",
		"stripeID": "payment_id_1001",
		"categoryCounts": [{"category_id": 1, "count": 400},{"category_id": 2, "count": 600}]
	}`)

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"users":[{"email":"user1001@test.com","id":1001}]}
Example (InsertBulk)
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 := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"users":[{"email":"user1002@test.com","id":1002},{"email":"user1003@test.com","id":1003}]}
Example (InsertInlineBulk)
gql := `mutation {
		users(insert: [
			{id: $id1, email: $email1, full_name: $full_name1},
			{id:, $id2, email: $email2, full_name: $full_name2}]) {
			id
			email
		}
	}`

vars := json.RawMessage(`{
		"id1": 1008,
		"email1": "one@test.com",
		"full_name1": "John One",
		"id2": 1009,
		"email2":  "two@test.com",
		"full_name2": "John Two"
	}`)

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"users":[{"email":"two@test.com","id":1009},{"email":"one@test.com","id":1008}]}
Example (InsertInlineWithValidation)
gql := `mutation 
		@constraint(variable: "email", format: "email", min: 1, max: 100)
		@constraint(variable: "full_name", requiredIf: { id: 1007 } ) 
		@constraint(variable: "id", greaterThan:1006  ) 
		@constraint(variable: "id", lessThanOrEqualsField:id  ) {
		users(insert: { id: $id, email: $email, full_name: $full_name }) {
			id
			email
			full_name
		}
	}`

vars := json.RawMessage(`{
		"id": 1007,
		"email": "not_an_email"
	}`)

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
	for _, e := range res.Validation {
		fmt.Println(e.Constraint, e.FieldName)
	}
} else {
	printJSON(res.Data)
}
// Ordered output:
// validation failed
// format email
// min email
// max email
// requiredIf full_name
Output:

Example (InsertIntoMultipleRelatedTables)
gql := `mutation {
		purchases(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 := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"purchases":[{"customer":{"email":"user1004@test.com","full_name":"User 1004","id":1004},"product":{"id":2002,"name":"Product 2002","price":2012.5},"quantity":5}]}
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",
			"comments": {
				"find": "children",
				"id": 5002,
				"body": "Comment body 5002",
				"created_at": "now"	
			}
		}
	}`)

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(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",
			"comments": {
				"find": "children",
				"connect": { "id": 5 }
			}
		}
	}`)

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"comments":[{"id":5003,"reply_to_id":null},{"id":5,"reply_to_id":5003}]}
Example (InsertIntoRecursiveRelationshipAndConnectTable2)
gql := `mutation {
  	comments(insert: $data) @object {
			id
			product {
				id
			}
			commenter {
				id
			}
			comments(find: "children") {
				id
			}
  	}
  }`

conf := newConfig(&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",
				"comments": {
					"connect": { "id": 6 },
					"find": "children"
				},
				"product": {
					"connect": { "id": 26 }
				},
				"commenter":{
					"connect":{ "id": 3 }
				}
			}
		}`)

ctx := context.WithValue(context.Background(), core.UserIDKey, 50)
res, err := gj.GraphQL(ctx, gql, vars, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"comments":{"commenter":{"id":3},"comments":[{"id":6}],"id":5004,"product":{"id":26}}}
Example (InsertIntoTableAndConnectToRelatedTableWithArrayColumn)
gql := `mutation {
		products(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 := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":[{"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"}],"id":2006,"name":"Product 2006"}]}
Example (InsertIntoTableAndConnectToRelatedTables)
gql := `mutation {
		products(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 := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":[{"id":2005,"name":"Product 2005","owner":{"email":"user6@test.com","full_name":"User 6","id":6}}]}
Example (InsertIntoTableAndRelatedTable1)
gql := `mutation {
		users(insert: $data) {
			id
			full_name
			email
			products {
				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}],
			"products": {
				"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 := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"users":[{"email":"user1005@test.com","full_name":"User 1005","id":1005,"products":[{"id":2003,"name":"Product 2003","price":2013.5}]}]}
Example (InsertIntoTableAndRelatedTable2)
gql := `mutation {
		products(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 := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":[{"id":2004,"name":"Product 2004","owner":{"email":"user1006@test.com","full_name":"User 1006","id":1006}}]}
Example (InsertIntoTableBulkInsertIntoRelatedTable)
gql := `mutation {
		users(insert: $data) {
			id
			full_name
			email
			products {
				id
				name
				price
			}
		}
	}`

vars := json.RawMessage(`{
		"data": {
			"id": 10051,
			"email": "user10051@test.com",
			"full_name": "User 10051",
			"stripe_id": "payment_id_10051",
			"category_counts": [
				{"category_id": 1, "count": 400},
				{"category_id": 2, "count": 600}
			],
			"products": [
				{
					"id": 20031,
					"name": "Product 20031",
					"description": "Description for product 20031",
					"price": 2013.5,
					"tags": ["Tag 1", "Tag 2"],
					"category_ids": [1, 2, 3, 4, 5],
					"owner_id": 3
				},
				{
					"id": 20032,
					"name": "Product 20032",
					"description": "Description for product 20032",
					"price": 2014.5,
					"tags": ["Tag 1", "Tag 2"],
					"category_ids": [1, 2, 3, 4, 5],
					"owner_id": 3
				}
			]
		}
	}`)

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"users":[{"email":"user10051@test.com","full_name":"User 10051","id":10051,"products":[{"id":20031,"name":"Product 20031","price":2013.5},{"id":20032,"name":"Product 20032","price":2014.5}]}]}
Example (InsertWithCamelToSnakeCase)
gql := `mutation {
		products(insert: $data) {
			id
			name
			owner {
				id
				email
			}
		}
	}`

vars := json.RawMessage(`{
		"data": {
			"id": 2007,
			"name": "Product 2007",
			"description": "Description for product 2007",
			"price": 2011.5,
			"tags": ["Tag 1", "Tag 2"],
			"categoryIds": [1, 2, 3, 4, 5]
		}
	}`)

conf := newConfig(&core.Config{DBType: dbType, DisableAllowList: true, EnableCamelcase: true})
err := conf.AddRoleTable("user", "products", core.Insert{
	Presets: map[string]string{"ownerId": "$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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":[{"id":2007,"name":"Product 2007","owner":{"email":"user3@test.com","id":3}}]}
Example (InsertWithPresets)
gql := `mutation {
		products(insert: $data) {
			id
			name
			owner {
				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 := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":[{"id":2001,"name":"Product 2001","owner":{"email":"user3@test.com","id":3}}]}
Example (InsertWithTransaction)
gql := `mutation {
		users(insert: {
			id: $id,
			email: $email,
			full_name: $fullName,
			stripe_id: $stripeID,
			category_counts: $categoryCounts
		}) {
			id
			email
		}
	}`

vars := json.RawMessage(`{
		"id": 1007,
		"email": "user1007@test.com",
		"fullName": "User 1007",
		"stripeID": "payment_id_1007",
		"categoryCounts": [{"category_id": 1, "count": 400},{"category_id": 2, "count": 600}]
	}`)

conf := newConfig(&core.Config{DBType: dbType, DisableAllowList: true})
gj, err := core.NewGraphJin(conf, db)
if err != nil {
	panic(err)
}

c := context.Background()
tx, err := db.BeginTx(c, nil)
if err != nil {
	panic(err)
}
defer tx.Rollback() //nolint:errcheck

c = context.WithValue(c, core.UserIDKey, 3)
res, err := gj.GraphQLTx(c, tx, gql, vars, nil)
if err != nil {
	fmt.Println(err)
	return
}
if err := tx.Commit(); err != nil {
	panic(err)
}
printJSON(res.Data)
Output:

{"users":[{"email":"user1007@test.com","id":1007}]}
Example (Query)
gql := `
	query {
		products(limit: 3) {
			id
			owner {
				id
				fullName: full_name
			}
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":[{"id":1,"owner":{"fullName":"User 1","id":1}},{"id":2,"owner":{"fullName":"User 2","id":2}},{"id":3,"owner":{"fullName":"User 3","id":3}}]}
Example (QueryBlockWithRoles)
gql := `query {
		users {
			id
			full_name
			email
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"users":null}
Example (QueryByID)
gql := `query {
		products(id: $id) {
			id
			name
		}
	}`

vars := json.RawMessage(`{
		"id": 2
	}`)

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":{"id":2,"name":"Product 2"}}
Example (QueryBySearch)
gql := `query {
		products(search: $query, limit: 5) {
			id
			name
		}
	}`

vars := json.RawMessage(`{
		"query": "\"Product 3\""
	}`)

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":[{"id":3,"name":"Product 3"}]}
Example (QueryChildrenWithParent)
gql := `query {
		products(limit: 2) {
			name
			price
			owner {
				email
			}
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":[{"name":"Product 1","owner":{"email":"user1@test.com"},"price":11.5},{"name":"Product 2","owner":{"email":"user2@test.com"},"price":12.5}]}
Example (QueryInTransaction)
gql := `
	query {
		products(limit: 3) {
			id
			owner {
				id
				fullName: full_name
			}
		}
	}`

conf := newConfig(&core.Config{DBType: dbType, DisableAllowList: true})
gj, err := core.NewGraphJin(conf, db)
if err != nil {
	panic(err)
}

c := context.Background()
tx, err := db.BeginTx(c, nil)
if err != nil {
	panic(err)
}
defer tx.Rollback() //nolint:errcheck

res, err := gj.GraphQLTx(c, tx, gql, nil, nil)
if err != nil {
	fmt.Println(err)
	return
}
if err := tx.Commit(); err != nil {
	panic(err)
}
printJSON(res.Data)
Output:

{"products":[{"id":1,"owner":{"fullName":"User 1","id":1}},{"id":2,"owner":{"fullName":"User 2","id":2}},{"id":3,"owner":{"fullName":"User 3","id":3}}]}
Example (QueryManyToManyViaJoinTable1)
gql := `query {
		products(limit: 2) {
			name
			customer {
				email
			}
			owner {
				email
			}
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":[{"customer":[{"email":"user2@test.com"}],"name":"Product 1","owner":{"email":"user1@test.com"}},{"customer":[{"email":"user3@test.com"}],"name":"Product 2","owner":{"email":"user2@test.com"}}]}
Example (QueryManyToManyViaJoinTable2)
gql := `query {
		users {
			email
			full_name
			products {
				name
			}
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"users":[{"email":"user1@test.com","full_name":"User 1","products":[{"name":"Product 1"}]},{"email":"user2@test.com","full_name":"User 2","products":[{"name":"Product 2"}]}]}
Example (QueryParentAndChildrenViaArrayColumn)
gql := `
	query {
		products(limit: 2) {
			name
			price
			categories {
				id
				name
			}
		}
		categories {
			name
			products {
				name
			}
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"categories":[{"name":"Category 1","products":[{"name":"Product 1"},{"name":"Product 2"}]},{"name":"Category 2","products":[{"name":"Product 1"},{"name":"Product 2"}]}],"products":[{"categories":[{"id":1,"name":"Category 1"},{"id":2,"name":"Category 2"}],"name":"Product 1","price":11.5},{"categories":[{"id":1,"name":"Category 1"},{"id":2,"name":"Category 2"}],"name":"Product 2","price":12.5}]}
Example (QueryParentsWithChildren)
gql := `query {
		users(order_by: { id: asc }, limit: 2) {
			email
			products {
				name
				price
			}
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(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 (QueryWithAddAndRemoveDirective1)
gql := `
	query {
		products(limit: 2, order_by: { id: asc }) @add(ifRole: "user") {
			id
			name
		}
		users(limit: 3, order_by: { id: asc }) @remove(ifRole: "user") {
			id
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"users":[{"id":1},{"id":2},{"id":3}]}
Example (QueryWithAddAndRemoveDirective2)
gql := `
	query {
		products(limit: 2, order_by: { id: asc })  {
			id 
			name @add(ifRole: "user")
		}
		users(limit: 3, order_by: { id: asc })  {
			id @remove(ifRole: "anon")
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":[{"id":1},{"id":2}],"users":[{},{},{}]}
Example (QueryWithAggregation)
gql := `query {
		products(where: { id: { lteq: 100 } }) {
			count_id
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":[{"count_id":100}]}
Example (QueryWithAggregationBlockedColumn)
gql := `query {
		products {
			sum_price
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

db column blocked: price (role: 'anon')
Example (QueryWithAlternateFieldNames)
gql := `query {
		comments(limit: 2) {
			id
			commenter {
				email
			}
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"comments":[{"commenter":{"email":"user1@test.com"},"id":1},{"commenter":{"email":"user2@test.com"},"id":2}]}
Example (QueryWithCamelToSnakeCase)
gql := `query {
		hotProducts(where: { productID: { eq: 55 } }, order_by: { productID: desc }) {
			countryCode
			countProductID
			products {
				id
			}
		}
	}`

conf := newConfig(&core.Config{DBType: dbType, DisableAllowList: true, EnableCamelcase: true})
conf.Tables = []core.Table{
	{
		Name: "hot_products",
		Columns: []core.Column{
			{Name: "product_id", Type: "int", ForeignKey: "products.id"},
		},
	},
}

gj, err := core.NewGraphJin(conf, db)
if err != nil {
	panic(err)
}

res, err := gj.GraphQL(context.Background(), gql, nil, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"hotProducts":[{"countProductID":1,"countryCode":"US","products":{"id":55}}]}
Example (QueryWithCursorPagination1)
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 := newConfig(&core.Config{
	DBType:           dbType,
	DisableAllowList: true,
	SecretKey:        "not_a_real_secret",
})
gj, err := core.NewGraphJin(conf, db)
if err != nil {
	panic(err)
}

res, err := gj.GraphQL(context.Background(), gql, vars, nil)
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 (QueryWithCursorPagination2)
gql := `query {
		products(
			first: 1
			after: $cursor
			where: { id: { lteq: 100 }}
			order_by: { price: desc }) {
			name
		}
		products_cursor
	}`

conf := newConfig(&core.Config{
	DBType:           dbType,
	DisableAllowList: true,
	SecretKey:        "not_a_real_secret",
})
gj, err := core.NewGraphJin(conf, db)
if err != nil {
	panic(err)
}

type result struct {
	Products json.RawMessage `json:"products"`
	Cursor   string          `json:"products_cursor"`
}

var val result
for i := 0; i < 25; i++ {
	vars := json.RawMessage(
		`{"cursor": "` + val.Cursor + `"}`)

	res, err := gj.GraphQL(context.Background(), gql, vars, nil)
	if err != nil {
		fmt.Println(err)
		return
	}

	if err := json.Unmarshal(res.Data, &val); err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(string(val.Products))
}
Output:

[{"name": "Product 100"}]
[{"name": "Product 99"}]
[{"name": "Product 98"}]
[{"name": "Product 97"}]
[{"name": "Product 96"}]
[{"name": "Product 95"}]
[{"name": "Product 94"}]
[{"name": "Product 93"}]
[{"name": "Product 92"}]
[{"name": "Product 91"}]
[{"name": "Product 90"}]
[{"name": "Product 89"}]
[{"name": "Product 88"}]
[{"name": "Product 87"}]
[{"name": "Product 86"}]
[{"name": "Product 85"}]
[{"name": "Product 84"}]
[{"name": "Product 83"}]
[{"name": "Product 82"}]
[{"name": "Product 81"}]
[{"name": "Product 80"}]
[{"name": "Product 79"}]
[{"name": "Product 78"}]
[{"name": "Product 77"}]
[{"name": "Product 76"}]
Example (QueryWithDynamicOrderBy)
gql := `
	query getProducts {
		products(order_by: $order, where: { id: { lt: 6 } }, limit: 5) {
			id
			price
		}
	}`

conf := newConfig(&core.Config{
	DBType:           dbType,
	DisableAllowList: true,
	Tables: []core.Table{{
		Name: "products",
		OrderBy: map[string][]string{
			"price_and_id": {"price desc", "id asc"},
			"just_id":      {"id asc"},
		},
	}},
})

gj, err := core.NewGraphJin(conf, db)
if err != nil {
	panic(err)
}

vars1 := json.RawMessage(`{ "order": "price_and_id" }`)

res1, err1 := gj.GraphQL(context.Background(), gql, vars1, nil)
if err != nil {
	fmt.Println(err1)
} else {
	printJSON(res1.Data)
}

vars2 := json.RawMessage(`{ "order": "just_id" }`)

res2, err2 := gj.GraphQL(context.Background(), gql, vars2, nil)
if err != nil {
	fmt.Println(err2)
} else {
	printJSON(res2.Data)
}
Output:

{"products":[{"id":5,"price":15.5},{"id":4,"price":14.5},{"id":3,"price":13.5},{"id":2,"price":12.5},{"id":1,"price":11.5}]}
{"products":[{"id":1,"price":11.5},{"id":2,"price":12.5},{"id":3,"price":13.5},{"id":4,"price":14.5},{"id":5,"price":15.5}]}
Example (QueryWithFragments1)
gql := `
	fragment userFields1 on user {
		id
		email
	}

	query {
		users {
			...userFields2
			stripe_id
			...userFields1
		}
	}

	fragment userFields2 on user {
		full_name
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"users":[{"email":"user1@test.com","full_name":"User 1","id":1,"stripe_id":"payment_id_1001"},{"email":"user2@test.com","full_name":"User 2","id":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 := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"users":[{"email":"user1@test.com","full_name":"User 1","id":1,"stripe_id":"payment_id_1001"},{"email":"user2@test.com","full_name":"User 2","id":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 := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"users":[{"email":"user1@test.com","full_name":"User 1","id":1,"stripe_id":"payment_id_1001"},{"email":"user2@test.com","full_name":"User 2","id":2,"stripe_id":"payment_id_1002"}]}
Example (QueryWithFunctionAndDirectives)
gql := `
	query {
		products(id: 51) {
			id
			name
			is_hot_product(args: {id: id}, skipIf: { id: { eq: 51 } })
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":{"id":51,"is_hot_product":null,"name":"Product 51"}}
Example (QueryWithFunctionFields)
gql := `
	query {
		products(id: 51) {
			id
			name
			is_hot_product(args: { id: id })
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":{"id":51,"is_hot_product":true,"name":"Product 51"}}
Example (QueryWithFunctionFieldsArgList)
gql := `
	query {
		products(id: 51) {
			id
			name
			is_hot_product(args: {a0: 51})
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":{"id":51,"is_hot_product":true,"name":"Product 51"}}
Example (QueryWithFunctionReturingTables)
gql := `query {
		get_oldest5_products(limit: 3) {
			id
			name
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"get_oldest5_products":[{"id":1,"name":"Product 1"},{"id":2,"name":"Product 2"},{"id":3,"name":"Product 3"}]}
Example (QueryWithFunctionReturingTablesWithArgs)
gql := `query {
		get_oldest_users(limit: 2, args: {a0: 4, a1: $tag}) {
			tag_name
			id
			full_name
		}
	}`

conf := newConfig(&core.Config{DBType: dbType, DisableAllowList: true})
gj, err := core.NewGraphJin(conf, db)
if err != nil {
	panic(err)
}

vars := json.RawMessage(`{ "tag": "boo" }`)
res, err := gj.GraphQL(context.Background(), gql, vars, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"get_oldest_users":[{"full_name":"User 1","id":1,"tag_name":"boo"},{"full_name":"User 2","id":2,"tag_name":"boo"}]}
Example (QueryWithFunctionReturingTablesWithNamedArgs)
gql := `query {
		get_oldest_users(limit: 2, args: { user_count: 4, tag: $tag }) {
			tag_name
			id
			full_name
		}
	}`

conf := newConfig(&core.Config{DBType: dbType, DisableAllowList: true})
gj, err := core.NewGraphJin(conf, db)
if err != nil {
	panic(err)
}

vars := json.RawMessage(`{ "tag": "boo" }`)
res, err := gj.GraphQL(context.Background(), gql, vars, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"get_oldest_users":[{"full_name":"User 1","id":1,"tag_name":"boo"},{"full_name":"User 2","id":2,"tag_name":"boo"}]}
Example (QueryWithFunctionReturingUserDefinedTypes)
gql := `query {
		get_product(limit: 2, args: { a0: 1 }) {
			id
			name
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"get_product":[{"id":1,"name":"Product 1"},{"id":2,"name":"Product 2"}]}
Example (QueryWithFunctionsBlocked)
gql := `query {
		products {
			sum_price
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

all db functions blocked: sum (role: 'anon')
Example (QueryWithFunctionsWithWhere)
gql := `query {
		products(where: { id: { lesser_or_equals: 100 } }) {
			max_price
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":[{"max_price":110.5}]}
Example (QueryWithJsonColumn)
gql := `query {
		users(id: 1) {
			id
			category_counts {
				count
				category {
					name
				}
			}
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"users":{"category_counts":[{"category":{"name":"Category 1"},"count":400},{"category":{"name":"Category 2"},"count":600}],"id":1}}
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 := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(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 {
		products(id: $id) {
			id
			name
			customer {
				email
			}
		}
		users(id: $id) {
			id
			email
		}
		purchases(id: $id) {
			id
		}
	}`

vars := json.RawMessage(`{ "id": 1 }`)

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":{"customer":[{"email":"user2@test.com"}],"id":1,"name":"Product 1"},"purchases":{"id":1},"users":{"email":"user1@test.com","id":1}}
Example (QueryWithNestedOrderBy)
gql := `
	query getProducts {
		products(order_by: { users: { email: desc }, id: desc}, where: { id: { lt: 6 } }, limit: 5) {
			id
			price
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":[{"id":5,"price":15.5},{"id":4,"price":14.5},{"id":3,"price":13.5},{"id":2,"price":12.5},{"id":1,"price":11.5}]}
Example (QueryWithOrderByList)
gql := `
	query getProducts {
		products(order_by: { id: [$list, "asc"] }, where: { id: { in: $list } }, limit: 5) {
			id
			price
		}
	}`

conf := newConfig(&core.Config{
	DBType:           dbType,
	DisableAllowList: true,
})

gj, err := core.NewGraphJin(conf, db)
if err != nil {
	panic(err)
}

vars := json.RawMessage(`{ "list": [3, 2, 1, 5] }`)
res, err := gj.GraphQL(context.Background(), gql, vars, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":[{"id":3,"price":13.5},{"id":2,"price":12.5},{"id":1,"price":11.5},{"id":5,"price":15.5}]}
Example (QueryWithRecursiveRelationship1)
gql := `query {
		reply : comments(id: $id) {
			id
			comments(
				where: { id: { lt: 50 } }, 
				limit: 5,
				find: "parents") {
				id
			}
		}
	}`

vars := json.RawMessage(`{"id": 50 }`)

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"reply":{"comments":[{"id":49},{"id":48},{"id":47},{"id":46},{"id":45}],"id":50}}
Example (QueryWithRecursiveRelationship2)
gql := `query {
		comments(id: 95) {
			id
			replies: comments(find: "children") {
				id
			}
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"comments":{"id":95,"replies":[{"id":96},{"id":97},{"id":98},{"id":99},{"id":100}]}}
Example (QueryWithRecursiveRelationshipAndAggregations)
gql := `query {
		comments(id: 95) {
			id
			replies: comments(find: "children") {
				count_id
			}
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"comments":{"id":95,"replies":[{"count_id":5}]}}
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("localhost:12345", nil)) //nolint:gosec
}()

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(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 (QueryWithScriptDirective)
gql := `query @script(name: "test.js") {
		usersByID(id: $id)  {
			id
			email
		}
	}`

script := `
	function request(vars) {
		return { id: 2 };
	}

	function response(json) {
		json.usersByID.email = "u...@test.com";
		return json;
	}
	`

dir, err := os.MkdirTemp("", "test")
if err != nil {
	panic(err)
}
defer os.RemoveAll(dir)

fs := fs.NewOsFSWithBase(dir)
if err := fs.CreateDir("scripts"); err != nil {
	panic(err)
}
err = fs.CreateFile(filepath.Join("scripts", "test.js"), []byte(script))
if err != nil {
	panic(err)
}

conf := newConfig(&core.Config{DBType: dbType, DisableAllowList: true})
gj, err := core.NewGraphJin(conf, db,
	core.OptionSetFS(fs),
	core.OptionSetScriptCompiler([]string{".js"}, js.New()))
if err != nil {
	panic(err)
}

res, err := gj.GraphQL(context.Background(), gql, nil, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"usersByID":{"email":"u...@test.com","id":2}}
Example (QueryWithScriptDirectiveUsingGraphQL)
gql := `query @script(name: "test.js") {
		usersByID(id: 2)  {
			id
			email
		}
	}`

script := `
	function response(json) {
		let val = graphql('query { users(id: 1) { id email } }')
		json.usersByID.email = val.users.email
		return json;
	}
	`

dir, err := os.MkdirTemp("", "test")
if err != nil {
	panic(err)
}
defer os.RemoveAll(dir)

fs := fs.NewOsFSWithBase(dir)
if err := fs.CreateDir("scripts"); err != nil {
	panic(err)
}
err = fs.CreateFile(filepath.Join("scripts", "test.js"), []byte(script))
if err != nil {
	panic(err)
}

conf := newConfig(&core.Config{DBType: dbType, DisableAllowList: true})
gj, err := core.NewGraphJin(conf, db,
	core.OptionSetFS(fs),
	core.OptionSetScriptCompiler([]string{".js"}, js.New()))
if err != nil {
	panic(err)
}

res, err := gj.GraphQL(context.Background(), gql, nil, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"usersByID":{"email":"user1@test.com","id":2}}
Example (QueryWithScriptDirectiveUsingHttp)
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, `{ "hello": "world" }`)
}))
defer ts.Close()

gql := `query @script(name: "test.js") {
		usersByID(id: 2)  {
			id
			email
		}
	}`

script := `
	function response(json) {
		let val = http.get("` + ts.URL + `")
		return JSON.parse(val);
	}
	`

dir, err := os.MkdirTemp("", "test")
if err != nil {
	panic(err)
}
defer os.RemoveAll(dir)

fs := fs.NewOsFSWithBase(dir)
if err := fs.CreateDir("scripts"); err != nil {
	panic(err)
}
err = fs.CreateFile(filepath.Join("scripts", "test.js"), []byte(script))
if err != nil {
	panic(err)
}

conf := newConfig(&core.Config{DBType: dbType, DisableAllowList: true})
gj, err := core.NewGraphJin(conf, db,
	core.OptionSetFS(fs),
	core.OptionSetScriptCompiler([]string{".js"}, js.New()))
if err != nil {
	panic(err)
}

res, err := gj.GraphQL(context.Background(), gql, nil, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"hello":"world"}
Example (QueryWithSkipAndIncludeDirective1)
gql := `
	query {
		products(limit: 2, order_by: { id: asc }) @include(ifRole: "user") {
			id
			name
		}
		users(limit: 3, order_by: { id: asc }) @skip(ifRole: "user") {
			id
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":null,"users":[{"id":1},{"id":2},{"id":3}]}
Example (QueryWithSkipAndIncludeDirective2)
gql := `
	query {
		products(limit: 2, order_by: { id: asc })  {
			id @skip(ifRole: "user")
			name @include(ifRole: "user")
		}
		users(limit: 3, order_by: { id: asc })  {
			id @include(ifRole: "anon")
		}
	}`

conf := newConfig(&core.Config{DBType: dbType, DisableAllowList: true})
gj, err := core.NewGraphJin(conf, db)
if err != nil {
	panic(err)
}

c := context.WithValue(context.Background(), core.UserIDKey, 1)
res, err := gj.GraphQL(c, gql, nil, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":[{"id":null,"name":"Product 1"},{"id":null,"name":"Product 2"}],"users":[{"id":null},{"id":null},{"id":null}]}
Example (QueryWithSkipAndIncludeDirective3)
gql := `
	query {
		products(limit: 2) @include(ifVar: $test) {
			id
			name
		}
		users(limit: 3) @skip(ifVar: $test) {
			id
		}
	}`

vars := json.RawMessage(`{ "test": true }`)

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":[{"id":1,"name":"Product 1"},{"id":2,"name":"Product 2"}],"users":null}
Example (QueryWithSkipAndIncludeDirective4)
gql := `
	query {
		products(limit: 2)  {
			id @skip(ifVar: $test)
			name @include(ifVar: $test)
		}
		users(limit: 3)  {
			id @skip(ifVar: $test)
		}
	}`

vars := json.RawMessage(`{ "test": true }`)

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":[{"id":null,"name":"Product 1"},{"id":null,"name":"Product 2"}],"users":[{"id":null},{"id":null},{"id":null}]}
Example (QueryWithSkipAndIncludeIfArg)
gql := `
	query {
		products(limit: 2, order_by: { id: asc })  {
			id(includeIf: { id: { eq: 1 } })
			name
		}
		users(limit: 3, order_by: { id: asc })  {
			id(skipIf: { id: { eq: 2 } })
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":[{"id":1,"name":"Product 1"},{"id":null,"name":"Product 2"}],"users":[{"id":1},{"id":null},{"id":3}]}
Example (QueryWithSkippingAuthRequiredSelectors)
gql := `query {
		products(limit: 2) {
			id
			name
			owner(where: { id: { eq: $user_id } }) {
				id
				email
			}
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":[{"id":1,"name":"Product 1","owner":null},{"id":2,"name":"Product 2","owner":null}]}
Example (QueryWithSyntheticTables)
gql := `query {
		me @object {
			email
		}
	}`

conf := newConfig(&core.Config{DBType: dbType, DisableAllowList: true})
conf.Tables = []core.Table{{Name: "me", Table: "users"}}
err := conf.AddRoleTable("user", "me", core.Query{
	Filters: []string{`{ id: $user_id }`},
	Limit:   1,
})
if err != nil {
	panic(err)
}

gj, err := core.NewGraphJin(conf, db)
if err != nil {
	panic(err)
}

ctx := context.WithValue(context.Background(), core.UserIDKey, 1)
res, err := gj.GraphQL(ctx, gql, nil, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"me":{"email":"user1@test.com"}}
Example (QueryWithTypename)
gql := `query getUser {
		__typename
		users(id: 1) {
		  id
		  email
		  __typename
		}
	  }`

conf := newConfig(&core.Config{DBType: dbType, DisableAllowList: true})
gj, err := core.NewGraphJin(conf, db)
if err != nil {
	panic(err)
}

res, err := gj.GraphQL(context.Background(), gql, json.RawMessage(`{"id":2}`), nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"__typename":"getUser","users":{"__typename":"users","email":"user1@test.com","id":1}}
Example (QueryWithUnionForPolymorphicRelationships)
gql := `
	fragment userFields on user {
		email
	}

	fragment productFields on product {
		name
	}

	query {
		notifications {
			id
			verb
			subject {
				...on users {
					...userFields
				}
				...on products {
					...productFields
				}
			}
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"notifications":[{"id":1,"subject":{"email":"user1@test.com"},"verb":"Joined"},{"id":2,"subject":{"name":"Product 2"},"verb":"Bought"}]}
Example (QueryWithUser)
gql := `
	query {
		products(where: { owner_id: { eq: $user_id } }) {
			id
			owner {
				id
			}
		}
	}`

conf := newConfig(&core.Config{DBType: dbType, DisableAllowList: true})
gj, err := core.NewGraphJin(conf, db)
if err != nil {
	panic(err)
}

ctx := context.WithValue(context.Background(), core.UserIDKey, 31)
res, err := gj.GraphQL(ctx, gql, nil, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":[{"id":31,"owner":{"id":31}}]}
Example (QueryWithVariableLimit)
gql := `query {
		products(limit: $limit) {
			id
		}
	}`

vars := json.RawMessage(`{
		"limit": 10
	}`)

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":[{"id":1},{"id":2},{"id":3},{"id":4},{"id":5},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10}]}
Example (QueryWithVariables)
gql := `query {
		products(id: $product_id, where: { price: { gt: $product_price } }) {
			id
			name
		}
	}`

vars := json.RawMessage(`{ "product_id": 70 }`)

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":{"id":70,"name":"Product 70"}}
Example (QueryWithVariablesDefaultValue)
gql := `query ($product_id = 70) {
		products(id: $product_id, where: { price: { gt: $product_price } }) {
			id
			name
		}
	}`

conf := newConfig(&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, nil, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":{"id":70,"name":"Product 70"}}
Example (QueryWithView)
gql := `query {
		hot_products(limit: 3) {
			product {
				id
				name
			}
		}
	}`

conf := newConfig(&core.Config{DBType: dbType, DisableAllowList: true})
conf.Tables = []core.Table{
	{
		Name: "hot_products",
		Columns: []core.Column{
			{Name: "product_id", Type: "int", ForeignKey: "products.id"},
		},
	},
}

gj, err := core.NewGraphJin(conf, db)
if err != nil {
	panic(err)
}

res, err := gj.GraphQL(context.Background(), gql, nil, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"hot_products":[{"product":{"id":51,"name":"Product 51"}},{"product":{"id":52,"name":"Product 52"}},{"product":{"id":53,"name":"Product 53"}}]}
Example (QueryWithWhere1)
gql := `query {
		products(where: {
			id: [3, 34], 
			or: { 
				name: { iregex: $name }, 
				description: { iregex: $name }
			}
		}) {
			id
		}
	}`

vars := json.RawMessage(`{
		"name": "Product 3"
	}`)

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":[{"id":3},{"id":34}]}
Example (QueryWithWhereGreaterThanOrLesserThan)
gql := `query {
		products(
			limit: 3
			where: {
				or: {
					price: { gt: 20 },
					price: { lt: 22 }
				} }
			) {
			id
			name
			price
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(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 (QueryWithWhereHasAnyKey)
gql := `query {
		products(
			where: { metadata: { has_key_any: ["foo", "bar"] } }
			limit: 3
		) {
			id
	}
}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":[{"id":1},{"id":2},{"id":3}]}
Example (QueryWithWhereIn)
gql := `query {
		products(where: { id: { in: $list } }) {
			id
		}
	}`

vars := json.RawMessage(`{
		"list": [1,2,3]
	}`)

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":[{"id":1},{"id":2},{"id":3}]}
Example (QueryWithWhereNotIsNullAndGreaterThan)
gql := `query {
		products(
			where: {
				and: [
					{ not: { id: { is_null: true } } },
					{ price: { gt: 10 } },
				] 
			} 
			limit: 3) {
			id
			name
			price
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(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 (QueryWithWhereOnRelatedTable)
gql := `query {
		products(where: { owner: { id: { or: [ { eq: $user_id }, { eq: 3 } ] } } }, limit: 2) {
			id
			owner {
				id
				email
			}
		}
	}`

conf := newConfig(&core.Config{DBType: dbType, DisableAllowList: true})
gj, err := core.NewGraphJin(conf, db)
if err != nil {
	panic(err)
}

ctx := context.WithValue(context.Background(), core.UserIDKey, 2)
res, err := gj.GraphQL(ctx, gql, nil, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":[{"id":2,"owner":{"email":"user2@test.com","id":2}},{"id":3,"owner":{"email":"user3@test.com","id":3}}]}
Example (SetArrayColumnToEmpty)
gql := `mutation {
		products(where: { id: 100 }, update: { tags: [] }) {
			id
			tags
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":[{"id":100,"tags":[]}]}
Example (SetArrayColumnToValue)
gql := `mutation {
		products(where: { id: 100 }, update: { tags: ["super", "great", "wow"] }) {
			id
			tags
		}
	}`

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":[{"id":100,"tags":["super","great","wow"]}]}
Example (Subscription)
gql := `subscription test {
		users(id: $id) {
			id
			email
			phone
		}
	}`

vars := json.RawMessage(`{ "id": 3 }`)

conf := newConfig(&core.Config{DBType: dbType, DisableAllowList: true, SubsPollDuration: 1})
gj, err := core.NewGraphJin(conf, db)
if err != nil {
	panic(err)
}

m, err := gj.Subscribe(context.Background(), gql, vars, nil)
if err != nil {
	fmt.Println(err)
	return
}
for i := 0; i < 10; i++ {
	msg := <-m.Result
	printJSON(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)
	if _, err := db.Exec(q); err != nil {
		panic(err)
	}
}
Output:

{"users":{"email":"user3@test.com","id":3,"phone":null}}
{"users":{"email":"user3@test.com","id":3,"phone":"650-447-0000"}}
{"users":{"email":"user3@test.com","id":3,"phone":"650-447-0001"}}
{"users":{"email":"user3@test.com","id":3,"phone":"650-447-0002"}}
{"users":{"email":"user3@test.com","id":3,"phone":"650-447-0003"}}
{"users":{"email":"user3@test.com","id":3,"phone":"650-447-0004"}}
{"users":{"email":"user3@test.com","id":3,"phone":"650-447-0005"}}
{"users":{"email":"user3@test.com","id":3,"phone":"650-447-0006"}}
{"users":{"email":"user3@test.com","id":3,"phone":"650-447-0007"}}
{"users":{"email":"user3@test.com","id":3,"phone":"650-447-0008"}}
Example (SubscriptionWithCursor)
// func TestSubCursor(t *testing.T) {
// query to fetch existing chat messages
// gql1 := `query {
// 	chats(first: 3, after: $cursor) {
// 		id
// 		body
// 	}
// 	chats_cursor
// }`

// query to subscribe to new chat messages
gql2 := `subscription {
		chats(first: 1, after: $cursor) {
			id
			body
		}
	}`

conf := newConfig(&core.Config{
	DBType:           dbType,
	DisableAllowList: true,
	SubsPollDuration: 1,
	SecretKey:        "not_a_real_secret",
})
gj, err := core.NewGraphJin(conf, db)
if err != nil {
	panic(err)
}

// struct to hold the cursor value from fetching the existing
// chat messages
// res := struct {
// 	Cursor string `json:"chats_cursor"`
// }{}

// execute query for existing chat messages
// m1, err := gj.GraphQL(context.Background(), gql1, nil, nil)
// if err != nil {
// 	fmt.Println(err)
// 	return
// }

// extract out the cursor `chats_cursor` to use in the subscription
// if err := json.Unmarshal(m1.Data, &res); err != nil {
// 	fmt.Println(err)
// 	return
// }

// replace cursor value to make test work since it's encrypted
// v1 := cursorRegex.ReplaceAllString(string(m1.Data), "cursor_was_here")
// fmt.Println(v1)

// create variables with the previously extracted cursor value to
// pass to the new chat messages subscription
// vars := json.RawMessage(`{ "cursor": "` + res.Cursor + `" }`)
vars := json.RawMessage(`{ "cursor": null }`)

// subscribe to new chat messages using the cursor
m2, err := gj.Subscribe(context.Background(), gql2, vars, nil)
if err != nil {
	fmt.Println(err)
	return
}

go func() {
	for i := 6; i < 20; i++ {
		// insert a new chat message
		q := fmt.Sprintf(`INSERT INTO chats (id, body) VALUES (%d, 'New chat message %d')`, i, i)
		if _, err := db.Exec(q); err != nil {
			panic(err)
		}
		time.Sleep(3 * time.Second)
	}
}()

for i := 0; i < 19; i++ {
	msg := <-m2.Result
	// replace cursor value to make test work since it's encrypted
	v2 := cursorRegex.ReplaceAllString(string(msg.Data), `cursor":"cursor_was_here`)
	printJSON([]byte(v2))
}
Output:

{"chats":[{"body":"This is chat message number 1","id":1}],"chats_cursor":"cursor_was_here"}
{"chats":[{"body":"This is chat message number 2","id":2}],"chats_cursor":"cursor_was_here"}
{"chats":[{"body":"This is chat message number 3","id":3}],"chats_cursor":"cursor_was_here"}
{"chats":[{"body":"This is chat message number 4","id":4}],"chats_cursor":"cursor_was_here"}
{"chats":[{"body":"This is chat message number 5","id":5}],"chats_cursor":"cursor_was_here"}
{"chats":[{"body":"New chat message 6","id":6}],"chats_cursor":"cursor_was_here"}
{"chats":[{"body":"New chat message 7","id":7}],"chats_cursor":"cursor_was_here"}
{"chats":[{"body":"New chat message 8","id":8}],"chats_cursor":"cursor_was_here"}
{"chats":[{"body":"New chat message 9","id":9}],"chats_cursor":"cursor_was_here"}
{"chats":[{"body":"New chat message 10","id":10}],"chats_cursor":"cursor_was_here"}
{"chats":[{"body":"New chat message 11","id":11}],"chats_cursor":"cursor_was_here"}
{"chats":[{"body":"New chat message 12","id":12}],"chats_cursor":"cursor_was_here"}
{"chats":[{"body":"New chat message 13","id":13}],"chats_cursor":"cursor_was_here"}
{"chats":[{"body":"New chat message 14","id":14}],"chats_cursor":"cursor_was_here"}
{"chats":[{"body":"New chat message 15","id":15}],"chats_cursor":"cursor_was_here"}
{"chats":[{"body":"New chat message 16","id":16}],"chats_cursor":"cursor_was_here"}
{"chats":[{"body":"New chat message 17","id":17}],"chats_cursor":"cursor_was_here"}
{"chats":[{"body":"New chat message 18","id":18}],"chats_cursor":"cursor_was_here"}
{"chats":[{"body":"New chat message 19","id":19}],"chats_cursor":"cursor_was_here"}
Example (Update)
gql := `mutation {
		products(id: $id, update: $data) {
			id
			name
		}
	}`

vars := json.RawMessage(`{ 
		"id": 100,
		"data": { 
			"name": "Updated Product 100",
			"description": "Description for updated product 100"
		} 
	}`)

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"products":{"id":100,"name":"Updated Product 100"}}
Example (UpdateMultipleRelatedTables1)
gql := `mutation {
		purchases(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 := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"purchases":{"customer":{"full_name":"Updated user related to purchase 100"},"product":{"description":"Updated product related to purchase 100"},"quantity":6}}
Example (UpdateTableAndConnectToRelatedTables)
gql := `mutation {
		users(id: $id, update: $data) {
			full_name
			products {
				id
			}
		}
	}`

vars := json.RawMessage(`{
		"id": 100,
		"data": {
			"full_name": "Updated user 100",
			"products": {
				"connect": { "id": 99 },
				"disconnect": { "id": 100 }
			}
		}
	}`)

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"users":{"full_name":"Updated user 100","products":[{"id":99}]}}
Example (UpdateTableAndRelatedTable)
gql := `mutation {
		users(id: $id, update: $data) {
			full_name
			products {
				id
			}
		}
	}`

vars := json.RawMessage(`{
		"id": 90,
		"data": {
			"full_name": "Updated user 90",
			"products": {
				"where": { "id": { "gt": 1 } },
				"name": "Updated Product 90"
			}
		}
	}`)

conf := newConfig(&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, nil)
if err != nil {
	fmt.Println(err)
} else {
	printJSON(res.Data)
}
Output:

{"users":{"full_name":"Updated user 90","products":[{"id":90}]}}
Example (VeryComplexQueryWithArrayColumns)

TODO: Fix: Does not work in MYSQL

gql := `query {
		products(
			# returns only 1 items
			limit: 1,
	
			# starts from item 10, commented out for now
			# offset: 10,
	
			# orders the response items by highest price
			order_by: { price: desc },
	
			# only items with an id >= 30 and < 30 are returned
			where: { id: { and: { greater_or_equals: 20, lt: 28 } } }) {
			id
			name
			price
			owner {
				full_name
				picture : avatar
				email
				category_counts(limit: 2) {
					count
					category {
						name
					}
				}
			}
			category(limit: 2) {
				id
				name
			}
		}
	}`

conf := newConfig(&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"},
		},
	},
	{
		Name:    "products",
		Columns: []core.Column{{Name: "category_ids", ForeignKey: "categories.id"}},
	},
}

gj, err := core.NewGraphJin(conf, db)
if err != nil {
	fmt.Println(err)
	return
}

res, err := gj.GraphQL(context.Background(), gql, nil, nil)
if err != nil {
	fmt.Println(err)
	return
}

printJSON(res.Data)
Output:

{"products":[{"category":[{"id":1,"name":"Category 1"},{"id":2,"name":"Category 2"}],"id":27,"name":"Product 27","owner":{"category_counts":[{"category":{"name":"Category 1"},"count":400},{"category":{"name":"Category 2"},"count":600}],"email":"user27@test.com","full_name":"User 27","picture":null},"price":37.5}]}

Index

Examples

Constants

View Source
const (
	// Name of the authentication provider. Eg. google, github, etc
	UserIDProviderKey contextkey = iota

	// The raw user id (jwt sub) value
	UserIDRawKey

	// 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

View Source
const (
	KIND_SCALAR      = "SCALAR"
	KIND_OBJECT      = "OBJECT"
	KIND_NONNULL     = "NON_NULL"
	KIND_LIST        = "LIST"
	KIND_UNION       = "UNION"
	KIND_ENUM        = "ENUM"
	KIND_INPUT_OBJ   = "INPUT_OBJECT"
	LOC_QUERY        = "QUERY"
	LOC_MUTATION     = "MUTATION"
	LOC_SUBSCRIPTION = "SUBSCRIPTION"
	LOC_FIELD        = "FIELD"

	SUFFIX_EXP      = "Expression"
	SUFFIX_LISTEXP  = "ListExpression"
	SUFFIX_INPUT    = "Input"
	SUFFIX_ORDER_BY = "OrderByInput"
	SUFFIX_WHERE    = "WhereInput"
	SUFFIX_ARGS     = "ArgsInput"
	SUFFIX_ENUM     = "Enum"
)
View Source
const (
	APQ_PX = "_apq"
)

Variables

View Source
var (
	TYPE_STRING  = "String"
	TYPE_INT     = "Int"
	TYPE_BOOLEAN = "Boolean"
	TYPE_FLOAT   = "Float"
	TYPE_JSON    = "JSON"
)
View Source
var (
	ErrNotFound = errors.New("not found in prepared statements")
)

Functions

This section is empty.

Types

type Cache added in v2.0.21

type Cache struct {
	// contains filtered or unexported fields
}

func (Cache) Get added in v2.0.21

func (c Cache) Get(key string) (val []byte, fromCache bool)

func (Cache) Set added in v2.0.21

func (c Cache) Set(key string, val []byte)

type Column

type Column struct {
	Name       string
	Type       string `jsonschema:"example=integer,example=text"`
	Primary    bool
	Array      bool
	ForeignKey string `` /* 138-byte string literal not displayed */
}

Configuration for a database table column

type Config

type Config struct {
	// Is used to encrypt opaque values such as the cursor. Auto-generated when not set
	SecretKey string `mapstructure:"secret_key" json:"secret_key" yaml:"secret_key"  jsonschema:"title=Secret Key"`

	// When set to true it disables the allow list workflow
	DisableAllowList bool `` /* 137-byte string literal not displayed */

	// When set to true a database schema file will be generated in dev mode and
	// used in production mode. Auto database discovery will be disabled
	// in production mode.
	EnableSchema bool `mapstructure:"enable_schema" json:"enable_schema" yaml:"enable_schema" jsonschema:"title=Enable Schema,default=false"`

	// When set to true an introspection json file will be generated in dev mode.
	// This file can be used with other GraphQL tooling to generate clients, enable
	// autocomplete, etc
	EnableIntrospection bool `` /* 152-byte string literal not displayed */

	// Forces the database session variable 'user.id' to be set to the user id
	SetUserID bool `mapstructure:"set_user_id" json:"set_user_id" yaml:"set_user_id" jsonschema:"title=Set User ID,default=false"`

	// This ensures that for anonymous users (role 'anon') all tables are blocked
	// from queries and mutations. To open access to tables for anonymous users
	// they have to be added to the 'anon' role config
	DefaultBlock bool `` /* 135-byte string literal not displayed */

	// This is a list of 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" json:"variables" yaml:"variables" jsonschema:"title=Variables"`

	// This is a list of variables that map to http header values
	HeaderVars map[string]string `mapstructure:"header_variables" json:"header_variables" yaml:"header_variables" jsonschema:"title=Header Variables"`

	// A list of tables and columns that should disallowed in any and all queries
	Blocklist []string `jsonschema:"title=Block List"`

	// The configs for custom resolvers. For example the `remote_api`
	// resolver would join json from a remote API into your query response
	Resolvers []ResolverConfig `jsonschema:"-"`

	// All table specific configuration such as aliased tables and relationships
	// between tables
	Tables []Table `jsonschema:"title=Tables"`

	// An SQL query if set enables attribute based access control. This query is
	// used to fetch the user attribute that then dynamically define the users role
	RolesQuery string `mapstructure:"roles_query" json:"roles_query" yaml:"roles_query" jsonschema:"title=Roles Query"`

	// Roles contains the configuration for all the roles you want to support 'user' and
	// 'anon' are two default roles. The 'user' role is used when a user ID is available
	// and 'anon' when it's not. Use the 'Roles Query' config to add more custom roles
	Roles []Role

	// Database type name Defaults to 'postgres' (options: mysql, postgres)
	DBType string `mapstructure:"db_type" json:"db_type" yaml:"db_type" jsonschema:"title=Database Type,enum=postgres,enum=mysql"`

	// Log warnings and other debug information
	Debug bool `jsonschema:"title=Debug,default=false"`

	// Database polling duration (in seconds) used by subscriptions to
	// query for updates.
	SubsPollDuration time.Duration `` /* 145-byte string literal not displayed */

	// The default max limit (number of rows) when a limit is not defined in
	// the query or the table role config.
	DefaultLimit int `mapstructure:"default_limit" json:"default_limit" yaml:"default_limit" jsonschema:"title=Default Row Limit,default=20"`

	// Disable all aggregation functions like count, sum, etc
	DisableAgg bool `` /* 148-byte string literal not displayed */

	// Disable all functions like count, length,  etc
	DisableFuncs bool `` /* 133-byte string literal not displayed */

	// Enable automatic coversion of camel case in GraphQL to snake case in SQL
	EnableCamelcase bool `` /* 130-byte string literal not displayed */

	// When enabled GraphJin runs with production level security defaults.
	// For example allow lists are enforced.
	Production bool `jsonschema:"title=Production Mode,default=false"`

	// Duration for polling the database to detect schema changes
	DBSchemaPollDuration time.Duration `` /* 172-byte string literal not displayed */

	// When set to true it disables production security features like enforcing the allow list
	DisableProdSecurity bool `` /* 159-byte string literal not displayed */
	// contains filtered or unexported fields
}

Configuration for the GraphJin compiler core

func NewConfig

func NewConfig(configPath, configFile string) (c *Config, err error)

func NewConfigWithFS added in v2.0.9

func NewConfigWithFS(fs plugin.FS, configFile string) (*Config, error)

func (*Config) AddRoleTable

func (c *Config) AddRoleTable(role, table string, conf interface{}) error

AddRoleTable function is a helper function to make it easy to add per-table row-level config

func (*Config) RemoveRoleTable

func (c *Config) RemoveRoleTable(role, table string) error

func (*Config) SetResolver

func (c *Config) SetResolver(name string, fn refunc) error

type Delete

type Delete struct {
	Filters []string
	Columns []string
	Block   bool
}

Table configuration for deleting from a table with a role

type Error

type Error struct {
	Message string `json:"message"`
}

type GraphJin

type GraphJin struct {
	atomic.Value
	// contains filtered or unexported fields
}

func NewGraphJin

func NewGraphJin(conf *Config, db *sql.DB, options ...Option) (g *GraphJin, err error)

NewGraphJin creates the GraphJin struct, this involves querying the database to learn its schemas and relationships

func NewGraphJinWithFS added in v2.0.9

func NewGraphJinWithFS(conf *Config, db *sql.DB, fs plugin.FS, options ...Option) (g *GraphJin, err error)

func (*GraphJin) GraphQL

func (g *GraphJin) GraphQL(c context.Context,
	query string,
	vars json.RawMessage,
	rc *ReqConfig,
) (res *Result, err error)

GraphQL function is our main function it takes a GraphQL query compiles it to SQL and executes returning the resulting JSON.

In production mode the compiling happens only once and from there on the compiled queries are directly executed.

In developer mode all named queries are saved into the queries folder and in production mode only queries from these saved queries can be used.

func (*GraphJin) GraphQLByName

func (g *GraphJin) GraphQLByName(c context.Context,
	name string,
	vars json.RawMessage,
	rc *ReqConfig,
) (res *Result, err error)

GraphQLByName is similar to the GraphQL function except that queries saved in the queries folder can directly be used just by their name (filename).

func (*GraphJin) GraphQLByNameTx added in v2.0.24

func (g *GraphJin) GraphQLByNameTx(c context.Context,
	tx *sql.Tx,
	name string,
	vars json.RawMessage,
	rc *ReqConfig,
) (res *Result, err error)

GraphQLByNameTx is similiar to the GraphQLByName function except that it can be used within a database transactions.

func (*GraphJin) GraphQLTx added in v2.0.24

func (g *GraphJin) GraphQLTx(c context.Context,
	tx *sql.Tx,
	query string,
	vars json.RawMessage,
	rc *ReqConfig,
) (res *Result, err error)

GraphQLTx is similiar to the GraphQL function except that it can be used within a database transactions.

func (*GraphJin) IsProd

func (g *GraphJin) IsProd() bool

IsProd return true for production mode or false for development mode

func (*GraphJin) Reload

func (g *GraphJin) Reload() error

Reload redoes database discover and reinitializes GraphJin.

func (*GraphJin) Subscribe

func (g *GraphJin) Subscribe(
	c context.Context,
	query string,
	vars json.RawMessage,
	rc *ReqConfig,
) (m *Member, err error)

Subscribe function is called on the GraphJin struct to subscribe to query. Any database changes that apply to the query are streamed back in realtime.

In developer mode all named queries are saved into the queries folder and in production mode only queries from these saved queries can be used.

func (*GraphJin) SubscribeByName

func (g *GraphJin) SubscribeByName(
	c context.Context,
	name string,
	vars json.RawMessage,
	rc *ReqConfig,
) (m *Member, err error)

SubscribeByName is similar to the Subscribe function except that queries saved in the queries folder can directly be used by their filename.

type Header struct {
	Type OpType
	Name string
}

func Operation

func Operation(query string) (h Header, err error)

Operation function return the operation type and name from the query. It uses a very fast algorithm to extract the operation without having to parse the query.

type Insert

type Insert struct {
	Filters []string
	Columns []string
	Presets map[string]string
	Block   bool
}

Table configuration for inserting into a table with a role

type Member

type Member struct {
	Result chan *Result
	// contains filtered or unexported fields
}

func (*Member) String

func (m *Member) String() string

func (*Member) Unsubscribe

func (m *Member) Unsubscribe()

type OpType

type OpType int
const (
	OpUnknown OpType = iota
	OpQuery
	OpSubscription
	OpMutation
)

type Option

type Option func(*graphjin) error

func OptionSetFS

func OptionSetFS(fs plugin.FS) Option

func OptionSetNamespace

func OptionSetNamespace(namespace string) Option

func OptionSetScriptCompiler

func OptionSetScriptCompiler(ext []string, se plugin.ScriptCompiler) Option

func OptionSetValidator

func OptionSetValidator(name string, v plugin.ValidationCompiler) Option

type Query

type Query struct {
	Limit int
	// Use filters to enforce table wide things like { disabled: false } where you never want disabled users to be shown.
	Filters          []string
	Columns          []string
	DisableFunctions bool `mapstructure:"disable_functions" json:"disable_functions" yaml:"disable_functions"`
	Block            bool
}

Table configuration for querying a table with a role

type ReqConfig

type ReqConfig struct {

	// APQKey is set when using GraphJin with automatic persisted queries
	APQKey string

	// Pass additional variables complex variables such as functions that return string values.
	Vars map[string]interface{}

	// Execute this query as part of a transaction
	Tx *sql.Tx
	// contains filtered or unexported fields
}

ReqConfig is used to pass request specific config values to the GraphQLEx and SubscribeEx functions. Dynamic variables can be set here.

func (*ReqConfig) GetNamespace

func (rc *ReqConfig) GetNamespace() (string, bool)

func (*ReqConfig) SetNamespace

func (rc *ReqConfig) SetNamespace(ns string)

SetNamespace is used to set namespace requests within a single instance of GraphJin. For example queries with the same name

type Resolver

type Resolver interface {
	Resolve(context.Context, 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

type ResolverConfig struct {
	Name      string
	Type      string
	Schema    string
	Table     string
	Column    string
	StripPath string        `mapstructure:"strip_path" json:"strip_path" yaml:"strip_path"`
	Props     ResolverProps `mapstructure:",remain"`
}

ResolverConfig struct defines a custom resolver

type ResolverProps

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

type ResolverReq struct {
	ID  string
	Sel *qcode.Select
	Log *log.Logger
	*ReqConfig
}

type Result

type Result struct {
	Vars       json.RawMessage   `json:"-"`
	Data       json.RawMessage   `json:"data,omitempty"`
	Hash       [sha256.Size]byte `json:"-"`
	Errors     []Error           `json:"errors,omitempty"`
	Validation []qcode.ValidErr  `json:"validation,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) CacheControl

func (r *Result) CacheControl() string

func (*Result) Namespace

func (r *Result) Namespace() string

func (*Result) Operation

func (r *Result) Operation() OpType

func (*Result) OperationName

func (r *Result) OperationName() string

func (*Result) QueryName

func (r *Result) QueryName() string

func (*Result) Role

func (r *Result) Role() string

func (*Result) SQL

func (r *Result) SQL() string

type Role

type Role struct {
	Name    string
	Comment string
	Match   string      `jsonschema:"title=Related To,example=other_table.id_column,example=users.id"`
	Tables  []RoleTable `jsonschema:"title=Table Configuration for Role"`
	// contains filtered or unexported fields
}

Configuration for user role

func (*Role) GetTable

func (r *Role) GetTable(schema, name string) *RoleTable

type RoleTable

type RoleTable struct {
	Name     string
	Schema   string
	ReadOnly bool `mapstructure:"read_only" json:"read_only" yaml:"read_only" jsonschema:"title=Read Only"`

	Query  *Query
	Insert *Insert
	Update *Update
	Upsert *Upsert
	Delete *Delete
}

Table configuration for a specific role (user role)

type Table

type Table struct {
	Name      string
	Schema    string
	Table     string // Inherits Table
	Type      string
	Blocklist []string
	Columns   []Column
	// Permitted order by options
	OrderBy map[string][]string `mapstructure:"order_by" json:"order_by" yaml:"order_by" jsonschema:"title=Order By Options,example=created_at desc"`
}

Configuration for a database table

type Update

type Update struct {
	Filters []string
	Columns []string
	Presets map[string]string
	Block   bool
}

Table configuration for updating a table with a role

type Upsert

type Upsert struct {
	Filters []string
	Columns []string
	Presets map[string]string
	Block   bool
}

Table configuration for creating/updating (upsert) a table with a role

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL