Implement FindTopN to limit number of results in find many operations (#37)
This commit is contained in:
parent
021de6214c
commit
d2338b8f46
7 changed files with 162 additions and 9 deletions
|
@ -8,6 +8,8 @@ import (
|
||||||
"github.com/sunboyy/repogen/internal/spec"
|
"github.com/sunboyy/repogen/internal/spec"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var errOccurred = codegen.RawStatement("err != nil")
|
||||||
|
|
||||||
var returnNilErr = codegen.ReturnStatement{
|
var returnNilErr = codegen.ReturnStatement{
|
||||||
codegen.Identifier("nil"),
|
codegen.Identifier("nil"),
|
||||||
codegen.Identifier("err"),
|
codegen.Identifier("err"),
|
||||||
|
@ -15,7 +17,7 @@ var returnNilErr = codegen.ReturnStatement{
|
||||||
|
|
||||||
var ifErrReturnNilErr = codegen.IfBlock{
|
var ifErrReturnNilErr = codegen.IfBlock{
|
||||||
Condition: []codegen.Statement{
|
Condition: []codegen.Statement{
|
||||||
codegen.RawStatement("err != nil"),
|
errOccurred,
|
||||||
},
|
},
|
||||||
Statements: []codegen.Statement{
|
Statements: []codegen.Statement{
|
||||||
returnNilErr,
|
returnNilErr,
|
||||||
|
@ -24,7 +26,7 @@ var ifErrReturnNilErr = codegen.IfBlock{
|
||||||
|
|
||||||
var ifErrReturn0Err = codegen.IfBlock{
|
var ifErrReturn0Err = codegen.IfBlock{
|
||||||
Condition: []codegen.Statement{
|
Condition: []codegen.Statement{
|
||||||
codegen.RawStatement("err != nil"),
|
errOccurred,
|
||||||
},
|
},
|
||||||
Statements: []codegen.Statement{
|
Statements: []codegen.Statement{
|
||||||
codegen.ReturnStatement{
|
codegen.ReturnStatement{
|
||||||
|
@ -36,7 +38,7 @@ var ifErrReturn0Err = codegen.IfBlock{
|
||||||
|
|
||||||
var ifErrReturnFalseErr = codegen.IfBlock{
|
var ifErrReturnFalseErr = codegen.IfBlock{
|
||||||
Condition: []codegen.Statement{
|
Condition: []codegen.Statement{
|
||||||
codegen.RawStatement("err != nil"),
|
errOccurred,
|
||||||
},
|
},
|
||||||
Statements: []codegen.Statement{
|
Statements: []codegen.Statement{
|
||||||
codegen.ReturnStatement{
|
codegen.ReturnStatement{
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package mongo
|
package mongo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/sunboyy/repogen/internal/code"
|
"github.com/sunboyy/repogen/internal/code"
|
||||||
"github.com/sunboyy/repogen/internal/codegen"
|
"github.com/sunboyy/repogen/internal/codegen"
|
||||||
"github.com/sunboyy/repogen/internal/spec"
|
"github.com/sunboyy/repogen/internal/spec"
|
||||||
|
@ -66,7 +68,7 @@ func (g findBodyGenerator) generateFindOneBody(querySpec querySpec,
|
||||||
).Build(),
|
).Build(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
codegen.RawStatement("err != nil"),
|
errOccurred,
|
||||||
},
|
},
|
||||||
Statements: []codegen.Statement{
|
Statements: []codegen.Statement{
|
||||||
returnNilErr,
|
returnNilErr,
|
||||||
|
@ -91,10 +93,7 @@ func (g findBodyGenerator) generateFindManyBody(querySpec querySpec,
|
||||||
Call("Find",
|
Call("Find",
|
||||||
codegen.Identifier("arg0"),
|
codegen.Identifier("arg0"),
|
||||||
querySpec.Code(),
|
querySpec.Code(),
|
||||||
codegen.NewChainBuilder("options").
|
g.findManyOptions(sortsCode),
|
||||||
Call("Find").
|
|
||||||
Call("SetSort", sortsCode).
|
|
||||||
Build(),
|
|
||||||
).Build(),
|
).Build(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -119,7 +118,7 @@ func (g findBodyGenerator) generateFindManyBody(querySpec querySpec,
|
||||||
).Build(),
|
).Build(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
codegen.RawStatement("err != nil"),
|
errOccurred,
|
||||||
},
|
},
|
||||||
Statements: []codegen.Statement{
|
Statements: []codegen.Statement{
|
||||||
returnNilErr,
|
returnNilErr,
|
||||||
|
@ -132,6 +131,21 @@ func (g findBodyGenerator) generateFindManyBody(querySpec querySpec,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g findBodyGenerator) findManyOptions(
|
||||||
|
sortsCode codegen.MapStatement) codegen.Statement {
|
||||||
|
|
||||||
|
optionsBuilder := codegen.NewChainBuilder("options").
|
||||||
|
Call("Find").
|
||||||
|
Call("SetSort", sortsCode)
|
||||||
|
if g.operation.Limit > 0 {
|
||||||
|
optionsBuilder = optionsBuilder.Call("SetLimit",
|
||||||
|
codegen.Identifier(strconv.Itoa(g.operation.Limit)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return optionsBuilder.Build()
|
||||||
|
}
|
||||||
|
|
||||||
func (g findBodyGenerator) generateSortMap() (
|
func (g findBodyGenerator) generateSortMap() (
|
||||||
codegen.MapStatement, error) {
|
codegen.MapStatement, error) {
|
||||||
|
|
||||||
|
|
|
@ -825,6 +825,38 @@ func TestGenerateMethod_Find(t *testing.T) {
|
||||||
if err := cursor.All(arg0, &entities); err != nil {
|
if err := cursor.All(arg0, &entities); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return entities, nil`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "find with limit",
|
||||||
|
MethodSpec: spec.MethodSpec{
|
||||||
|
Name: "FindTop5AllOrderByAgeDesc",
|
||||||
|
Params: []code.Param{
|
||||||
|
{Name: "ctx", Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
|
||||||
|
},
|
||||||
|
Returns: []code.Type{
|
||||||
|
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
|
||||||
|
code.TypeError,
|
||||||
|
},
|
||||||
|
Operation: spec.FindOperation{
|
||||||
|
Mode: spec.QueryModeMany,
|
||||||
|
Sorts: []spec.Sort{
|
||||||
|
{FieldReference: spec.FieldReference{ageField}, Ordering: spec.OrderingDescending},
|
||||||
|
},
|
||||||
|
Limit: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ExpectedBody: ` cursor, err := r.collection.Find(arg0, bson.M{
|
||||||
|
}, options.Find().SetSort(bson.M{
|
||||||
|
"age": -1,
|
||||||
|
}).SetLimit(5))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var entities []*UserModel
|
||||||
|
if err := cursor.All(arg0, &entities); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return entities, nil`,
|
return entities, nil`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,9 @@ var (
|
||||||
ErrInvalidParam = errors.New("spec: parameters do not match the query")
|
ErrInvalidParam = errors.New("spec: parameters do not match the query")
|
||||||
ErrInvalidUpdateFields = errors.New("spec: update fields are invalid")
|
ErrInvalidUpdateFields = errors.New("spec: update fields are invalid")
|
||||||
ErrContextParamRequired = errors.New("spec: context parameter is required")
|
ErrContextParamRequired = errors.New("spec: context parameter is required")
|
||||||
|
ErrLimitAmountRequired = errors.New("spec: limit amount is required")
|
||||||
|
ErrLimitNonPositive = errors.New("spec: limit value must be positive")
|
||||||
|
ErrLimitOnFindOne = errors.New("spec: cannot specify limit on find one")
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewUnsupportedReturnError creates unsupportedReturnError
|
// NewUnsupportedReturnError creates unsupportedReturnError
|
||||||
|
|
|
@ -41,6 +41,7 @@ type FindOperation struct {
|
||||||
Mode QueryMode
|
Mode QueryMode
|
||||||
Query QuerySpec
|
Query QuerySpec
|
||||||
Sorts []Sort
|
Sorts []Sort
|
||||||
|
Limit int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns "Find" operation name
|
// Name returns "Find" operation name
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package spec
|
package spec
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/fatih/camelcase"
|
"github.com/fatih/camelcase"
|
||||||
"github.com/sunboyy/repogen/internal/code"
|
"github.com/sunboyy/repogen/internal/code"
|
||||||
)
|
)
|
||||||
|
@ -114,6 +116,14 @@ func (p interfaceMethodParser) parseFindOperation(tokens []string) (Operation, e
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
limit, tokens, err := p.parseFindTop(tokens)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if mode == QueryModeOne && limit != 0 {
|
||||||
|
return nil, ErrLimitOnFindOne
|
||||||
|
}
|
||||||
|
|
||||||
queryTokens, sortTokens := p.splitQueryAndSortTokens(tokens)
|
queryTokens, sortTokens := p.splitQueryAndSortTokens(tokens)
|
||||||
|
|
||||||
querySpec, err := p.parseQuery(queryTokens, 1)
|
querySpec, err := p.parseQuery(queryTokens, 1)
|
||||||
|
@ -138,9 +148,32 @@ func (p interfaceMethodParser) parseFindOperation(tokens []string) (Operation, e
|
||||||
Mode: mode,
|
Mode: mode,
|
||||||
Query: querySpec,
|
Query: querySpec,
|
||||||
Sorts: sorts,
|
Sorts: sorts,
|
||||||
|
Limit: limit,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p interfaceMethodParser) parseFindTop(tokens []string) (int, []string,
|
||||||
|
error) {
|
||||||
|
|
||||||
|
if len(tokens) >= 1 && tokens[0] == "Top" {
|
||||||
|
if len(tokens) < 2 {
|
||||||
|
return 0, nil, ErrLimitAmountRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
limit, err := strconv.Atoi(tokens[1])
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, ErrLimitAmountRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
if limit <= 0 {
|
||||||
|
return 0, nil, ErrLimitNonPositive
|
||||||
|
}
|
||||||
|
return limit, tokens[2:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, tokens, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (p interfaceMethodParser) parseSort(rawTokens []string) ([]Sort, error) {
|
func (p interfaceMethodParser) parseSort(rawTokens []string) ([]Sort, error) {
|
||||||
if len(rawTokens) == 0 {
|
if len(rawTokens) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
|
@ -745,6 +745,30 @@ func TestParseInterfaceMethod_Find(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "FindTopNByArg method",
|
||||||
|
Method: code.Method{
|
||||||
|
Name: "FindTop5ByGenderOrderByAgeDesc",
|
||||||
|
Params: []code.Param{
|
||||||
|
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
|
||||||
|
{Type: code.SimpleType("Gender")},
|
||||||
|
},
|
||||||
|
Returns: []code.Type{
|
||||||
|
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
|
||||||
|
code.TypeError,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ExpectedOperation: spec.FindOperation{
|
||||||
|
Mode: spec.QueryModeMany,
|
||||||
|
Query: spec.QuerySpec{Predicates: []spec.Predicate{
|
||||||
|
{FieldReference: spec.FieldReference{genderField}, Comparator: spec.ComparatorEqual, ParamIndex: 1},
|
||||||
|
}},
|
||||||
|
Sorts: []spec.Sort{
|
||||||
|
{FieldReference: spec.FieldReference{ageField}, Ordering: spec.OrderingDescending},
|
||||||
|
},
|
||||||
|
Limit: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testTable {
|
for _, testCase := range testTable {
|
||||||
|
@ -1603,6 +1627,50 @@ func TestParseInterfaceMethod_Find_Invalid(t *testing.T) {
|
||||||
},
|
},
|
||||||
ExpectedError: spec.NewUnsupportedReturnError(code.TypeInt, 1),
|
ExpectedError: spec.NewUnsupportedReturnError(code.TypeInt, 1),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "find method with Top keyword but no number and query",
|
||||||
|
Method: code.Method{
|
||||||
|
Name: "FindTop",
|
||||||
|
Returns: []code.Type{
|
||||||
|
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
|
||||||
|
code.TypeError,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ExpectedError: spec.ErrLimitAmountRequired,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "find method with Top keyword but no number",
|
||||||
|
Method: code.Method{
|
||||||
|
Name: "FindTopAll",
|
||||||
|
Returns: []code.Type{
|
||||||
|
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
|
||||||
|
code.TypeError,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ExpectedError: spec.ErrLimitAmountRequired,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "find method with TopN keyword where N is not positive",
|
||||||
|
Method: code.Method{
|
||||||
|
Name: "FindTop0All",
|
||||||
|
Returns: []code.Type{
|
||||||
|
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
|
||||||
|
code.TypeError,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ExpectedError: spec.ErrLimitNonPositive,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "find one method with TopN keyword",
|
||||||
|
Method: code.Method{
|
||||||
|
Name: "FindTop5All",
|
||||||
|
Returns: []code.Type{
|
||||||
|
code.PointerType{ContainedType: code.SimpleType("UserModel")},
|
||||||
|
code.TypeError,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ExpectedError: spec.ErrLimitOnFindOne,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "find method without query",
|
Name: "find method without query",
|
||||||
Method: code.Method{
|
Method: code.Method{
|
||||||
|
|
Loading…
Reference in a new issue