Add config driven custom table relationships

This commit is contained in:
Vikram Rangnekar
2019-12-09 01:48:18 -05:00
parent 679dd1fc83
commit 0e16eee93b
18 changed files with 790 additions and 500 deletions

View File

@ -96,7 +96,7 @@ func initAllowList(cpath string) {
}
func (al *allowList) add(req *gqlReq) {
if len(req.ref) == 0 || len(req.Query) == 0 {
if al.saveChan == nil || len(req.ref) == 0 || len(req.Query) == 0 {
return
}

View File

@ -36,6 +36,10 @@ func argMap(ctx context.Context, vars []byte) func(w io.Writer, tag string) (int
if len(fields) == 0 {
return 0, nil
}
v := fields[0].Value
if len(v) >= 2 && v[0] == '"' && v[len(v)-1] == '"' {
fields[0].Value = v[1 : len(v)-1]
}
return w.Write(fields[0].Value)
}

View File

@ -81,11 +81,17 @@ type config struct {
roles map[string]*configRole
}
type configColumn struct {
Name string
ForeignKey string `mapstructure:"related_to"`
}
type configTable struct {
Name string
Table string
Blocklist []string
Remotes []configRemote
Columns []configColumn
}
type configRemote struct {
@ -226,6 +232,7 @@ func (c *config) Init(vi *viper.Viper) error {
if _, ok := c.roles[role.Name]; ok {
errlog.Fatal().Msgf("duplicate role '%s' found", role.Name)
}
role.Name = strings.ToLower(role.Name)
role.Match = sanitize(role.Match)
role.tablesMap = make(map[string]*configRoleTable)
@ -296,6 +303,28 @@ func (c *config) getAliasMap() map[string][]string {
return m
}
func (c *config) isABCLEnabled() bool {
if len(c.RolesQuery) == 0 {
return false
}
switch len(c.Roles) {
case 0, 1:
return false
case 2:
_, ok1 := c.roles["anon"]
_, ok2 := c.roles["user"]
return !(ok1 && ok2)
}
return true
}
func (c *config) isAnonRoleDefined() bool {
_, ok := c.roles["anon"]
return ok
}
var varRe1 = regexp.MustCompile(`(?mi)\$([a-zA-Z0-9_.]+)`)
var varRe2 = regexp.MustCompile(`\{\{([a-zA-Z0-9_.]+)\}\}`)

112
serv/config_compile.go Normal file
View File

@ -0,0 +1,112 @@
package serv
import (
"fmt"
"strings"
"github.com/dosco/super-graph/psql"
"github.com/dosco/super-graph/qcode"
)
func addForeignKeys(c *config, di *psql.DBInfo) error {
for _, t := range c.Tables {
for _, c := range t.Columns {
if err := addForeignKey(di, c, t); err != nil {
return err
}
}
}
return nil
}
func addForeignKey(di *psql.DBInfo, c configColumn, t configTable) error {
c1, ok := di.GetColumn(t.Name, c.Name)
if !ok {
return fmt.Errorf(
"Invalid table '%s' or column '%s in config",
t.Name, c.Name)
}
v := strings.SplitN(c.ForeignKey, ".", 2)
if len(v) != 2 {
return fmt.Errorf(
"Invalid foreign_key in config for table '%s' and column '%s",
t.Name, c.Name)
}
fkt, fkc := v[0], v[1]
c2, ok := di.GetColumn(fkt, fkc)
if !ok {
return fmt.Errorf(
"Invalid foreign_key in config for table '%s' and column '%s",
t.Name, c.Name)
}
c1.FKeyTable = fkt
c1.FKeyColID = []int16{c2.ID}
return nil
}
func addRoles(c *config, qc *qcode.Compiler) error {
for _, r := range c.Roles {
for _, t := range r.Tables {
if err := addRole(qc, r, t); err != nil {
return err
}
}
}
return nil
}
func addRole(qc *qcode.Compiler, r configRole, t configRoleTable) error {
blockFilter := []string{"false"}
query := qcode.QueryConfig{
Limit: t.Query.Limit,
Filters: t.Query.Filters,
Columns: t.Query.Columns,
DisableFunctions: t.Query.DisableFunctions,
}
if t.Query.Block {
query.Filters = blockFilter
}
insert := qcode.InsertConfig{
Filters: t.Insert.Filters,
Columns: t.Insert.Columns,
Presets: t.Insert.Presets,
}
if t.Query.Block {
insert.Filters = blockFilter
}
update := qcode.UpdateConfig{
Filters: t.Insert.Filters,
Columns: t.Insert.Columns,
Presets: t.Insert.Presets,
}
if t.Query.Block {
update.Filters = blockFilter
}
delete := qcode.DeleteConfig{
Filters: t.Insert.Filters,
Columns: t.Insert.Columns,
}
if t.Query.Block {
delete.Filters = blockFilter
}
return qc.AddRole(r.Name, t.Name, qcode.TRConfig{
Query: query,
Insert: insert,
Update: update,
Delete: delete,
})
}

View File

@ -77,7 +77,7 @@ func (c *coreContext) resolvePreparedSQL() ([]byte, *stmt, error) {
mutation := (qt == qcode.QTMutation)
anonQuery := (qt == qcode.QTQuery && c.req.role == "anon")
useRoleQuery := len(conf.RolesQuery) != 0 && mutation
useRoleQuery := conf.isABCLEnabled() && mutation
useTx := useRoleQuery || conf.DB.SetUserID
if useTx {
@ -127,7 +127,7 @@ func (c *coreContext) resolvePreparedSQL() ([]byte, *stmt, error) {
row = db.QueryRow(c.Context, ps.sd.SQL, vars...)
}
if mutation || anonQuery {
if mutation || anonQuery || !conf.isABCLEnabled() {
err = row.Scan(&root)
} else {
err = row.Scan(&role, &root)

View File

@ -24,14 +24,16 @@ func buildStmt(qt qcode.QType, gql, vars []byte, role string) ([]stmt, error) {
return buildRoleStmt(gql, vars, role)
case qcode.QTQuery:
switch {
case role == "anon":
return buildRoleStmt(gql, vars, role)
if role == "anon" {
return buildRoleStmt(gql, vars, "anon")
}
default:
if conf.isABCLEnabled() {
return buildMultiStmt(gql, vars)
}
return buildRoleStmt(gql, vars, "user")
default:
return nil, fmt.Errorf("unknown query type '%d'", qt)
}

View File

@ -77,7 +77,15 @@ func prepareStmt(gql string, vars []byte) error {
switch qt {
case qcode.QTQuery:
stmts1, err := buildMultiStmt(q, vars)
var stmts1 []stmt
var err error
if conf.isABCLEnabled() {
stmts1, err = buildMultiStmt(q, vars)
} else {
stmts1, err = buildRoleStmt(q, vars, "user")
}
if err != nil {
return err
}
@ -87,14 +95,16 @@ func prepareStmt(gql string, vars []byte) error {
return err
}
stmts2, err := buildRoleStmt(q, vars, "anon")
if err != nil {
return err
}
if conf.isAnonRoleDefined() {
stmts2, err := buildRoleStmt(q, vars, "anon")
if err != nil {
return err
}
err = prepare(tx, &stmts2[0], gqlHash(gql, vars, "anon"))
if err != nil {
return err
err = prepare(tx, &stmts2[0], gqlHash(gql, vars, "anon"))
if err != nil {
return err
}
}
case qcode.QTMutation:
@ -142,7 +152,7 @@ func prepare(tx pgx.Tx, st *stmt, key string) error {
// nolint: errcheck
func prepareRoleStmt(tx pgx.Tx) error {
if len(conf.RolesQuery) == 0 {
if !conf.isABCLEnabled() {
return nil
}

View File

@ -36,7 +36,6 @@ func initResolvers() error {
func initRemotes(t configTable) error {
h := xxhash.New()
var err error
for _, r := range t.Remotes {
// defines the table column to be used as an id in the
@ -46,21 +45,20 @@ func initRemotes(t configTable) error {
// if no table column specified in the config then
// use the primary key of the table as the id
if len(idcol) == 0 {
idcol, err = pcompile.IDColumn(t.Name)
pcol, err := pcompile.IDColumn(t.Name)
if err != nil {
return err
}
idcol = pcol.Key
}
idk := fmt.Sprintf("__%s_%s", t.Name, idcol)
// register a relationship between the remote data
// and the database table
val := &psql.DBRel{
Type: psql.RelRemote,
Col1: idcol,
Col2: idk,
}
val := &psql.DBRel{Type: psql.RelRemote}
val.Left.Col = idcol
val.Right.Col = idk
err := pcompile.AddRelationship(strings.ToLower(r.Name), t.Name, val)
if err != nil {

View File

@ -15,77 +15,29 @@ import (
)
func initCompilers(c *config) (*qcode.Compiler, *psql.Compiler, error) {
var err error
schema, err = psql.NewDBSchema(db, c.getAliasMap())
di, err := psql.GetDBInfo(db)
if err != nil {
return nil, nil, err
}
conf := qcode.Config{
if err = addForeignKeys(c, di); err != nil {
return nil, nil, err
}
schema, err = psql.NewDBSchema(db, di, c.getAliasMap())
if err != nil {
return nil, nil, err
}
qc, err := qcode.NewCompiler(qcode.Config{
Blocklist: c.DB.Blocklist,
}
qc, err := qcode.NewCompiler(conf)
})
if err != nil {
return nil, nil, err
}
blockFilter := []string{"false"}
for _, r := range c.Roles {
for _, t := range r.Tables {
query := qcode.QueryConfig{
Limit: t.Query.Limit,
Filters: t.Query.Filters,
Columns: t.Query.Columns,
DisableFunctions: t.Query.DisableFunctions,
}
if t.Query.Block {
query.Filters = blockFilter
}
insert := qcode.InsertConfig{
Filters: t.Insert.Filters,
Columns: t.Insert.Columns,
Presets: t.Insert.Presets,
}
if t.Query.Block {
insert.Filters = blockFilter
}
update := qcode.UpdateConfig{
Filters: t.Insert.Filters,
Columns: t.Insert.Columns,
Presets: t.Insert.Presets,
}
if t.Query.Block {
update.Filters = blockFilter
}
delete := qcode.DeleteConfig{
Filters: t.Insert.Filters,
Columns: t.Insert.Columns,
}
if t.Query.Block {
delete.Filters = blockFilter
}
err := qc.AddRole(r.Name, t.Name, qcode.TRConfig{
Query: query,
Insert: insert,
Update: update,
Delete: delete,
})
if err != nil {
return nil, nil, err
}
}
if err := addRoles(c, qc); err != nil {
return nil, nil, err
}
pc := psql.NewCompiler(psql.Config{