Redesign config file architecture
This commit is contained in:
parent
e3660473cc
commit
2d02f2afda
22
dev.yml
22
dev.yml
|
@ -64,7 +64,7 @@ database:
|
||||||
|
|
||||||
# Define defaults to for the field key and values below
|
# Define defaults to for the field key and values below
|
||||||
defaults:
|
defaults:
|
||||||
filter: ["{ id: { _eq: $user_id } }"]
|
filter: ["{ user_id: { eq: $user_id } }"]
|
||||||
|
|
||||||
# Fields and table names that you wish to block
|
# Fields and table names that you wish to block
|
||||||
blacklist:
|
blacklist:
|
||||||
|
@ -77,12 +77,20 @@ database:
|
||||||
|
|
||||||
fields:
|
fields:
|
||||||
- name: users
|
- name: users
|
||||||
filter: ["{ id: { _eq: $user_id } }"]
|
filter: ["{ id: { eq: $user_id } }"]
|
||||||
|
|
||||||
|
- name: products
|
||||||
|
filter: [
|
||||||
|
"{ price: { gt: 0 } }",
|
||||||
|
"{ price: { lt: 8 } }"
|
||||||
|
]
|
||||||
|
|
||||||
|
- name: customers
|
||||||
|
filter: none
|
||||||
|
|
||||||
|
- name: me
|
||||||
|
table: users
|
||||||
|
filter: ["{ id: { eq: $user_id } }"]
|
||||||
|
|
||||||
# - name: posts
|
# - name: posts
|
||||||
# filter: ["{ account_id: { _eq: $account_id } }"]
|
# filter: ["{ account_id: { _eq: $account_id } }"]
|
||||||
|
|
||||||
- name: my_products
|
|
||||||
table: products
|
|
||||||
filter: ["{ id: { _eq: $user_id } }"]
|
|
||||||
|
|
||||||
|
|
44
psql/psql.go
44
psql/psql.go
|
@ -13,20 +13,25 @@ import (
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Schema *DBSchema
|
Schema *DBSchema
|
||||||
Vars map[string]string
|
Vars map[string]string
|
||||||
|
TableMap map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Compiler struct {
|
type Compiler struct {
|
||||||
schema *DBSchema
|
schema *DBSchema
|
||||||
vars map[string]string
|
vars map[string]string
|
||||||
|
tmap map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCompiler(conf Config) *Compiler {
|
func NewCompiler(conf Config) *Compiler {
|
||||||
return &Compiler{conf.Schema, conf.Vars}
|
return &Compiler{conf.Schema, conf.Vars, conf.TableMap}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Compiler) Compile(w io.Writer, qc *qcode.QCode) error {
|
func (c *Compiler) Compile(w io.Writer, qc *qcode.QCode) error {
|
||||||
st := util.NewStack()
|
st := util.NewStack()
|
||||||
ti, _ := c.schema.GetTable(qc.Query.Select.Table)
|
ti, err := c.getTable(qc.Query.Select)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
st.Push(&selectBlockClose{nil, qc.Query.Select})
|
st.Push(&selectBlockClose{nil, qc.Query.Select})
|
||||||
st.Push(&selectBlock{nil, qc.Query.Select, ti, c})
|
st.Push(&selectBlock{nil, qc.Query.Select, ti, c})
|
||||||
|
@ -47,12 +52,13 @@ func (c *Compiler) Compile(w io.Writer, qc *qcode.QCode) error {
|
||||||
v.render(w, c.schema, childCols, childIDs)
|
v.render(w, c.schema, childCols, childIDs)
|
||||||
|
|
||||||
for i := range childIDs {
|
for i := range childIDs {
|
||||||
ti, err := c.schema.GetTable(v.sel.Table)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
sub := v.sel.Joins[childIDs[i]]
|
sub := v.sel.Joins[childIDs[i]]
|
||||||
|
|
||||||
|
ti, err := c.getTable(sub)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
st.Push(&joinClose{sub})
|
st.Push(&joinClose{sub})
|
||||||
st.Push(&selectBlockClose{v.sel, sub})
|
st.Push(&selectBlockClose{v.sel, sub})
|
||||||
st.Push(&selectBlock{v.sel, sub, ti, c})
|
st.Push(&selectBlock{v.sel, sub, ti, c})
|
||||||
|
@ -75,6 +81,13 @@ func (c *Compiler) Compile(w io.Writer, qc *qcode.QCode) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Compiler) getTable(sel *qcode.Select) (*DBTableInfo, error) {
|
||||||
|
if tn, ok := c.tmap[sel.Table]; ok {
|
||||||
|
return c.schema.GetTable(tn)
|
||||||
|
}
|
||||||
|
return c.schema.GetTable(sel.Table)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Compiler) relationshipColumns(parent *qcode.Select) (
|
func (c *Compiler) relationshipColumns(parent *qcode.Select) (
|
||||||
cols []*qcode.Column, childIDs []int) {
|
cols []*qcode.Column, childIDs []int) {
|
||||||
|
|
||||||
|
@ -275,33 +288,34 @@ func (v *selectBlock) renderBaseSelect(w io.Writer, schema *DBSchema, childCols
|
||||||
|
|
||||||
isRoot := v.parent == nil
|
isRoot := v.parent == nil
|
||||||
isFil := v.sel.Where != nil
|
isFil := v.sel.Where != nil
|
||||||
|
isSearch := v.sel.Args["search"] != nil
|
||||||
isAgg := false
|
isAgg := false
|
||||||
|
|
||||||
_, isSearch := v.sel.Args["search"]
|
|
||||||
|
|
||||||
io.WriteString(w, " FROM (SELECT ")
|
io.WriteString(w, " FROM (SELECT ")
|
||||||
|
|
||||||
for i, col := range v.sel.Cols {
|
for i, col := range v.sel.Cols {
|
||||||
cn := col.Name
|
cn := col.Name
|
||||||
_, isRealCol := v.schema.ColMap[TCKey{v.sel.Table, cn}]
|
|
||||||
|
_, isRealCol := v.ti.Columns[cn]
|
||||||
|
|
||||||
if !isRealCol {
|
if !isRealCol {
|
||||||
|
if isSearch {
|
||||||
switch {
|
switch {
|
||||||
case isSearch && cn == "search_rank":
|
case cn == "search_rank":
|
||||||
cn = v.ti.TSVCol
|
cn = v.ti.TSVCol
|
||||||
arg := v.sel.Args["search"]
|
arg := v.sel.Args["search"]
|
||||||
|
|
||||||
fmt.Fprintf(w, `ts_rank("%s"."%s", to_tsquery('%s')) AS %s`,
|
fmt.Fprintf(w, `ts_rank("%s"."%s", to_tsquery('%s')) AS %s`,
|
||||||
v.sel.Table, cn, arg.Val, col.Name)
|
v.sel.Table, cn, arg.Val, col.Name)
|
||||||
|
|
||||||
case isSearch && strings.HasPrefix(cn, "search_headline_"):
|
case strings.HasPrefix(cn, "search_headline_"):
|
||||||
cn = cn[16:]
|
cn = cn[16:]
|
||||||
arg := v.sel.Args["search"]
|
arg := v.sel.Args["search"]
|
||||||
|
|
||||||
fmt.Fprintf(w, `ts_headline("%s"."%s", to_tsquery('%s')) AS %s`,
|
fmt.Fprintf(w, `ts_headline("%s"."%s", to_tsquery('%s')) AS %s`,
|
||||||
v.sel.Table, cn, arg.Val, col.Name)
|
v.sel.Table, cn, arg.Val, col.Name)
|
||||||
|
}
|
||||||
default:
|
} else {
|
||||||
pl := funcPrefixLen(cn)
|
pl := funcPrefixLen(cn)
|
||||||
if pl == 0 {
|
if pl == 0 {
|
||||||
fmt.Fprintf(w, `'%s not defined' AS %s`, cn, col.Name)
|
fmt.Fprintf(w, `'%s not defined' AS %s`, cn, col.Name)
|
||||||
|
@ -330,7 +344,11 @@ func (v *selectBlock) renderBaseSelect(w io.Writer, schema *DBSchema, childCols
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tn, ok := v.tmap[v.sel.Table]; ok {
|
||||||
|
fmt.Fprintf(w, ` FROM "%s" AS "%s"`, tn, v.sel.Table)
|
||||||
|
} else {
|
||||||
fmt.Fprintf(w, ` FROM "%s"`, v.sel.Table)
|
fmt.Fprintf(w, ` FROM "%s"`, v.sel.Table)
|
||||||
|
}
|
||||||
|
|
||||||
if isRoot && isFil {
|
if isRoot && isFil {
|
||||||
io.WriteString(w, ` WHERE (`)
|
io.WriteString(w, ` WHERE (`)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package psql
|
package psql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -18,18 +19,35 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
fm := qcode.NewFilterMap(map[string]string{
|
var err error
|
||||||
"users": "{ id: { _eq: $user_id } }",
|
|
||||||
"posts": "{ account_id: { _eq: $account_id } }",
|
|
||||||
})
|
|
||||||
|
|
||||||
bl := qcode.NewBlacklist([]string{
|
qcompile, err = qcode.NewCompiler(qcode.Config{
|
||||||
|
Filter: []string{
|
||||||
|
`{ user_id: { _eq: $user_id } }`,
|
||||||
|
},
|
||||||
|
FilterMap: map[string][]string{
|
||||||
|
"users": []string{
|
||||||
|
"{ id: { eq: $user_id } }",
|
||||||
|
},
|
||||||
|
"products": []string{
|
||||||
|
"{ price: { gt: 0 } }",
|
||||||
|
"{ price: { lt: 8 } }",
|
||||||
|
},
|
||||||
|
"customers": []string{},
|
||||||
|
"mes": []string{
|
||||||
|
"{ id: { eq: $user_id } }",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Blacklist: []string{
|
||||||
"secret",
|
"secret",
|
||||||
"password",
|
"password",
|
||||||
"token",
|
"token",
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
qcompile = qcode.NewCompiler(fm, bl)
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
tables := []*DBTable{
|
tables := []*DBTable{
|
||||||
&DBTable{Name: "customers", Type: "table"},
|
&DBTable{Name: "customers", Type: "table"},
|
||||||
|
@ -81,17 +99,26 @@ func TestMain(m *testing.M) {
|
||||||
&DBColumn{ID: 7, Name: "returned", Type: "timestamp without time zone", NotNull: false, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}},
|
&DBColumn{ID: 7, Name: "returned", Type: "timestamp without time zone", NotNull: false, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}},
|
||||||
}
|
}
|
||||||
|
|
||||||
schema := initSchema()
|
schema := &DBSchema{
|
||||||
|
Tables: make(map[string]*DBTableInfo),
|
||||||
|
RelMap: make(map[TTKey]*DBRel),
|
||||||
|
}
|
||||||
|
|
||||||
for i, t := range tables {
|
for i, t := range tables {
|
||||||
updateSchema(schema, t, columns[i])
|
schema.updateSchema(t, columns[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
vars := NewVariables(map[string]string{
|
vars := NewVariables(map[string]string{
|
||||||
"account_id": "select account_id from users where id = $user_id",
|
"account_id": "select account_id from users where id = $user_id",
|
||||||
})
|
})
|
||||||
|
|
||||||
pcompile = NewCompiler(schema, vars)
|
pcompile = NewCompiler(Config{
|
||||||
|
Schema: schema,
|
||||||
|
Vars: vars,
|
||||||
|
TableMap: map[string]string{
|
||||||
|
"mes": "users",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
os.Exit(m.Run())
|
os.Exit(m.Run())
|
||||||
}
|
}
|
||||||
|
@ -134,7 +161,7 @@ func withComplexArgs(t *testing.T) {
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
sql := `SELECT json_object_agg('products', products) FROM (SELECT coalesce(json_agg("products" ORDER BY "products_0.ob.price" DESC), '[]') AS "products" FROM (SELECT DISTINCT ON ("products_0.ob.price") row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name", "products_0"."price" AS "price") AS "sel_0")) AS "products", "products_0"."price" AS "products_0.ob.price" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."id") < (28)) AND (("products"."id") >= (20))) LIMIT ('30') :: integer) AS "products_0" ORDER BY "products_0.ob.price" DESC LIMIT ('30') :: integer) AS "products_0") AS "done_1337";`
|
sql := `SELECT json_object_agg('products', products) FROM (SELECT coalesce(json_agg("products" ORDER BY "products_0.ob.price" DESC), '[]') AS "products" FROM (SELECT DISTINCT ON ("products_0.ob.price") row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name", "products_0"."price" AS "price") AS "sel_0")) AS "products", "products_0"."price" AS "products_0.ob.price" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."price") > (0)) AND (("products"."price") < (8)) AND (("products"."id") < (28)) AND (("products"."id") >= (20))) LIMIT ('30') :: integer) AS "products_0" ORDER BY "products_0.ob.price" DESC LIMIT ('30') :: integer) AS "products_0") AS "done_1337";`
|
||||||
|
|
||||||
resSQL, err := compileGQLToPSQL(gql)
|
resSQL, err := compileGQLToPSQL(gql)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -162,7 +189,7 @@ func withWhereMultiOr(t *testing.T) {
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
sql := `SELECT json_object_agg('products', products) FROM (SELECT coalesce(json_agg("products"), '[]') AS "products" FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name", "products_0"."price" AS "price") AS "sel_0")) AS "products" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."price") < (20)) OR (("products"."price") > (10)) OR NOT (("products"."id") IS NULL)) LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "products_0") AS "done_1337";`
|
sql := `SELECT json_object_agg('products', products) FROM (SELECT coalesce(json_agg("products"), '[]') AS "products" FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name", "products_0"."price" AS "price") AS "sel_0")) AS "products" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."price") > (0)) AND (("products"."price") < (8)) AND (("products"."price") < (20)) OR (("products"."price") > (10)) OR NOT (("products"."id") IS NULL)) LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "products_0") AS "done_1337";`
|
||||||
|
|
||||||
resSQL, err := compileGQLToPSQL(gql)
|
resSQL, err := compileGQLToPSQL(gql)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -188,7 +215,7 @@ func withWhereIsNull(t *testing.T) {
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
sql := `SELECT json_object_agg('products', products) FROM (SELECT coalesce(json_agg("products"), '[]') AS "products" FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name", "products_0"."price" AS "price") AS "sel_0")) AS "products" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."price") > (10)) AND NOT (("products"."id") IS NULL)) LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "products_0") AS "done_1337";`
|
sql := `SELECT json_object_agg('products', products) FROM (SELECT coalesce(json_agg("products"), '[]') AS "products" FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name", "products_0"."price" AS "price") AS "sel_0")) AS "products" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."price") > (0)) AND (("products"."price") < (8)) AND (("products"."price") > (10)) AND NOT (("products"."id") IS NULL)) LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "products_0") AS "done_1337";`
|
||||||
|
|
||||||
resSQL, err := compileGQLToPSQL(gql)
|
resSQL, err := compileGQLToPSQL(gql)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -214,7 +241,7 @@ func withWhereAndList(t *testing.T) {
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
sql := `SELECT json_object_agg('products', products) FROM (SELECT coalesce(json_agg("products"), '[]') AS "products" FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name", "products_0"."price" AS "price") AS "sel_0")) AS "products" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."price") > (10)) AND NOT (("products"."id") IS NULL)) LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "products_0") AS "done_1337";`
|
sql := `SELECT json_object_agg('products', products) FROM (SELECT coalesce(json_agg("products"), '[]') AS "products" FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name", "products_0"."price" AS "price") AS "sel_0")) AS "products" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."price") > (0)) AND (("products"."price") < (8)) AND (("products"."price") > (10)) AND NOT (("products"."id") IS NULL)) LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "products_0") AS "done_1337";`
|
||||||
|
|
||||||
resSQL, err := compileGQLToPSQL(gql)
|
resSQL, err := compileGQLToPSQL(gql)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -260,7 +287,7 @@ func belongsTo(t *testing.T) {
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
sql := `SELECT json_object_agg('products', products) FROM (SELECT coalesce(json_agg("products"), '[]') AS "products" FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."name" AS "name", "products_0"."price" AS "price", "users_1.join"."users" AS "users") AS "sel_0")) AS "products" FROM (SELECT "products"."name", "products"."price", "products"."user_id" FROM "products" LIMIT ('20') :: integer) AS "products_0" LEFT OUTER JOIN LATERAL (SELECT coalesce(json_agg("users"), '[]') AS "users" FROM (SELECT row_to_json((SELECT "sel_1" FROM (SELECT "users_1"."email" AS "email") AS "sel_1")) AS "users" FROM (SELECT "users"."email" FROM "users" WHERE ((("users"."id") = ("products_0"."user_id"))) LIMIT ('20') :: integer) AS "users_1" LIMIT ('20') :: integer) AS "users_1") AS "users_1.join" ON ('true') LIMIT ('20') :: integer) AS "products_0") AS "done_1337";`
|
sql := `SELECT json_object_agg('products', products) FROM (SELECT coalesce(json_agg("products"), '[]') AS "products" FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."name" AS "name", "products_0"."price" AS "price", "users_1.join"."users" AS "users") AS "sel_0")) AS "products" FROM (SELECT "products"."name", "products"."price", "products"."user_id" FROM "products" WHERE ((("products"."price") > (0)) AND (("products"."price") < (8))) LIMIT ('20') :: integer) AS "products_0" LEFT OUTER JOIN LATERAL (SELECT coalesce(json_agg("users"), '[]') AS "users" FROM (SELECT row_to_json((SELECT "sel_1" FROM (SELECT "users_1"."email" AS "email") AS "sel_1")) AS "users" FROM (SELECT "users"."email" FROM "users" WHERE ((("users"."id") = ("products_0"."user_id"))) LIMIT ('20') :: integer) AS "users_1" LIMIT ('20') :: integer) AS "users_1") AS "users_1.join" ON ('true') LIMIT ('20') :: integer) AS "products_0") AS "done_1337";`
|
||||||
|
|
||||||
resSQL, err := compileGQLToPSQL(gql)
|
resSQL, err := compileGQLToPSQL(gql)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -283,7 +310,7 @@ func manyToMany(t *testing.T) {
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
sql := `SELECT json_object_agg('products', products) FROM (SELECT coalesce(json_agg("products"), '[]') AS "products" FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."name" AS "name", "customers_1.join"."customers" AS "customers") AS "sel_0")) AS "products" FROM (SELECT "products"."name", "products"."id" FROM "products" LIMIT ('20') :: integer) AS "products_0" LEFT OUTER JOIN LATERAL (SELECT coalesce(json_agg("customers"), '[]') AS "customers" FROM (SELECT row_to_json((SELECT "sel_1" FROM (SELECT "customers_1"."email" AS "email", "customers_1"."full_name" AS "full_name") AS "sel_1")) AS "customers" 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" LIMIT ('20') :: integer) AS "customers_1") AS "customers_1.join" ON ('true') LIMIT ('20') :: integer) AS "products_0") AS "done_1337";`
|
sql := `SELECT json_object_agg('products', products) FROM (SELECT coalesce(json_agg("products"), '[]') AS "products" FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."name" AS "name", "customers_1.join"."customers" AS "customers") AS "sel_0")) AS "products" FROM (SELECT "products"."name", "products"."id" FROM "products" WHERE ((("products"."price") > (0)) AND (("products"."price") < (8))) LIMIT ('20') :: integer) AS "products_0" LEFT OUTER JOIN LATERAL (SELECT coalesce(json_agg("customers"), '[]') AS "customers" FROM (SELECT row_to_json((SELECT "sel_1" FROM (SELECT "customers_1"."email" AS "email", "customers_1"."full_name" AS "full_name") AS "sel_1")) AS "customers" 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" LIMIT ('20') :: integer) AS "customers_1") AS "customers_1.join" ON ('true') LIMIT ('20') :: integer) AS "products_0") AS "done_1337";`
|
||||||
|
|
||||||
resSQL, err := compileGQLToPSQL(gql)
|
resSQL, err := compileGQLToPSQL(gql)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -320,13 +347,13 @@ func manyToManyReverse(t *testing.T) {
|
||||||
|
|
||||||
func fetchByID(t *testing.T) {
|
func fetchByID(t *testing.T) {
|
||||||
gql := `query {
|
gql := `query {
|
||||||
product(id: 4) {
|
product(id: 15) {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
sql := `SELECT json_object_agg('product', products) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name") AS "sel_0")) AS "products" FROM (SELECT "products"."id", "products"."name" FROM "products" WHERE ((("id") = ('4'))) LIMIT ('1') :: integer) AS "products_0" LIMIT ('1') :: integer) AS "done_1337";`
|
sql := `SELECT json_object_agg('product', products) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name") AS "sel_0")) AS "products" FROM (SELECT "products"."id", "products"."name" FROM "products" WHERE ((("products"."price") > (0)) AND (("products"."price") < (8)) AND (("id") = ('15'))) LIMIT ('1') :: integer) AS "products_0" LIMIT ('1') :: integer) AS "done_1337";`
|
||||||
|
|
||||||
resSQL, err := compileGQLToPSQL(gql)
|
resSQL, err := compileGQLToPSQL(gql)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -340,13 +367,13 @@ func fetchByID(t *testing.T) {
|
||||||
|
|
||||||
func searchQuery(t *testing.T) {
|
func searchQuery(t *testing.T) {
|
||||||
gql := `query {
|
gql := `query {
|
||||||
products(search: "Amazing") {
|
products(search: "Imperial") {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
sql := `SELECT json_object_agg('products', products) FROM (SELECT coalesce(json_agg("products"), '[]') AS "products" FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name") AS "sel_0")) AS "products" FROM (SELECT "products"."id", "products"."name" FROM "products" WHERE ((("tsv") @@ to_tsquery('Amazing'))) LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "products_0") AS "done_1337";`
|
sql := `SELECT json_object_agg('products', products) FROM (SELECT coalesce(json_agg("products"), '[]') AS "products" FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name") AS "sel_0")) AS "products" FROM (SELECT "products"."id", "products"."name" FROM "products" WHERE ((("products"."price") > (0)) AND (("products"."price") < (8)) AND (("tsv") @@ to_tsquery('Imperial'))) LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "products_0") AS "done_1337";`
|
||||||
|
|
||||||
resSQL, err := compileGQLToPSQL(gql)
|
resSQL, err := compileGQLToPSQL(gql)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -366,7 +393,7 @@ func aggFunction(t *testing.T) {
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
sql := `SELECT json_object_agg('products', products) FROM (SELECT coalesce(json_agg("products"), '[]') AS "products" FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."name" AS "name", "products_0"."count_price" AS "count_price") AS "sel_0")) AS "products" FROM (SELECT "products"."name", count("products"."price") AS count_price FROM "products" GROUP BY "products"."name" LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "products_0") AS "done_1337";`
|
sql := `SELECT json_object_agg('products', products) FROM (SELECT coalesce(json_agg("products"), '[]') AS "products" FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."name" AS "name", "products_0"."count_price" AS "count_price") AS "sel_0")) AS "products" FROM (SELECT "products"."name", count("products"."price") AS count_price FROM "products" WHERE ((("products"."price") > (0)) AND (("products"."price") < (8))) GROUP BY "products"."name" LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "products_0") AS "done_1337";`
|
||||||
|
|
||||||
resSQL, err := compileGQLToPSQL(gql)
|
resSQL, err := compileGQLToPSQL(gql)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -386,7 +413,26 @@ func aggFunctionWithFilter(t *testing.T) {
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
sql := `SELECT json_object_agg('products', products) FROM (SELECT coalesce(json_agg("products"), '[]') AS "products" FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."max_price" AS "max_price") AS "sel_0")) AS "products" FROM (SELECT "products"."id", max("products"."price") AS max_price FROM "products" WHERE ((("products"."id") > (10))) GROUP BY "products"."id" LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "products_0") AS "done_1337";`
|
sql := `SELECT json_object_agg('products', products) FROM (SELECT coalesce(json_agg("products"), '[]') AS "products" FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."max_price" AS "max_price") AS "sel_0")) AS "products" FROM (SELECT "products"."id", max("products"."price") AS max_price FROM "products" WHERE ((("products"."price") > (0)) AND (("products"."price") < (8)) AND (("products"."id") > (10))) GROUP BY "products"."id" LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "products_0") AS "done_1337";`
|
||||||
|
|
||||||
|
resSQL, err := compileGQLToPSQL(gql)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resSQL != sql {
|
||||||
|
t.Fatal(errNotExpected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func syntheticTables(t *testing.T) {
|
||||||
|
gql := `query {
|
||||||
|
me {
|
||||||
|
email
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
sql := `SELECT json_object_agg('me', mes) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "mes_0"."email" AS "email") AS "sel_0")) AS "mes" FROM (SELECT "mes"."email" FROM "users" AS "mes" WHERE ((("mes"."id") = ('{{user_id}}'))) LIMIT ('1') :: integer) AS "mes_0" LIMIT ('1') :: integer) AS "done_1337";`
|
||||||
|
|
||||||
resSQL, err := compileGQLToPSQL(gql)
|
resSQL, err := compileGQLToPSQL(gql)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -411,6 +457,7 @@ func TestCompileGQL(t *testing.T) {
|
||||||
t.Run("manyToManyReverse", manyToManyReverse)
|
t.Run("manyToManyReverse", manyToManyReverse)
|
||||||
t.Run("aggFunction", aggFunction)
|
t.Run("aggFunction", aggFunction)
|
||||||
t.Run("aggFunctionWithFilter", aggFunctionWithFilter)
|
t.Run("aggFunctionWithFilter", aggFunctionWithFilter)
|
||||||
|
t.Run("syntheticTables", syntheticTables)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkCompileGQLToSQL(b *testing.B) {
|
func BenchmarkCompileGQLToSQL(b *testing.B) {
|
||||||
|
|
|
@ -16,16 +16,15 @@ type TTKey struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type DBSchema struct {
|
type DBSchema struct {
|
||||||
ColMap map[TCKey]*DBColumn
|
|
||||||
ColIDMap map[int]*DBColumn
|
|
||||||
Tables map[string]*DBTableInfo
|
Tables map[string]*DBTableInfo
|
||||||
|
|
||||||
RelMap map[TTKey]*DBRel
|
RelMap map[TTKey]*DBRel
|
||||||
}
|
}
|
||||||
|
|
||||||
type DBTableInfo struct {
|
type DBTableInfo struct {
|
||||||
|
Name string
|
||||||
PrimaryCol string
|
PrimaryCol string
|
||||||
TSVCol string
|
TSVCol string
|
||||||
|
Columns map[string]*DBColumn
|
||||||
}
|
}
|
||||||
|
|
||||||
type RelType int
|
type RelType int
|
||||||
|
@ -45,7 +44,10 @@ type DBRel struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDBSchema(db *pg.DB) (*DBSchema, error) {
|
func NewDBSchema(db *pg.DB) (*DBSchema, error) {
|
||||||
schema := initSchema()
|
schema := &DBSchema{
|
||||||
|
Tables: make(map[string]*DBTableInfo),
|
||||||
|
RelMap: make(map[TTKey]*DBRel),
|
||||||
|
}
|
||||||
|
|
||||||
tables, err := GetTables(db)
|
tables, err := GetTables(db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -58,41 +60,39 @@ func NewDBSchema(db *pg.DB) (*DBSchema, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSchema(schema, t, cols)
|
schema.updateSchema(t, cols)
|
||||||
}
|
}
|
||||||
|
|
||||||
return schema, nil
|
return schema, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func initSchema() *DBSchema {
|
func (s *DBSchema) updateSchema(t *DBTable, cols []*DBColumn) {
|
||||||
return &DBSchema{
|
|
||||||
ColMap: make(map[TCKey]*DBColumn),
|
|
||||||
ColIDMap: make(map[int]*DBColumn),
|
|
||||||
Tables: make(map[string]*DBTableInfo),
|
|
||||||
RelMap: make(map[TTKey]*DBRel),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateSchema(schema *DBSchema, t *DBTable, cols []*DBColumn) {
|
|
||||||
// Current table
|
// Current table
|
||||||
ct := strings.ToLower(t.Name)
|
ti := &DBTableInfo{
|
||||||
schema.Tables[ct] = &DBTableInfo{}
|
Name: t.Name,
|
||||||
|
Columns: make(map[string]*DBColumn, len(cols)),
|
||||||
|
}
|
||||||
|
|
||||||
// Foreign key columns in current table
|
// Foreign key columns in current table
|
||||||
var jcols []*DBColumn
|
var jcols []*DBColumn
|
||||||
|
colByID := make(map[int]*DBColumn)
|
||||||
|
|
||||||
for _, c := range cols {
|
for i := range cols {
|
||||||
schema.ColMap[TCKey{ct, strings.ToLower(c.Name)}] = c
|
c := cols[i]
|
||||||
schema.ColIDMap[c.ID] = c
|
ti.Columns[strings.ToLower(c.Name)] = cols[i]
|
||||||
|
colByID[c.ID] = cols[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ct := strings.ToLower(t.Name)
|
||||||
|
s.Tables[ct] = ti
|
||||||
|
|
||||||
for _, c := range cols {
|
for _, c := range cols {
|
||||||
switch {
|
switch {
|
||||||
case c.Type == "tsvector":
|
case c.Type == "tsvector":
|
||||||
schema.Tables[ct].TSVCol = c.Name
|
s.Tables[ct].TSVCol = c.Name
|
||||||
|
|
||||||
case c.PrimaryKey:
|
case c.PrimaryKey:
|
||||||
schema.Tables[ct].PrimaryCol = c.Name
|
s.Tables[ct].PrimaryCol = c.Name
|
||||||
|
|
||||||
case len(c.FKeyTable) != 0:
|
case len(c.FKeyTable) != 0:
|
||||||
if len(c.FKeyColID) == 0 {
|
if len(c.FKeyColID) == 0 {
|
||||||
|
@ -101,7 +101,7 @@ func updateSchema(schema *DBSchema, t *DBTable, cols []*DBColumn) {
|
||||||
|
|
||||||
// Foreign key column name
|
// Foreign key column name
|
||||||
ft := strings.ToLower(c.FKeyTable)
|
ft := strings.ToLower(c.FKeyTable)
|
||||||
fc, ok := schema.ColIDMap[c.FKeyColID[0]]
|
fc, ok := colByID[c.FKeyColID[0]]
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -109,12 +109,12 @@ func updateSchema(schema *DBSchema, t *DBTable, cols []*DBColumn) {
|
||||||
// Belongs-to relation between current table and the
|
// Belongs-to relation between current table and the
|
||||||
// table in the foreign key
|
// table in the foreign key
|
||||||
rel1 := &DBRel{RelBelongTo, "", "", c.Name, fc.Name}
|
rel1 := &DBRel{RelBelongTo, "", "", c.Name, fc.Name}
|
||||||
schema.RelMap[TTKey{ct, ft}] = rel1
|
s.RelMap[TTKey{ct, ft}] = rel1
|
||||||
|
|
||||||
// One-to-many relation between the foreign key table and the
|
// One-to-many relation between the foreign key table and the
|
||||||
// the current table
|
// the current table
|
||||||
rel2 := &DBRel{RelOneToMany, "", "", fc.Name, c.Name}
|
rel2 := &DBRel{RelOneToMany, "", "", fc.Name, c.Name}
|
||||||
schema.RelMap[TTKey{ft, ct}] = rel2
|
s.RelMap[TTKey{ft, ct}] = rel2
|
||||||
|
|
||||||
jcols = append(jcols, c)
|
jcols = append(jcols, c)
|
||||||
}
|
}
|
||||||
|
@ -130,22 +130,24 @@ func updateSchema(schema *DBSchema, t *DBTable, cols []*DBColumn) {
|
||||||
for i := range jcols {
|
for i := range jcols {
|
||||||
for n := range jcols {
|
for n := range jcols {
|
||||||
if n != i {
|
if n != i {
|
||||||
updateSchemaOTMT(schema, ct, jcols[i], jcols[n])
|
s.updateSchemaOTMT(ct, jcols[i], jcols[n], colByID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateSchemaOTMT(schema *DBSchema, ct string, col1, col2 *DBColumn) {
|
func (s *DBSchema) updateSchemaOTMT(
|
||||||
|
ct string, col1, col2 *DBColumn, colByID map[int]*DBColumn) {
|
||||||
|
|
||||||
t1 := strings.ToLower(col1.FKeyTable)
|
t1 := strings.ToLower(col1.FKeyTable)
|
||||||
t2 := strings.ToLower(col2.FKeyTable)
|
t2 := strings.ToLower(col2.FKeyTable)
|
||||||
|
|
||||||
fc1, ok := schema.ColIDMap[col1.FKeyColID[0]]
|
fc1, ok := colByID[col1.FKeyColID[0]]
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fc2, ok := schema.ColIDMap[col2.FKeyColID[0]]
|
fc2, ok := colByID[col2.FKeyColID[0]]
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -154,13 +156,13 @@ func updateSchemaOTMT(schema *DBSchema, ct string, col1, col2 *DBColumn) {
|
||||||
// 2nd foreign key table
|
// 2nd foreign key table
|
||||||
//rel1 := &DBRel{RelOneToManyThrough, ct, fc1.Name, col1.Name}
|
//rel1 := &DBRel{RelOneToManyThrough, ct, fc1.Name, col1.Name}
|
||||||
rel1 := &DBRel{RelOneToManyThrough, ct, col2.Name, fc2.Name, col1.Name}
|
rel1 := &DBRel{RelOneToManyThrough, ct, col2.Name, fc2.Name, col1.Name}
|
||||||
schema.RelMap[TTKey{t1, t2}] = rel1
|
s.RelMap[TTKey{t1, t2}] = rel1
|
||||||
|
|
||||||
// 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{RelOneToManyThrough, ct, col2.Name, fc2.Name}
|
//rel2 := &DBRel{RelOneToManyThrough, ct, col2.Name, fc2.Name}
|
||||||
rel2 := &DBRel{RelOneToManyThrough, ct, col1.Name, fc1.Name, col2.Name}
|
rel2 := &DBRel{RelOneToManyThrough, ct, col1.Name, fc1.Name, col2.Name}
|
||||||
schema.RelMap[TTKey{t2, t1}] = rel2
|
s.RelMap[TTKey{t2, t1}] = rel2
|
||||||
}
|
}
|
||||||
|
|
||||||
type DBTable struct {
|
type DBTable struct {
|
||||||
|
@ -242,13 +244,13 @@ WHERE c.relkind = 'r'::char
|
||||||
|
|
||||||
stmt, err := db.Prepare(sqlStmt)
|
stmt, err := db.Prepare(sqlStmt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Error fetching columns: %s", err)
|
return nil, fmt.Errorf("error fetching columns: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var t []*DBColumn
|
var t []*DBColumn
|
||||||
_, err = stmt.Query(&t, schema, table)
|
_, err = stmt.Query(&t, schema, table)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Error fetching columns: %s", err)
|
return nil, fmt.Errorf("error fetching columns: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return t, nil
|
return t, nil
|
||||||
|
@ -257,7 +259,7 @@ WHERE c.relkind = 'r'::char
|
||||||
func (s *DBSchema) GetTable(table string) (*DBTableInfo, error) {
|
func (s *DBSchema) GetTable(table string) (*DBTableInfo, error) {
|
||||||
t, ok := s.Tables[table]
|
t, ok := s.Tables[table]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("table info not found '%s'", table)
|
return nil, fmt.Errorf("unknown table '%s'", table)
|
||||||
}
|
}
|
||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,8 @@ type Paging struct {
|
||||||
type ExpOp int
|
type ExpOp int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
OpAnd ExpOp = iota + 1
|
OpNop ExpOp = iota
|
||||||
|
OpAnd
|
||||||
OpOr
|
OpOr
|
||||||
OpNot
|
OpNot
|
||||||
OpEquals
|
OpEquals
|
||||||
|
@ -92,6 +93,8 @@ func (t ExpOp) String() string {
|
||||||
var v string
|
var v string
|
||||||
|
|
||||||
switch t {
|
switch t {
|
||||||
|
case OpNop:
|
||||||
|
v = "op-nop"
|
||||||
case OpAnd:
|
case OpAnd:
|
||||||
v = "op-and"
|
v = "op-and"
|
||||||
case OpOr:
|
case OpOr:
|
||||||
|
@ -333,7 +336,12 @@ func (com *Compiler) compileQuery(op *Operation) (*Query, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if fil, ok := com.fm[selRoot.Table]; ok {
|
fil, ok := com.fm[selRoot.Table]
|
||||||
|
if !ok {
|
||||||
|
fil = com.fl
|
||||||
|
}
|
||||||
|
|
||||||
|
if fil != nil && fil.Op != OpNop {
|
||||||
if selRoot.Where != nil {
|
if selRoot.Where != nil {
|
||||||
selRoot.Where = &Exp{Op: OpAnd, Children: []*Exp{fil, selRoot.Where}}
|
selRoot.Where = &Exp{Op: OpAnd, Children: []*Exp{fil, selRoot.Where}}
|
||||||
} else {
|
} else {
|
||||||
|
@ -788,6 +796,10 @@ func compileFilter(filter []string) (*Exp, error) {
|
||||||
var fl *Exp
|
var fl *Exp
|
||||||
com := &Compiler{}
|
com := &Compiler{}
|
||||||
|
|
||||||
|
if len(filter) == 0 {
|
||||||
|
return &Exp{Op: OpNop}, nil
|
||||||
|
}
|
||||||
|
|
||||||
for i := range filter {
|
for i := range filter {
|
||||||
node, err := ParseArgValue(filter[i])
|
node, err := ParseArgValue(filter[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
15
serv/serv.go
15
serv/serv.go
|
@ -189,9 +189,21 @@ func initCompilers(c *config) (*qcode.Compiler, *psql.Compiler, error) {
|
||||||
cdb := c.DB
|
cdb := c.DB
|
||||||
|
|
||||||
fm := make(map[string][]string, len(cdb.Fields))
|
fm := make(map[string][]string, len(cdb.Fields))
|
||||||
|
tmap := make(map[string]string, len(cdb.Fields))
|
||||||
|
|
||||||
for i := range cdb.Fields {
|
for i := range cdb.Fields {
|
||||||
f := cdb.Fields[i]
|
f := cdb.Fields[i]
|
||||||
fm[strings.ToLower(f.Name)] = f.Filter
|
name := flect.Pluralize(strings.ToLower(f.Name))
|
||||||
|
if len(f.Filter) != 0 {
|
||||||
|
if f.Filter[0] == "none" {
|
||||||
|
fm[name] = []string{}
|
||||||
|
} else {
|
||||||
|
fm[name] = f.Filter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(f.Table) != 0 {
|
||||||
|
tmap[name] = f.Table
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
qc, err := qcode.NewCompiler(qcode.Config{
|
qc, err := qcode.NewCompiler(qcode.Config{
|
||||||
|
@ -211,6 +223,7 @@ func initCompilers(c *config) (*qcode.Compiler, *psql.Compiler, error) {
|
||||||
pc := psql.NewCompiler(psql.Config{
|
pc := psql.NewCompiler(psql.Config{
|
||||||
Schema: schema,
|
Schema: schema,
|
||||||
Vars: cdb.Variables,
|
Vars: cdb.Variables,
|
||||||
|
TableMap: tmap,
|
||||||
})
|
})
|
||||||
|
|
||||||
return qc, pc, nil
|
return qc, pc, nil
|
||||||
|
|
Loading…
Reference in New Issue