super-graph/psql/mutate.go

295 lines
6.0 KiB
Go
Raw Permalink Normal View History

2019-09-05 06:09:56 +02:00
package psql
import (
"errors"
2019-09-06 06:34:23 +02:00
"fmt"
2019-09-05 06:09:56 +02:00
"io"
"github.com/dosco/super-graph/jsn"
"github.com/dosco/super-graph/qcode"
)
2019-10-14 08:51:36 +02:00
var noLimit = qcode.Paging{NoLimit: true}
2019-10-03 09:08:01 +02:00
func (co *Compiler) compileMutation(qc *qcode.QCode, w io.Writer, vars Variables) (uint32, error) {
2019-09-05 06:09:56 +02:00
if len(qc.Selects) == 0 {
return 0, errors.New("empty query")
}
c := &compilerContext{w, qc.Selects, co}
root := &qc.Selects[0]
2019-10-05 08:17:08 +02:00
ti, err := c.schema.GetTable(root.Table)
if err != nil {
return 0, err
}
io.WriteString(c.w, `WITH `)
2019-10-05 08:17:08 +02:00
quoted(c.w, ti.Name)
io.WriteString(c.w, ` AS `)
2019-10-05 08:17:08 +02:00
2019-10-14 08:51:36 +02:00
switch qc.Type {
case qcode.QTInsert:
2019-10-05 08:17:08 +02:00
if _, err := c.renderInsert(qc, w, vars, ti); err != nil {
2019-09-06 06:34:23 +02:00
return 0, err
}
2019-10-14 08:51:36 +02:00
case qcode.QTUpdate:
2019-10-05 08:17:08 +02:00
if _, err := c.renderUpdate(qc, w, vars, ti); err != nil {
return 0, err
}
2019-10-14 08:51:36 +02:00
case qcode.QTUpsert:
2019-10-05 08:17:08 +02:00
if _, err := c.renderUpsert(qc, w, vars, ti); err != nil {
2019-09-06 06:34:23 +02:00
return 0, err
}
2019-10-14 08:51:36 +02:00
case qcode.QTDelete:
2019-10-05 08:17:08 +02:00
if _, err := c.renderDelete(qc, w, vars, ti); err != nil {
2019-09-06 07:17:45 +02:00
return 0, err
}
2019-09-06 06:34:23 +02:00
default:
return 0, errors.New("valid mutations are 'insert', 'update', 'upsert' and 'delete'")
2019-09-05 06:09:56 +02:00
}
2019-10-05 08:17:08 +02:00
io.WriteString(c.w, ` RETURNING *) `)
2019-10-14 08:51:36 +02:00
root.Paging = noLimit
2019-10-03 09:08:01 +02:00
root.DistinctOn = root.DistinctOn[:]
root.OrderBy = root.OrderBy[:]
root.Where = nil
root.Args = nil
2019-09-05 06:09:56 +02:00
return c.compileQuery(qc, w)
}
func (c *compilerContext) renderInsert(qc *qcode.QCode, w io.Writer,
2019-10-05 08:17:08 +02:00
vars Variables, ti *DBTableInfo) (uint32, error) {
2019-09-05 06:09:56 +02:00
2019-10-14 08:51:36 +02:00
insert, ok := vars[qc.ActionVar]
2019-09-05 06:09:56 +02:00
if !ok {
2019-10-14 08:51:36 +02:00
return 0, fmt.Errorf("Variable '%s' not defined", qc.ActionVar)
2019-09-06 06:34:23 +02:00
}
2019-09-05 06:09:56 +02:00
jt, array, err := jsn.Tree(insert)
if err != nil {
return 0, err
}
io.WriteString(c.w, `(WITH "input" AS (SELECT '{{`)
io.WriteString(c.w, qc.ActionVar)
io.WriteString(c.w, `}}' :: json AS j) INSERT INTO `)
2019-10-05 08:17:08 +02:00
quoted(c.w, ti.Name)
2019-09-06 06:34:23 +02:00
io.WriteString(c.w, ` (`)
c.renderInsertUpdateColumns(qc, w, jt, ti, false)
2019-09-06 06:34:23 +02:00
io.WriteString(c.w, `)`)
2019-09-05 06:09:56 +02:00
io.WriteString(c.w, ` SELECT `)
c.renderInsertUpdateColumns(qc, w, jt, ti, true)
io.WriteString(c.w, ` FROM input i, `)
2019-09-05 06:09:56 +02:00
if array {
io.WriteString(c.w, `json_populate_recordset`)
2019-09-05 06:09:56 +02:00
} else {
io.WriteString(c.w, `json_populate_record`)
2019-09-05 06:09:56 +02:00
}
io.WriteString(c.w, `(NULL::`)
io.WriteString(c.w, ti.Name)
io.WriteString(c.w, `, i.j) t`)
2019-09-05 06:09:56 +02:00
if w := qc.Selects[0].Where; w != nil && w.Op == qcode.OpFalse {
io.WriteString(c.w, ` WHERE false`)
}
2019-09-05 06:09:56 +02:00
return 0, nil
}
func (c *compilerContext) renderInsertUpdateColumns(qc *qcode.QCode, w io.Writer,
jt map[string]interface{}, ti *DBTableInfo, values bool) (uint32, error) {
2019-10-14 08:51:36 +02:00
root := &qc.Selects[0]
2019-09-06 06:34:23 +02:00
i := 0
for _, cn := range ti.ColumnNames {
if _, ok := jt[cn]; !ok {
continue
}
if _, ok := root.PresetMap[cn]; ok {
continue
}
2019-10-14 08:51:36 +02:00
if len(root.Allowed) != 0 {
if _, ok := root.Allowed[cn]; !ok {
continue
}
}
2019-09-06 06:34:23 +02:00
if i != 0 {
io.WriteString(c.w, `, `)
}
io.WriteString(c.w, `"`)
io.WriteString(c.w, cn)
io.WriteString(c.w, `"`)
2019-09-06 06:34:23 +02:00
i++
}
if i != 0 && len(root.PresetList) != 0 {
io.WriteString(c.w, `, `)
}
for i := range root.PresetList {
2019-11-07 08:37:24 +01:00
cn := root.PresetList[i]
col, ok := ti.Columns[cn]
if !ok {
continue
}
if i != 0 {
io.WriteString(c.w, `, `)
}
if values {
io.WriteString(c.w, `'`)
2019-11-07 08:37:24 +01:00
io.WriteString(c.w, root.PresetMap[cn])
io.WriteString(c.w, `' :: `)
io.WriteString(c.w, col.Type)
} else {
io.WriteString(c.w, `"`)
2019-11-07 08:37:24 +01:00
io.WriteString(c.w, cn)
io.WriteString(c.w, `"`)
}
}
2019-09-06 06:34:23 +02:00
return 0, nil
}
func (c *compilerContext) renderUpdate(qc *qcode.QCode, w io.Writer,
2019-10-05 08:17:08 +02:00
vars Variables, ti *DBTableInfo) (uint32, error) {
2019-09-06 06:34:23 +02:00
root := &qc.Selects[0]
2019-10-14 08:51:36 +02:00
update, ok := vars[qc.ActionVar]
2019-09-06 06:34:23 +02:00
if !ok {
2019-10-14 08:51:36 +02:00
return 0, fmt.Errorf("Variable '%s' not defined", qc.ActionVar)
2019-09-06 06:34:23 +02:00
}
2019-09-05 06:09:56 +02:00
2019-09-06 06:34:23 +02:00
jt, array, err := jsn.Tree(update)
if err != nil {
return 0, err
}
io.WriteString(c.w, `(WITH "input" AS (SELECT '{{`)
io.WriteString(c.w, qc.ActionVar)
io.WriteString(c.w, `}}' :: json AS j) UPDATE `)
2019-10-05 08:17:08 +02:00
quoted(c.w, ti.Name)
2019-09-06 06:34:23 +02:00
io.WriteString(c.w, ` SET (`)
c.renderInsertUpdateColumns(qc, w, jt, ti, false)
2019-09-06 06:34:23 +02:00
io.WriteString(c.w, `) = (SELECT `)
c.renderInsertUpdateColumns(qc, w, jt, ti, true)
io.WriteString(c.w, ` FROM input i, `)
2019-09-06 06:34:23 +02:00
if array {
io.WriteString(c.w, `json_populate_recordset`)
2019-09-06 06:34:23 +02:00
} else {
io.WriteString(c.w, `json_populate_record`)
2019-09-06 06:34:23 +02:00
}
io.WriteString(c.w, `(NULL::`)
io.WriteString(c.w, ti.Name)
io.WriteString(c.w, `, i.j) t)`)
2019-09-06 06:34:23 +02:00
io.WriteString(c.w, ` WHERE `)
if err := c.renderWhere(root, ti); err != nil {
return 0, err
}
2019-10-05 08:17:08 +02:00
return 0, nil
}
func (c *compilerContext) renderDelete(qc *qcode.QCode, w io.Writer,
2019-10-05 08:17:08 +02:00
vars Variables, ti *DBTableInfo) (uint32, error) {
root := &qc.Selects[0]
io.WriteString(c.w, `(DELETE FROM `)
2019-10-05 08:17:08 +02:00
quoted(c.w, ti.Name)
io.WriteString(c.w, ` WHERE `)
if err := c.renderWhere(root, ti); err != nil {
return 0, err
}
2019-09-06 06:34:23 +02:00
return 0, nil
}
func (c *compilerContext) renderUpsert(qc *qcode.QCode, w io.Writer,
2019-10-05 08:17:08 +02:00
vars Variables, ti *DBTableInfo) (uint32, error) {
root := &qc.Selects[0]
2019-09-06 06:34:23 +02:00
2019-10-14 08:51:36 +02:00
upsert, ok := vars[qc.ActionVar]
2019-10-05 08:17:08 +02:00
if !ok {
2019-10-14 08:51:36 +02:00
return 0, fmt.Errorf("Variable '%s' not defined", qc.ActionVar)
2019-10-05 08:17:08 +02:00
}
jt, _, err := jsn.Tree(upsert)
2019-09-06 07:17:45 +02:00
if err != nil {
return 0, err
2019-09-05 06:09:56 +02:00
}
2019-10-05 08:17:08 +02:00
if _, err := c.renderInsert(qc, w, vars, ti); err != nil {
return 0, err
}
io.WriteString(c.w, ` ON CONFLICT (`)
2019-10-05 08:17:08 +02:00
i := 0
2019-09-06 07:17:45 +02:00
2019-10-05 08:17:08 +02:00
for _, cn := range ti.ColumnNames {
if _, ok := jt[cn]; !ok {
continue
}
if col, ok := ti.Columns[cn]; !ok || !(col.UniqueKey || col.PrimaryKey) {
continue
}
if i != 0 {
io.WriteString(c.w, `, `)
}
io.WriteString(c.w, cn)
2019-10-05 08:17:08 +02:00
i++
}
if i == 0 {
io.WriteString(c.w, ti.PrimaryCol)
2019-09-06 07:17:45 +02:00
}
io.WriteString(c.w, `)`)
if root.Where != nil {
io.WriteString(c.w, ` WHERE `)
if err := c.renderWhere(root, ti); err != nil {
return 0, err
}
}
2019-09-06 07:17:45 +02:00
io.WriteString(c.w, ` DO UPDATE SET `)
2019-10-05 08:17:08 +02:00
i = 0
for _, cn := range ti.ColumnNames {
if _, ok := jt[cn]; !ok {
continue
}
if i != 0 {
io.WriteString(c.w, `, `)
}
io.WriteString(c.w, cn)
2019-10-05 08:17:08 +02:00
io.WriteString(c.w, ` = EXCLUDED.`)
io.WriteString(c.w, cn)
2019-10-05 08:17:08 +02:00
i++
}
2019-09-06 07:17:45 +02:00
2019-09-05 06:09:56 +02:00
return 0, nil
}
2019-09-20 06:19:11 +02:00
func quoted(w io.Writer, identifier string) {
io.WriteString(w, `"`)
io.WriteString(w, identifier)
io.WriteString(w, `"`)
2019-09-20 06:19:11 +02:00
}