u
2 years ago
5 changed files with 462 additions and 0 deletions
-
113apijson/query_context.go
-
198apijson/query_node.go
-
118apijson/sqlparser.go
-
29query_node_test.go
-
4vql.go
@ -0,0 +1,113 @@ |
|||
package apijson |
|||
|
|||
import ( |
|||
"fmt" |
|||
"net/http" |
|||
"strings" |
|||
|
|||
"github.com/micro/go-micro/logger" |
|||
) |
|||
|
|||
type QueryContext struct { |
|||
req map[string]interface{} |
|||
code int |
|||
nodeTree map[string]*QueryNode |
|||
nodePathMap map[string]*QueryNode |
|||
err error |
|||
explain bool |
|||
} |
|||
|
|||
func NewQueryContext(req map[string]interface{}) *QueryContext { |
|||
return &QueryContext{ |
|||
req: req, |
|||
code: http.StatusOK, |
|||
nodeTree: make(map[string]*QueryNode), |
|||
nodePathMap: make(map[string]*QueryNode), |
|||
} |
|||
} |
|||
|
|||
func (c *QueryContext) Response() map[string]interface{} { |
|||
c.doParse() |
|||
if c.err == nil { |
|||
c.doQuery() |
|||
} |
|||
resultMap := make(map[string]interface{}) |
|||
resultMap["ok"] = c.code == http.StatusOK |
|||
resultMap["code"] = c.code |
|||
if c.err != nil { |
|||
resultMap["msg"] = c.err.Error() |
|||
} else { |
|||
for k, v := range c.nodeTree { |
|||
//logger.Debugf("response.nodeMap K: %s, V: %v", k, v)
|
|||
resultMap[k] = v.Result() |
|||
} |
|||
} |
|||
return resultMap |
|||
} |
|||
|
|||
func (c *QueryContext) doParse() { |
|||
//startTime := time.Now().Nanosecond()
|
|||
for key := range c.req { |
|||
if c.err != nil { |
|||
return |
|||
} |
|||
if key == "@explain" { |
|||
c.explain = c.req[key].(bool) |
|||
} else if c.nodeTree[key] == nil { |
|||
c.parseByKey(key) |
|||
} |
|||
} |
|||
} |
|||
|
|||
func (c *QueryContext) doQuery() { |
|||
for _, n := range c.nodeTree { |
|||
if c.err != nil { |
|||
return |
|||
} |
|||
n.doQueryData() |
|||
} |
|||
} |
|||
|
|||
func (c *QueryContext) parseByKey(key string) { |
|||
queryObject := c.req[key] |
|||
if queryObject == nil { |
|||
c.err = fmt.Errorf("值不能为空, key: %s, value: %v", key, queryObject) |
|||
return |
|||
} |
|||
if queryMap, ok := queryObject.(map[string]interface{}); !ok { |
|||
c.err = fmt.Errorf("值类型不对, key: %s, value: %v", key, queryObject) |
|||
} else { |
|||
node := NewQueryNode(c, key, key, queryMap) |
|||
logger.Debugf("parse %s: %+v", key, node) |
|||
c.nodeTree[key] = node |
|||
} |
|||
} |
|||
|
|||
func (c *QueryContext) End(code int, msg string) { |
|||
c.code = code |
|||
logger.Errorf("发生错误,终止处理, code: %d, msg: %s", code, msg) |
|||
} |
|||
|
|||
func (c *QueryContext) findResult(value string) interface{} { |
|||
i := strings.LastIndex(value, "/") |
|||
path := value[0:i] |
|||
node := c.nodePathMap[path] |
|||
if node == nil { |
|||
c.err = fmt.Errorf("关联查询参数有误: %s", value) |
|||
return nil |
|||
} |
|||
if node.running { |
|||
c.err = fmt.Errorf("有循环依赖") |
|||
return nil |
|||
} |
|||
node.doQueryData() |
|||
if c.err != nil { |
|||
return nil |
|||
} |
|||
if node.CurrentData == nil { |
|||
logger.Info("查询结果为空,queryPath: " + value) |
|||
return nil |
|||
} |
|||
key := value[i+1:] |
|||
return node.CurrentData[key] |
|||
} |
@ -0,0 +1,198 @@ |
|||
package apijson |
|||
|
|||
import ( |
|||
"fmt" |
|||
"net/http" |
|||
"strings" |
|||
"time" |
|||
|
|||
"github.com/micro/go-micro/logger" |
|||
) |
|||
|
|||
func NewQueryNode(c *QueryContext, path, key string, queryMap map[string]interface{}) *QueryNode { |
|||
n := &QueryNode{ |
|||
ctx: c, |
|||
Key: strings.ToLower(key), |
|||
Path: path, |
|||
RequestMap: queryMap, |
|||
start: time.Now().UnixNano(), |
|||
sqlExecutor: &MysqlExecutor{}, |
|||
isList: strings.HasSuffix(key, "[]"), |
|||
} |
|||
c.nodePathMap[path] = n |
|||
if n.isList { |
|||
n.parseList() |
|||
} else { |
|||
n.parseOne() |
|||
} |
|||
return n |
|||
} |
|||
|
|||
type QueryNode struct { |
|||
ctx *QueryContext |
|||
start int64 |
|||
depth int8 |
|||
running bool |
|||
completed bool |
|||
isList bool |
|||
page interface{} |
|||
count interface{} |
|||
|
|||
sqlExecutor *MysqlExecutor |
|||
primaryKey string |
|||
relateKV map[string]string |
|||
|
|||
Key string |
|||
Path string |
|||
RequestMap map[string]interface{} |
|||
CurrentData map[string]interface{} |
|||
ResultList []map[string]interface{} |
|||
children map[string]*QueryNode |
|||
} |
|||
|
|||
func (n *QueryNode) parseList() { |
|||
root := n.ctx |
|||
if root.err != nil { |
|||
return |
|||
} |
|||
if value, exists := n.RequestMap[n.Key[0:len(n.Key)-2]]; exists { |
|||
if kvs, ok := value.(map[string]interface{}); ok { |
|||
root.err = n.sqlExecutor.ParseTable(n.Key) |
|||
n.parseKVs(kvs) |
|||
} else { |
|||
root.err = fmt.Errorf("列表同名参数展开出错,listKey: %s, object: %v", n.Key, value) |
|||
root.code = http.StatusBadRequest |
|||
} |
|||
return |
|||
} |
|||
for field, value := range n.RequestMap { |
|||
if value == nil { |
|||
root.err = fmt.Errorf("field of [%s] value error, %s is nil", n.Key, field) |
|||
return |
|||
} |
|||
switch field { |
|||
case "page": |
|||
n.page = value |
|||
case "count": |
|||
n.count = value |
|||
default: |
|||
if kvs, ok := value.(map[string]interface{}); ok { |
|||
child := NewQueryNode(root, n.Path+"/"+field, field, kvs) |
|||
if root.err != nil { |
|||
return |
|||
} |
|||
if n.children == nil { |
|||
n.children = make(map[string]*QueryNode) |
|||
} |
|||
n.children[field] = child |
|||
if nonDepend(n, child) && len(n.primaryKey) == 0 { |
|||
n.primaryKey = field |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
func nonDepend(parent, child *QueryNode) bool { |
|||
if len(child.relateKV) == 0 { |
|||
return true |
|||
} |
|||
for _, v := range child.relateKV { |
|||
if strings.HasPrefix(v, parent.Path) { |
|||
return false |
|||
} |
|||
} |
|||
return true |
|||
} |
|||
|
|||
func (n *QueryNode) parseOne() { |
|||
root := n.ctx |
|||
root.err = n.sqlExecutor.ParseTable(n.Key) |
|||
if root.err != nil { |
|||
root.code = http.StatusBadRequest |
|||
return |
|||
} |
|||
n.sqlExecutor.PageSize(0, 1) |
|||
n.parseKVs(n.RequestMap) |
|||
} |
|||
|
|||
func (n *QueryNode) parseKVs(kvs map[string]interface{}) { |
|||
root := n.ctx |
|||
for field, value := range kvs { |
|||
logger.Debugf("%s -> parse %s %v", n.Key, field, value) |
|||
if value == nil { |
|||
root.err = fmt.Errorf("field value error, %s is nil", field) |
|||
root.code = http.StatusBadRequest |
|||
return |
|||
} |
|||
if queryPath, ok := value.(string); ok && strings.HasSuffix(field, "@") { // @ 结尾表示有关联查询
|
|||
if n.relateKV == nil { |
|||
n.relateKV = make(map[string]string) |
|||
} |
|||
fullPath := queryPath |
|||
if strings.HasPrefix(queryPath, "/") { |
|||
fullPath = n.Path + queryPath |
|||
} |
|||
n.relateKV[field[0:len(field)-1]] = fullPath |
|||
} else { |
|||
n.sqlExecutor.ParseCondition(field, value) |
|||
} |
|||
} |
|||
} |
|||
|
|||
func (n *QueryNode) Result() interface{} { |
|||
if n.isList { |
|||
return n.ResultList |
|||
} |
|||
if len(n.ResultList) > 0 { |
|||
return n.ResultList[0] |
|||
} |
|||
return nil |
|||
} |
|||
|
|||
func (n *QueryNode) doQueryData() { |
|||
if n.completed { |
|||
return |
|||
} |
|||
n.running = true |
|||
defer func() { n.running, n.completed = false, true }() |
|||
root := n.ctx |
|||
if len(n.relateKV) > 0 { |
|||
for field, queryPath := range n.relateKV { |
|||
value := root.findResult(queryPath) |
|||
if root.err != nil { |
|||
return |
|||
} |
|||
n.sqlExecutor.ParseCondition(field, value) |
|||
} |
|||
} |
|||
if !n.isList { |
|||
n.ResultList, root.err = n.sqlExecutor.Exec() |
|||
if len(n.ResultList) > 0 { |
|||
n.CurrentData = n.ResultList[0] |
|||
return |
|||
} |
|||
return |
|||
} |
|||
primary := n.children[n.primaryKey] |
|||
primary.sqlExecutor.PageSize(n.page, n.count) |
|||
primary.doQueryData() |
|||
if root.err != nil { |
|||
return |
|||
} |
|||
listData := primary.ResultList |
|||
n.ResultList = make([]map[string]interface{}, len(listData)) |
|||
for i, x := range listData { |
|||
n.ResultList[i] = make(map[string]interface{}) |
|||
n.ResultList[i][n.primaryKey] = x |
|||
primary.CurrentData = x |
|||
if len(n.children) > 0 { |
|||
for _, child := range n.children { |
|||
if child != primary { |
|||
child.doQueryData() |
|||
n.ResultList[i][child.Key] = child.Result() |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,118 @@ |
|||
package apijson |
|||
|
|||
import ( |
|||
"bytes" |
|||
"strconv" |
|||
"strings" |
|||
) |
|||
|
|||
const DefaultLimit = 1000 |
|||
|
|||
type MysqlExecutor struct { |
|||
table string |
|||
columns []string |
|||
where []string |
|||
params []interface{} |
|||
order string |
|||
group string |
|||
limit int |
|||
page int |
|||
} |
|||
|
|||
func (e *MysqlExecutor) Table() string { |
|||
return e.table |
|||
} |
|||
|
|||
func (e *MysqlExecutor) ParseTable(t string) error { |
|||
if strings.HasSuffix(t, "[]") { |
|||
t = t[0 : len(t)-2] |
|||
} |
|||
e.table = t |
|||
return nil |
|||
} |
|||
|
|||
func (e *MysqlExecutor) ToSQL() string { |
|||
var buf bytes.Buffer |
|||
buf.WriteString("SELECT ") |
|||
if e.columns == nil { |
|||
buf.WriteString("*") |
|||
} else { |
|||
buf.WriteString(strings.Join(e.columns, ",")) |
|||
} |
|||
buf.WriteString(" FROM ") |
|||
buf.WriteString(e.table) |
|||
if len(e.where) > 0 { |
|||
buf.WriteString(" WHERE ") |
|||
buf.WriteString(strings.Join(e.where, " and ")) |
|||
} |
|||
if e.order != "" { |
|||
buf.WriteString(" ORDER BY ") |
|||
buf.WriteString(e.order) |
|||
} |
|||
buf.WriteString(" LIMIT ") |
|||
buf.WriteString(strconv.Itoa(e.limit)) |
|||
if e.limit > 1 { |
|||
buf.WriteString(" OFFSET ") |
|||
buf.WriteString(strconv.Itoa(e.limit * e.page)) |
|||
} |
|||
return buf.String() |
|||
} |
|||
|
|||
func (e *MysqlExecutor) ParseCondition(field string, value interface{}) { |
|||
if values, ok := value.([]interface{}); ok { |
|||
// 数组使用 IN 条件
|
|||
condition := field + " in (" |
|||
for i, v := range values { |
|||
if i == 0 { |
|||
condition += "?" |
|||
} else { |
|||
condition += ",?" |
|||
} |
|||
e.params = append(e.params, v) |
|||
} |
|||
e.where = append(e.where, condition+")") |
|||
} else if valueStr, ok := value.(string); ok { |
|||
if strings.HasPrefix(field, "@") { |
|||
switch field[1:] { |
|||
case "order": |
|||
e.order = valueStr |
|||
case "column": |
|||
e.columns = strings.Split(valueStr, ",") |
|||
} |
|||
} else { |
|||
e.where = append(e.where, field+"=?") |
|||
e.params = append(e.params, valueStr) |
|||
} |
|||
} else { |
|||
e.where = append(e.where, field+"=?") |
|||
e.params = append(e.params, value) |
|||
} |
|||
} |
|||
|
|||
func (e *MysqlExecutor) Exec() ([]map[string]interface{}, error) { |
|||
sql := e.ToSQL() |
|||
return QueryAll(sql, e.params...) |
|||
} |
|||
|
|||
var QueryAll = func(sql string, args ...interface{}) ([]map[string]interface{}, error) { |
|||
return nil, nil |
|||
} |
|||
|
|||
func SetQueryAll(f func(sql string, args ...interface{}) ([]map[string]interface{}, error)) { |
|||
QueryAll = f |
|||
} |
|||
|
|||
func (e *MysqlExecutor) PageSize(page interface{}, count interface{}) { |
|||
e.page = parseNum(page, 0) |
|||
e.limit = parseNum(count, 10) |
|||
} |
|||
|
|||
func parseNum(value interface{}, defaultVal int) int { |
|||
if n, ok := value.(float64); ok { |
|||
return int(n) |
|||
} |
|||
if n, ok := value.(int); ok { |
|||
return n |
|||
} |
|||
return defaultVal |
|||
} |
@ -0,0 +1,29 @@ |
|||
package vql |
|||
|
|||
import ( |
|||
"encoding/json" |
|||
"log" |
|||
"testing" |
|||
|
|||
"git.ouxuan.net/3136352472/vql/apijson" |
|||
) |
|||
|
|||
func TestNewQueryNode(t *testing.T) { |
|||
apijson.SetQueryAll(func(sql string, args ...interface{}) ([]map[string]interface{}, error) { |
|||
log.Println(sql, args) |
|||
sqlx, err := Analyze(sql) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
log.Println(sqlx.ToSql()) |
|||
return nil, nil |
|||
}) |
|||
|
|||
reqStr := `{ "Moment": { "id":12 } }` |
|||
req := make(map[string]interface{}) |
|||
json.Unmarshal([]byte(reqStr), &req) |
|||
|
|||
ctx := apijson.NewQueryContext(req) |
|||
|
|||
log.Println(ctx.Response()) |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue