-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 7806655
Showing
38 changed files
with
2,025 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
.idea | ||
chaincode |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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", | ||
}, | ||
} |
Oops, something went wrong.