2020-02-10 07:45:37 +01:00
|
|
|
//nolint:errcheck
|
|
|
|
package psql
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"io"
|
|
|
|
"strings"
|
|
|
|
|
2020-04-10 08:27:43 +02:00
|
|
|
"github.com/dosco/super-graph/core/internal/qcode"
|
2020-02-10 07:45:37 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
func (c *compilerContext) renderBaseColumns(
|
|
|
|
sel *qcode.Select,
|
|
|
|
ti *DBTableInfo,
|
2020-05-27 01:41:28 +02:00
|
|
|
childCols []*qcode.Column) ([]int, bool, error) {
|
2020-02-10 07:45:37 +01:00
|
|
|
|
|
|
|
var realColsRendered []int
|
|
|
|
|
|
|
|
colcount := (len(sel.Cols) + len(sel.OrderBy) + 1)
|
|
|
|
colmap := make(map[string]struct{}, colcount)
|
|
|
|
|
|
|
|
isSearch := sel.Args["search"] != nil
|
|
|
|
isCursorPaged := sel.Paging.Type != qcode.PtOffset
|
|
|
|
isAgg := false
|
|
|
|
|
|
|
|
i := 0
|
|
|
|
for n, col := range sel.Cols {
|
|
|
|
cn := col.Name
|
|
|
|
colmap[cn] = struct{}{}
|
|
|
|
|
|
|
|
_, isRealCol := ti.ColMap[cn]
|
|
|
|
|
|
|
|
if isRealCol {
|
|
|
|
c.renderComma(i)
|
|
|
|
realColsRendered = append(realColsRendered, n)
|
|
|
|
colWithTable(c.w, ti.Name, cn)
|
|
|
|
|
2020-03-14 06:35:42 +01:00
|
|
|
} else {
|
2020-02-10 07:45:37 +01:00
|
|
|
switch {
|
2020-03-14 06:35:42 +01:00
|
|
|
case isSearch && cn == "search_rank":
|
2020-02-10 07:45:37 +01:00
|
|
|
if err := c.renderColumnSearchRank(sel, ti, col, i); err != nil {
|
|
|
|
return nil, false, err
|
|
|
|
}
|
|
|
|
|
2020-03-14 06:35:42 +01:00
|
|
|
case isSearch && strings.HasPrefix(cn, "search_headline_"):
|
2020-02-10 07:45:37 +01:00
|
|
|
if err := c.renderColumnSearchHeadline(sel, ti, col, i); err != nil {
|
|
|
|
return nil, false, err
|
|
|
|
}
|
|
|
|
|
2020-03-14 06:35:42 +01:00
|
|
|
case cn == "__typename":
|
|
|
|
if err := c.renderColumnTypename(sel, ti, col, i); err != nil {
|
|
|
|
return nil, false, err
|
|
|
|
}
|
2020-03-16 06:40:47 +01:00
|
|
|
|
|
|
|
case strings.HasSuffix(cn, "_cursor"):
|
|
|
|
continue
|
|
|
|
|
2020-03-14 06:35:42 +01:00
|
|
|
default:
|
|
|
|
if err := c.renderColumnFunction(sel, ti, col, i); err != nil {
|
|
|
|
return nil, false, err
|
|
|
|
}
|
2020-03-16 06:40:47 +01:00
|
|
|
|
2020-03-14 06:35:42 +01:00
|
|
|
isAgg = true
|
2020-02-10 07:45:37 +01:00
|
|
|
}
|
|
|
|
}
|
2020-03-16 06:40:47 +01:00
|
|
|
i++
|
|
|
|
|
2020-02-10 07:45:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if isCursorPaged {
|
|
|
|
if _, ok := colmap[ti.PrimaryCol.Key]; !ok {
|
|
|
|
colmap[ti.PrimaryCol.Key] = struct{}{}
|
|
|
|
c.renderComma(i)
|
|
|
|
colWithTable(c.w, ti.Name, ti.PrimaryCol.Name)
|
|
|
|
}
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, ob := range sel.OrderBy {
|
|
|
|
if _, ok := colmap[ob.Col]; ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
colmap[ob.Col] = struct{}{}
|
|
|
|
c.renderComma(i)
|
|
|
|
colWithTable(c.w, ti.Name, ob.Col)
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, col := range childCols {
|
|
|
|
if _, ok := colmap[col.Name]; ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
c.renderComma(i)
|
|
|
|
colWithTable(c.w, col.Table, col.Name)
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
|
|
|
|
return realColsRendered, isAgg, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *compilerContext) renderColumnSearchRank(sel *qcode.Select, ti *DBTableInfo, col qcode.Column, columnsRendered int) error {
|
|
|
|
if isColumnBlocked(sel, col.Name) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if ti.TSVCol == nil {
|
|
|
|
return errors.New("no ts_vector column found")
|
|
|
|
}
|
|
|
|
cn := ti.TSVCol.Name
|
|
|
|
arg := sel.Args["search"]
|
|
|
|
|
|
|
|
c.renderComma(columnsRendered)
|
|
|
|
//fmt.Fprintf(w, `ts_rank("%s"."%s", websearch_to_tsquery('%s')) AS %s`,
|
|
|
|
//c.sel.Name, cn, arg.Val, col.Name)
|
|
|
|
io.WriteString(c.w, `ts_rank(`)
|
|
|
|
colWithTable(c.w, ti.Name, cn)
|
|
|
|
if c.schema.ver >= 110000 {
|
2020-05-27 01:41:28 +02:00
|
|
|
io.WriteString(c.w, `, websearch_to_tsquery(`)
|
2020-02-10 07:45:37 +01:00
|
|
|
} else {
|
2020-05-27 01:41:28 +02:00
|
|
|
io.WriteString(c.w, `, to_tsquery(`)
|
2020-02-10 07:45:37 +01:00
|
|
|
}
|
2020-05-27 01:41:28 +02:00
|
|
|
c.renderValueExp(Param{Name: arg.Val, Type: "string"})
|
|
|
|
io.WriteString(c.w, `))`)
|
2020-02-10 07:45:37 +01:00
|
|
|
alias(c.w, col.Name)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *compilerContext) renderColumnSearchHeadline(sel *qcode.Select, ti *DBTableInfo, col qcode.Column, columnsRendered int) error {
|
|
|
|
cn := col.Name[16:]
|
|
|
|
|
|
|
|
if isColumnBlocked(sel, cn) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
arg := sel.Args["search"]
|
|
|
|
|
|
|
|
c.renderComma(columnsRendered)
|
|
|
|
//fmt.Fprintf(w, `ts_headline("%s"."%s", websearch_to_tsquery('%s')) AS %s`,
|
|
|
|
//c.sel.Name, cn, arg.Val, col.Name)
|
|
|
|
io.WriteString(c.w, `ts_headline(`)
|
|
|
|
colWithTable(c.w, ti.Name, cn)
|
|
|
|
if c.schema.ver >= 110000 {
|
2020-05-27 01:41:28 +02:00
|
|
|
io.WriteString(c.w, `, websearch_to_tsquery(`)
|
2020-02-10 07:45:37 +01:00
|
|
|
} else {
|
2020-05-27 01:41:28 +02:00
|
|
|
io.WriteString(c.w, `, to_tsquery(`)
|
2020-02-10 07:45:37 +01:00
|
|
|
}
|
2020-05-27 01:41:28 +02:00
|
|
|
c.renderValueExp(Param{Name: arg.Val, Type: "string"})
|
|
|
|
io.WriteString(c.w, `))`)
|
2020-02-10 07:45:37 +01:00
|
|
|
alias(c.w, col.Name)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-03-14 06:35:42 +01:00
|
|
|
func (c *compilerContext) renderColumnTypename(sel *qcode.Select, ti *DBTableInfo, col qcode.Column, columnsRendered int) error {
|
|
|
|
if isColumnBlocked(sel, col.Name) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
c.renderComma(columnsRendered)
|
|
|
|
io.WriteString(c.w, `(`)
|
|
|
|
squoted(c.w, ti.Name)
|
|
|
|
io.WriteString(c.w, ` :: text)`)
|
|
|
|
alias(c.w, col.Name)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-02-10 07:45:37 +01:00
|
|
|
func (c *compilerContext) renderColumnFunction(sel *qcode.Select, ti *DBTableInfo, col qcode.Column, columnsRendered int) error {
|
2020-04-23 02:51:14 +02:00
|
|
|
pl := funcPrefixLen(c.schema.fm, col.Name)
|
2020-02-10 07:45:37 +01:00
|
|
|
// if pl == 0 {
|
|
|
|
// //fmt.Fprintf(w, `'%s not defined' AS %s`, cn, col.Name)
|
|
|
|
// io.WriteString(c.w, `'`)
|
|
|
|
// io.WriteString(c.w, col.Name)
|
|
|
|
// io.WriteString(c.w, ` not defined'`)
|
|
|
|
// alias(c.w, col.Name)
|
|
|
|
// }
|
|
|
|
|
|
|
|
if pl == 0 || !sel.Functions {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
cn := col.Name[pl:]
|
|
|
|
|
|
|
|
if isColumnBlocked(sel, cn) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-03-14 06:35:42 +01:00
|
|
|
fn := col.Name[:pl-1]
|
2020-02-10 07:45:37 +01:00
|
|
|
|
|
|
|
c.renderComma(columnsRendered)
|
|
|
|
|
|
|
|
//fmt.Fprintf(w, `%s("%s"."%s") AS %s`, fn, c.sel.Name, cn, col.Name)
|
|
|
|
io.WriteString(c.w, fn)
|
|
|
|
io.WriteString(c.w, `(`)
|
|
|
|
colWithTable(c.w, ti.Name, cn)
|
|
|
|
io.WriteString(c.w, `)`)
|
|
|
|
alias(c.w, col.Name)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *compilerContext) renderComma(columnsRendered int) {
|
|
|
|
if columnsRendered != 0 {
|
|
|
|
io.WriteString(c.w, `, `)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func isColumnBlocked(sel *qcode.Select, name string) bool {
|
|
|
|
if len(sel.Allowed) != 0 {
|
|
|
|
if _, ok := sel.Allowed[name]; !ok {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|