Compare commits

..

5 Commits

Author SHA1 Message Date
Vikram Rangnekar
7557a4c29c fix: issue with fragments related crash 2020-06-15 10:46:52 -04:00
Vikram Rangnekar
dd4accfdd2 fix: implement various deepsource suggestions 2020-06-15 10:16:47 -04:00
Vikram Rangnekar
06214a3850 feat: add support for polymorphic database relationships 2020-06-15 03:09:18 -04:00
Vikram Rangnekar
7b5548a2c6 fix: jit failing on anon queries 2020-06-10 00:38:46 -04:00
Vikram Rangnekar
00cfa251a2 fix: issue with jit performance 2020-06-09 19:06:16 -04:00
35 changed files with 526 additions and 332 deletions

View File

@ -85,12 +85,11 @@ type SuperGraph struct {
allowList *allow.List allowList *allow.List
encKey [32]byte encKey [32]byte
hashSeed maphash.Seed hashSeed maphash.Seed
queries map[uint64]query queries map[uint64]*query
roles map[string]*Role roles map[string]*Role
getRole *sql.Stmt getRole *sql.Stmt
rmap map[uint64]resolvFn rmap map[uint64]resolvFn
abacEnabled bool abacEnabled bool
anonExists bool
qc *qcode.Compiler qc *qcode.Compiler
pc *psql.Compiler pc *psql.Compiler
ge *graphql.Engine ge *graphql.Engine
@ -140,7 +139,7 @@ func newSuperGraph(conf *Config, db *sql.DB, dbinfo *psql.DBInfo) (*SuperGraph,
return nil, err return nil, err
} }
if len(conf.SecretKey) != 0 { if conf.SecretKey != "" {
sk := sha256.Sum256([]byte(conf.SecretKey)) sk := sha256.Sum256([]byte(conf.SecretKey))
conf.SecretKey = "" conf.SecretKey = ""
sg.encKey = sk sg.encKey = sk

View File

@ -82,7 +82,7 @@ func (sg *SuperGraph) buildMultiStmt(query, vars []byte) ([]stmt, error) {
} }
} }
if len(sg.conf.RolesQuery) == 0 { if sg.conf.RolesQuery == "" {
return nil, errors.New("roles_query not defined") return nil, errors.New("roles_query not defined")
} }
@ -133,7 +133,7 @@ func (sg *SuperGraph) renderUserQuery(md psql.Metadata, stmts []stmt) (string, e
io.WriteString(w, `SELECT "_sg_auth_info"."role", (CASE "_sg_auth_info"."role" `) io.WriteString(w, `SELECT "_sg_auth_info"."role", (CASE "_sg_auth_info"."role" `)
for _, s := range stmts { for _, s := range stmts {
if len(s.role.Match) == 0 && if s.role.Match == "" &&
s.role.Name != "user" && s.role.Name != "anon" { s.role.Name != "user" && s.role.Name != "anon" {
continue continue
} }
@ -150,7 +150,7 @@ func (sg *SuperGraph) renderUserQuery(md psql.Metadata, stmts []stmt) (string, e
io.WriteString(w, `(SELECT (CASE`) io.WriteString(w, `(SELECT (CASE`)
for _, s := range stmts { for _, s := range stmts {
if len(s.role.Match) == 0 { if s.role.Match == "" {
continue continue
} }
io.WriteString(w, ` WHEN `) io.WriteString(w, ` WHEN `)

View File

@ -72,6 +72,7 @@ type Config struct {
type Table struct { type Table struct {
Name string Name string
Table string Table string
Type string
Blocklist []string Blocklist []string
Remotes []Remote Remotes []Remote
Columns []Column Columns []Column
@ -151,7 +152,7 @@ type Delete struct {
// AddRoleTable function is a helper function to make it easy to add per-table // AddRoleTable function is a helper function to make it easy to add per-table
// row-level config // row-level config
func (c *Config) AddRoleTable(role string, table string, conf interface{}) error { func (c *Config) AddRoleTable(role, table string, conf interface{}) error {
var r *Role var r *Role
for i := range c.Roles { for i := range c.Roles {

View File

@ -172,14 +172,15 @@ func (c *scontext) resolvePreparedSQL() ([]byte, *stmt, error) {
h := maphash.Hash{} h := maphash.Hash{}
h.SetSeed(c.sg.hashSeed) h.SetSeed(c.sg.hashSeed)
id := queryID(&h, c.res.name, role)
q, ok := c.sg.queries[queryID(&h, c.res.name, role)] q, ok := c.sg.queries[id]
if !ok { if !ok {
return nil, nil, errNotFound return nil, nil, errNotFound
} }
if q.sd == nil { if q.sd == nil {
q.Do(func() { c.sg.prepare(&q, role) }) q.Do(func() { c.sg.prepare(q, role) })
if q.err != nil { if q.err != nil {
return nil, nil, err return nil, nil, err
@ -304,7 +305,7 @@ func (c *scontext) resolveSQL() ([]byte, *stmt, error) {
err = row.Scan(&root) err = row.Scan(&root)
} }
if len(role) == 0 { if role == "" {
c.res.role = c.role c.res.role = c.role
} else { } else {
c.res.role = role c.res.role = role

View File

@ -21,7 +21,7 @@ func (sg *SuperGraph) initConfig() error {
for i := 0; i < len(c.Tables); i++ { for i := 0; i < len(c.Tables); i++ {
t := &c.Tables[i] t := &c.Tables[i]
t.Name = flect.Pluralize(strings.ToLower(t.Name)) // t.Name = flect.Pluralize(strings.ToLower(t.Name))
if _, ok := tm[t.Name]; ok { if _, ok := tm[t.Name]; ok {
sg.conf.Tables = append(c.Tables[:i], c.Tables[i+1:]...) sg.conf.Tables = append(c.Tables[:i], c.Tables[i+1:]...)
@ -100,21 +100,26 @@ func getDBTableAliases(c *Config) map[string][]string {
for i := range c.Tables { for i := range c.Tables {
t := c.Tables[i] t := c.Tables[i]
if len(t.Table) == 0 || len(t.Columns) != 0 { if t.Table != "" && t.Type == "" {
continue
}
m[t.Table] = append(m[t.Table], t.Name) m[t.Table] = append(m[t.Table], t.Name)
} }
}
return m return m
} }
func addTables(c *Config, di *psql.DBInfo) error { func addTables(c *Config, di *psql.DBInfo) error {
var err error
for _, t := range c.Tables { for _, t := range c.Tables {
if t.Table == "" || len(t.Columns) == 0 { switch t.Type {
continue case "json", "jsonb":
err = addJsonTable(di, t.Columns, t)
case "polymorphic":
err = addVirtualTable(di, t.Columns, t)
} }
if err := addTable(di, t.Columns, t); err != nil {
if err != nil {
return err return err
} }
@ -122,17 +127,18 @@ func addTables(c *Config, di *psql.DBInfo) error {
return nil return nil
} }
func addTable(di *psql.DBInfo, cols []Column, t Table) error { func addJsonTable(di *psql.DBInfo, cols []Column, t Table) error {
// This is for jsonb columns that want to be tables.
bc, ok := di.GetColumn(t.Table, t.Name) bc, ok := di.GetColumn(t.Table, t.Name)
if !ok { if !ok {
return fmt.Errorf( return fmt.Errorf(
"Column '%s' not found on table '%s'", "json table: column '%s' not found on table '%s'",
t.Name, t.Table) t.Name, t.Table)
} }
if bc.Type != "json" && bc.Type != "jsonb" { if bc.Type != "json" && bc.Type != "jsonb" {
return fmt.Errorf( return fmt.Errorf(
"Column '%s' in table '%s' is of type '%s'. Only JSON or JSONB is valid", "json table: column '%s' in table '%s' is of type '%s'. Only JSON or JSONB is valid",
t.Name, t.Table, bc.Type) t.Name, t.Table, bc.Type)
} }
@ -159,8 +165,38 @@ func addTable(di *psql.DBInfo, cols []Column, t Table) error {
return nil return nil
} }
func addVirtualTable(di *psql.DBInfo, cols []Column, t Table) error {
if len(cols) == 0 {
return fmt.Errorf("polymorphic table: no id column specified")
}
c := cols[0]
if c.ForeignKey == "" {
return fmt.Errorf("polymorphic table: no 'related_to' specified on id column")
}
s := strings.SplitN(c.ForeignKey, ".", 2)
if len(s) != 2 {
return fmt.Errorf("polymorphic table: foreign key must be <type column>.<foreign key column>")
}
di.VTables = append(di.VTables, psql.VirtualTable{
Name: t.Name,
IDColumn: c.Name,
TypeColumn: s[0],
FKeyColumn: s[1],
})
return nil
}
func addForeignKeys(c *Config, di *psql.DBInfo) error { func addForeignKeys(c *Config, di *psql.DBInfo) error {
for _, t := range c.Tables { for _, t := range c.Tables {
if t.Type != "" {
continue
}
for _, c := range t.Columns { for _, c := range t.Columns {
if c.ForeignKey == "" { if c.ForeignKey == "" {
continue continue
@ -174,30 +210,52 @@ func addForeignKeys(c *Config, di *psql.DBInfo) error {
} }
func addForeignKey(di *psql.DBInfo, c Column, t Table) error { func addForeignKey(di *psql.DBInfo, c Column, t Table) error {
c1, ok := di.GetColumn(t.Name, c.Name) var tn string
if t.Type == "polymorphic" {
tn = t.Table
} else {
tn = t.Name
}
c1, ok := di.GetColumn(tn, c.Name)
if !ok { if !ok {
return fmt.Errorf( return fmt.Errorf(
"Invalid table '%s' or column '%s' in Config", "config: invalid table '%s' or column '%s' defined",
t.Name, c.Name) tn, c.Name)
} }
v := strings.SplitN(c.ForeignKey, ".", 2) v := strings.SplitN(c.ForeignKey, ".", 2)
if len(v) != 2 { if len(v) != 2 {
return fmt.Errorf( return fmt.Errorf(
"Invalid foreign_key in Config for table '%s' and column '%s", "config: invalid foreign_key defined for table '%s' and column '%s': %s",
t.Name, c.Name) tn, c.Name, c.ForeignKey)
}
// check if it's a polymorphic foreign key
if _, ok := di.GetColumn(tn, v[0]); ok {
c2, ok := di.GetColumn(tn, v[1])
if !ok {
return fmt.Errorf(
"config: invalid column '%s' for polymorphic relationship on table '%s' and column '%s'",
v[1], tn, c.Name)
}
c1.FKeyTable = v[0]
c1.FKeyColID = []int16{c2.ID}
return nil
} }
fkt, fkc := v[0], v[1] fkt, fkc := v[0], v[1]
c2, ok := di.GetColumn(fkt, fkc) c3, ok := di.GetColumn(fkt, fkc)
if !ok { if !ok {
return fmt.Errorf( return fmt.Errorf(
"Invalid foreign_key in Config for table '%s' and column '%s", "config: foreign_key for table '%s' and column '%s' points to unknown table '%s' and column '%s'",
t.Name, c.Name) t.Name, c.Name, v[0], v[1])
} }
c1.FKeyTable = fkt c1.FKeyTable = fkt
c1.FKeyColID = []int16{c2.ID} c1.FKeyColID = []int16{c3.ID}
return nil return nil
} }

View File

@ -146,7 +146,7 @@ func (al *List) Load() ([]Item, error) {
return parse(string(b), al.filepath) return parse(string(b), al.filepath)
} }
func parse(b string, filename string) ([]Item, error) { func parse(b, filename string) ([]Item, error) {
var items []Item var items []Item
var s scanner.Scanner var s scanner.Scanner

View File

@ -14,7 +14,7 @@ func TestGQLName1(t *testing.T) {
name := QueryName(q) name := QueryName(q)
if len(name) != 0 { if name != "" {
t.Fatal("Name should be empty, not ", name) t.Fatal("Name should be empty, not ", name)
} }
} }

View File

@ -156,15 +156,16 @@ func (co *Compiler) compileQueryWithMetadata(
if id < closeBlock { if id < closeBlock {
sel := &c.s[id] sel := &c.s[id]
if len(sel.Cols) == 0 {
continue
}
ti, err := c.schema.GetTable(sel.Name) ti, err := c.schema.GetTable(sel.Name)
if err != nil { if err != nil {
return c.md, err return c.md, err
} }
if sel.Type != qcode.STUnion {
if len(sel.Cols) == 0 {
continue
}
if sel.ParentID == -1 { if sel.ParentID == -1 {
io.WriteString(c.w, `(`) io.WriteString(c.w, `(`)
} else { } else {
@ -178,16 +179,17 @@ func (co *Compiler) compileQueryWithMetadata(
if err := c.renderSelect(sel, ti, vars); err != nil { if err := c.renderSelect(sel, ti, vars); err != nil {
return c.md, err return c.md, err
} }
}
for _, cid := range sel.Children { for _, cid := range sel.Children {
if hasBit(c.md.skipped, uint32(cid)) { if hasBit(c.md.skipped, uint32(cid)) {
continue continue
} }
child := &c.s[cid] child := &c.s[cid]
if child.SkipRender { if child.SkipRender {
continue continue
} }
st.Push(child.ID + closeBlock) st.Push(child.ID + closeBlock)
st.Push(child.ID) st.Push(child.ID)
} }
@ -195,6 +197,7 @@ func (co *Compiler) compileQueryWithMetadata(
} else { } else {
sel := &c.s[(id - closeBlock)] sel := &c.s[(id - closeBlock)]
if sel.Type != qcode.STUnion {
ti, err := c.schema.GetTable(sel.Name) ti, err := c.schema.GetTable(sel.Name)
if err != nil { if err != nil {
return c.md, err return c.md, err
@ -218,7 +221,9 @@ func (co *Compiler) compileQueryWithMetadata(
} else { } else {
c.renderLateralJoinClose(sel) c.renderLateralJoinClose(sel)
} }
}
if sel.Type != qcode.STMember {
if len(sel.Args) != 0 { if len(sel.Args) != 0 {
for _, v := range sel.Args { for _, v := range sel.Args {
qcode.FreeNode(v) qcode.FreeNode(v)
@ -226,6 +231,7 @@ func (co *Compiler) compileQueryWithMetadata(
} }
} }
} }
}
return c.md, nil return c.md, nil
} }
@ -359,6 +365,16 @@ func (c *compilerContext) initSelect(sel *qcode.Select, ti *DBTableInfo, vars Va
c.md.skipped |= (1 << uint(id)) c.md.skipped |= (1 << uint(id))
} }
case RelPolymorphic:
if _, ok := colmap[rel.Left.Col]; !ok {
cols = append(cols, &qcode.Column{Table: ti.Name, Name: rel.Left.Col, FieldName: rel.Left.Col})
colmap[rel.Left.Col] = struct{}{}
}
if _, ok := colmap[rel.Right.Table]; !ok {
cols = append(cols, &qcode.Column{Table: ti.Name, Name: rel.Right.Table, FieldName: rel.Right.Table})
colmap[rel.Right.Table] = struct{}{}
}
default: default:
return nil, fmt.Errorf("unknown relationship %s", rel) return nil, fmt.Errorf("unknown relationship %s", rel)
} }
@ -437,14 +453,22 @@ func (c *compilerContext) renderSelect(sel *qcode.Select, ti *DBTableInfo, vars
var rel *DBRel var rel *DBRel
var err error var err error
// Relationships must be between union parents and their parents
if sel.ParentID != -1 { if sel.ParentID != -1 {
parent := c.s[sel.ParentID] if sel.Type == qcode.STMember && sel.UParentID != -1 {
cn := c.s[sel.ParentID].Name
pn := c.s[sel.UParentID].Name
rel, err = c.schema.GetRel(cn, pn)
} else {
pn := c.s[sel.ParentID].Name
rel, err = c.schema.GetRel(ti.Name, pn)
}
}
rel, err = c.schema.GetRel(ti.Name, parent.Name)
if err != nil { if err != nil {
return err return err
} }
}
childCols, err := c.initSelect(sel, ti, vars) childCols, err := c.initSelect(sel, ti, vars)
if err != nil { if err != nil {
@ -529,30 +553,27 @@ func (c *compilerContext) renderJoin(sel *qcode.Select, ti *DBTableInfo) error {
} }
func (c *compilerContext) renderJoinByName(table, parent string, id int32) error { func (c *compilerContext) renderJoinByName(table, parent string, id int32) error {
rel, err := c.schema.GetRel(table, parent) rel, _ := c.schema.GetRel(table, parent)
if err != nil {
return err
}
// This join is only required for one-to-many relations since // This join is only required for one-to-many relations since
// these make use of join tables that need to be pulled in. // these make use of join tables that need to be pulled in.
if rel.Type != RelOneToManyThrough { if rel == nil || rel.Type != RelOneToManyThrough {
return err return nil
} }
pt, err := c.schema.GetTable(parent) // pt, err := c.schema.GetTable(parent)
if err != nil { // if err != nil {
return err // return err
} // }
//fmt.Fprintf(w, ` LEFT OUTER JOIN "%s" ON (("%s"."%s") = ("%s_%d"."%s"))`, //fmt.Fprintf(w, ` LEFT OUTER JOIN "%s" ON (("%s"."%s") = ("%s_%d"."%s"))`,
//rel.Through, rel.Through, rel.ColT, c.parent.Name, c.parent.ID, rel.Left.Col) //rel.Through, rel.Through, rel.ColT, c.parent.Name, c.parent.ID, rel.Left.Col)
io.WriteString(c.w, ` LEFT OUTER JOIN "`) io.WriteString(c.w, ` LEFT OUTER JOIN "`)
io.WriteString(c.w, rel.Through) io.WriteString(c.w, rel.Through.Table)
io.WriteString(c.w, `" ON ((`) io.WriteString(c.w, `" ON ((`)
colWithTable(c.w, rel.Through, rel.ColT) colWithTable(c.w, rel.Through.Table, rel.Through.ColL)
io.WriteString(c.w, `) = (`) io.WriteString(c.w, `) = (`)
colWithTableID(c.w, pt.Name, id, rel.Left.Col) colWithTable(c.w, rel.Left.Table, rel.Left.Col)
io.WriteString(c.w, `))`) io.WriteString(c.w, `))`)
return nil return nil
@ -639,10 +660,33 @@ func (c *compilerContext) renderJoinColumns(sel *qcode.Select, ti *DBTableInfo,
continue continue
} }
if childSel.Type == qcode.STUnion {
rel, err := c.schema.GetRel(childSel.Name, ti.Name)
if err != nil {
return err
}
io.WriteString(c.w, `(CASE `)
for _, uid := range childSel.Children {
unionSel := &c.s[uid]
io.WriteString(c.w, `WHEN `)
colWithTableID(c.w, ti.Name, sel.ID, rel.Right.Table)
io.WriteString(c.w, ` = `)
squoted(c.w, unionSel.Name)
io.WriteString(c.w, ` THEN `)
io.WriteString(c.w, `"__sj_`)
int32String(c.w, unionSel.ID)
io.WriteString(c.w, `"."json"`)
}
io.WriteString(c.w, `END)`)
alias(c.w, childSel.FieldName)
} else {
io.WriteString(c.w, `"__sj_`) io.WriteString(c.w, `"__sj_`)
int32String(c.w, childSel.ID) int32String(c.w, childSel.ID)
io.WriteString(c.w, `"."json"`) io.WriteString(c.w, `"."json"`)
alias(c.w, childSel.FieldName) alias(c.w, childSel.FieldName)
}
if childSel.Paging.Type != qcode.PtOffset { if childSel.Paging.Type != qcode.PtOffset {
io.WriteString(c.w, `, "__sj_`) io.WriteString(c.w, `, "__sj_`)
@ -697,7 +741,8 @@ func (c *compilerContext) renderBaseSelect(sel *qcode.Select, ti *DBTableInfo, r
} }
io.WriteString(c.w, ` WHERE (`) io.WriteString(c.w, ` WHERE (`)
if err := c.renderRelationship(sel, ti); err != nil {
if err := c.renderRelationship(sel, rel); err != nil {
return err return err
} }
if isFil { if isFil {
@ -729,7 +774,7 @@ func (c *compilerContext) renderBaseSelect(sel *qcode.Select, ti *DBTableInfo, r
case ti.IsSingular: case ti.IsSingular:
io.WriteString(c.w, ` LIMIT ('1') :: integer`) io.WriteString(c.w, ` LIMIT ('1') :: integer`)
case len(sel.Paging.Limit) != 0: case sel.Paging.Limit != "":
//fmt.Fprintf(w, ` LIMIT ('%s') :: integer`, c.sel.Paging.Limit) //fmt.Fprintf(w, ` LIMIT ('%s') :: integer`, c.sel.Paging.Limit)
io.WriteString(c.w, ` LIMIT ('`) io.WriteString(c.w, ` LIMIT ('`)
io.WriteString(c.w, sel.Paging.Limit) io.WriteString(c.w, sel.Paging.Limit)
@ -742,7 +787,7 @@ func (c *compilerContext) renderBaseSelect(sel *qcode.Select, ti *DBTableInfo, r
io.WriteString(c.w, ` LIMIT ('20') :: integer`) io.WriteString(c.w, ` LIMIT ('20') :: integer`)
} }
if len(sel.Paging.Offset) != 0 { if sel.Paging.Offset != "" {
//fmt.Fprintf(w, ` OFFSET ('%s') :: integer`, c.sel.Paging.Offset) //fmt.Fprintf(w, ` OFFSET ('%s') :: integer`, c.sel.Paging.Offset)
io.WriteString(c.w, ` OFFSET ('`) io.WriteString(c.w, ` OFFSET ('`)
io.WriteString(c.w, sel.Paging.Offset) io.WriteString(c.w, sel.Paging.Offset)
@ -811,22 +856,25 @@ func (c *compilerContext) renderCursorCTE(sel *qcode.Select) error {
return nil return nil
} }
func (c *compilerContext) renderRelationship(sel *qcode.Select, ti *DBTableInfo) error { func (c *compilerContext) renderRelationshipByName(table, parent string) error {
parent := c.s[sel.ParentID]
pti, err := c.schema.GetTable(parent.Name)
if err != nil {
return err
}
return c.renderRelationshipByName(ti.Name, pti.Name, parent.ID)
}
func (c *compilerContext) renderRelationshipByName(table, parent string, id int32) error {
rel, err := c.schema.GetRel(table, parent) rel, err := c.schema.GetRel(table, parent)
if err != nil { if err != nil {
return err return err
} }
return c.renderRelationship(nil, rel)
}
func (c *compilerContext) renderRelationship(sel *qcode.Select, rel *DBRel) error {
var pid int32
switch {
case sel == nil:
pid = int32(-1)
case sel.Type == qcode.STMember:
pid = sel.UParentID
default:
pid = sel.ParentID
}
io.WriteString(c.w, `((`) io.WriteString(c.w, `((`)
@ -838,19 +886,19 @@ func (c *compilerContext) renderRelationshipByName(table, parent string, id int3
switch { switch {
case !rel.Left.Array && rel.Right.Array: case !rel.Left.Array && rel.Right.Array:
colWithTable(c.w, table, rel.Left.Col) colWithTable(c.w, rel.Left.Table, rel.Left.Col)
io.WriteString(c.w, `) = any (`) io.WriteString(c.w, `) = any (`)
colWithTableID(c.w, parent, id, rel.Right.Col) colWithTableID(c.w, rel.Right.Table, pid, rel.Right.Col)
case rel.Left.Array && !rel.Right.Array: case rel.Left.Array && !rel.Right.Array:
colWithTableID(c.w, parent, id, rel.Right.Col) colWithTableID(c.w, rel.Right.Table, pid, rel.Right.Col)
io.WriteString(c.w, `) = any (`) io.WriteString(c.w, `) = any (`)
colWithTable(c.w, table, rel.Left.Col) colWithTable(c.w, rel.Left.Table, rel.Left.Col)
default: default:
colWithTable(c.w, table, rel.Left.Col) colWithTable(c.w, rel.Left.Table, rel.Left.Col)
io.WriteString(c.w, `) = (`) io.WriteString(c.w, `) = (`)
colWithTableID(c.w, parent, id, rel.Right.Col) colWithTableID(c.w, rel.Right.Table, pid, rel.Right.Col)
} }
case RelOneToManyThrough: case RelOneToManyThrough:
@ -860,25 +908,34 @@ func (c *compilerContext) renderRelationshipByName(table, parent string, id int3
switch { switch {
case !rel.Left.Array && rel.Right.Array: case !rel.Left.Array && rel.Right.Array:
colWithTable(c.w, table, rel.Left.Col) colWithTable(c.w, rel.Left.Table, rel.Left.Col)
io.WriteString(c.w, `) = any (`) io.WriteString(c.w, `) = any (`)
colWithTable(c.w, rel.Through, rel.Right.Col) colWithTable(c.w, rel.Through.Table, rel.Through.ColR)
case rel.Left.Array && !rel.Right.Array: case rel.Left.Array && !rel.Right.Array:
colWithTable(c.w, rel.Through, rel.Right.Col) colWithTable(c.w, rel.Through.Table, rel.Through.ColR)
io.WriteString(c.w, `) = any (`) io.WriteString(c.w, `) = any (`)
colWithTable(c.w, table, rel.Left.Col) colWithTable(c.w, rel.Left.Table, rel.Left.Col)
default: default:
colWithTable(c.w, table, rel.Left.Col) colWithTable(c.w, rel.Through.Table, rel.Through.ColR)
io.WriteString(c.w, `) = (`) io.WriteString(c.w, `) = (`)
colWithTable(c.w, rel.Through, rel.Right.Col) colWithTable(c.w, rel.Right.Table, rel.Right.Col)
} }
case RelEmbedded: case RelEmbedded:
colWithTable(c.w, rel.Left.Table, rel.Left.Col) colWithTable(c.w, rel.Left.Table, rel.Left.Col)
io.WriteString(c.w, `) = (`) io.WriteString(c.w, `) = (`)
colWithTableID(c.w, parent, id, rel.Left.Col) colWithTableID(c.w, rel.Left.Table, pid, rel.Left.Col)
case RelPolymorphic:
colWithTable(c.w, sel.Name, rel.Right.Col)
io.WriteString(c.w, `) = (`)
colWithTableID(c.w, rel.Left.Table, pid, rel.Left.Col)
io.WriteString(c.w, `) AND (`)
colWithTableID(c.w, rel.Left.Table, pid, rel.Right.Table)
io.WriteString(c.w, `) = (`)
squoted(c.w, sel.Name)
} }
io.WriteString(c.w, `))`) io.WriteString(c.w, `))`)
@ -954,13 +1011,10 @@ func (c *compilerContext) renderExp(ex *qcode.Exp, ti *DBTableInfo, skipNested b
return err return err
} }
} else { } else if err := c.renderOp(val, ti); err != nil {
//fmt.Fprintf(w, `(("%s"."%s") `, c.sel.Name, val.Col)
if err := c.renderOp(val, ti); err != nil {
return err return err
} }
} }
}
//qcode.FreeExp(val) //qcode.FreeExp(val)
default: default:
@ -991,7 +1045,7 @@ func (c *compilerContext) renderNestedWhere(ex *qcode.Exp, ti *DBTableInfo) erro
io.WriteString(c.w, ` WHERE `) io.WriteString(c.w, ` WHERE `)
if err := c.renderRelationshipByName(cti.Name, ti.Name, -1); err != nil { if err := c.renderRelationshipByName(cti.Name, ti.Name); err != nil {
return err return err
} }
@ -1020,7 +1074,7 @@ func (c *compilerContext) renderOp(ex *qcode.Exp, ti *DBTableInfo) error {
return nil return nil
} }
if len(ex.Col) != 0 { if ex.Col != "" {
if col, ok = ti.ColMap[ex.Col]; !ok { if col, ok = ti.ColMap[ex.Col]; !ok {
return fmt.Errorf("no column '%s' found ", ex.Col) return fmt.Errorf("no column '%s' found ", ex.Col)
} }
@ -1260,7 +1314,7 @@ func funcPrefixLen(fm map[string]*DBFunction, fn string) int {
return 0 return 0
} }
func hasBit(n uint32, pos uint32) bool { func hasBit(n, pos uint32) bool {
val := n & (1 << pos) val := n & (1 << pos)
return (val > 0) return (val > 0)
} }

View File

@ -381,25 +381,25 @@ func withFragment3(t *testing.T) {
compileGQLToPSQL(t, gql, nil, "anon") compileGQLToPSQL(t, gql, nil, "anon")
} }
func withInlineFragment(t *testing.T) { // func withInlineFragment(t *testing.T) {
gql := ` // gql := `
query { // query {
users { // users {
... on users { // ... on users {
id // id
email // email
} // }
created_at // created_at
... on user { // ... on user {
first_name // first_name
last_name // last_name
} // }
} // }
} // }
` // `
compileGQLToPSQL(t, gql, nil, "anon") // compileGQLToPSQL(t, gql, nil, "anon")
} // }
func withCursor(t *testing.T) { func withCursor(t *testing.T) {
gql := `query { gql := `query {
@ -497,7 +497,7 @@ func TestCompileQuery(t *testing.T) {
t.Run("withFragment1", withFragment1) t.Run("withFragment1", withFragment1)
t.Run("withFragment2", withFragment2) t.Run("withFragment2", withFragment2)
t.Run("withFragment3", withFragment3) t.Run("withFragment3", withFragment3)
t.Run("withInlineFragment", withInlineFragment) //t.Run("withInlineFragment", withInlineFragment)
t.Run("jsonColumnAsTable", jsonColumnAsTable) t.Run("jsonColumnAsTable", jsonColumnAsTable)
t.Run("withCursor", withCursor) t.Run("withCursor", withCursor)
t.Run("nullForAuthRequiredInAnon", nullForAuthRequiredInAnon) t.Run("nullForAuthRequiredInAnon", nullForAuthRequiredInAnon)

View File

@ -11,6 +11,7 @@ type DBSchema struct {
ver int ver int
t map[string]*DBTableInfo t map[string]*DBTableInfo
rm map[string]map[string]*DBRel rm map[string]map[string]*DBRel
vt map[string]*VirtualTable
fm map[string]*DBFunction fm map[string]*DBFunction
} }
@ -33,14 +34,18 @@ const (
RelOneToOne RelType = iota + 1 RelOneToOne RelType = iota + 1
RelOneToMany RelOneToMany
RelOneToManyThrough RelOneToManyThrough
RelPolymorphic
RelEmbedded RelEmbedded
RelRemote RelRemote
) )
type DBRel struct { type DBRel struct {
Type RelType Type RelType
Through string Through struct {
ColT string Table string
ColL string
ColR string
}
Left struct { Left struct {
col *DBColumn col *DBColumn
Table string Table string
@ -60,6 +65,7 @@ func NewDBSchema(info *DBInfo, aliases map[string][]string) (*DBSchema, error) {
ver: info.Version, ver: info.Version,
t: make(map[string]*DBTableInfo), t: make(map[string]*DBTableInfo),
rm: make(map[string]map[string]*DBRel), rm: make(map[string]map[string]*DBRel),
vt: make(map[string]*VirtualTable),
fm: make(map[string]*DBFunction, len(info.Functions)), fm: make(map[string]*DBFunction, len(info.Functions)),
} }
@ -70,6 +76,10 @@ func NewDBSchema(info *DBInfo, aliases map[string][]string) (*DBSchema, error) {
} }
} }
if err := schema.virtualRels(info.VTables); err != nil {
return nil, err
}
for i, t := range info.Tables { for i, t := range info.Tables {
err := schema.firstDegreeRels(t, info.Columns[i]) err := schema.firstDegreeRels(t, info.Columns[i])
if err != nil { if err != nil {
@ -102,7 +112,7 @@ func (s *DBSchema) addTable(
singular := flect.Singularize(t.Key) singular := flect.Singularize(t.Key)
plural := flect.Pluralize(t.Key) plural := flect.Pluralize(t.Key)
s.t[singular] = &DBTableInfo{ ts := &DBTableInfo{
Name: t.Name, Name: t.Name,
Type: t.Type, Type: t.Type,
IsSingular: true, IsSingular: true,
@ -112,8 +122,9 @@ func (s *DBSchema) addTable(
Singular: singular, Singular: singular,
Plural: plural, Plural: plural,
} }
s.t[singular] = ts
s.t[plural] = &DBTableInfo{ tp := &DBTableInfo{
Name: t.Name, Name: t.Name,
Type: t.Type, Type: t.Type,
IsSingular: false, IsSingular: false,
@ -123,14 +134,15 @@ func (s *DBSchema) addTable(
Singular: singular, Singular: singular,
Plural: plural, Plural: plural,
} }
s.t[plural] = tp
if al, ok := aliases[t.Key]; ok { if al, ok := aliases[t.Key]; ok {
for i := range al { for i := range al {
k1 := flect.Singularize(al[i]) k1 := flect.Singularize(al[i])
s.t[k1] = s.t[singular] s.t[k1] = ts
k2 := flect.Pluralize(al[i]) k2 := flect.Pluralize(al[i])
s.t[k2] = s.t[plural] s.t[k2] = tp
} }
} }
@ -154,6 +166,54 @@ func (s *DBSchema) addTable(
return nil return nil
} }
func (s *DBSchema) virtualRels(vts []VirtualTable) error {
for _, vt := range vts {
s.vt[vt.Name] = &vt
for _, t := range s.t {
idCol, ok := t.ColMap[vt.IDColumn]
if !ok {
continue
}
if _, ok = t.ColMap[vt.TypeColumn]; !ok {
continue
}
nt := DBTable{
ID: -1,
Name: vt.Name,
Key: strings.ToLower(vt.Name),
Type: "virtual",
}
if err := s.addTable(nt, nil, nil); err != nil {
return err
}
rel := &DBRel{Type: RelPolymorphic}
rel.Left.col = idCol
rel.Left.Table = t.Name
rel.Left.Col = idCol.Name
rcol := DBColumn{
Name: vt.FKeyColumn,
Key: strings.ToLower(vt.FKeyColumn),
Type: idCol.Type,
}
rel.Right.col = &rcol
rel.Right.Table = vt.TypeColumn
rel.Right.Col = rcol.Name
if err := s.SetRel(vt.Name, t.Name, rel); err != nil {
return err
}
}
}
return nil
}
func (s *DBSchema) firstDegreeRels(t DBTable, cols []DBColumn) error { func (s *DBSchema) firstDegreeRels(t DBTable, cols []DBColumn) error {
ct := t.Key ct := t.Key
cti, ok := s.t[ct] cti, ok := s.t[ct]
@ -164,7 +224,7 @@ func (s *DBSchema) firstDegreeRels(t DBTable, cols []DBColumn) error {
for i := range cols { for i := range cols {
c := cols[i] c := cols[i]
if len(c.FKeyTable) == 0 { if c.FKeyTable == "" {
continue continue
} }
@ -268,7 +328,7 @@ func (s *DBSchema) secondDegreeRels(t DBTable, cols []DBColumn) error {
for i := range cols { for i := range cols {
c := cols[i] c := cols[i]
if len(c.FKeyTable) == 0 { if c.FKeyTable == "" {
continue continue
} }
@ -344,16 +404,17 @@ func (s *DBSchema) updateSchemaOTMT(
// One-to-many-through relation between 1nd foreign key table and the // One-to-many-through relation between 1nd foreign key table and the
// 2nd foreign key table // 2nd foreign key table
rel1 := &DBRel{Type: RelOneToManyThrough} rel1 := &DBRel{Type: RelOneToManyThrough}
rel1.Through = ti.Name rel1.Through.Table = ti.Name
rel1.ColT = col2.Name rel1.Through.ColL = col1.Name
rel1.Through.ColR = col2.Name
rel1.Left.col = &col2 rel1.Left.col = fc1
rel1.Left.Table = col2.FKeyTable rel1.Left.Table = col1.FKeyTable
rel1.Left.Col = fc2.Name rel1.Left.Col = fc1.Name
rel1.Right.col = &col1 rel1.Right.col = fc2
rel1.Right.Table = ti.Name rel1.Right.Table = t2
rel1.Right.Col = col1.Name rel1.Right.Col = fc2.Name
if err := s.SetRel(t1, t2, rel1); err != nil { if err := s.SetRel(t1, t2, rel1); err != nil {
return err return err
@ -362,16 +423,17 @@ func (s *DBSchema) updateSchemaOTMT(
// One-to-many-through relation between 2nd foreign key table and the // One-to-many-through relation between 2nd foreign key table and the
// 1nd foreign key table // 1nd foreign key table
rel2 := &DBRel{Type: RelOneToManyThrough} rel2 := &DBRel{Type: RelOneToManyThrough}
rel2.Through = ti.Name rel2.Through.Table = ti.Name
rel2.ColT = col1.Name rel2.Through.ColL = col2.Name
rel2.Through.ColR = col1.Name
rel1.Left.col = fc1 rel2.Left.col = fc2
rel2.Left.Table = col1.FKeyTable rel2.Left.Table = col2.FKeyTable
rel2.Left.Col = fc1.Name rel2.Left.Col = fc2.Name
rel1.Right.col = &col2 rel2.Right.col = fc1
rel2.Right.Table = ti.Name rel2.Right.Table = t1
rel2.Right.Col = col2.Name rel2.Right.Col = fc1.Name
if err := s.SetRel(t2, t1, rel2); err != nil { if err := s.SetRel(t2, t1, rel2); err != nil {
return err return err

View File

@ -14,14 +14,18 @@ func (rt RelType) String() string {
return "remote" return "remote"
case RelEmbedded: case RelEmbedded:
return "embedded" return "embedded"
case RelPolymorphic:
return "polymorphic"
} }
return "" return ""
} }
func (re *DBRel) String() string { func (re *DBRel) String() string {
if re.Type == RelOneToManyThrough { if re.Type == RelOneToManyThrough {
return fmt.Sprintf("'%s.%s' --(Through: %s)--> '%s.%s'", return fmt.Sprintf("'%s.%s' --(%s.%s, %s.%s)--> '%s.%s'",
re.Left.Table, re.Left.Col, re.Through, re.Right.Table, re.Right.Col) re.Left.Table, re.Left.Col,
re.Through.Table, re.Through.ColL, re.Through.Table, re.Through.ColR,
re.Right.Table, re.Right.Col)
} }
return fmt.Sprintf("'%s.%s' --(%s)--> '%s.%s'", return fmt.Sprintf("'%s.%s' --(%s)--> '%s.%s'",
re.Left.Table, re.Left.Col, re.Type, re.Right.Table, re.Right.Col) re.Left.Table, re.Left.Col, re.Type, re.Right.Table, re.Right.Col)

View File

@ -14,9 +14,17 @@ type DBInfo struct {
Tables []DBTable Tables []DBTable
Columns [][]DBColumn Columns [][]DBColumn
Functions []DBFunction Functions []DBFunction
VTables []VirtualTable
colMap map[string]map[string]*DBColumn colMap map[string]map[string]*DBColumn
} }
type VirtualTable struct {
Name string
IDColumn string
TypeColumn string
FKeyColumn string
}
func GetDBInfo(db *sql.DB, schema string) (*DBInfo, error) { func GetDBInfo(db *sql.DB, schema string) (*DBInfo, error) {
di := &DBInfo{} di := &DBInfo{}
var version string var version string

View File

@ -67,9 +67,9 @@ SELECT jsonb_build_object('products', "__sj_0"."json") as "__root" FROM (SELECT
=== RUN TestCompileQuery/oneToManyArray === RUN TestCompileQuery/oneToManyArray
SELECT jsonb_build_object('tags', "__sj_0"."json", 'product', "__sj_2"."json") as "__root" FROM (SELECT to_jsonb("__sr_2".*) AS "json"FROM (SELECT "products_2"."name" AS "name", "products_2"."price" AS "price", "__sj_3"."json" AS "tags" FROM (SELECT "products"."name", "products"."price", "products"."tags" FROM "products" LIMIT ('1') :: integer) AS "products_2" LEFT OUTER JOIN LATERAL (SELECT coalesce(jsonb_agg("__sj_3"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_3".*) AS "json"FROM (SELECT "tags_3"."id" AS "id", "tags_3"."name" AS "name" FROM (SELECT "tags"."id", "tags"."name" FROM "tags" WHERE ((("tags"."slug") = any ("products_2"."tags"))) LIMIT ('20') :: integer) AS "tags_3") AS "__sr_3") AS "__sj_3") AS "__sj_3" ON ('true')) AS "__sr_2") AS "__sj_2", (SELECT coalesce(jsonb_agg("__sj_0"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "tags_0"."name" AS "name", "__sj_1"."json" AS "product" FROM (SELECT "tags"."name", "tags"."slug" FROM "tags" LIMIT ('20') :: integer) AS "tags_0" LEFT OUTER JOIN LATERAL (SELECT to_jsonb("__sr_1".*) AS "json"FROM (SELECT "products_1"."name" AS "name" FROM (SELECT "products"."name" FROM "products" WHERE ((("tags_0"."slug") = any ("products"."tags"))) LIMIT ('1') :: integer) AS "products_1") AS "__sr_1") AS "__sj_1" ON ('true')) AS "__sr_0") AS "__sj_0") AS "__sj_0" SELECT jsonb_build_object('tags', "__sj_0"."json", 'product', "__sj_2"."json") as "__root" FROM (SELECT to_jsonb("__sr_2".*) AS "json"FROM (SELECT "products_2"."name" AS "name", "products_2"."price" AS "price", "__sj_3"."json" AS "tags" FROM (SELECT "products"."name", "products"."price", "products"."tags" FROM "products" LIMIT ('1') :: integer) AS "products_2" LEFT OUTER JOIN LATERAL (SELECT coalesce(jsonb_agg("__sj_3"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_3".*) AS "json"FROM (SELECT "tags_3"."id" AS "id", "tags_3"."name" AS "name" FROM (SELECT "tags"."id", "tags"."name" FROM "tags" WHERE ((("tags"."slug") = any ("products_2"."tags"))) LIMIT ('20') :: integer) AS "tags_3") AS "__sr_3") AS "__sj_3") AS "__sj_3" ON ('true')) AS "__sr_2") AS "__sj_2", (SELECT coalesce(jsonb_agg("__sj_0"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "tags_0"."name" AS "name", "__sj_1"."json" AS "product" FROM (SELECT "tags"."name", "tags"."slug" FROM "tags" LIMIT ('20') :: integer) AS "tags_0" LEFT OUTER JOIN LATERAL (SELECT to_jsonb("__sr_1".*) AS "json"FROM (SELECT "products_1"."name" AS "name" FROM (SELECT "products"."name" FROM "products" WHERE ((("tags_0"."slug") = any ("products"."tags"))) LIMIT ('1') :: integer) AS "products_1") AS "__sr_1") AS "__sj_1" ON ('true')) AS "__sr_0") AS "__sj_0") AS "__sj_0"
=== RUN TestCompileQuery/manyToMany === RUN TestCompileQuery/manyToMany
SELECT jsonb_build_object('products', "__sj_0"."json") as "__root" FROM (SELECT coalesce(jsonb_agg("__sj_0"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "products_0"."name" AS "name", "__sj_1"."json" AS "customers" FROM (SELECT "products"."name", "products"."id" FROM "products" WHERE (((("products"."price") > '0' :: numeric(7,2)) AND (("products"."price") < '8' :: numeric(7,2)))) LIMIT ('20') :: integer) AS "products_0" LEFT OUTER JOIN LATERAL (SELECT coalesce(jsonb_agg("__sj_1"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_1".*) AS "json"FROM (SELECT "customers_1"."email" AS "email", "customers_1"."full_name" AS "full_name" FROM (SELECT "customers"."email", "customers"."full_name" FROM "customers" LEFT OUTER JOIN "purchases" ON (("purchases"."product_id") = ("products_0"."id")) WHERE ((("customers"."id") = ("purchases"."customer_id"))) LIMIT ('20') :: integer) AS "customers_1") AS "__sr_1") AS "__sj_1") AS "__sj_1" ON ('true')) AS "__sr_0") AS "__sj_0") AS "__sj_0" SELECT jsonb_build_object('products', "__sj_0"."json") as "__root" FROM (SELECT coalesce(jsonb_agg("__sj_0"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "products_0"."name" AS "name", "__sj_1"."json" AS "customers" FROM (SELECT "products"."name", "products"."id" FROM "products" WHERE (((("products"."price") > '0' :: numeric(7,2)) AND (("products"."price") < '8' :: numeric(7,2)))) LIMIT ('20') :: integer) AS "products_0" LEFT OUTER JOIN LATERAL (SELECT coalesce(jsonb_agg("__sj_1"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_1".*) AS "json"FROM (SELECT "customers_1"."email" AS "email", "customers_1"."full_name" AS "full_name" FROM (SELECT "customers"."email", "customers"."full_name" FROM "customers" LEFT OUTER JOIN "purchases" ON (("purchases"."customer_id") = ("customers"."id")) WHERE ((("purchases"."product_id") = ("products"."id"))) LIMIT ('20') :: integer) AS "customers_1") AS "__sr_1") AS "__sj_1") AS "__sj_1" ON ('true')) AS "__sr_0") AS "__sj_0") AS "__sj_0"
=== RUN TestCompileQuery/manyToManyReverse === RUN TestCompileQuery/manyToManyReverse
SELECT jsonb_build_object('customers', "__sj_0"."json") as "__root" FROM (SELECT coalesce(jsonb_agg("__sj_0"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "customers_0"."email" AS "email", "customers_0"."full_name" AS "full_name", "__sj_1"."json" AS "products" FROM (SELECT "customers"."email", "customers"."full_name", "customers"."id" FROM "customers" LIMIT ('20') :: integer) AS "customers_0" LEFT OUTER JOIN LATERAL (SELECT coalesce(jsonb_agg("__sj_1"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_1".*) AS "json"FROM (SELECT "products_1"."name" AS "name" FROM (SELECT "products"."name" FROM "products" LEFT OUTER JOIN "purchases" ON (("purchases"."customer_id") = ("customers_0"."id")) WHERE ((("products"."id") = ("purchases"."product_id")) AND ((("products"."price") > '0' :: numeric(7,2)) AND (("products"."price") < '8' :: numeric(7,2)))) LIMIT ('20') :: integer) AS "products_1") AS "__sr_1") AS "__sj_1") AS "__sj_1" ON ('true')) AS "__sr_0") AS "__sj_0") AS "__sj_0" SELECT jsonb_build_object('customers', "__sj_0"."json") as "__root" FROM (SELECT coalesce(jsonb_agg("__sj_0"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "customers_0"."email" AS "email", "customers_0"."full_name" AS "full_name", "__sj_1"."json" AS "products" FROM (SELECT "customers"."email", "customers"."full_name", "customers"."id" FROM "customers" LIMIT ('20') :: integer) AS "customers_0" LEFT OUTER JOIN LATERAL (SELECT coalesce(jsonb_agg("__sj_1"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_1".*) AS "json"FROM (SELECT "products_1"."name" AS "name" FROM (SELECT "products"."name" FROM "products" LEFT OUTER JOIN "purchases" ON (("purchases"."product_id") = ("products"."id")) WHERE ((("purchases"."customer_id") = ("customers"."id")) AND ((("products"."price") > '0' :: numeric(7,2)) AND (("products"."price") < '8' :: numeric(7,2)))) LIMIT ('20') :: integer) AS "products_1") AS "__sr_1") AS "__sj_1") AS "__sj_1" ON ('true')) AS "__sr_0") AS "__sj_0") AS "__sj_0"
=== RUN TestCompileQuery/aggFunction === RUN TestCompileQuery/aggFunction
SELECT jsonb_build_object('products', "__sj_0"."json") as "__root" FROM (SELECT coalesce(jsonb_agg("__sj_0"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "products_0"."name" AS "name", "products_0"."count_price" AS "count_price" FROM (SELECT "products"."name", count("products"."price") AS "count_price" FROM "products" WHERE (((("products"."price") > '0' :: numeric(7,2)) AND (("products"."price") < '8' :: numeric(7,2)))) GROUP BY "products"."name" LIMIT ('20') :: integer) AS "products_0") AS "__sr_0") AS "__sj_0") AS "__sj_0" SELECT jsonb_build_object('products', "__sj_0"."json") as "__root" FROM (SELECT coalesce(jsonb_agg("__sj_0"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "products_0"."name" AS "name", "products_0"."count_price" AS "count_price" FROM (SELECT "products"."name", count("products"."price") AS "count_price" FROM "products" WHERE (((("products"."price") > '0' :: numeric(7,2)) AND (("products"."price") < '8' :: numeric(7,2)))) GROUP BY "products"."name" LIMIT ('20') :: integer) AS "products_0") AS "__sr_0") AS "__sj_0") AS "__sj_0"
=== RUN TestCompileQuery/aggFunctionBlockedByCol === RUN TestCompileQuery/aggFunctionBlockedByCol
@ -85,15 +85,13 @@ SELECT jsonb_build_object('product', "__sj_0"."json") as "__root" FROM (SELECT t
=== RUN TestCompileQuery/withWhereOnRelations === RUN TestCompileQuery/withWhereOnRelations
SELECT jsonb_build_object('users', "__sj_0"."json") as "__root" FROM (SELECT coalesce(jsonb_agg("__sj_0"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "users_0"."id" AS "id", "users_0"."email" AS "email" FROM (SELECT "users"."id", "users"."email" FROM "users" WHERE (NOT EXISTS (SELECT 1 FROM products WHERE (("products"."user_id") = ("users"."id")) AND ((("products"."price") > '3' :: numeric(7,2))))) LIMIT ('20') :: integer) AS "users_0") AS "__sr_0") AS "__sj_0") AS "__sj_0" SELECT jsonb_build_object('users', "__sj_0"."json") as "__root" FROM (SELECT coalesce(jsonb_agg("__sj_0"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "users_0"."id" AS "id", "users_0"."email" AS "email" FROM (SELECT "users"."id", "users"."email" FROM "users" WHERE (NOT EXISTS (SELECT 1 FROM products WHERE (("products"."user_id") = ("users"."id")) AND ((("products"."price") > '3' :: numeric(7,2))))) LIMIT ('20') :: integer) AS "users_0") AS "__sr_0") AS "__sj_0") AS "__sj_0"
=== RUN TestCompileQuery/multiRoot === RUN TestCompileQuery/multiRoot
SELECT jsonb_build_object('customer', "__sj_0"."json", 'user', "__sj_1"."json", 'product', "__sj_2"."json") as "__root" FROM (SELECT to_jsonb("__sr_2".*) AS "json"FROM (SELECT "products_2"."id" AS "id", "products_2"."name" AS "name", "__sj_3"."json" AS "customers", "__sj_4"."json" AS "customer" FROM (SELECT "products"."id", "products"."name" FROM "products" WHERE (((("products"."price") > '0' :: numeric(7,2)) AND (("products"."price") < '8' :: numeric(7,2)))) LIMIT ('1') :: integer) AS "products_2" LEFT OUTER JOIN LATERAL (SELECT to_jsonb("__sr_4".*) AS "json"FROM (SELECT "customers_4"."email" AS "email" FROM (SELECT "customers"."email" FROM "customers" LEFT OUTER JOIN "purchases" ON (("purchases"."product_id") = ("products_2"."id")) WHERE ((("customers"."id") = ("purchases"."customer_id"))) LIMIT ('1') :: integer) AS "customers_4") AS "__sr_4") AS "__sj_4" ON ('true') LEFT OUTER JOIN LATERAL (SELECT coalesce(jsonb_agg("__sj_3"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_3".*) AS "json"FROM (SELECT "customers_3"."email" AS "email" FROM (SELECT "customers"."email" FROM "customers" LEFT OUTER JOIN "purchases" ON (("purchases"."product_id") = ("products_2"."id")) WHERE ((("customers"."id") = ("purchases"."customer_id"))) LIMIT ('20') :: integer) AS "customers_3") AS "__sr_3") AS "__sj_3") AS "__sj_3" ON ('true')) AS "__sr_2") AS "__sj_2", (SELECT to_jsonb("__sr_1".*) AS "json"FROM (SELECT "users_1"."id" AS "id", "users_1"."email" AS "email" FROM (SELECT "users"."id", "users"."email" FROM "users" LIMIT ('1') :: integer) AS "users_1") AS "__sr_1") AS "__sj_1", (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "customers_0"."id" AS "id" FROM (SELECT "customers"."id" FROM "customers" LIMIT ('1') :: integer) AS "customers_0") AS "__sr_0") AS "__sj_0" SELECT jsonb_build_object('customer', "__sj_0"."json", 'user', "__sj_1"."json", 'product', "__sj_2"."json") as "__root" FROM (SELECT to_jsonb("__sr_2".*) AS "json"FROM (SELECT "products_2"."id" AS "id", "products_2"."name" AS "name", "__sj_3"."json" AS "customers", "__sj_4"."json" AS "customer" FROM (SELECT "products"."id", "products"."name" FROM "products" WHERE (((("products"."price") > '0' :: numeric(7,2)) AND (("products"."price") < '8' :: numeric(7,2)))) LIMIT ('1') :: integer) AS "products_2" LEFT OUTER JOIN LATERAL (SELECT to_jsonb("__sr_4".*) AS "json"FROM (SELECT "customers_4"."email" AS "email" FROM (SELECT "customers"."email" FROM "customers" LEFT OUTER JOIN "purchases" ON (("purchases"."customer_id") = ("customers"."id")) WHERE ((("purchases"."product_id") = ("products"."id"))) LIMIT ('1') :: integer) AS "customers_4") AS "__sr_4") AS "__sj_4" ON ('true') LEFT OUTER JOIN LATERAL (SELECT coalesce(jsonb_agg("__sj_3"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_3".*) AS "json"FROM (SELECT "customers_3"."email" AS "email" FROM (SELECT "customers"."email" FROM "customers" LEFT OUTER JOIN "purchases" ON (("purchases"."customer_id") = ("customers"."id")) WHERE ((("purchases"."product_id") = ("products"."id"))) LIMIT ('20') :: integer) AS "customers_3") AS "__sr_3") AS "__sj_3") AS "__sj_3" ON ('true')) AS "__sr_2") AS "__sj_2", (SELECT to_jsonb("__sr_1".*) AS "json"FROM (SELECT "users_1"."id" AS "id", "users_1"."email" AS "email" FROM (SELECT "users"."id", "users"."email" FROM "users" LIMIT ('1') :: integer) AS "users_1") AS "__sr_1") AS "__sj_1", (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "customers_0"."id" AS "id" FROM (SELECT "customers"."id" FROM "customers" LIMIT ('1') :: integer) AS "customers_0") AS "__sr_0") AS "__sj_0"
=== RUN TestCompileQuery/withFragment1 === RUN TestCompileQuery/withFragment1
SELECT jsonb_build_object('users', "__sj_0"."json") as "__root" FROM (SELECT coalesce(jsonb_agg("__sj_0"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "users_0"."first_name" AS "first_name", "users_0"."last_name" AS "last_name", "users_0"."created_at" AS "created_at", "users_0"."id" AS "id", "users_0"."email" AS "email" FROM (SELECT , "users"."created_at", "users"."id", "users"."email" FROM "users" GROUP BY "users"."created_at", "users"."id", "users"."email" LIMIT ('20') :: integer) AS "users_0") AS "__sr_0") AS "__sj_0") AS "__sj_0" SELECT jsonb_build_object('users', "__sj_0"."json") as "__root" FROM (SELECT coalesce(jsonb_agg("__sj_0"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "users_0"."first_name" AS "first_name", "users_0"."last_name" AS "last_name", "users_0"."created_at" AS "created_at", "users_0"."id" AS "id", "users_0"."email" AS "email" FROM (SELECT , "users"."created_at", "users"."id", "users"."email" FROM "users" GROUP BY "users"."created_at", "users"."id", "users"."email" LIMIT ('20') :: integer) AS "users_0") AS "__sr_0") AS "__sj_0") AS "__sj_0"
=== RUN TestCompileQuery/withFragment2 === RUN TestCompileQuery/withFragment2
SELECT jsonb_build_object('users', "__sj_0"."json") as "__root" FROM (SELECT coalesce(jsonb_agg("__sj_0"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "users_0"."first_name" AS "first_name", "users_0"."last_name" AS "last_name", "users_0"."created_at" AS "created_at", "users_0"."id" AS "id", "users_0"."email" AS "email" FROM (SELECT , "users"."created_at", "users"."id", "users"."email" FROM "users" GROUP BY "users"."created_at", "users"."id", "users"."email" LIMIT ('20') :: integer) AS "users_0") AS "__sr_0") AS "__sj_0") AS "__sj_0" SELECT jsonb_build_object('users', "__sj_0"."json") as "__root" FROM (SELECT coalesce(jsonb_agg("__sj_0"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "users_0"."first_name" AS "first_name", "users_0"."last_name" AS "last_name", "users_0"."created_at" AS "created_at", "users_0"."id" AS "id", "users_0"."email" AS "email" FROM (SELECT , "users"."created_at", "users"."id", "users"."email" FROM "users" GROUP BY "users"."created_at", "users"."id", "users"."email" LIMIT ('20') :: integer) AS "users_0") AS "__sr_0") AS "__sj_0") AS "__sj_0"
=== RUN TestCompileQuery/withFragment3 === RUN TestCompileQuery/withFragment3
SELECT jsonb_build_object('users', "__sj_0"."json") as "__root" FROM (SELECT coalesce(jsonb_agg("__sj_0"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "users_0"."first_name" AS "first_name", "users_0"."last_name" AS "last_name", "users_0"."created_at" AS "created_at", "users_0"."id" AS "id", "users_0"."email" AS "email" FROM (SELECT , "users"."created_at", "users"."id", "users"."email" FROM "users" GROUP BY "users"."created_at", "users"."id", "users"."email" LIMIT ('20') :: integer) AS "users_0") AS "__sr_0") AS "__sj_0") AS "__sj_0" SELECT jsonb_build_object('users', "__sj_0"."json") as "__root" FROM (SELECT coalesce(jsonb_agg("__sj_0"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "users_0"."first_name" AS "first_name", "users_0"."last_name" AS "last_name", "users_0"."created_at" AS "created_at", "users_0"."id" AS "id", "users_0"."email" AS "email" FROM (SELECT , "users"."created_at", "users"."id", "users"."email" FROM "users" GROUP BY "users"."created_at", "users"."id", "users"."email" LIMIT ('20') :: integer) AS "users_0") AS "__sr_0") AS "__sj_0") AS "__sj_0"
=== RUN TestCompileQuery/withInlineFragment
SELECT jsonb_build_object('users', "__sj_0"."json") as "__root" FROM (SELECT coalesce(jsonb_agg("__sj_0"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "users_0"."id" AS "id", "users_0"."email" AS "email", "users_0"."created_at" AS "created_at", "users_0"."first_name" AS "first_name", "users_0"."last_name" AS "last_name" FROM (SELECT "users"."id", "users"."email", "users"."created_at" FROM "users" GROUP BY "users"."id", "users"."email", "users"."created_at" LIMIT ('20') :: integer) AS "users_0") AS "__sr_0") AS "__sj_0") AS "__sj_0"
=== RUN TestCompileQuery/jsonColumnAsTable === RUN TestCompileQuery/jsonColumnAsTable
SELECT jsonb_build_object('products', "__sj_0"."json") as "__root" FROM (SELECT coalesce(jsonb_agg("__sj_0"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name", "__sj_1"."json" AS "tag_count" FROM (SELECT "products"."id", "products"."name" FROM "products" LIMIT ('20') :: integer) AS "products_0" LEFT OUTER JOIN LATERAL (SELECT to_jsonb("__sr_1".*) AS "json"FROM (SELECT "tag_count_1"."count" AS "count", "__sj_2"."json" AS "tags" FROM (SELECT "tag_count"."count", "tag_count"."tag_id" FROM "products", json_to_recordset("products"."tag_count") AS "tag_count"(tag_id bigint, count int) WHERE ((("products"."id") = ("products_0"."id"))) LIMIT ('1') :: integer) AS "tag_count_1" LEFT OUTER JOIN LATERAL (SELECT coalesce(jsonb_agg("__sj_2"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_2".*) AS "json"FROM (SELECT "tags_2"."name" AS "name" FROM (SELECT "tags"."name" FROM "tags" WHERE ((("tags"."id") = ("tag_count_1"."tag_id"))) LIMIT ('20') :: integer) AS "tags_2") AS "__sr_2") AS "__sj_2") AS "__sj_2" ON ('true')) AS "__sr_1") AS "__sj_1" ON ('true')) AS "__sr_0") AS "__sj_0") AS "__sj_0" SELECT jsonb_build_object('products', "__sj_0"."json") as "__root" FROM (SELECT coalesce(jsonb_agg("__sj_0"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name", "__sj_1"."json" AS "tag_count" FROM (SELECT "products"."id", "products"."name" FROM "products" LIMIT ('20') :: integer) AS "products_0" LEFT OUTER JOIN LATERAL (SELECT to_jsonb("__sr_1".*) AS "json"FROM (SELECT "tag_count_1"."count" AS "count", "__sj_2"."json" AS "tags" FROM (SELECT "tag_count"."count", "tag_count"."tag_id" FROM "products", json_to_recordset("products"."tag_count") AS "tag_count"(tag_id bigint, count int) WHERE ((("products"."id") = ("products_0"."id"))) LIMIT ('1') :: integer) AS "tag_count_1" LEFT OUTER JOIN LATERAL (SELECT coalesce(jsonb_agg("__sj_2"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_2".*) AS "json"FROM (SELECT "tags_2"."name" AS "name" FROM (SELECT "tags"."name" FROM "tags" WHERE ((("tags"."id") = ("tag_count_1"."tag_id"))) LIMIT ('20') :: integer) AS "tags_2") AS "__sr_2") AS "__sj_2") AS "__sj_2" ON ('true')) AS "__sr_1") AS "__sj_1" ON ('true')) AS "__sr_0") AS "__sj_0") AS "__sj_0"
=== RUN TestCompileQuery/withCursor === RUN TestCompileQuery/withCursor
@ -128,7 +126,6 @@ SELECT jsonb_build_object('users', "__sj_0"."json") as "__root" FROM (SELECT coa
--- PASS: TestCompileQuery/withFragment1 (0.00s) --- PASS: TestCompileQuery/withFragment1 (0.00s)
--- PASS: TestCompileQuery/withFragment2 (0.00s) --- PASS: TestCompileQuery/withFragment2 (0.00s)
--- PASS: TestCompileQuery/withFragment3 (0.00s) --- PASS: TestCompileQuery/withFragment3 (0.00s)
--- PASS: TestCompileQuery/withInlineFragment (0.00s)
--- PASS: TestCompileQuery/jsonColumnAsTable (0.00s) --- PASS: TestCompileQuery/jsonColumnAsTable (0.00s)
--- PASS: TestCompileQuery/withCursor (0.00s) --- PASS: TestCompileQuery/withCursor (0.00s)
--- PASS: TestCompileQuery/nullForAuthRequiredInAnon (0.00s) --- PASS: TestCompileQuery/nullForAuthRequiredInAnon (0.00s)
@ -149,8 +146,8 @@ WITH "_sg_input" AS (SELECT $1 :: json AS j), "products" AS (UPDATE "products" S
=== RUN TestCompileUpdate/nestedUpdateOneToManyWithConnect === RUN TestCompileUpdate/nestedUpdateOneToManyWithConnect
WITH "_sg_input" AS (SELECT $1 :: json AS j), "users" AS (UPDATE "users" SET ("full_name", "email", "created_at", "updated_at") = (SELECT CAST( i.j ->>'full_name' AS character varying), CAST( i.j ->>'email' AS character varying), CAST( i.j ->>'created_at' AS timestamp without time zone), CAST( i.j ->>'updated_at' AS timestamp without time zone) FROM "_sg_input" i) WHERE (("users"."id") = $2 :: bigint) RETURNING "users".*), "products_c" AS ( UPDATE "products" SET "user_id" = "users"."id" FROM "users" WHERE ("products"."id"= ((i.j->'product'->'connect'->>'id'))::bigint) RETURNING "products".*), "products_d" AS ( UPDATE "products" SET "user_id" = NULL FROM "users" WHERE ("products"."id"= ((i.j->'product'->'disconnect'->>'id'))::bigint) RETURNING "products".*), "products" AS (SELECT * FROM "products_c" UNION ALL SELECT * FROM "products_d") SELECT jsonb_build_object('user', "__sj_0"."json") as "__root" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "users_0"."id" AS "id", "users_0"."full_name" AS "full_name", "users_0"."email" AS "email", "__sj_1"."json" AS "product" FROM (SELECT "users"."id", "users"."full_name", "users"."email" FROM "users" LIMIT ('1') :: integer) AS "users_0" LEFT OUTER JOIN LATERAL (SELECT to_jsonb("__sr_1".*) AS "json"FROM (SELECT "products_1"."id" AS "id", "products_1"."name" AS "name", "products_1"."price" AS "price" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."user_id") = ("users_0"."id"))) LIMIT ('1') :: integer) AS "products_1") AS "__sr_1") AS "__sj_1" ON ('true')) AS "__sr_0") AS "__sj_0" WITH "_sg_input" AS (SELECT $1 :: json AS j), "users" AS (UPDATE "users" SET ("full_name", "email", "created_at", "updated_at") = (SELECT CAST( i.j ->>'full_name' AS character varying), CAST( i.j ->>'email' AS character varying), CAST( i.j ->>'created_at' AS timestamp without time zone), CAST( i.j ->>'updated_at' AS timestamp without time zone) FROM "_sg_input" i) WHERE (("users"."id") = $2 :: bigint) RETURNING "users".*), "products_c" AS ( UPDATE "products" SET "user_id" = "users"."id" FROM "users" WHERE ("products"."id"= ((i.j->'product'->'connect'->>'id'))::bigint) RETURNING "products".*), "products_d" AS ( UPDATE "products" SET "user_id" = NULL FROM "users" WHERE ("products"."id"= ((i.j->'product'->'disconnect'->>'id'))::bigint) RETURNING "products".*), "products" AS (SELECT * FROM "products_c" UNION ALL SELECT * FROM "products_d") SELECT jsonb_build_object('user', "__sj_0"."json") as "__root" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "users_0"."id" AS "id", "users_0"."full_name" AS "full_name", "users_0"."email" AS "email", "__sj_1"."json" AS "product" FROM (SELECT "users"."id", "users"."full_name", "users"."email" FROM "users" LIMIT ('1') :: integer) AS "users_0" LEFT OUTER JOIN LATERAL (SELECT to_jsonb("__sr_1".*) AS "json"FROM (SELECT "products_1"."id" AS "id", "products_1"."name" AS "name", "products_1"."price" AS "price" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."user_id") = ("users_0"."id"))) LIMIT ('1') :: integer) AS "products_1") AS "__sr_1") AS "__sj_1" ON ('true')) AS "__sr_0") AS "__sj_0"
=== RUN TestCompileUpdate/nestedUpdateOneToOneWithConnect === RUN TestCompileUpdate/nestedUpdateOneToOneWithConnect
WITH "_sg_input" AS (SELECT $1 :: json AS j), "_x_users" AS (SELECT "id" FROM "_sg_input" i,"users" WHERE "users"."id"= ((i.j->'user'->'connect'->>'id'))::bigint AND "users"."email"= ((i.j->'user'->'connect'->>'email'))::character varying LIMIT 1), "products" AS (UPDATE "products" SET ("name", "price", "user_id") = (SELECT CAST( i.j ->>'name' AS character varying), CAST( i.j ->>'price' AS numeric(7,2)), "_x_users"."id" FROM "_sg_input" i, "_x_users") WHERE (("products"."id") = $2 :: bigint) RETURNING "products".*) SELECT jsonb_build_object('product', "__sj_0"."json") as "__root" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name", "__sj_1"."json" AS "user" FROM (SELECT "products"."id", "products"."name", "products"."user_id" FROM "products" LIMIT ('1') :: integer) AS "products_0" LEFT OUTER JOIN LATERAL (SELECT to_jsonb("__sr_1".*) AS "json"FROM (SELECT "users_1"."id" AS "id", "users_1"."full_name" AS "full_name", "users_1"."email" AS "email" FROM (SELECT "users"."id", "users"."full_name", "users"."email" FROM "users" WHERE ((("users"."id") = ("products_0"."user_id"))) LIMIT ('1') :: integer) AS "users_1") AS "__sr_1") AS "__sj_1" ON ('true')) AS "__sr_0") AS "__sj_0"
WITH "_sg_input" AS (SELECT $1 :: json AS j), "_x_users" AS (SELECT "id" FROM "_sg_input" i,"users" WHERE "users"."email"= ((i.j->'user'->'connect'->>'email'))::character varying AND "users"."id"= ((i.j->'user'->'connect'->>'id'))::bigint LIMIT 1), "products" AS (UPDATE "products" SET ("name", "price", "user_id") = (SELECT CAST( i.j ->>'name' AS character varying), CAST( i.j ->>'price' AS numeric(7,2)), "_x_users"."id" FROM "_sg_input" i, "_x_users") WHERE (("products"."id") = $2 :: bigint) RETURNING "products".*) SELECT jsonb_build_object('product', "__sj_0"."json") as "__root" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name", "__sj_1"."json" AS "user" FROM (SELECT "products"."id", "products"."name", "products"."user_id" FROM "products" LIMIT ('1') :: integer) AS "products_0" LEFT OUTER JOIN LATERAL (SELECT to_jsonb("__sr_1".*) AS "json"FROM (SELECT "users_1"."id" AS "id", "users_1"."full_name" AS "full_name", "users_1"."email" AS "email" FROM (SELECT "users"."id", "users"."full_name", "users"."email" FROM "users" WHERE ((("users"."id") = ("products_0"."user_id"))) LIMIT ('1') :: integer) AS "users_1") AS "__sr_1") AS "__sj_1" ON ('true')) AS "__sr_0") AS "__sj_0" WITH "_sg_input" AS (SELECT $1 :: json AS j), "_x_users" AS (SELECT "id" FROM "_sg_input" i,"users" WHERE "users"."email"= ((i.j->'user'->'connect'->>'email'))::character varying AND "users"."id"= ((i.j->'user'->'connect'->>'id'))::bigint LIMIT 1), "products" AS (UPDATE "products" SET ("name", "price", "user_id") = (SELECT CAST( i.j ->>'name' AS character varying), CAST( i.j ->>'price' AS numeric(7,2)), "_x_users"."id" FROM "_sg_input" i, "_x_users") WHERE (("products"."id") = $2 :: bigint) RETURNING "products".*) SELECT jsonb_build_object('product', "__sj_0"."json") as "__root" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name", "__sj_1"."json" AS "user" FROM (SELECT "products"."id", "products"."name", "products"."user_id" FROM "products" LIMIT ('1') :: integer) AS "products_0" LEFT OUTER JOIN LATERAL (SELECT to_jsonb("__sr_1".*) AS "json"FROM (SELECT "users_1"."id" AS "id", "users_1"."full_name" AS "full_name", "users_1"."email" AS "email" FROM (SELECT "users"."id", "users"."full_name", "users"."email" FROM "users" WHERE ((("users"."id") = ("products_0"."user_id"))) LIMIT ('1') :: integer) AS "users_1") AS "__sr_1") AS "__sj_1" ON ('true')) AS "__sr_0") AS "__sj_0"
WITH "_sg_input" AS (SELECT $1 :: json AS j), "_x_users" AS (SELECT "id" FROM "_sg_input" i,"users" WHERE "users"."id"= ((i.j->'user'->'connect'->>'id'))::bigint AND "users"."email"= ((i.j->'user'->'connect'->>'email'))::character varying LIMIT 1), "products" AS (UPDATE "products" SET ("name", "price", "user_id") = (SELECT CAST( i.j ->>'name' AS character varying), CAST( i.j ->>'price' AS numeric(7,2)), "_x_users"."id" FROM "_sg_input" i, "_x_users") WHERE (("products"."id") = $2 :: bigint) RETURNING "products".*) SELECT jsonb_build_object('product', "__sj_0"."json") as "__root" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name", "__sj_1"."json" AS "user" FROM (SELECT "products"."id", "products"."name", "products"."user_id" FROM "products" LIMIT ('1') :: integer) AS "products_0" LEFT OUTER JOIN LATERAL (SELECT to_jsonb("__sr_1".*) AS "json"FROM (SELECT "users_1"."id" AS "id", "users_1"."full_name" AS "full_name", "users_1"."email" AS "email" FROM (SELECT "users"."id", "users"."full_name", "users"."email" FROM "users" WHERE ((("users"."id") = ("products_0"."user_id"))) LIMIT ('1') :: integer) AS "users_1") AS "__sr_1") AS "__sj_1" ON ('true')) AS "__sr_0") AS "__sj_0"
=== RUN TestCompileUpdate/nestedUpdateOneToOneWithDisconnect === RUN TestCompileUpdate/nestedUpdateOneToOneWithDisconnect
WITH "_sg_input" AS (SELECT $1 :: json AS j), "_x_users" AS (SELECT * FROM (VALUES(NULL::bigint)) AS LOOKUP("id")), "products" AS (UPDATE "products" SET ("name", "price", "user_id") = (SELECT CAST( i.j ->>'name' AS character varying), CAST( i.j ->>'price' AS numeric(7,2)), "_x_users"."id" FROM "_sg_input" i, "_x_users") WHERE (("products"."id") = $2 :: bigint) RETURNING "products".*) SELECT jsonb_build_object('product', "__sj_0"."json") as "__root" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name", "products_0"."user_id" AS "user_id" FROM (SELECT "products"."id", "products"."name", "products"."user_id" FROM "products" LIMIT ('1') :: integer) AS "products_0") AS "__sr_0") AS "__sj_0" WITH "_sg_input" AS (SELECT $1 :: json AS j), "_x_users" AS (SELECT * FROM (VALUES(NULL::bigint)) AS LOOKUP("id")), "products" AS (UPDATE "products" SET ("name", "price", "user_id") = (SELECT CAST( i.j ->>'name' AS character varying), CAST( i.j ->>'price' AS numeric(7,2)), "_x_users"."id" FROM "_sg_input" i, "_x_users") WHERE (("products"."id") = $2 :: bigint) RETURNING "products".*) SELECT jsonb_build_object('product', "__sj_0"."json") as "__root" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name", "products_0"."user_id" AS "user_id" FROM (SELECT "products"."id", "products"."name", "products"."user_id" FROM "products" LIMIT ('1') :: integer) AS "products_0") AS "__sr_0") AS "__sj_0"
--- PASS: TestCompileUpdate (0.02s) --- PASS: TestCompileUpdate (0.02s)
@ -163,4 +160,4 @@ WITH "_sg_input" AS (SELECT $1 :: json AS j), "_x_users" AS (SELECT * FROM (VALU
--- PASS: TestCompileUpdate/nestedUpdateOneToOneWithConnect (0.00s) --- PASS: TestCompileUpdate/nestedUpdateOneToOneWithConnect (0.00s)
--- PASS: TestCompileUpdate/nestedUpdateOneToOneWithDisconnect (0.00s) --- PASS: TestCompileUpdate/nestedUpdateOneToOneWithDisconnect (0.00s)
PASS PASS
ok github.com/dosco/super-graph/core/internal/psql 0.371s ok github.com/dosco/super-graph/core/internal/psql 0.323s

View File

@ -121,14 +121,12 @@ func (c *compilerContext) renderUpdateStmt(w io.Writer, qc *qcode.QCode, item re
} }
io.WriteString(w, `)`) io.WriteString(w, `)`)
} else { } else if qc.Selects[0].Where != nil {
if qc.Selects[0].Where != nil {
io.WriteString(w, `WHERE `) io.WriteString(w, `WHERE `)
if err := c.renderWhere(&qc.Selects[0], ti); err != nil { if err := c.renderWhere(&qc.Selects[0], ti); err != nil {
return err return err
} }
} }
}
io.WriteString(w, ` RETURNING `) io.WriteString(w, ` RETURNING `)
quoted(w, ti.Name) quoted(w, ti.Name)

View File

@ -141,8 +141,7 @@ func (l *lexer) current() (Pos, Pos) {
func (l *lexer) emit(t itemType) { func (l *lexer) emit(t itemType) {
l.items = append(l.items, item{t, l.start, l.pos, l.line}) l.items = append(l.items, item{t, l.start, l.pos, l.line})
// Some items contain text internally. If so, count their newlines. // Some items contain text internally. If so, count their newlines.
switch t { if t == itemStringVal {
case itemStringVal:
for i := l.start; i < l.pos; i++ { for i := l.start; i < l.pos; i++ {
if l.input[i] == '\n' { if l.input[i] == '\n' {
l.line++ l.line++
@ -404,15 +403,15 @@ func isAlphaNumeric(r rune) bool {
return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r) return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
} }
func equals(b []byte, s Pos, e Pos, val []byte) bool { func equals(b []byte, s, e Pos, val []byte) bool {
return bytes.EqualFold(b[s:e], val) return bytes.EqualFold(b[s:e], val)
} }
func contains(b []byte, s Pos, e Pos, chars string) bool { func contains(b []byte, s, e Pos, chars string) bool {
return bytes.ContainsAny(b[s:e], chars) return bytes.ContainsAny(b[s:e], chars)
} }
func lowercase(b []byte, s Pos, e Pos) { func lowercase(b []byte, s, e Pos) {
for i := s; i < e; i++ { for i := s; i < e; i++ {
if b[i] >= 'A' && b[i] <= 'Z' { if b[i] >= 'A' && b[i] <= 'Z' {
b[i] = ('a' + (b[i] - 'A')) b[i] = ('a' + (b[i] - 'A'))

View File

@ -71,6 +71,7 @@ type Field struct {
argsA [5]Arg argsA [5]Arg
Children []int32 Children []int32
childrenA [5]int32 childrenA [5]int32
Union bool
} }
type Arg struct { type Arg struct {
@ -155,7 +156,7 @@ func Parse(gql []byte) (*Operation, error) {
if p.peek(itemFragment) { if p.peek(itemFragment) {
p.ignore() p.ignore()
if f, err := p.parseFragment(false); err != nil { if f, err := p.parseFragment(); err != nil {
fragPool.Put(f) fragPool.Put(f)
return nil, err return nil, err
} }
@ -181,7 +182,7 @@ func Parse(gql []byte) (*Operation, error) {
return op, nil return op, nil
} }
func (p *Parser) parseFragment(inline bool) (*Fragment, error) { func (p *Parser) parseFragment() (*Fragment, error) {
var err error var err error
frag := fragPool.Get().(*Fragment) frag := fragPool.Get().(*Fragment)
@ -190,7 +191,7 @@ func (p *Parser) parseFragment(inline bool) (*Fragment, error) {
if p.peek(itemName) { if p.peek(itemName) {
frag.Name = p.val(p.next()) frag.Name = p.val(p.next())
} else if !inline { } else {
return frag, errors.New("fragment: missing name") return frag, errors.New("fragment: missing name")
} }
@ -217,7 +218,6 @@ func (p *Parser) parseFragment(inline bool) (*Fragment, error) {
return frag, fmt.Errorf("fragment: %v", err) return frag, fmt.Errorf("fragment: %v", err)
} }
if !inline {
if p.frags == nil { if p.frags == nil {
p.frags = make(map[uint64]*Fragment) p.frags = make(map[uint64]*Fragment)
} }
@ -227,7 +227,6 @@ func (p *Parser) parseFragment(inline bool) (*Fragment, error) {
p.h.Reset() p.h.Reset()
p.frags[k] = frag p.frags[k] = frag
}
return frag, nil return frag, nil
} }
@ -405,18 +404,27 @@ func (p *Parser) parseNormalFields(st *Stack, fields []Field) ([]Field, error) {
} }
func (p *Parser) parseFragmentFields(st *Stack, fields []Field) ([]Field, error) { func (p *Parser) parseFragmentFields(st *Stack, fields []Field) ([]Field, error) {
var fr *Fragment
var err error var err error
pid := st.Peek()
if p.peek(itemOn) { if p.peek(itemOn) {
if fr, err = p.parseFragment(true); err != nil { p.ignore()
fields[pid].Union = true
if fields, err = p.parseNormalFields(st, fields); err != nil {
return nil, err return nil, err
} }
defer fragPool.Put(fr)
// If parent is a union selector than copy over args from the parent
// to the first child which is the root selector for each union type.
for i := pid + 1; i < int32(len(fields)); i++ {
f := &fields[i]
if f.ParentID == pid {
f.Args = fields[pid].Args
}
}
} else { } else {
var ok bool
if !p.peek(itemName) { if !p.peek(itemName) {
return nil, fmt.Errorf("expecting a fragment name, got: %s", p.next()) return nil, fmt.Errorf("expecting a fragment name, got: %s", p.next())
} }
@ -426,15 +434,16 @@ func (p *Parser) parseFragmentFields(st *Stack, fields []Field) ([]Field, error)
id := p.h.Sum64() id := p.h.Sum64()
p.h.Reset() p.h.Reset()
if fr, ok = p.frags[id]; !ok { fr, ok := p.frags[id]
if !ok {
return nil, fmt.Errorf("no fragment named '%s' defined", name) return nil, fmt.Errorf("no fragment named '%s' defined", name)
} }
} ff := fr.Fields
n := int32(len(fields)) n := int32(len(fields))
fields = append(fields, fr.Fields...) fields = append(fields, ff...)
for i := 0; i < len(fr.Fields); i++ { for i := 0; i < len(ff); i++ {
k := (n + int32(i)) k := (n + int32(i))
f := &fields[k] f := &fields[k]
f.ID = int32(k) f.ID = int32(k)
@ -442,27 +451,29 @@ func (p *Parser) parseFragmentFields(st *Stack, fields []Field) ([]Field, error)
// If this is the top-level point the parent to the parent of the // If this is the top-level point the parent to the parent of the
// previous field. // previous field.
if f.ParentID == -1 { if f.ParentID == -1 {
pid := st.Peek()
f.ParentID = pid f.ParentID = pid
if f.ParentID != -1 { if f.ParentID != -1 {
fields[pid].Children = append(fields[f.ParentID].Children, f.ID) fields[pid].Children = append(fields[pid].Children, f.ID)
} }
// Update all the other parents id's by our new place in this new array // Update all the other parents id's by our new place in this new array
} else { } else {
f.ParentID += n f.ParentID += n
} }
// Copy over children since fields append is not a deep copy
f.Children = make([]int32, len(f.Children)) f.Children = make([]int32, len(f.Children))
copy(f.Children, fr.Fields[i].Children) copy(f.Children, ff[i].Children)
// Copy over args since args append is not a deep copy
f.Args = make([]Arg, len(f.Args)) f.Args = make([]Arg, len(f.Args))
copy(f.Args, fr.Fields[i].Args) copy(f.Args, ff[i].Args)
// Update all the children which is needed. // Update all the children which is needed.
for j := range f.Children { for j := range f.Children {
f.Children[j] += n f.Children[j] += n
} }
} }
}
return fields, nil return fields, nil
} }
@ -494,24 +505,6 @@ func (p *Parser) parseField(f *Field) error {
return nil return nil
} }
// func (p *Parser) parseInlineFragmentFields(st *Stack, fields []Field) ([]Field, error) {
// var err error
// if p.peek(itemName) {
// p.ignore()
// // frag.On = p.vall(p.next())
// } else {
// return nil, errors.New("inline fragment: missing table name after 'on' keyword")
// }
// fields, err = p.parseNormalFields(st, fields)
// if err != nil {
// return nil, fmt.Errorf("inline fragment: %v", err)
// }
// return fields, nil
// }
func (p *Parser) parseOpParams(args []Arg) ([]Arg, error) { func (p *Parser) parseOpParams(args []Arg) ([]Arg, error) {
for { for {
if len(args) >= maxArgs { if len(args) >= maxArgs {
@ -578,11 +571,9 @@ func (p *Parser) parseList() (*Node, error) {
} }
if ty == 0 { if ty == 0 {
ty = node.Type ty = node.Type
} else { } else if ty != node.Type {
if ty != node.Type {
return nil, errors.New("All values in a list must be of the same type") return nil, errors.New("All values in a list must be of the same type")
} }
}
node.Parent = parent node.Parent = parent
nodes = append(nodes, node) nodes = append(nodes, node)
} }

View File

@ -12,6 +12,7 @@ import (
) )
type QType int type QType int
type SType int
type Action int type Action int
const ( const (
@ -19,7 +20,8 @@ const (
) )
const ( const (
QTQuery QType = iota + 1 QTUnknown QType = iota
QTQuery
QTMutation QTMutation
QTInsert QTInsert
QTUpdate QTUpdate
@ -27,6 +29,12 @@ const (
QTUpsert QTUpsert
) )
const (
STNone SType = iota
STUnion
STMember
)
type QCode struct { type QCode struct {
Type QType Type QType
ActionVar string ActionVar string
@ -38,6 +46,8 @@ type QCode struct {
type Select struct { type Select struct {
ID int32 ID int32
ParentID int32 ParentID int32
UParentID int32
Type SType
Args map[string]*Node Args map[string]*Node
Name string Name string
FieldName string FieldName string
@ -372,7 +382,11 @@ func (com *Compiler) compileQuery(qc *QCode, op *Operation, role string) error {
}) })
s := &selects[(len(selects) - 1)] s := &selects[(len(selects) - 1)]
if len(field.Alias) != 0 { if field.Union {
s.Type = STUnion
}
if field.Alias != "" {
s.FieldName = field.Alias s.FieldName = field.Alias
} else { } else {
s.FieldName = s.Name s.FieldName = s.Name
@ -383,6 +397,11 @@ func (com *Compiler) compileQuery(qc *QCode, op *Operation, role string) error {
} else { } else {
p := &selects[s.ParentID] p := &selects[s.ParentID]
p.Children = append(p.Children, s.ID) p.Children = append(p.Children, s.ID)
if p.Type == STUnion {
s.Type = STMember
s.UParentID = p.ParentID
}
} }
if skipRender { if skipRender {
@ -462,6 +481,7 @@ func (com *Compiler) compileQuery(qc *QCode, op *Operation, role string) error {
} }
qc.Selects = selects[:id] qc.Selects = selects[:id]
return nil return nil
} }
@ -609,15 +629,13 @@ func (com *Compiler) compileArgNode(st *util.Stack, node *Node, usePool bool) (*
} }
// Objects inside a list // Objects inside a list
if len(node.Name) == 0 { if node.Name == "" {
pushChildren(st, node.exp, node) pushChildren(st, node.exp, node)
continue continue
} else { } else if _, ok := com.bl[node.Name]; ok {
if _, ok := com.bl[node.Name]; ok {
continue continue
} }
}
ex, err := newExp(st, node, usePool) ex, err := newExp(st, node, usePool)
if err != nil { if err != nil {
@ -1030,7 +1048,7 @@ func setWhereColName(ex *Exp, node *Node) {
if n.Type != NodeObj { if n.Type != NodeObj {
continue continue
} }
if len(n.Name) != 0 { if n.Name != "" {
k := n.Name k := n.Name
if k == "and" || k == "or" || k == "not" || if k == "and" || k == "or" || k == "not" ||
k == "_and" || k == "_or" || k == "_not" { k == "_and" || k == "_or" || k == "_not" {

View File

@ -29,6 +29,8 @@ func al(b byte) bool {
func (qt QType) String() string { func (qt QType) String() string {
switch qt { switch qt {
case QTUnknown:
return "unknown"
case QTQuery: case QTQuery:
return "query" return "query"
case QTMutation: case QTMutation:

View File

@ -64,7 +64,7 @@ func (sg *SuperGraph) initPrepared() error {
return fmt.Errorf("role query: %w", err) return fmt.Errorf("role query: %w", err)
} }
sg.queries = make(map[uint64]query) sg.queries = make(map[uint64]*query)
list, err := sg.allowList.Load() list, err := sg.allowList.Load()
if err != nil { if err != nil {
@ -75,22 +75,20 @@ func (sg *SuperGraph) initPrepared() error {
h.SetSeed(sg.hashSeed) h.SetSeed(sg.hashSeed)
for _, v := range list { for _, v := range list {
if len(v.Query) == 0 { if v.Query == "" {
continue continue
} }
qt := qcode.GetQType(v.Query) qt := qcode.GetQType(v.Query)
switch qt { switch qt {
case qcode.QTQuery: case qcode.QTQuery:
sg.queries[queryID(&h, v.Name, "user")] = query{ai: v, qt: qt} sg.queries[queryID(&h, v.Name, "user")] = &query{ai: v, qt: qt}
sg.queries[queryID(&h, v.Name, "anon")] = &query{ai: v, qt: qt}
if sg.anonExists {
sg.queries[queryID(&h, v.Name, "anon")] = query{ai: v, qt: qt}
}
case qcode.QTMutation: case qcode.QTMutation:
for _, role := range sg.conf.Roles { for _, role := range sg.conf.Roles {
sg.queries[queryID(&h, v.Name, role.Name)] = query{ai: v, qt: qt} sg.queries[queryID(&h, v.Name, role.Name)] = &query{ai: v, qt: qt}
} }
} }
} }
@ -115,7 +113,7 @@ func (sg *SuperGraph) prepareRoleStmt() error {
io.WriteString(w, `(SELECT (CASE`) io.WriteString(w, `(SELECT (CASE`)
for _, role := range sg.conf.Roles { for _, role := range sg.conf.Roles {
if len(role.Match) == 0 { if role.Match == "" {
continue continue
} }
io.WriteString(w, ` WHEN `) io.WriteString(w, ` WHEN `)
@ -161,7 +159,7 @@ func (sg *SuperGraph) initAllowList() error {
} }
// nolint: errcheck // nolint: errcheck
func queryID(h *maphash.Hash, name string, role string) uint64 { func queryID(h *maphash.Hash, name, role string) uint64 {
h.WriteString(name) h.WriteString(name)
h.WriteString(role) h.WriteString(role)
v := h.Sum64() v := h.Sum64()

View File

@ -238,7 +238,7 @@ func (sg *SuperGraph) parentFieldIds(h *maphash.Hash, sel []qcode.Select, skippe
return fm, sm return fm, sm
} }
func isSkipped(n uint32, pos uint32) bool { func isSkipped(n, pos uint32) bool {
return ((n & (1 << pos)) != 0) return ((n & (1 << pos)) != 0)
} }

View File

@ -46,7 +46,7 @@ func (sg *SuperGraph) initRemotes(t Table) error {
// if no table column specified in the config then // if no table column specified in the config then
// use the primary key of the table as the id // use the primary key of the table as the id
if len(idcol) == 0 { if idcol == "" {
pcol, err := sg.pc.IDColumn(t.Name) pcol, err := sg.pc.IDColumn(t.Name)
if err != nil { if err != nil {
return err return err

View File

@ -3,7 +3,7 @@ package core
import "hash/maphash" import "hash/maphash"
// nolint: errcheck // nolint: errcheck
func mkkey(h *maphash.Hash, k1 string, k2 string) uint64 { func mkkey(h *maphash.Hash, k1, k2 string) uint64 {
h.WriteString(k1) h.WriteString(k1)
h.WriteString(k2) h.WriteString(k2)
v := h.Sum64() v := h.Sum64()

View File

@ -155,7 +155,7 @@ func cmdVersion(cmd *cobra.Command, args []string) {
} }
func BuildDetails() string { func BuildDetails() string {
if len(version) == 0 { if version == "" {
return ` return `
Super Graph (unknown version) Super Graph (unknown version)
For documentation, visit https://supergraph.dev For documentation, visit https://supergraph.dev

View File

@ -88,6 +88,10 @@ func cmdNew(cmd *cobra.Command, args []string) {
} }
}) })
ifNotExists(path.Join(appConfigPath, "allow.list"), func(p string) error {
return ioutil.WriteFile(p, []byte{}, 0644)
})
// Create app migrations folder and add relevant files // Create app migrations folder and add relevant files
appMigrationsPath := path.Join(appConfigPath, "migrations") appMigrationsPath := path.Join(appConfigPath, "migrations")

View File

@ -80,7 +80,7 @@ func cmdDBSeed(cmd *cobra.Command, args []string) {
func graphQLFunc(sg *core.SuperGraph, query string, data interface{}, opt map[string]string) map[string]interface{} { func graphQLFunc(sg *core.SuperGraph, query string, data interface{}, opt map[string]string) map[string]interface{} {
ct := context.Background() ct := context.Background()
if v, ok := opt["user_id"]; ok && len(v) != 0 { if v, ok := opt["user_id"]; ok && v != "" {
ct = context.WithValue(ct, core.UserIDKey, v) ct = context.WithValue(ct, core.UserIDKey, v)
} }
@ -144,7 +144,7 @@ func (c *csvSource) Values() ([]interface{}, error) {
for _, v := range c.rows[c.i] { for _, v := range c.rows[c.i] {
switch { switch {
case len(v) == 0: case v == "":
vals = append(vals, "") vals = append(vals, "")
case isDigit(v): case isDigit(v):
var n int var n int
@ -243,7 +243,7 @@ func avatarURL(size int) string {
return fmt.Sprintf("https://i.pravatar.cc/%d?%d", size, rand.Intn(5000)) return fmt.Sprintf("https://i.pravatar.cc/%d?%d", size, rand.Intn(5000))
} }
func imageURL(width int, height int) string { func imageURL(width, height int) string {
return fmt.Sprintf("https://picsum.photos/%d/%d?%d", width, height, rand.Intn(5000)) return fmt.Sprintf("https://picsum.photos/%d/%d?%d", width, height, rand.Intn(5000))
} }

View File

@ -90,7 +90,7 @@ func newViper(configPath, configFile string) *viper.Viper {
} }
func GetConfigName() string { func GetConfigName() string {
if len(os.Getenv("GO_ENV")) == 0 { if os.Getenv("GO_ENV") == "" {
return "dev" return "dev"
} }

View File

@ -105,7 +105,7 @@ func apiV1(w http.ResponseWriter, r *http.Request) {
} }
if err == nil { if err == nil {
if len(conf.CacheControl) != 0 && res.Operation() == core.OpQuery { if conf.CacheControl != "" && res.Operation() == core.OpQuery {
w.Header().Set("Cache-Control", conf.CacheControl) w.Header().Set("Cache-Control", conf.CacheControl)
} }
//nolint: errcheck //nolint: errcheck

View File

@ -47,17 +47,17 @@ func SimpleHandler(ac *Auth, next http.Handler) (http.HandlerFunc, error) {
ctx := r.Context() ctx := r.Context()
userIDProvider := r.Header.Get("X-User-ID-Provider") userIDProvider := r.Header.Get("X-User-ID-Provider")
if len(userIDProvider) != 0 { if userIDProvider != "" {
ctx = context.WithValue(ctx, core.UserIDProviderKey, userIDProvider) ctx = context.WithValue(ctx, core.UserIDProviderKey, userIDProvider)
} }
userID := r.Header.Get("X-User-ID") userID := r.Header.Get("X-User-ID")
if len(userID) != 0 { if userID != "" {
ctx = context.WithValue(ctx, core.UserIDKey, userID) ctx = context.WithValue(ctx, core.UserIDKey, userID)
} }
userRole := r.Header.Get("X-User-Role") userRole := r.Header.Get("X-User-Role")
if len(userRole) != 0 { if userRole != "" {
ctx = context.WithValue(ctx, core.UserRoleKey, userRole) ctx = context.WithValue(ctx, core.UserRoleKey, userRole)
} }
@ -68,11 +68,11 @@ func SimpleHandler(ac *Auth, next http.Handler) (http.HandlerFunc, error) {
func HeaderHandler(ac *Auth, next http.Handler) (http.HandlerFunc, error) { func HeaderHandler(ac *Auth, next http.Handler) (http.HandlerFunc, error) {
hdr := ac.Header hdr := ac.Header
if len(hdr.Name) == 0 { if hdr.Name == "" {
return nil, fmt.Errorf("auth '%s': no header.name defined", ac.Name) return nil, fmt.Errorf("auth '%s': no header.name defined", ac.Name)
} }
if !hdr.Exists && len(hdr.Value) == 0 { if !hdr.Exists && hdr.Value == "" {
return nil, fmt.Errorf("auth '%s': no header.value defined", ac.Name) return nil, fmt.Errorf("auth '%s': no header.value defined", ac.Name)
} }
@ -82,7 +82,7 @@ func HeaderHandler(ac *Auth, next http.Handler) (http.HandlerFunc, error) {
switch { switch {
case hdr.Exists: case hdr.Exists:
fo1 = (len(value) == 0) fo1 = (value == "")
default: default:
fo1 = (value != hdr.Value) fo1 = (value != hdr.Value)

View File

@ -44,10 +44,10 @@ func JwtHandler(ac *Auth, next http.Handler) (http.HandlerFunc, error) {
publicKeyFile := ac.JWT.PubKeyFile publicKeyFile := ac.JWT.PubKeyFile
switch { switch {
case len(secret) != 0: case secret != "":
key = []byte(secret) key = []byte(secret)
case len(publicKeyFile) != 0: case publicKeyFile != "":
kd, err := ioutil.ReadFile(publicKeyFile) kd, err := ioutil.ReadFile(publicKeyFile)
if err != nil { if err != nil {
return nil, err return nil, err
@ -74,7 +74,7 @@ func JwtHandler(ac *Auth, next http.Handler) (http.HandlerFunc, error) {
var tok string var tok string
if len(cookie) != 0 { if cookie != "" {
ck, err := r.Cookie(cookie) ck, err := r.Cookie(cookie)
if err != nil { if err != nil {
next.ServeHTTP(w, r) next.ServeHTTP(w, r)

View File

@ -165,7 +165,7 @@ func railsAuth(ac *Auth) (*rails.Auth, error) {
} }
version := ac.Rails.Version version := ac.Rails.Version
if len(version) == 0 { if version == "" {
return nil, errors.New("no auth.rails.version defined") return nil, errors.New("no auth.rails.version defined")
} }

View File

@ -199,7 +199,7 @@ func (m *Migrator) LoadMigrations(path string) error {
for _, v := range strings.Split(upSQL, "\n") { for _, v := range strings.Split(upSQL, "\n") {
// Only account for regular single line comment, empty line and space/comment combination // Only account for regular single line comment, empty line and space/comment combination
cleanString := strings.TrimSpace(v) cleanString := strings.TrimSpace(v)
if len(cleanString) != 0 && if cleanString != "" &&
!strings.HasPrefix(cleanString, "--") { !strings.HasPrefix(cleanString, "--") {
containsSQL = true containsSQL = true
break break

File diff suppressed because one or more lines are too long

View File

@ -27,7 +27,7 @@ func initWatcher() {
} }
var d dir var d dir
if len(cpath) == 0 || cpath == "./" { if cpath == "" || cpath == "./" {
d = Dir("./config", ReExec) d = Dir("./config", ReExec)
} else { } else {
d = Dir(cpath, ReExec) d = Dir(cpath, ReExec)
@ -52,11 +52,11 @@ func startHTTP() {
hp := strings.SplitN(conf.HostPort, ":", 2) hp := strings.SplitN(conf.HostPort, ":", 2)
if len(hp) == 2 { if len(hp) == 2 {
if len(conf.Host) != 0 { if conf.Host != "" {
hp[0] = conf.Host hp[0] = conf.Host
} }
if len(conf.Port) != 0 { if conf.Port != "" {
hp[1] = conf.Port hp[1] = conf.Port
} }
@ -64,7 +64,7 @@ func startHTTP() {
} }
} }
if len(conf.hostPort) == 0 { if conf.hostPort == "" {
conf.hostPort = defaultHP conf.hostPort = defaultHP
} }
@ -123,7 +123,7 @@ func routeHandler() (http.Handler, error) {
return mux, nil return mux, nil
} }
if len(conf.APIPath) != 0 { if conf.APIPath != "" {
apiRoute = path.Join("/", conf.APIPath, "/v1/graphql") apiRoute = path.Join("/", conf.APIPath, "/v1/graphql")
} }

View File

@ -134,7 +134,7 @@ database:
type: postgres type: postgres
host: db host: db
port: 5432 port: 5432
dbname: {{- .AppNameSlug -}}_development dbname: {{ .AppNameSlug -}}_development
user: postgres user: postgres
password: postgres password: postgres

View File

@ -82,7 +82,7 @@ database:
type: postgres type: postgres
host: db host: db
port: 5432 port: 5432
dbname: {{- .AppNameSlug -}}_production dbname: {{ .AppNameSlug -}}_production
user: postgres user: postgres
password: postgres password: postgres
#pool_size: 10 #pool_size: 10