diff --git a/psql/insert.go b/psql/insert.go index ca3118e..131ee4c 100644 --- a/psql/insert.go +++ b/psql/insert.go @@ -18,10 +18,6 @@ func (co *Compiler) compileMutation(qc *qcode.QCode, w *bytes.Buffer, vars Varia c := &compilerContext{w, qc.Selects, co} root := &qc.Selects[0] - c.w.WriteString(`WITH `) - c.w.WriteString(root.Table) - c.w.WriteString(` AS (`) - switch root.Action { case qcode.ActionInsert: 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 } + case qcode.ActionDelete: + if _, err := c.renderDelete(qc, w, vars); err != nil { + return 0, err + } + default: return 0, errors.New("valid mutations are 'insert' and 'update'") } - c.w.WriteString(`) `) - return c.compileQuery(qc, w) } @@ -60,14 +59,17 @@ func (c *compilerContext) renderInsert(qc *qcode.QCode, w *bytes.Buffer, vars Va 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) io.WriteString(c.w, ` (`) - c.renderInsertColumns(qc, w, jt, ti) + c.renderInsertUpdateColumns(qc, w, jt, ti) io.WriteString(c.w, `)`) c.w.WriteString(` SELECT `) - c.renderInsertColumns(qc, w, jt, ti) + c.renderInsertUpdateColumns(qc, w, jt, ti) c.w.WriteString(` FROM input i, `) 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(root.Table) - c.w.WriteString(`, i.j) t RETURNING * `) + c.w.WriteString(`, i.j) t RETURNING *) `) 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) { i := 0 @@ -119,13 +121,16 @@ func (c *compilerContext) renderUpdate(qc *qcode.QCode, w *bytes.Buffer, vars Va 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) io.WriteString(c.w, ` SET (`) - c.renderInsertColumns(qc, w, jt, ti) + c.renderInsertUpdateColumns(qc, w, jt, ti) c.w.WriteString(`) = (SELECT `) - c.renderInsertColumns(qc, w, jt, ti) + c.renderInsertUpdateColumns(qc, w, jt, ti) c.w.WriteString(` FROM input i, `) if array { @@ -144,28 +149,28 @@ func (c *compilerContext) renderUpdate(qc *qcode.QCode, w *bytes.Buffer, vars Va return 0, err } - io.WriteString(c.w, ` RETURNING *`) + io.WriteString(c.w, ` RETURNING *) `) return 0, nil } -func (c *compilerContext) renderUpdateColumns(qc *qcode.QCode, w *bytes.Buffer, - jt map[string]interface{}, ti *DBTableInfo) (uint32, error) { +func (c *compilerContext) renderDelete(qc *qcode.QCode, w *bytes.Buffer, vars Variables) (uint32, error) { + root := &qc.Selects[0] - i := 0 - for _, cn := range ti.ColumnNames { - if _, ok := jt[cn]; !ok { - continue - } - if i != 0 { - io.WriteString(c.w, `, `) - } - c.w.WriteString(cn) - c.w.WriteString(` = {{`) - c.w.WriteString(cn) - c.w.WriteString(`}}`) - i++ + ti, err := c.schema.GetTable(root.Table) + if err != nil { + return 0, err } + 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 } diff --git a/psql/insert_test.go b/psql/insert_test.go index 6542361..98ec870 100644 --- a/psql/insert_test.go +++ b/psql/insert_test.go @@ -2,7 +2,6 @@ package psql import ( "encoding/json" - "fmt" "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{ "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{ "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{ "update": json.RawMessage(` { "name": "my_name", "woo": { "hoo": "goo" }, "description": "my_desc" }`), @@ -73,7 +72,29 @@ func singleUpdate(t *testing.T) { 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 { t.Fatal(errNotExpected) @@ -84,4 +105,6 @@ func TestCompileInsert(t *testing.T) { t.Run("singleInsert", singleInsert) t.Run("bulkInsert", bulkInsert) t.Run("singleUpdate", singleUpdate) + t.Run("delete", delete) + } diff --git a/psql/select_test.go b/psql/select_test.go index acbbfed..77b83a8 100644 --- a/psql/select_test.go +++ b/psql/select_test.go @@ -42,7 +42,7 @@ func TestMain(m *testing.M) { "secret", "password", "token", - } + }, }) if err != nil { diff --git a/qcode/qcode.go b/qcode/qcode.go index 47a1d78..78538ff 100644 --- a/qcode/qcode.go +++ b/qcode/qcode.go @@ -664,10 +664,18 @@ func (com *Compiler) compileArgOffset(sel *Select, arg *Arg) error { } func (com *Compiler) compileArgAction(sel *Select, arg *Arg) error { - if arg.Val.Type != nodeVar { - return fmt.Errorf("value for argument '%s' must be a variable", arg.Name) + switch sel.Action { + 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 }