Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
sneljo1 committed Dec 19, 2017
0 parents commit 7806655
Show file tree
Hide file tree
Showing 38 changed files with 2,025 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.idea
chaincode
263 changes: 263 additions & 0 deletions query/query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
package query

import (
"errors"
"test/couchdbMockQuery/query/rules"
"reflect"
"fmt"
"strings"
"encoding/json"
)

var Registry []rules.IGenericRule

func init() {

Registry = []rules.IGenericRule{
// ===
// Condition Operators == http:https://docs.couchdb.org/en/2.1.0/api/database/find.html?highlight=find#condition-operators
// ===

/// Operator type - (In)equality
rules.IGenericRule(rules.Lt),
rules.IGenericRule(rules.Lte),

rules.IGenericRule(rules.Eq),
rules.IGenericRule(rules.Ne),

rules.IGenericRule(rules.Gte),
rules.IGenericRule(rules.Gt),

/// Operator type - Object
rules.IGenericRule(rules.Exists),
rules.IGenericRule(rules.Type),

/// Operator type - Array
rules.IGenericRule(rules.In),
rules.IGenericRule(rules.Nin),
rules.IGenericRule(rules.Size),

/// Operator type - Miscellaneous
rules.IGenericRule(rules.Mod),
rules.IGenericRule(rules.Regex),

// ===
// Combination Operators == http:https://docs.couchdb.org/en/2.1.0/api/database/find.html?highlight=find#combination-operators
// ===

rules.IGenericRule(rules.And(test)),
rules.IGenericRule(rules.Or(test)),
rules.IGenericRule(rules.Not(test)),
rules.IGenericRule(rules.Nor(test)),
rules.IGenericRule(rules.All),
rules.IGenericRule(rules.ElemMatch(test)),
}
}

type StateCouchDBQueryObject struct {
Key string
Value interface{}
}

type StateCouchDBQueryResult []StateCouchDBQueryObject

// Query engine which controls and accept the whole query. It makes sure every field is filtered.
func ParseCouchDBQuery(data map[string]interface{}, userQueryMap map[string]interface{}) (StateCouchDBQueryResult, error) {

userQueryAsBytes, err := json.Marshal(userQueryMap)

if err != nil {
return nil, err
}

return ParseCouchDBQueryString(data, string(userQueryAsBytes))

}

func ParseCouchDBQueryString(data map[string]interface{}, userQueryString string) (StateCouchDBQueryResult, error) {

var userQuery map[string]interface{}
parseErr := json.Unmarshal([]byte(userQueryString), &userQuery)

if parseErr != nil {
return StateCouchDBQueryResult{}, errors.New("Error parsing query string")
}

if userQuery["selector"] == nil {
return StateCouchDBQueryResult{}, errors.New("Invalid query, selector required")
}

var response StateCouchDBQueryResult

for k, v := range data {
result, err := test(v, userQuery["selector"])

if err != nil {
return []StateCouchDBQueryObject{}, err

} else if result {
response = append(response, StateCouchDBQueryObject{
k,
v,
})

}
}

// Take slice between skip and limit

var skip = 0
var limit = len(response)

if userQuery["skip"] != nil {
skip, ok := userQuery["skip"].(int)

if ok {
skip = skip
} else {
return StateCouchDBQueryResult{}, errors.New("Skip must be an integer")
}
}

if userQuery["limit"] != nil {
limit, ok := userQuery["limit"].(int)

if ok {
limit = limit
} else {
return StateCouchDBQueryResult{}, errors.New("Limit must be an integer")
}

}

response = response[skip:limit]

return response, nil

}

// Matcher function which delegates the work to the different rules based on the key and compares values to
// implicit match values.

func test(data interface{}, userQuery interface{}) (bool, error) {

if canDecend(userQuery) && reflect.TypeOf(userQuery).Kind() == reflect.Map {

userQuery, ok := userQuery.(map[string]interface{})

if !ok {
return false, errors.New("Data is not a map")
}

for k, v := range userQuery {

if string(k[0]) == "$" {
rule, err := getRule(k)

if err != nil {
return false, err
}

matched, err1 := rule.Match(data, v)

if err1 != nil {
return false, err1
}

return matched, nil

} else {

dvp, dk := resolveSubdocumentQuery(data, k)

if dvp != nil && len(dk) == 1 {
return test(dvp[dk[0]], v)
}

return test(nil, v)
}
}

} else {

return rules.Eq.Match(data, userQuery)
}

return false, nil
}

