Compare commits

...

6 Commits

16 changed files with 261 additions and 122 deletions

View File

@ -7,7 +7,7 @@
![Docker build](https://img.shields.io/docker/cloud/build/dosco/super-graph.svg?style=flat-square) ![Docker build](https://img.shields.io/docker/cloud/build/dosco/super-graph.svg?style=flat-square)
[![Discord Chat](https://img.shields.io/discord/628796009539043348.svg)](https://discord.gg/6pSWCTZ) [![Discord Chat](https://img.shields.io/discord/628796009539043348.svg)](https://discord.gg/6pSWCTZ)
Super Graph gives you a high performance GraphQL API without you having to write any code. GraphQL is automatically compiled efficient an SQL query. Use it either as a library or a standalone service. Super Graph gives you a high performance GraphQL API without you having to write any code. GraphQL is automagically compiled into an efficient SQL query. Use it either as a library or a standalone service.
## Using it as a service ## Using it as a service

View File

@ -91,7 +91,6 @@ auth:
# password: "" # password: ""
# max_idle: 80 # max_idle: 80
# max_active: 12000 # max_active: 12000
# In most cases you don't need these # In most cases you don't need these
# salt: "encrypted cookie" # salt: "encrypted cookie"
# sign_salt: "signed encrypted cookie" # sign_salt: "signed encrypted cookie"
@ -165,7 +164,6 @@ tables:
- name: email - name: email
related_to: products.name related_to: products.name
roles_query: "SELECT * FROM users WHERE id = $user_id" roles_query: "SELECT * FROM users WHERE id = $user_id"
roles: roles:

View File

@ -210,3 +210,15 @@ func (sg *SuperGraph) GraphQL(c context.Context, query string, vars json.RawMess
func (sg *SuperGraph) GraphQLSchema() (string, error) { func (sg *SuperGraph) GraphQLSchema() (string, error) {
return sg.ge.Schema.String(), nil return sg.ge.Schema.String(), nil
} }
// Operation function return the operation type from the query. It uses a very fast algorithm to
// extract the operation without having to parse the query.
func Operation(query string) OpType {
return OpType(qcode.GetQType(query))
}
// Name function return the operation name from the query. It uses a very fast algorithm to
// extract the operation name without having to parse the query.
func Name(query string) string {
return allow.QueryName(query)
}

View File

@ -61,6 +61,9 @@ type Config struct {
// Inflections is to add additionally singular to plural mappings // Inflections is to add additionally singular to plural mappings
// to the engine (eg. sheep: sheep) // to the engine (eg. sheep: sheep)
Inflections map[string]string `mapstructure:"inflections"` Inflections map[string]string `mapstructure:"inflections"`
// Database schema name. Defaults to 'public'
DBSchema string `mapstructure:"db_schema"`
} }
// Table struct defines a database table // Table struct defines a database table
@ -104,6 +107,7 @@ type Role struct {
// RoleTable struct contains role specific access control values for a database table // RoleTable struct contains role specific access control values for a database table
type RoleTable struct { type RoleTable struct {
Name string Name string
ReadOnly *bool `mapstructure:"read_only"`
Query Query Query Query
Insert Insert Insert Insert
@ -117,7 +121,7 @@ type Query struct {
Filters []string Filters []string
Columns []string Columns []string
DisableFunctions bool `mapstructure:"disable_functions"` DisableFunctions bool `mapstructure:"disable_functions"`
Block bool Block *bool
} }
// Insert struct contains access control values for insert operations // Insert struct contains access control values for insert operations
@ -125,7 +129,7 @@ type Insert struct {
Filters []string Filters []string
Columns []string Columns []string
Presets map[string]string Presets map[string]string
Block bool Block *bool
} }
// Insert struct contains access control values for update operations // Insert struct contains access control values for update operations
@ -133,14 +137,14 @@ type Update struct {
Filters []string Filters []string
Columns []string Columns []string
Presets map[string]string Presets map[string]string
Block bool Block *bool
} }
// Delete struct contains access control values for delete operations // Delete struct contains access control values for delete operations
type Delete struct { type Delete struct {
Filters []string Filters []string
Columns []string Columns []string
Block bool Block *bool
} }
// ReadInConfig function reads in the config file for the environment specified in the GO_ENV // ReadInConfig function reads in the config file for the environment specified in the GO_ENV

View File

@ -14,8 +14,10 @@ import (
"github.com/valyala/fasttemplate" "github.com/valyala/fasttemplate"
) )
type OpType int
const ( const (
OpQuery int = iota OpQuery OpType = iota
OpMutation OpMutation
) )
@ -56,16 +58,27 @@ type scontext struct {
func (sg *SuperGraph) initCompilers() error { func (sg *SuperGraph) initCompilers() error {
var err error var err error
var schema string
if sg.conf.DBSchema == "" {
schema = "public"
} else {
schema = sg.conf.DBSchema
}
// If sg.di is not null then it's probably set // If sg.di is not null then it's probably set
// for tests // for tests
if sg.dbinfo == nil { if sg.dbinfo == nil {
sg.dbinfo, err = psql.GetDBInfo(sg.db) sg.dbinfo, err = psql.GetDBInfo(sg.db, schema)
if err != nil { if err != nil {
return err return err
} }
} }
if len(sg.dbinfo.Tables) == 0 {
return fmt.Errorf("no tables found in database (schema: %s)", schema)
}
if err = addTables(sg.conf, sg.dbinfo); err != nil { if err = addTables(sg.conf, sg.dbinfo); err != nil {
return err return err
} }
@ -334,7 +347,7 @@ func (c *scontext) executeRoleQuery(tx *sql.Tx) (string, error) {
return role, nil return role, nil
} }
func (r *Result) Operation() int { func (r *Result) Operation() OpType {
switch r.op { switch r.op {
case qcode.QTQuery: case qcode.QTQuery:
return OpQuery return OpQuery

View File

@ -216,49 +216,70 @@ func addRoles(c *Config, qc *qcode.Compiler) error {
} }
func addRole(qc *qcode.Compiler, r Role, t RoleTable) error { func addRole(qc *qcode.Compiler, r Role, t RoleTable) error {
blockFilter := []string{"false"} blocked := struct {
readOnly bool
query bool
insert bool
update bool
delete bool
}{true, true, true, true, true}
if r.Name == "anon" {
blocked.query = false
} else {
blocked.readOnly = false
blocked.query = false
blocked.insert = false
blocked.update = false
blocked.delete = false
}
if t.ReadOnly != nil {
blocked.readOnly = *t.ReadOnly
}
if t.Query.Block != nil {
blocked.query = *t.Query.Block
}
if t.Insert.Block != nil {
blocked.insert = *t.Insert.Block
}
if t.Update.Block != nil {
blocked.update = *t.Update.Block
}
if t.Delete.Block != nil {
blocked.delete = *t.Delete.Block
}
query := qcode.QueryConfig{ query := qcode.QueryConfig{
Limit: t.Query.Limit, Limit: t.Query.Limit,
Filters: t.Query.Filters, Filters: t.Query.Filters,
Columns: t.Query.Columns, Columns: t.Query.Columns,
DisableFunctions: t.Query.DisableFunctions, DisableFunctions: t.Query.DisableFunctions,
} Block: blocked.query,
if t.Query.Block {
query.Filters = blockFilter
} }
insert := qcode.InsertConfig{ insert := qcode.InsertConfig{
Filters: t.Insert.Filters, Filters: t.Insert.Filters,
Columns: t.Insert.Columns, Columns: t.Insert.Columns,
Presets: t.Insert.Presets, Presets: t.Insert.Presets,
} Block: blocked.insert,
if t.Insert.Block {
insert.Filters = blockFilter
} }
update := qcode.UpdateConfig{ update := qcode.UpdateConfig{
Filters: t.Update.Filters, Filters: t.Update.Filters,
Columns: t.Update.Columns, Columns: t.Update.Columns,
Presets: t.Update.Presets, Presets: t.Update.Presets,
} Block: blocked.update,
if t.Update.Block {
update.Filters = blockFilter
} }
delete := qcode.DeleteConfig{ delete := qcode.DeleteConfig{
Filters: t.Delete.Filters, Filters: t.Delete.Filters,
Columns: t.Delete.Columns, Columns: t.Delete.Columns,
} Block: blocked.delete,
if t.Delete.Block {
delete.Filters = blockFilter
} }
return qc.AddRole(r.Name, t.Name, qcode.TRConfig{ return qc.AddRole(r.Name, t.Name, qcode.TRConfig{
ReadOnly: blocked.readOnly,
Query: query, Query: query,
Insert: insert, Insert: insert,
Update: update, Update: update,

View File

@ -10,6 +10,7 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/chirino/graphql/schema"
"github.com/dosco/super-graph/jsn" "github.com/dosco/super-graph/jsn"
) )
@ -140,6 +141,7 @@ func (al *List) Set(vars []byte, query, comment string) error {
func (al *List) Load() ([]Item, error) { func (al *List) Load() ([]Item, error) {
var list []Item var list []Item
varString := "variables"
b, err := ioutil.ReadFile(al.filepath) b, err := ioutil.ReadFile(al.filepath)
if err != nil { if err != nil {
@ -180,9 +182,9 @@ func (al *List) Load() ([]Item, error) {
s = e s = e
} }
ty = AL_QUERY ty = AL_QUERY
} else if matchPrefix(b, e, "variables") { } else if matchPrefix(b, e, varString) {
if c == 0 { if c == 0 {
s = e + len("variables") + 1 s = e + len(varString) + 1
} }
ty = AL_VARS ty = AL_VARS
} else if b[e] == '{' { } else if b[e] == '{' {
@ -234,7 +236,21 @@ func (al *List) Load() ([]Item, error) {
func (al *List) save(item Item) error { func (al *List) save(item Item) error {
var buf bytes.Buffer var buf bytes.Buffer
item.Name = QueryName(item.Query) qd := &schema.QueryDocument{}
if err := qd.Parse(item.Query); err != nil {
fmt.Println("##", item.Query)
return err
}
qd.WriteTo(&buf)
query := buf.String()
buf.Reset()
// fmt.Println(">", query)
item.Name = QueryName(query)
item.key = strings.ToLower(item.Name) item.key = strings.ToLower(item.Name)
if len(item.Name) == 0 { if len(item.Name) == 0 {

View File

@ -17,7 +17,7 @@ type DBInfo struct {
colMap map[string]map[string]*DBColumn colMap map[string]map[string]*DBColumn
} }
func GetDBInfo(db *sql.DB) (*DBInfo, error) { func GetDBInfo(db *sql.DB, schema string) (*DBInfo, error) {
di := &DBInfo{} di := &DBInfo{}
var version string var version string
@ -31,13 +31,13 @@ func GetDBInfo(db *sql.DB) (*DBInfo, error) {
return nil, err return nil, err
} }
di.Tables, err = GetTables(db) di.Tables, err = GetTables(db, schema)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, t := range di.Tables { for _, t := range di.Tables {
cols, err := GetColumns(db, "public", t.Name) cols, err := GetColumns(db, schema, t.Name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -47,7 +47,7 @@ func GetDBInfo(db *sql.DB) (*DBInfo, error) {
di.colMap = newColMap(di.Tables, di.Columns) di.colMap = newColMap(di.Tables, di.Columns)
di.Functions, err = GetFunctions(db) di.Functions, err = GetFunctions(db, schema)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -96,7 +96,7 @@ type DBTable struct {
Type string Type string
} }
func GetTables(db *sql.DB) ([]DBTable, error) { func GetTables(db *sql.DB, schema string) ([]DBTable, error) {
sqlStmt := ` sqlStmt := `
SELECT SELECT
c.relname as "name", c.relname as "name",
@ -108,14 +108,12 @@ SELECT
FROM pg_catalog.pg_class c FROM pg_catalog.pg_class c
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN ('r','v','m','f','') WHERE c.relkind IN ('r','v','m','f','')
AND n.nspname <> ('pg_catalog') AND n.nspname = $1
AND n.nspname <> ('information_schema')
AND n.nspname !~ ('^pg_toast')
AND pg_catalog.pg_table_is_visible(c.oid);` AND pg_catalog.pg_table_is_visible(c.oid);`
var tables []DBTable var tables []DBTable
rows, err := db.Query(sqlStmt) rows, err := db.Query(sqlStmt, schema)
if err != nil { if err != nil {
return nil, fmt.Errorf("Error fetching tables: %s", err) return nil, fmt.Errorf("Error fetching tables: %s", err)
} }
@ -264,7 +262,7 @@ type DBFuncParam struct {
Type string Type string
} }
func GetFunctions(db *sql.DB) ([]DBFunction, error) { func GetFunctions(db *sql.DB, schema string) ([]DBFunction, error) {
sqlStmt := ` sqlStmt := `
SELECT SELECT
routines.routine_name, routines.routine_name,
@ -278,11 +276,11 @@ RIGHT JOIN
information_schema.parameters information_schema.parameters
ON (routines.specific_name = parameters.specific_name and parameters.ordinal_position IS NOT NULL) ON (routines.specific_name = parameters.specific_name and parameters.ordinal_position IS NOT NULL)
WHERE WHERE
routines.specific_schema = 'public' routines.specific_schema = $1
ORDER BY ORDER BY
routines.routine_name, parameters.ordinal_position;` routines.routine_name, parameters.ordinal_position;`
rows, err := db.Query(sqlStmt) rows, err := db.Query(sqlStmt, schema)
if err != nil { if err != nil {
return nil, fmt.Errorf("Error fetching functions: %s", err) return nil, fmt.Errorf("Error fetching functions: %s", err)
} }

View File

@ -16,26 +16,31 @@ type QueryConfig struct {
Filters []string Filters []string
Columns []string Columns []string
DisableFunctions bool DisableFunctions bool
Block bool
} }
type InsertConfig struct { type InsertConfig struct {
Filters []string Filters []string
Columns []string Columns []string
Presets map[string]string Presets map[string]string
Block bool
} }
type UpdateConfig struct { type UpdateConfig struct {
Filters []string Filters []string
Columns []string Columns []string
Presets map[string]string Presets map[string]string
Block bool
} }
type DeleteConfig struct { type DeleteConfig struct {
Filters []string Filters []string
Columns []string Columns []string
Block bool
} }
type TRConfig struct { type TRConfig struct {
ReadOnly bool
Query QueryConfig Query QueryConfig
Insert InsertConfig Insert InsertConfig
Update UpdateConfig Update UpdateConfig
@ -43,14 +48,14 @@ type TRConfig struct {
} }
type trval struct { type trval struct {
readOnly bool
query struct { query struct {
limit string limit string
fil *Exp fil *Exp
filNU bool filNU bool
cols map[string]struct{} cols map[string]struct{}
disable struct { disable struct{ funcs bool }
funcs bool block bool
}
} }
insert struct { insert struct {
@ -59,6 +64,7 @@ type trval struct {
cols map[string]struct{} cols map[string]struct{}
psmap map[string]string psmap map[string]string
pslist []string pslist []string
block bool
} }
update struct { update struct {
@ -67,12 +73,14 @@ type trval struct {
cols map[string]struct{} cols map[string]struct{}
psmap map[string]string psmap map[string]string
pslist []string pslist []string
block bool
} }
delete struct { delete struct {
fil *Exp fil *Exp
filNU bool filNU bool
cols map[string]struct{} cols map[string]struct{}
block bool
} }
} }

View File

@ -207,7 +207,7 @@ func NewFilter() *Exp {
func (com *Compiler) AddRole(role, table string, trc TRConfig) error { func (com *Compiler) AddRole(role, table string, trc TRConfig) error {
var err error var err error
trv := &trval{} trv := &trval{readOnly: trc.ReadOnly}
// query config // query config
trv.query.fil, trv.query.filNU, err = compileFilter(trc.Query.Filters) trv.query.fil, trv.query.filNU, err = compileFilter(trc.Query.Filters)
@ -219,6 +219,7 @@ func (com *Compiler) AddRole(role, table string, trc TRConfig) error {
} }
trv.query.cols = listToMap(trc.Query.Columns) trv.query.cols = listToMap(trc.Query.Columns)
trv.query.disable.funcs = trc.Query.DisableFunctions trv.query.disable.funcs = trc.Query.DisableFunctions
trv.query.block = trc.Query.Block
// insert config // insert config
trv.insert.fil, trv.insert.filNU, err = compileFilter(trc.Insert.Filters) trv.insert.fil, trv.insert.filNU, err = compileFilter(trc.Insert.Filters)
@ -228,6 +229,7 @@ func (com *Compiler) AddRole(role, table string, trc TRConfig) error {
trv.insert.cols = listToMap(trc.Insert.Columns) trv.insert.cols = listToMap(trc.Insert.Columns)
trv.insert.psmap = parsePresets(trc.Insert.Presets) trv.insert.psmap = parsePresets(trc.Insert.Presets)
trv.insert.pslist = mapToList(trv.insert.psmap) trv.insert.pslist = mapToList(trv.insert.psmap)
trv.insert.block = trc.Insert.Block
// update config // update config
trv.update.fil, trv.update.filNU, err = compileFilter(trc.Update.Filters) trv.update.fil, trv.update.filNU, err = compileFilter(trc.Update.Filters)
@ -237,6 +239,7 @@ func (com *Compiler) AddRole(role, table string, trc TRConfig) error {
trv.update.cols = listToMap(trc.Update.Columns) trv.update.cols = listToMap(trc.Update.Columns)
trv.update.psmap = parsePresets(trc.Update.Presets) trv.update.psmap = parsePresets(trc.Update.Presets)
trv.update.pslist = mapToList(trv.update.psmap) trv.update.pslist = mapToList(trv.update.psmap)
trv.update.block = trc.Update.Block
// delete config // delete config
trv.delete.fil, trv.delete.filNU, err = compileFilter(trc.Delete.Filters) trv.delete.fil, trv.delete.filNU, err = compileFilter(trc.Delete.Filters)
@ -244,6 +247,7 @@ func (com *Compiler) AddRole(role, table string, trc TRConfig) error {
return err return err
} }
trv.delete.cols = listToMap(trc.Delete.Columns) trv.delete.cols = listToMap(trc.Delete.Columns)
trv.delete.block = trc.Delete.Block
singular := flect.Singularize(table) singular := flect.Singularize(table)
plural := flect.Pluralize(table) plural := flect.Pluralize(table)
@ -330,6 +334,28 @@ func (com *Compiler) compileQuery(qc *QCode, op *Operation, role string) error {
trv := com.getRole(role, field.Name) trv := com.getRole(role, field.Name)
switch action {
case QTQuery:
if trv.query.block {
continue
}
case QTInsert:
if trv.insert.block || trv.readOnly {
return fmt.Errorf("insert blocked: %s", field.Name)
}
case QTUpdate:
if trv.update.block || trv.readOnly {
return fmt.Errorf("update blocked: %s", field.Name)
}
case QTDelete:
if trv.delete.block || trv.readOnly {
return fmt.Errorf("delete blocked: %s", field.Name)
}
}
selects = append(selects, Select{ selects = append(selects, Select{
ID: id, ID: id,
ParentID: parentID, ParentID: parentID,
@ -946,6 +972,9 @@ func newExp(st *util.Stack, node *Node, usePool bool) (*Exp, error) {
ex.Op = OpDistinct ex.Op = OpDistinct
ex.Val = node.Val ex.Val = node.Val
default: default:
if len(node.Children) == 0 {
return nil, fmt.Errorf("[Where] invalid operation: %s", name)
}
pushChildren(st, node.exp, node) pushChildren(st, node.exp, node)
return nil, nil // skip node return nil, nil // skip node
} }
@ -965,8 +994,9 @@ func newExp(st *util.Stack, node *Node, usePool bool) (*Exp, error) {
case NodeVar: case NodeVar:
ex.Type = ValVar ex.Type = ValVar
default: default:
return nil, fmt.Errorf("[Where] valid values include string, int, float, boolean and list: %s", node.Type) return nil, fmt.Errorf("[Where] invalid values for: %s", name)
} }
setWhereColName(ex, node) setWhereColName(ex, node)
} }
@ -1015,6 +1045,7 @@ func setWhereColName(ex *Exp, node *Node) {
ex.Col = list[listlen-1] ex.Col = list[listlen-1]
ex.NestedCols = list[:listlen] ex.NestedCols = list[:listlen]
} }
} }
func setOrderByColName(ob *OrderBy, node *Node) { func setOrderByColName(ob *OrderBy, node *Node) {

View File

@ -1,41 +1,40 @@
0e384e19.bc97038f.js,1589756749060,098da569a72fb34a077f0e411b009ccdcc0e98c04a1a403a952bbea6a51aa195 01a106d5.06939d67.js,1589776216137,2e1ce67f6cf79a8a8e2070fc4ea4a6104ac73a5b26a1ab10b62f6cd8e45a8074
01a106d5.e6fa2a6a.js,1589756749060,7ee597df32dc6163edea35383043c7e5affb12525acb3250f92aa18157ab8e41 1.1c32171f.js.LICENSE.txt,1589776216144,31b4d50dbbd144da150dcdcf0ccef8f6cf8b6b5204d5c9adde3b24466777fad5
1.1c32171f.js.LICENSE.txt,1589756749064,31b4d50dbbd144da150dcdcf0ccef8f6cf8b6b5204d5c9adde3b24466777fad5 0e384e19.7f29b403.js,1589776216137,e2c3882226f2a601b65e4bb1fdb771296c1946f9f125c90af4a8f451dfd2c867
19.fdfbe826.js.LICENSE.txt,1589756749064,6ad95a8099127a8d42b5ace6d148064b1d3e922174f08d75d0ee2220ebeacd0b 19.fdfbe826.js.LICENSE.txt,1589776216145,6ad95a8099127a8d42b5ace6d148064b1d3e922174f08d75d0ee2220ebeacd0b
17896441.183211f5.js,1589756749060,7736db62d7498a8d3a10a617b1bdfac08c8f29dc03329f4ad3320f2571c223c0 17896441.183211f5.js,1589776216137,7736db62d7498a8d3a10a617b1bdfac08c8f29dc03329f4ad3320f2571c223c0
20ac7829.c04b4a1e.js,1589756749060,5b95f479848ccd6959630d4a24bd551d0dbc74457911e9b6f3498655bfaf8ea7 20ac7829.c04b4a1e.js,1589776216137,5b95f479848ccd6959630d4a24bd551d0dbc74457911e9b6f3498655bfaf8ea7
404.html,1589756750606,fe7e6a0f354000328576c9a2d8794d55dae6de0a9883e2f9dda7be4c91aa8f2a 1.1c32171f.js,1589776216137,5441b74bfad9f5a37ba0e6123621c73c3e3b9064bda6b9dcf62fdb7381bf8e41
1.1c32171f.js,1589756749061,5441b74bfad9f5a37ba0e6123621c73c3e3b9064bda6b9dcf62fdb7381bf8e41 2.8f12478f.js,1589776216137,3ac7ca0df8fca86145f5decbd86c8adfbc6b5b11a5be96fc96cc9bc33d6306e6
395f47e2.9ce46e61.js,1589756749060,fad9479ae57496cd96b3daf191197ec9899c9571bba4d35723a7a29e33468999 395f47e2.28d67f37.js,1589776216137,8a9b6bc2afdd99ca2b1827c8289352fab6163e30151b9701c29a7863b6cd00b6
2.8f12478f.js,1589756749061,3ac7ca0df8fca86145f5decbd86c8adfbc6b5b11a5be96fc96cc9bc33d6306e6 404.html,1589776218438,0a748eaa7614b1982623360ba8554c0f498b0796ead3cc429a2c84d287084b50
3d9c95a4.5f37b350.js,1589756749061,9458017327095622c54f0716951d83affe3149089f7dc56025627d979255bf43 3d9c95a4.c89589de.js,1589776216137,d5c45e5a3671f303683451d448e2e5d5b464f041cde683af6e824b9e7f951412
969d212d.82b6da9d.js,1589756749061,444b2a6c59ec2a0847279a4fed62b148c967fecae0b550433885f71ed5d9e13b 9225b3a9.a5e6036b.js,1589776216137,ec9a0d4b34d8751f74348d0da369625a18f320c9ed5ab3c5ccf047ead2551bd8
c4f5d8e4.47e70b85.js,1589756749064,6f986b48720724e7c8a715812b5f6625c71c8eca258bb4b410a447eb5da52734 741df2ae.e13b96b2.js,1589776216137,12028f0cbdf783ac91ea42db64d91190ebd4df24cc74162f953aacc75d16d078
741df2ae.162c25b8.js,1589756749061,8a68c30fa75945a2bff9899a3447003bcfc33f3b46111d37842e1802ad6d3fca 969d212d.9fc45877.js,1589776216138,8323c9f2db042bfaa2ebba43d9500bed881a694d0bfc27fd796cec95bb032dc5
9225b3a9.a07e65fb.js,1589756749061,5a6bb09b0b56b1d14c482dc1423e2eca5128a23fd54ff0fd261865543225d8cf c4f5d8e4.47e70b85.js,1589776216145,6f986b48720724e7c8a715812b5f6625c71c8eca258bb4b410a447eb5da52734
index.html,1589756750606,43c17105a27007e3368d4275ec5fd617eea945b78868c43c73ba32f74184cb62 index.html,1589776218438,89f81ec3d3be439a827bd61448dcaddb71c33422df7baa88a7bbcdf784dbc0b2
main.f771ef31.js.LICENSE.txt,1589756749064,1d906c3b83eacffe298d21eeb73e6e73e96310983224783d236195098e6765a7 98ce8162.b5ace15d.js,1589776216137,935e1c6dd08f7e9d0d00221559b95f0f649e28ddf64be6bbb7b3e65bae1aba72
98ce8162.1704a8b8.js,1589756749061,d109a5c0b42fa7f7753dfe95fddeca8db1520956f724e90219be70d1e40c18f2 main.e30d99cd.js.LICENSE.txt,1589776216144,1d906c3b83eacffe298d21eeb73e6e73e96310983224783d236195098e6765a7
sitemap.xml,1589756750623,660ed269bf0306ba47ecdfb638e487147784d614c43c6c4a8e84194973baf183 runtime~main.366c29ad.js,1589776216145,0e550cc9522cd99c5fa4097c7db629eef56127a7f8ade0b7c9954cc8f6a01239
99e04881.2a79a052.js,1589756749064,8f5258dd347ac81b78c9837f762d9f247607011dd16553f34c2031c4ccbd38ea 5043549d.62508ecf.js,1589776216137,383959b80d2b0c6416e83c9640ea03c666fe92c407e13a6f022b58072feeafd2
runtime~main.82a8fb74.js,1589756749064,d921e2c431192f04f24dd17747b3f87ddc673c8ad16fd59a769dae8c1a37e23b 99e04881.197dcef6.js,1589776216144,af99883cbd4d58fbac7cbf814be33032b77bc8daf856aed54bdf0bf27ed5708d
5043549d.cb3b9121.js,1589756749061,3e19828fe02c125eff01c388067ee2d817bdc6f963c063ff7d1be9fba440f658 sitemap.xml,1589776218455,660ed269bf0306ba47ecdfb638e487147784d614c43c6c4a8e84194973baf183
styles.9155f1d2.js,1589756749060,f1e0863928710e67338dc88c37f47ef3ff164d36c4bba40d005561094c9c3284 styles.9155f1d2.js,1589776216137,f1e0863928710e67338dc88c37f47ef3ff164d36c4bba40d005561094c9c3284
db32d859.d7e74c54.js,1589756749064,e800ed98ca5b6a8dcb800e1b8fd8fe57564e22a41899181f07147ae08a153f24 db32d859.a032827a.js,1589776216145,36d575ffad747898726a97cb7a3551e636f744218595bea5c060536eb8d8390f
docs/advanced/index.html,1589756750609,9f7260c581cb4e71c46896af29c537e0fb02fac3228d4a991435bdf995f542e8 docs/advanced/index.html,1589776218439,31171870786a597597de9417978a27253581c013962e39959ae4c0777bf86c28
docs/deploy/index.html,1589756750609,555db8271ebd923ff2ffebe51ade18aa2f7f78650bd9d358fdcd8fb2eecd8de3 docs/deploy/index.html,1589776218440,7a4735edb93006311b704e62b843bf89bc4354fdf0fdc22a0c5802e39878c193
docs/home/index.html,1589756750609,5b095aec72d5e0a50aac298598c1b27964656d67db94097dc1c8e0bfac4c59b9 docs/home/index.html,1589776218440,c7fbb0c1084c6ef8858775c5083b6b416b8188942d4402a5a625eadb3bc00942
docs/react/index.html,1589756750609,bea10eea368d0ae5848e4dd34ccd0215b1fef58940768dd2d9c8fad246aee172 docs/intro/index.html,1589776218440,c7a50ae98c0b279f422e55c2eeb9f7ba1c7c1a8bcac07be11fd6e05ced224094
img/super-graph-logo.svg,1589756750606,648bfe286ee36b88ad9accf8cda7eb016c8fe3c0aa7fe6777856ac1e2dac9b32 img/super-graph-logo.svg,1589776218438,66a865c4936f44ea811464b967f221b615b7553e85dca0d6f1ef620da3911857
docs/intro/index.html,1589756750609,f9eefab7ad7aefec779dc43f2ee17df1cb5c1edd8878e723bf3c136c18468f9a docs/react/index.html,1589776218440,f76fc976f3491d9aacf19ce3b34bee1339f87c673a9da95f192683615618f210
docs/why/index.html,1589756750609,7e4ecb00fc6fb663f826e424c3f09727fbafa5323e1de71ea178946a9584eae2 docs/why/index.html,1589776218440,4aa380fe4e5d8476645e368d1f708d5d1344331c572383db823c3499fa0c99cc
docs/security/index.html,1589756750609,a612631d3364369d079780ed377b84d1570a15233ab4ed66392ffc7248eaf4fa docs/security/index.html,1589776218440,0c7d466dc143935db8c02a448952cae2465635e4b6782b1682449bbd56807917
styles.8ee0cad4.css,1589756749060,34b2e79c5c5b1f7afda4376e422e8ccb2c3c04213ca09d788f0c68ecf153d6e6 styles.8ee0cad4.css,1589776216137,34b2e79c5c5b1f7afda4376e422e8ccb2c3c04213ca09d788f0c68ecf153d6e6
docs/config/index.html,1589756750609,964692badbdd80f41ab481b4753046ec17583692b57843becadb18c84bc5fa52 docs/config/index.html,1589776218440,25b6e87a42c163ac966e80acebca8708f56ae95ba8f3ed8b98ff7fd70ca5a222
docs/start/index.html,1589756750609,8b6da7abe0dc0bacb97c6ba04e9b63d0de47c7d8c7db51b1b1d03c3fad740043 docs/internals/index.html,1589776218440,b6f2136a1c832f421a46329fb1f39269d820c55a0dfc9351848271a5501d8e6e
img/favicon.png,1589756750610,1a9ddda9df7e965340c2b5fdfc9b1f76ec11c2fcc800ec7908ca8827f3c95741 docs/start/index.html,1589776218440,485ec2c61117d8940d8028f34d51d421995a814d5b9d4d5a1870adaed48aec2c
docs/internals/index.html,1589756750607,d69ff057c545f868959dc8d54c577947d6c25ea982d992d377646eaffbef255c docs/graphql/index.html,1589776218440,3bd79f703fe67656884f3121bfddc3a4fc4d9e5bb2bf9271c94014058fbbd806
docs/graphql/index.html,1589756750609,f8c7f0f5d5566eca1f8ae39b941aee0ed82adc52ce50dc922982de8d02d2a59a main.e30d99cd.js,1589776216144,98a4087d6f537aaddbc1225aaabfb4d12d1394772deb618d4d457685cee59311
main.f771ef31.js,1589756749064,2dcb5a6634b787512f51a25f69f7f862f027548def31c5c79894ffff7375c59a 19.fdfbe826.js,1589776216144,b8abb73aea5fc0aa50d7e8b8bd38984e3b3aec62de2faf66fb3e55fd1428f8a7
19.fdfbe826.js,1589756749064,b8abb73aea5fc0aa50d7e8b8bd38984e3b3aec62de2faf66fb3e55fd1428f8a7 server.bundle.js,1589776218438,826db37f1de931e8b088c1ff20b4a3c2fe0c3d54d9ff4020e500f0df1b83a616
server.bundle.js,1589756750606,aceff68a47f9236060259eca732a163a2e660e59f43195289cafba2abd244165

View File

@ -1,21 +1,45 @@
--- ---
id: home id: home
title: Super Graph title: Super Graph
hide_title: true
sidebar_label: Home sidebar_label: Home
--- ---
import useBaseUrl from '@docusaurus/useBaseUrl'; // Add to the top of the file below the front matter. import useBaseUrl from '@docusaurus/useBaseUrl'; // Add to the top of the file below the front matter.
<img alt="Super Graph Logo" src={useBaseUrl('img/super-graph-logo.svg')} height="60" />; <div class="hero shadow--lw margin-bottom--lg">
<div class="container">
<div class="row">
<div class="col col--2">
<img
class="avatar__photo avatar__photo--xl"
alt="Super Graph Logo"
src={useBaseUrl('img/super-graph-logo.svg')}
height="70"
/>
</div>
<div class="col col--10"><h1 class="hero__title">Super Graph</h1></div>
</div>
<p class="hero__subtitle">Fetch data without code!</p>
<div class="margin-bottom--lg">
<a class="button button--secondary button--outline button--lg" href="start">
Skip Intro
</a>
</div>
<p>Stop fighting ORM's and complex SQL just to fetch the data you need. Instead try Super Graph it automagically tranforms GraphQL into efficient SQL.</p>
</div>
</div>
## Fetch data without code! :::info cut development time
### Stop strugging with ORM's and complex SQL just to fetch the data you need. Instead try asking nicely with GraphQL.
:::info
80% of all web app development is either reading from or writing to a database. 100x your developer productivity and save valuable time by making that super simple. 80% of all web app development is either reading from or writing to a database. 100x your developer productivity and save valuable time by making that super simple.
::: :::
### Fetching data with GraphQL
Just imagine the code or SQL you'll need to fetch this data, the user, all his posts, all the votes on the posts, the authors information and the related tags. Oh yeah and you also need efficient cursor based pagination. And Remember you also need to maintain this code forever.
Instead just describe the data you need in GraphQL and give that to Super Graph it'll automatically learn your database and generate the most efficient SQL query fetching your data in the JSON structure you expected.
```graphql ```graphql
query { query {
user(id: 5) { user(id: 5) {
@ -45,7 +69,10 @@ query {
} }
``` ```
### Super Graph automatially compiles your GraphQL into an efficient SQL query giving you the results you wanted. ### Instant results
Here's the data Super Graph fetched using the GraphQL above, it's even in the JSON structure you
wanted it in. All this without you writing any code or SQL.
```json ```json
{ {
@ -77,6 +104,8 @@ query {
"created_at": "2020-05-13T13:51:21.729501+00:00" "created_at": "2020-05-13T13:51:21.729501+00:00"
}, },
... ...
],
"posts_cursor": "a8d4j2k9d83dy373hd2nskw2sjs8"
} }
} }
``` ```

View File

@ -3,7 +3,7 @@ module.exports = {
tagline: "Fetch data without code", tagline: "Fetch data without code",
url: "https://supergraph.dev", url: "https://supergraph.dev",
baseUrl: "/", baseUrl: "/",
favicon: "img/favicon.svg", favicon: "img/super-graph-logo.svg",
organizationName: "dosco", // Usually your GitHub org/user name. organizationName: "dosco", // Usually your GitHub org/user name.
projectName: "super-graph", // Usually your repo name. projectName: "super-graph", // Usually your repo name.
themeConfig: { themeConfig: {
@ -11,7 +11,7 @@ module.exports = {
title: "Super Graph", title: "Super Graph",
logo: { logo: {
alt: "Super Graph Logo", alt: "Super Graph Logo",
src: "img/favicon.png", src: "img/super-graph-logo.svg",
}, },
links: [ links: [
{ {
@ -56,13 +56,13 @@ module.exports = {
sidebarPath: require.resolve("./sidebars.js"), sidebarPath: require.resolve("./sidebars.js"),
// Please change this to your repo. // Please change this to your repo.
editUrl: editUrl:
"https://github.com/facebook/docusaurus/edit/master/website/", "https://github.com/dosco/super-graph/edit/master/docs/website",
}, },
blog: { blog: {
showReadingTime: true, showReadingTime: true,
// Please change this to your repo. // Please change this to your repo.
editUrl: editUrl:
"https://github.com/facebook/docusaurus/edit/master/website/blog/", "https://github.com/dosco/super-graph/edit/master/docs/website",
}, },
theme: { theme: {
customCss: require.resolve("./src/css/custom.css"), customCss: require.resolve("./src/css/custom.css"),

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

View File

@ -2,7 +2,7 @@
<svg width="378px" height="314px" viewBox="0 0 378 314" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <svg width="378px" height="314px" viewBox="0 0 378 314" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>battery-technology-svgrepo-com</title> <title>battery-technology-svgrepo-com</title>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="battery-technology-svgrepo-com" fill="#000000" fill-rule="nonzero"> <g id="battery-technology-svgrepo-com" fill="#444AA9" fill-rule="nonzero">
<path d="M234,269.076 L210,269.076 C196.8,269.076 186,258.276 186,245.076 L186,69.076 C186,55.876 196.8,45.076 210,45.076 L370,45.076 C374.4,45.076 378,41.476 378,37.076 C378,32.676 374.4,29.076 370,29.076 L210,29.076 C188,29.076 170,47.076 170,69.076 L170,245.076 C170,267.076 188,285.076 210,285.076 L234,285.076 C238.4,285.076 242,281.476 242,277.076 C242,272.676 238.4,269.076 234,269.076 Z" id="Path" transform="translate(274.000000, 157.076000) scale(-1, -1) translate(-274.000000, -157.076000) "></path> <path d="M234,269.076 L210,269.076 C196.8,269.076 186,258.276 186,245.076 L186,69.076 C186,55.876 196.8,45.076 210,45.076 L370,45.076 C374.4,45.076 378,41.476 378,37.076 C378,32.676 374.4,29.076 370,29.076 L210,29.076 C188,29.076 170,47.076 170,69.076 L170,245.076 C170,267.076 188,285.076 210,285.076 L234,285.076 C238.4,285.076 242,281.476 242,277.076 C242,272.676 238.4,269.076 234,269.076 Z" id="Path" transform="translate(274.000000, 157.076000) scale(-1, -1) translate(-274.000000, -157.076000) "></path>
<path d="M64,269.076 L40,269.076 C26.8,269.076 16,258.276 16,245.076 L16,69.076 C16,55.876 26.8,45.076 40,45.076 L200,45.076 C204.4,45.076 208,41.476 208,37.076 C208,32.676 204.4,29.076 200,29.076 L40,29.076 C18,29.076 0,47.076 0,69.076 L0,245.076 C0,267.076 18,285.076 40,285.076 L64,285.076 C68.4,285.076 72,281.476 72,277.076 C72,272.676 68.4,269.076 64,269.076 Z" id="Path"></path> <path d="M64,269.076 L40,269.076 C26.8,269.076 16,258.276 16,245.076 L16,69.076 C16,55.876 26.8,45.076 40,45.076 L200,45.076 C204.4,45.076 208,41.476 208,37.076 C208,32.676 204.4,29.076 200,29.076 L40,29.076 C18,29.076 0,47.076 0,69.076 L0,245.076 C0,267.076 18,285.076 40,285.076 L64,285.076 C68.4,285.076 72,281.476 72,277.076 C72,272.676 68.4,269.076 64,269.076 Z" id="Path"></path>
<path d="M196,269.076 L184,269.076 C179.6,269.076 176,272.676 176,277.076 C176,281.476 179.6,285.076 184,285.076 L196,285.076 C200.4,285.076 204,281.476 204,277.076 C204,272.676 200.4,269.076 196,269.076 Z" id="Path"></path> <path d="M196,269.076 L184,269.076 C179.6,269.076 176,272.676 176,277.076 C176,281.476 179.6,285.076 184,285.076 L196,285.076 C200.4,285.076 204,281.476 204,277.076 C204,272.676 200.4,269.076 196,269.076 Z" id="Path"></path>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -45,6 +45,16 @@ func initConf() (*Config, error) {
logLevel = LogLevelNone logLevel = LogLevelNone
} }
// copy over db_schema from the core config
if c.DB.Schema == "" {
c.DB.Schema = c.DBSchema
}
// set default database schema
if c.DB.Schema == "" {
c.DB.Schema = "public"
}
// Auths: validate and sanitize // Auths: validate and sanitize
am := make(map[string]struct{}) am := make(map[string]struct{})