2019-11-28 07:25:46 +01:00
|
|
|
//nolint:errcheck
|
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
|
|
|
|
2019-10-24 08:07:42 +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
|
|
|
|
}
|
|
|
|
|
2019-10-24 08:07:42 +02:00
|
|
|
io.WriteString(c.w, `WITH `)
|
2019-10-05 08:17:08 +02:00
|
|
|
quoted(c.w, ti.Name)
|
2019-10-24 08:07:42 +02:00
|
|
|
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:
|
2019-10-06 22:28:10 +02:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2019-10-24 08:07:42 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-11-21 08:14:12 +01:00
|
|
|
io.WriteString(c.w, `(WITH "input" AS (SELECT '{{`)
|
2019-10-24 08:07:42 +02:00
|
|
|
io.WriteString(c.w, qc.ActionVar)
|
2019-11-21 08:14:12 +01:00
|
|
|
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, ` (`)
|
2019-10-30 07:30:31 +01:00
|
|
|
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
|
|
|
|
2019-10-24 08:07:42 +02:00
|
|
|
io.WriteString(c.w, ` SELECT `)
|
2019-10-30 07:30:31 +01:00
|
|
|
c.renderInsertUpdateColumns(qc, w, jt, ti, true)
|
2019-10-24 08:07:42 +02:00
|
|
|
io.WriteString(c.w, ` FROM input i, `)
|
2019-09-05 06:09:56 +02:00
|
|
|
|
|
|
|
if array {
|
2019-10-24 08:07:42 +02:00
|
|
|
io.WriteString(c.w, `json_populate_recordset`)
|
2019-09-05 06:09:56 +02:00
|
|
|
} else {
|
2019-10-24 08:07:42 +02:00
|
|
|
io.WriteString(c.w, `json_populate_record`)
|
2019-09-05 06:09:56 +02:00
|
|
|
}
|
|
|
|
|
2019-10-24 08:07:42 +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
|
|
|
|
2019-10-26 07:34:29 +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
|
|
|
|
}
|
|
|
|
|
2019-10-24 08:07:42 +02:00
|
|
|
func (c *compilerContext) renderInsertUpdateColumns(qc *qcode.QCode, w io.Writer,
|
2019-10-30 07:30:31 +01:00
|
|
|
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
|
|
|
|
}
|
2019-10-30 07:30:31 +01:00
|
|
|
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, `, `)
|
|
|
|
}
|
2019-10-30 07:30:31 +01:00
|
|
|
io.WriteString(c.w, `"`)
|
2019-10-24 08:07:42 +02:00
|
|
|
io.WriteString(c.w, cn)
|
2019-10-30 07:30:31 +01:00
|
|
|
io.WriteString(c.w, `"`)
|
2019-09-06 06:34:23 +02:00
|
|
|
i++
|
|
|
|
}
|
|
|
|
|
2019-10-30 07:30:31 +01:00
|
|
|
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
|
|
|
|
}
|
2019-10-30 07:30:31 +01:00
|
|
|
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)
|
|
|
|
|
2019-10-30 07:30:31 +01:00
|
|
|
} else {
|
|
|
|
io.WriteString(c.w, `"`)
|
2019-11-07 08:37:24 +01:00
|
|
|
io.WriteString(c.w, cn)
|
2019-10-30 07:30:31 +01:00
|
|
|
io.WriteString(c.w, `"`)
|
|
|
|
}
|
|
|
|
}
|
2019-09-06 06:34:23 +02:00
|
|
|
return 0, nil
|
|
|
|
}
|
|
|
|
|
2019-10-24 08:07:42 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-11-21 08:14:12 +01:00
|
|
|
io.WriteString(c.w, `(WITH "input" AS (SELECT '{{`)
|
2019-10-24 08:07:42 +02:00
|
|
|
io.WriteString(c.w, qc.ActionVar)
|
2019-11-21 08:14:12 +01:00
|
|
|
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 (`)
|
2019-10-30 07:30:31 +01:00
|
|
|
c.renderInsertUpdateColumns(qc, w, jt, ti, false)
|
2019-09-06 06:34:23 +02:00
|
|
|
|
2019-10-24 08:07:42 +02:00
|
|
|
io.WriteString(c.w, `) = (SELECT `)
|
2019-10-30 07:30:31 +01:00
|
|
|
c.renderInsertUpdateColumns(qc, w, jt, ti, true)
|
2019-10-24 08:07:42 +02:00
|
|
|
io.WriteString(c.w, ` FROM input i, `)
|
2019-09-06 06:34:23 +02:00
|
|
|
|
|
|
|
if array {
|
2019-10-24 08:07:42 +02:00
|
|
|
io.WriteString(c.w, `json_populate_recordset`)
|
2019-09-06 06:34:23 +02:00
|
|
|
} else {
|
2019-10-24 08:07:42 +02:00
|
|
|
io.WriteString(c.w, `json_populate_record`)
|
2019-09-06 06:34:23 +02:00
|
|
|
}
|
|
|
|
|
2019-10-24 08:07:42 +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
|
|
|
|
}
|
|
|
|
|
2019-10-24 08:07:42 +02:00
|
|
|
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]
|
|
|
|
|
2019-10-24 08:07:42 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-10-24 08:07:42 +02:00
|
|
|
func (c *compilerContext) renderUpsert(qc *qcode.QCode, w io.Writer,
|
2019-10-05 08:17:08 +02:00
|
|
|
vars Variables, ti *DBTableInfo) (uint32, error) {
|
2019-11-05 05:44:42 +01:00
|
|
|
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
|
|
|
|
}
|
2019-10-03 16:24:11 +02:00
|
|
|
|
2019-11-05 05:44:42 +01:00
|
|
|
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, `, `)
|
|
|
|
}
|
2019-10-24 08:07:42 +02:00
|
|
|
io.WriteString(c.w, cn)
|
2019-10-05 08:17:08 +02:00
|
|
|
i++
|
|
|
|
}
|
|
|
|
if i == 0 {
|
2019-10-24 08:07:42 +02:00
|
|
|
io.WriteString(c.w, ti.PrimaryCol)
|
2019-09-06 07:17:45 +02:00
|
|
|
}
|
2019-11-05 05:44:42 +01: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
|
|
|
|
2019-11-05 05:44:42 +01: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, `, `)
|
|
|
|
}
|
2019-10-24 08:07:42 +02:00
|
|
|
io.WriteString(c.w, cn)
|
2019-10-05 08:17:08 +02:00
|
|
|
io.WriteString(c.w, ` = EXCLUDED.`)
|
2019-10-24 08:07:42 +02:00
|
|
|
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
|
|
|
|
2019-10-24 08:07:42 +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
|
|
|
}
|