Add delete mutation

This commit is contained in:
Vikram Rangnekar 2019-09-06 01:17:45 -04:00
parent e765176bfa
commit fe4d1107ac
4 changed files with 75 additions and 39 deletions

View File

@ -18,10 +18,6 @@ func (co *Compiler) compileMutation(qc *qcode.QCode, w *bytes.Buffer, vars Varia
c := &compilerContext{w, qc.Selects, co} c := &compilerContext{w, qc.Selects, co}
root := &qc.Selects[0] root := &qc.Selects[0]
c.w.WriteString(`WITH `)
c.w.WriteString(root.Table)
c.w.WriteString(` AS (`)
switch root.Action { switch root.Action {
case qcode.ActionInsert: case qcode.ActionInsert:
if _, err := c.renderInsert(qc, w, vars); err != nil { if _, err := c.renderInsert(qc, w, vars); err != nil {
@ -33,12 +29,15 @@ func (co *Compiler) compileMutation(qc *qcode.QCode, w *bytes.Buffer, vars Varia
return 0, err return 0, err
} }
case qcode.ActionDelete:
if _, err := c.renderDelete(qc, w, vars); err != nil {
return 0, err
}
default: default:
return 0, errors.New("valid mutations are 'insert' and 'update'") return 0, errors.New("valid mutations are 'insert' and 'update'")
} }
c.w.WriteString(`) `)
return c.compileQuery(qc, w) return c.compileQuery(qc, w)
} }
@ -60,14 +59,17 @@ func (c *compilerContext) renderInsert(qc *qcode.QCode, w *bytes.Buffer, vars Va
return 0, err return 0, err
} }
c.w.WriteString(`WITH input AS (SELECT {{insert}}::json AS j) INSERT INTO `) c.w.WriteString(`WITH `)
c.w.WriteString(root.Table)
c.w.WriteString(` AS (WITH input AS (SELECT {{insert}}::json AS j) INSERT INTO `)
c.w.WriteString(root.Table) c.w.WriteString(root.Table)
io.WriteString(c.w, ` (`) io.WriteString(c.w, ` (`)
c.renderInsertColumns(qc, w, jt, ti) c.renderInsertUpdateColumns(qc, w, jt, ti)
io.WriteString(c.w, `)`) io.WriteString(c.w, `)`)
c.w.WriteString(` SELECT `) c.w.WriteString(` SELECT `)
c.renderInsertColumns(qc, w, jt, ti) c.renderInsertUpdateColumns(qc, w, jt, ti)
c.w.WriteString(` FROM input i, `) c.w.WriteString(` FROM input i, `)
if array { if array {
@ -78,12 +80,12 @@ func (c *compilerContext) renderInsert(qc *qcode.QCode, w *bytes.Buffer, vars Va
c.w.WriteString(`(NULL::`) c.w.WriteString(`(NULL::`)
c.w.WriteString(root.Table) c.w.WriteString(root.Table)
c.w.WriteString(`, i.j) t RETURNING * `) c.w.WriteString(`, i.j) t RETURNING *) `)
return 0, nil return 0, nil
} }
func (c *compilerContext) renderInsertColumns(qc *qcode.QCode, w *bytes.Buffer, func (c *compilerContext) renderInsertUpdateColumns(qc *qcode.QCode, w *bytes.Buffer,
jt map[string]interface{}, ti *DBTableInfo) (uint32, error) { jt map[string]interface{}, ti *DBTableInfo) (uint32, error) {
i := 0 i := 0
@ -119,13 +121,16 @@ func (c *compilerContext) renderUpdate(qc *qcode.QCode, w *bytes.Buffer, vars Va
return 0, err return 0, err
} }
c.w.WriteString(`WITH input AS (SELECT {{update}}::json AS j) UPDATE `) c.w.WriteString(`WITH `)
c.w.WriteString(root.Table)
c.w.WriteString(` AS (WITH input AS (SELECT {{update}}::json AS j) UPDATE `)
c.w.WriteString(root.Table) c.w.WriteString(root.Table)
io.WriteString(c.w, ` SET (`) io.WriteString(c.w, ` SET (`)
c.renderInsertColumns(qc, w, jt, ti) c.renderInsertUpdateColumns(qc, w, jt, ti)
c.w.WriteString(`) = (SELECT `) c.w.WriteString(`) = (SELECT `)
c.renderInsertColumns(qc, w, jt, ti) c.renderInsertUpdateColumns(qc, w, jt, ti)
c.w.WriteString(` FROM input i, `) c.w.WriteString(` FROM input i, `)
if array { if array {
@ -144,28 +149,28 @@ func (c *compilerContext) renderUpdate(qc *qcode.QCode, w *bytes.Buffer, vars Va
return 0, err return 0, err
} }
io.WriteString(c.w, ` RETURNING *`) io.WriteString(c.w, ` RETURNING *) `)
return 0, nil return 0, nil
} }
func (c *compilerContext) renderUpdateColumns(qc *qcode.QCode, w *bytes.Buffer, func (c *compilerContext) renderDelete(qc *qcode.QCode, w *bytes.Buffer, vars Variables) (uint32, error) {
jt map[string]interface{}, ti *DBTableInfo) (uint32, error) { root := &qc.Selects[0]
i := 0 ti, err := c.schema.GetTable(root.Table)
for _, cn := range ti.ColumnNames { if err != nil {
if _, ok := jt[cn]; !ok { return 0, err
continue
}
if i != 0 {
io.WriteString(c.w, `, `)
}
c.w.WriteString(cn)
c.w.WriteString(` = {{`)
c.w.WriteString(cn)
c.w.WriteString(`}}`)
i++
} }
c.w.WriteString(`DELETE FROM `)
c.w.WriteString(root.Table)
io.WriteString(c.w, ` WHERE `)
if err := c.renderWhere(root, ti); err != nil {
return 0, err
}
io.WriteString(c.w, ` RETURNING *) `)
return 0, nil return 0, nil
} }

View File

@ -2,7 +2,6 @@ package psql
import ( import (
"encoding/json" "encoding/json"
"fmt"
"testing" "testing"
) )
@ -14,7 +13,7 @@ func singleInsert(t *testing.T) {
} }
}` }`
sql := `WITH product AS (WITH input AS (SELECT {{insert}}::json AS j) INSERT INTO product (name, description) SELECT name, description FROM input i, json_populate_record(NULL::product, i.j) t RETURNING * ) SELECT json_object_agg('product', product) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "product_0"."id" AS "id", "product_0"."name" AS "name") AS "sel_0")) AS "product" FROM (SELECT "product"."id", "product"."name" FROM "products" AS "product" WHERE ((("product"."price") > 0) AND (("product"."price") < 8) AND (("id") = 15)) LIMIT ('1') :: integer) AS "product_0" LIMIT ('1') :: integer) AS "done_1337";` sql := `WITH product AS (WITH input AS (SELECT {{insert}}::json AS j) INSERT INTO product (name, description) SELECT name, description FROM input i, json_populate_record(NULL::product, i.j) t RETURNING *) SELECT json_object_agg('product', product) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "product_0"."id" AS "id", "product_0"."name" AS "name") AS "sel_0")) AS "product" FROM (SELECT "product"."id", "product"."name" FROM "products" AS "product" WHERE ((("product"."price") > 0) AND (("product"."price") < 8) AND (("id") = 15)) LIMIT ('1') :: integer) AS "product_0" LIMIT ('1') :: integer) AS "done_1337";`
vars := map[string]json.RawMessage{ vars := map[string]json.RawMessage{
"insert": json.RawMessage(` { "name": "my_name", "woo": { "hoo": "goo" }, "description": "my_desc" }`), "insert": json.RawMessage(` { "name": "my_name", "woo": { "hoo": "goo" }, "description": "my_desc" }`),
@ -38,7 +37,7 @@ func bulkInsert(t *testing.T) {
} }
}` }`
sql := `WITH product AS (WITH input AS (SELECT {{insert}}::json AS j) INSERT INTO product (name, description) SELECT name, description FROM input i, json_populate_recordset(NULL::product, i.j) t RETURNING * ) SELECT json_object_agg('product', product) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "product_0"."id" AS "id", "product_0"."name" AS "name") AS "sel_0")) AS "product" FROM (SELECT "product"."id", "product"."name" FROM "products" AS "product" WHERE ((("product"."price") > 0) AND (("product"."price") < 8) AND (("id") = 15)) LIMIT ('1') :: integer) AS "product_0" LIMIT ('1') :: integer) AS "done_1337";` sql := `WITH product AS (WITH input AS (SELECT {{insert}}::json AS j) INSERT INTO product (name, description) SELECT name, description FROM input i, json_populate_recordset(NULL::product, i.j) t RETURNING *) SELECT json_object_agg('product', product) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "product_0"."id" AS "id", "product_0"."name" AS "name") AS "sel_0")) AS "product" FROM (SELECT "product"."id", "product"."name" FROM "products" AS "product" WHERE ((("product"."price") > 0) AND (("product"."price") < 8) AND (("id") = 15)) LIMIT ('1') :: integer) AS "product_0" LIMIT ('1') :: integer) AS "done_1337";`
vars := map[string]json.RawMessage{ vars := map[string]json.RawMessage{
"insert": json.RawMessage(` [{ "name": "my_name", "woo": { "hoo": "goo" }, "description": "my_desc" }]`), "insert": json.RawMessage(` [{ "name": "my_name", "woo": { "hoo": "goo" }, "description": "my_desc" }]`),
@ -62,7 +61,7 @@ func singleUpdate(t *testing.T) {
} }
}` }`
sql := `test` sql := `WITH product AS (WITH input AS (SELECT {{update}}::json AS j) UPDATE product SET (name, description) = (SELECT name, description FROM input i, json_populate_record(NULL::product, i.j) t) WHERE (("product"."price") > 0) AND (("product"."price") < 8) AND (("product"."id") = 1) AND (("id") = 15) RETURNING *) SELECT json_object_agg('product', product) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "product_0"."id" AS "id", "product_0"."name" AS "name") AS "sel_0")) AS "product" FROM (SELECT "product"."id", "product"."name" FROM "products" AS "product" WHERE ((("product"."price") > 0) AND (("product"."price") < 8) AND (("product"."id") = 1) AND (("id") = 15)) LIMIT ('1') :: integer) AS "product_0" LIMIT ('1') :: integer) AS "done_1337";`
vars := map[string]json.RawMessage{ vars := map[string]json.RawMessage{
"update": json.RawMessage(` { "name": "my_name", "woo": { "hoo": "goo" }, "description": "my_desc" }`), "update": json.RawMessage(` { "name": "my_name", "woo": { "hoo": "goo" }, "description": "my_desc" }`),
@ -73,7 +72,29 @@ func singleUpdate(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
fmt.Println(string(resSQL)) if string(resSQL) != sql {
t.Fatal(errNotExpected)
}
}
func delete(t *testing.T) {
gql := `mutation {
product(delete: true, where: { id: { eq: 1 } }) {
id
name
}
}`
sql := `DELETE FROM product WHERE (("product"."price") > 0) AND (("product"."price") < 8) AND (("product"."id") = 1) RETURNING *) SELECT json_object_agg('product', product) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "product_0"."id" AS "id", "product_0"."name" AS "name") AS "sel_0")) AS "product" FROM (SELECT "product"."id", "product"."name" FROM "products" AS "product" WHERE ((("product"."price") > 0) AND (("product"."price") < 8) AND (("product"."id") = 1)) LIMIT ('1') :: integer) AS "product_0" LIMIT ('1') :: integer) AS "done_1337";`
vars := map[string]json.RawMessage{
"update": json.RawMessage(` { "name": "my_name", "woo": { "hoo": "goo" }, "description": "my_desc" }`),
}
resSQL, err := compileGQLToPSQL(gql, vars)
if err != nil {
t.Fatal(err)
}
if string(resSQL) != sql { if string(resSQL) != sql {
t.Fatal(errNotExpected) t.Fatal(errNotExpected)
@ -84,4 +105,6 @@ func TestCompileInsert(t *testing.T) {
t.Run("singleInsert", singleInsert) t.Run("singleInsert", singleInsert)
t.Run("bulkInsert", bulkInsert) t.Run("bulkInsert", bulkInsert)
t.Run("singleUpdate", singleUpdate) t.Run("singleUpdate", singleUpdate)
t.Run("delete", delete)
} }

View File

@ -42,7 +42,7 @@ func TestMain(m *testing.M) {
"secret", "secret",
"password", "password",
"token", "token",
} },
}) })
if err != nil { if err != nil {

View File

@ -664,10 +664,18 @@ func (com *Compiler) compileArgOffset(sel *Select, arg *Arg) error {
} }
func (com *Compiler) compileArgAction(sel *Select, arg *Arg) error { func (com *Compiler) compileArgAction(sel *Select, arg *Arg) error {
if arg.Val.Type != nodeVar { switch sel.Action {
return fmt.Errorf("value for argument '%s' must be a variable", arg.Name) case ActionDelete:
if arg.Val.Type != nodeBool {
return fmt.Errorf("value for argument '%s' must be a boolean", arg.Name)
}
default:
if arg.Val.Type != nodeVar {
return fmt.Errorf("value for argument '%s' must be a variable", arg.Name)
}
sel.ActionVar = arg.Val.Val
} }
sel.ActionVar = arg.Val.Val
return nil return nil
} }