// getRule function is used to find rule based on it's name
func getRule(name string) (rules.IGenericRule, error) {

for _, rule := range Registry {
if rule.GetName() == name {
return rule, nil
}
}

return nil, fmt.Errorf("Rule %s not found!", name)

}

// resolveSubdocumentQuery function is used to match names like "foo.bar" to get the "bar" value in a subdocument
func resolveSubdocumentQuery(data interface{}, key string) (map[string]interface{}, []string) {

var last []string
var deepObject map[string]interface{}

stack := strings.Split(key, ".")

if len(stack) > 0 {
item := stack[len(stack)-1]

stack = stack[:len(stack)-1]

last = append([]string{item}, last...)
}

_, ok := data.(map[string]interface{})

if ok {
deepObject, _ = data.(map[string]interface{})

for len(stack) > 0 {
x := stack[0]
stack = stack[1:]

t := interface{}(deepObject[x])

newObj, ok := t.(map[string]interface{})

if ok {
deepObject = newObj
} else {
stack = stack[:len(stack)-1]
}

}
}

last = append(stack, last...)

if deepObject == nil {
deepObject = data.(map[string]interface{})
}

return deepObject, last

}

func canDecend(a interface{}) bool {

rt := reflect.TypeOf(a)

if (rt.Kind() == reflect.Slice || rt.Kind() == reflect.Slice) && reflect.ValueOf(a).Len() > 0 {
return true
}

if rt.Kind() == reflect.Map && len(reflect.ValueOf(a).MapKeys()) > 0 {
return true
}

return false
}
41 changes: 41 additions & 0 deletions query/rules/Rule.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package rules

import (
"errors"
"reflect"
)

type Rule struct {
Name string
}

type IGenericRule interface {
GetName() string
Match(valA interface{}, valB interface{}) (bool, error)
}

func (r Rule) Match(valA interface{}, valB interface{}) (*bool, error) {
return nil, errors.New(r.Name + " not implemented!")
}

type Testfunc func(interface{}, interface{}) (bool, error)

// Utils

func HasElem(s interface{}, elem interface{}) bool {
arrV := reflect.ValueOf(s)

if arrV.Kind() == reflect.Slice {
for i := 0; i < arrV.Len(); i++ {

result, _ := Eq.Match(elem, arrV.Index(i).Interface())

if result {
return result
}

}
}

return false
}
61 changes: 61 additions & 0 deletions query/rules/all.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package rules

import (
"reflect"
"errors"
)

type all struct {
Rule
}

func (r all) Match(valA interface{}, valB interface{}) (bool, error) {

akind := reflect.TypeOf(valA).Kind()
bkind := reflect.TypeOf(valB).Kind()

if akind != reflect.Array && akind != reflect.Slice {
return false, errors.New("All parameter must be an array")
}

if bkind != reflect.Array && bkind != reflect.Slice {
return false, errors.New("All value must be an array")
}

all := true

reflectOfB := reflect.ValueOf(valB)

parsedValB := make([]interface{}, reflectOfB.Len())

for i := 0; i < reflectOfB.Len(); i++ {
parsedValB[i] = reflectOfB.Index(i).Interface()
}

reflectOfA := reflect.ValueOf(valA)

parsedValA := make([]interface{}, reflectOfA.Len())

for i := 0; i < reflectOfA.Len(); i++ {
parsedValA[i] = reflectOfA.Index(i).Interface()
}

for _, v := range parsedValB {
if !HasElem(parsedValA, v) {
all = false
break
}
}

return all, nil
}

func (r all) GetName() string {
return r.Rule.Name
}

var All = all{
Rule{
"$all",
},
}

0 comments on commit 7806655

Please sign in to comment.