Skip to content

Commit

Permalink
Feat: new has_in_common expression
Browse files Browse the repository at this point in the history
  • Loading branch information
dosco committed Oct 27, 2022
1 parent 4d87517 commit 940ad6d
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 167 deletions.
167 changes: 167 additions & 0 deletions core/array_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
//go:build !mysql

package core_test

import (
"context"
"encoding/json"
"fmt"

"github.com/dosco/graphjin/core"
)

func 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}]}
}

func 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"}]}
}

// TODO: Fix: Does not work in MYSQL
func Example_veryComplexQueryWithArrayColumns() {
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":"[email protected]","full_name":"User 27","picture":null},"price":37.5}]}
}
45 changes: 0 additions & 45 deletions core/insert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -490,51 +490,6 @@ func Example_insertIntoTableAndConnectToRelatedTables() {
// Output: {"products":[{"id":2005,"name":"Product 2005","owner":{"email":"[email protected]","full_name":"User 6","id":6}}]}
}

func 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"}]}
}

func Example_insertWithCamelToSnakeCase() {
gql := `mutation {
products(insert: $data) {
Expand Down
53 changes: 27 additions & 26 deletions core/internal/psql/exp.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,8 @@ func (c *expContext) renderOp(ex *qcode.Exp) {
c.w.WriteString(`@>`)
case qcode.OpContainedIn:
c.w.WriteString(`<@`)
case qcode.OpHasInCommon:
c.w.WriteString(`&&`)
case qcode.OpHasKey:
c.w.WriteString(`?`)
case qcode.OpHasKeyAny:
Expand Down Expand Up @@ -288,9 +290,10 @@ func (c *expContext) renderOp(ex *qcode.Exp) {
}

func (c *expContext) renderValPrefix(ex *qcode.Exp) bool {
if c.ct == "mysql" && (ex.Op == qcode.OpHasKey ||
switch {
case c.ct == "mysql" && (ex.Op == qcode.OpHasKey ||
ex.Op == qcode.OpHasKeyAny ||
ex.Op == qcode.OpHasKeyAll) {
ex.Op == qcode.OpHasKeyAll):
var optype string
switch ex.Op {
case qcode.OpHasKey, qcode.OpHasKeyAny:
Expand All @@ -306,30 +309,34 @@ func (c *expContext) renderValPrefix(ex *qcode.Exp) bool {
}
c.w.WriteString(") = 1")
return true
}

if ex.Right.ValType == qcode.ValVar {
return c.renderValVarPrefix(ex)
}
return false
}

func (c *expContext) renderValVarPrefix(ex *qcode.Exp) bool {
if ex.Op == qcode.OpIn || ex.Op == qcode.OpNotIn {
if c.ct == "mysql" {
c.w.WriteString(`JSON_CONTAINS(`)
c.renderParam(Param{Name: ex.Right.Val, Type: ex.Left.Col.Type, IsArray: true})
c.w.WriteString(`, CAST(`)
c.colWithTable(c.ti.Name, ex.Left.Col.Name)
c.w.WriteString(` AS JSON), '$')`)
return true
}
case c.ct == "mysql" && ex.Right.ValType == qcode.ValVar &&
(ex.Op == qcode.OpIn || ex.Op == qcode.OpNotIn):
c.w.WriteString(`JSON_CONTAINS(`)
c.renderParam(Param{Name: ex.Right.Val, Type: ex.Left.Col.Type, IsArray: true})
c.w.WriteString(`, CAST(`)
c.colWithTable(c.ti.Name, ex.Left.Col.Name)
c.w.WriteString(` AS JSON), '$')`)
return true
}
return false
}

func (c *expContext) renderVal(ex *qcode.Exp) {
if ex.Right.Col.Name != "" {
switch {
case ex.Right.ValType == qcode.ValVar:
c.renderValVar(ex)

case !ex.Right.Col.Array && (ex.Op == qcode.OpContains ||
ex.Op == qcode.OpContainedIn ||
ex.Op == qcode.OpHasInCommon):
c.w.WriteString(`CAST(ARRAY[`)
c.colWithTable(c.ti.Name, ex.Right.Col.Name)
c.w.WriteString(`] AS `)
c.w.WriteString(ex.Right.Col.Type)
c.w.WriteString(`[])`)

case ex.Right.Col.Name != "":
var table string
if ex.Right.Table == "" {
table = ex.Right.Col.Table
Expand All @@ -353,12 +360,6 @@ func (c *expContext) renderVal(ex *qcode.Exp) {
}
}
c.w.WriteString(`)`)
return
}

switch ex.Right.ValType {
case qcode.ValVar:
c.renderValVar(ex)

default:
if len(ex.Right.Path) == 0 {
Expand Down
7 changes: 5 additions & 2 deletions core/internal/qcode/exp.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ func (ast *aexpst) parseNode(av aexp, node *graph.Node) (*Exp, error) {
}
setListVal(ex, node)
if ex.Left.Col.Array {
ex.Op = OpContains
ex.Op = OpHasInCommon
} else {
ex.Op = OpIn
}
Expand All @@ -209,7 +209,7 @@ func (ast *aexpst) parseNode(av aexp, node *graph.Node) (*Exp, error) {
return nil, err
}
if ex.Left.Col.Array {
ex.Op = OpContains
ex.Op = OpHasInCommon
setListVal(ex, node)
} else {
if ex.Right.ValType, err = getExpType(node); err != nil {
Expand Down Expand Up @@ -354,6 +354,9 @@ func (ast *aexpst) processOpAndVal(av aexp, ex *Exp, node *graph.Node) (bool, er
case "contained_in":
ex.Op = OpContainedIn
setListVal(ex, node)
case "has_in_common":
ex.Op = OpHasInCommon
setListVal(ex, node)
case "has_key":
ex.Op = OpHasKey
ex.Right.Val = node.Val
Expand Down
35 changes: 18 additions & 17 deletions core/internal/qcode/gen_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions core/internal/qcode/qcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ const (
OpNotIRegex
OpContains
OpContainedIn
OpHasInCommon
OpHasKey
OpHasKeyAny
OpHasKeyAll
Expand Down
Loading

0 comments on commit 940ad6d

Please sign in to comment.