This repository has been archived by the owner on Mar 11, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes #101 This change implements a very simple "query by example" functionality where you can add two parameters to the url "api/workitem" - page: <start>,<length> implements paging. If only one value is given, it is taken as the length parameters - filter: you can pass in a json object string. Only work items matching (with '=') the field values given in the example json will be selected. Example: http://localhost:8080/api/workitem?filter={"system.owner": "tmaeder","Name": "First","Type": "1"}&page=1,2 This change has a query parser, which produces expression trees, which will in turn be compiled for use with Gorm. While the query system architecture is in place, only a small amount of expressions is implemented (for example, only "=", no other comparisons). The intention is to extend the system as needed. * First cut of simple work item listing * Extract work item storage to a repository struct * Always start from original db * Extracted transaction support and errors * Cleanup, doc & minor fixes * Support for paging parameters * Conditional value rendering depending on json context or regular context * Fix test, update doc * Fixed some comments * Added comment * Prevent sql injection * Remove unused function
- Loading branch information
Showing
12 changed files
with
681 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,202 @@ | ||
// Package criteria holds a representation of expression trees and code for their manipulation | ||
// This package serves to decouple the concrete query language from the execution of the queries against the database | ||
package criteria | ||
|
||
// Expression is used to express conditions for selecting an entity | ||
type Expression interface { | ||
// Accept calls the visitor callback of the appropriate type | ||
Accept(visitor ExpressionVisitor) interface{} | ||
// SetAnnotation puts the given annotation on the expression | ||
SetAnnotation(key string, value interface{}) | ||
// Annotation reads back values set with SetAnnotation | ||
Annotation(key string) interface{} | ||
// Returns the parent expression or nil | ||
Parent() Expression | ||
setParent(parent Expression) | ||
} | ||
|
||
// IterateParents calls f for every member of the parent chain | ||
// Stops iterating if f returns false | ||
func IterateParents(exp Expression, f func(Expression) bool) { | ||
if exp != nil { | ||
exp = exp.Parent() | ||
} | ||
for exp != nil { | ||
if !f(exp) { | ||
return | ||
} | ||
exp = exp.Parent() | ||
} | ||
} | ||
|
||
// BinaryExpression represents expressions with 2 children | ||
// This could be generalized to n-ary expressions, but that is not neccessary right now | ||
type BinaryExpression interface { | ||
Expression | ||
Left() Expression | ||
Right() Expression | ||
} | ||
|
||
// ExpressionVisitor is an implementation of the visitor pattern for expressions | ||
type ExpressionVisitor interface { | ||
Field(t *FieldExpression) interface{} | ||
And(a *AndExpression) interface{} | ||
Or(a *OrExpression) interface{} | ||
Equals(e *EqualsExpression) interface{} | ||
Parameter(v *ParameterExpression) interface{} | ||
Literal(c *LiteralExpression) interface{} | ||
} | ||
|
||
type expression struct { | ||
parent Expression | ||
annotations map[string]interface{} | ||
} | ||
|
||
func (exp *expression) SetAnnotation(key string, value interface{}) { | ||
if exp.annotations == nil { | ||
exp.annotations = map[string]interface{}{} | ||
} | ||
exp.annotations[key] = value | ||
} | ||
|
||
func (exp *expression) Annotation(key string) interface{} { | ||
return exp.annotations[key] | ||
} | ||
|
||
func (exp *expression) Parent() Expression { | ||
result := exp.parent | ||
return result | ||
} | ||
|
||
func (exp *expression) setParent(parent Expression) { | ||
exp.parent = parent | ||
} | ||
|
||
// access a Field | ||
|
||
// FieldExpression represents access to a field of the tested object | ||
type FieldExpression struct { | ||
expression | ||
FieldName string | ||
} | ||
|
||
// Accept implements ExpressionVisitor | ||
func (t *FieldExpression) Accept(visitor ExpressionVisitor) interface{} { | ||
return visitor.Field(t) | ||
} | ||
|
||
// Field constructs a FieldExpression | ||
func Field(id string) Expression { | ||
return &FieldExpression{expression{}, id} | ||
} | ||
|
||
// Parameter (free variable of the expression) | ||
|
||
// A ParameterExpression represents a parameter to be passed upon evaluation of the expression | ||
type ParameterExpression struct { | ||
expression | ||
} | ||
|
||
// Accept implements ExpressionVisitor | ||
func (t *ParameterExpression) Accept(visitor ExpressionVisitor) interface{} { | ||
return visitor.Parameter(t) | ||
} | ||
|
||
// Parameter constructs a value expression. | ||
func Parameter() Expression { | ||
return &ParameterExpression{} | ||
} | ||
|
||
// literal value | ||
|
||
// A LiteralExpression represents a single constant value in the expression, think "5" or "asdf" | ||
// the type of literals is not restricted at this level, but compilers or interpreters will have limitations on what they handle | ||
type LiteralExpression struct { | ||
expression | ||
Value interface{} | ||
} | ||
|
||
// Accept implements ExpressionVisitor | ||
func (t *LiteralExpression) Accept(visitor ExpressionVisitor) interface{} { | ||
return visitor.Literal(t) | ||
} | ||
|
||
// Literal constructs a literal expression | ||
func Literal(value interface{}) Expression { | ||
return &LiteralExpression{expression{}, value} | ||
} | ||
|
||
// binaryExpression is an "abstract" type for binary expressions. | ||
type binaryExpression struct { | ||
expression | ||
left Expression | ||
right Expression | ||
} | ||
|
||
// Left implements BinaryExpression | ||
func (exp *binaryExpression) Left() Expression { | ||
return exp.left | ||
} | ||
|
||
// Right implements BinaryExpression | ||
func (exp *binaryExpression) Right() Expression { | ||
return exp.right | ||
} | ||
|
||
// make sure the children have the correct parent | ||
func reparent(parent BinaryExpression) Expression { | ||
parent.Left().setParent(parent) | ||
parent.Right().setParent(parent) | ||
return parent | ||
} | ||
|
||
// And | ||
|
||
// AndExpression represents the conjunction operation of two terms | ||
type AndExpression struct { | ||
binaryExpression | ||
} | ||
|
||
// Accept implements ExpressionVisitor | ||
func (t *AndExpression) Accept(visitor ExpressionVisitor) interface{} { | ||
return visitor.And(t) | ||
} | ||
|
||
// And constructs an AndExpression | ||
func And(left Expression, right Expression) Expression { | ||
return reparent(&AndExpression{binaryExpression{expression{}, left, right}}) | ||
} | ||
|
||
// Or | ||
|
||
// OrExpression represents the disjunction operation of two terms | ||
type OrExpression struct { | ||
binaryExpression | ||
} | ||
|
||
// Accept implements ExpressionVisitor | ||
func (t *OrExpression) Accept(visitor ExpressionVisitor) interface{} { | ||
return visitor.Or(t) | ||
} | ||
|
||
// Or constructs an OrExpression | ||
func Or(left Expression, right Expression) Expression { | ||
return reparent(&OrExpression{binaryExpression{expression{}, left, right}}) | ||
} | ||
|
||
// == | ||
|
||
// EqualsExpression represents the equality operator | ||
type EqualsExpression struct { | ||
binaryExpression | ||
} | ||
|
||
// Accept implements ExpressionVisitor | ||
func (t *EqualsExpression) Accept(visitor ExpressionVisitor) interface{} { | ||
return visitor.Equals(t) | ||
} | ||
|
||
// Equals constructs an EqualsExpression | ||
func Equals(left Expression, right Expression) Expression { | ||
return reparent(&EqualsExpression{binaryExpression{expression{}, left, right}}) | ||
} |
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,12 @@ | ||
package criteria | ||
|
||
import "testing" | ||
|
||
func TestGetParent(t *testing.T) { | ||
l := Field("a") | ||
r := Literal(5) | ||
expr := Equals(l, r) | ||
if l.Parent() != expr { | ||
t.Errorf("parent should be %v, but is %v", expr, l.Parent()) | ||
} | ||
} |
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,46 @@ | ||
package criteria | ||
|
||
// IteratePostOrder walks the expression tree in depth-first, left to right order | ||
// The iteration stops if visitorFunction returns false | ||
func IteratePostOrder(exp Expression, visitorFunction func(exp Expression) bool) { | ||
exp.Accept(&postOrderIterator{visitorFunction}) | ||
} | ||
|
||
// implements ExpressionVisitor | ||
type postOrderIterator struct { | ||
visit func(exp Expression) bool | ||
} | ||
|
||
func (i *postOrderIterator) Field(exp *FieldExpression) interface{} { | ||
return i.visit(exp) | ||
} | ||
|
||
func (i *postOrderIterator) And(exp *AndExpression) interface{} { | ||
return i.binary(exp) | ||
} | ||
|
||
func (i *postOrderIterator) Or(exp *OrExpression) interface{} { | ||
return i.binary(exp) | ||
} | ||
|
||
func (i *postOrderIterator) Equals(exp *EqualsExpression) interface{} { | ||
return i.binary(exp) | ||
} | ||
|
||
func (i *postOrderIterator) Parameter(exp *ParameterExpression) interface{} { | ||
return i.visit(exp) | ||
} | ||
|
||
func (i *postOrderIterator) Literal(exp *LiteralExpression) interface{} { | ||
return i.visit(exp) | ||
} | ||
|
||
func (i *postOrderIterator) binary(exp BinaryExpression) bool { | ||
if exp.Left().Accept(i) == false { | ||
return false | ||
} | ||
if exp.Right().Accept(i) == false { | ||
return false | ||
} | ||
return i.visit(exp) | ||
} |
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,39 @@ | ||
package criteria | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
) | ||
|
||
func TestIterator(t *testing.T) { | ||
// test left-to-right, depth first iteration | ||
visited := []Expression{} | ||
l := Field("a") | ||
r := Literal(5) | ||
expr := Equals(l, r) | ||
expected := []Expression{l, r, expr} | ||
recorder := func(expr Expression) bool { | ||
visited = append(visited, expr) | ||
return true | ||
} | ||
IteratePostOrder(expr, recorder) | ||
if !reflect.DeepEqual(expected, visited) { | ||
t.Errorf("Visited should be %v, but is %v", expected, visited) | ||
} | ||
|
||
// test early iteration cutoff with false return from iterator function | ||
visited = []Expression{} | ||
recorder = func(expr Expression) bool { | ||
visited = append(visited, expr) | ||
if expr == r { | ||
return false | ||
} | ||
return true | ||
} | ||
IteratePostOrder(expr, recorder) | ||
expected = []Expression{l, r} | ||
if !reflect.DeepEqual(expected, visited) { | ||
t.Errorf("Visited should be %v, but is %v", expected, visited) | ||
} | ||
|
||
} |
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
Oops, something went wrong.