Add Or operator

This commit is contained in:
sunboyy 2021-01-19 19:35:54 +07:00
parent bc878c778c
commit 153deb8f7d
8 changed files with 178 additions and 47 deletions

View file

@ -3,6 +3,9 @@
<a href="https://github.com/sunboyy/repogen/actions?query=workflow%3Abuild"> <a href="https://github.com/sunboyy/repogen/actions?query=workflow%3Abuild">
<img src="https://github.com/sunboyy/repogen/workflows/build/badge.svg" alt="build status badge"> <img src="https://github.com/sunboyy/repogen/workflows/build/badge.svg" alt="build status badge">
</a> </a>
<a href="https://codecov.io/gh/sunboyy/repogen">
<img src="https://codecov.io/gh/sunboyy/repogen/branch/main/graph/badge.svg?token=9BD5Y8X7NO"/>
</a>
Repogen is a code generator for database repository in Golang. (WIP) Repogen is a code generator for database repository in Golang. (WIP)

View file

@ -132,12 +132,15 @@ func (g mongoRepositoryGenerator) generateFindImplementation(operation spec.Find
return "", fmt.Errorf("struct field %s does not have bson tag", predicateSpec.Field) return "", fmt.Errorf("struct field %s does not have bson tag", predicateSpec.Field)
} }
predicates = append(predicates, predicate{Field: bsonTag[0], Operator: predicateSpec.Operator}) predicates = append(predicates, predicate{Field: bsonTag[0], Comparator: predicateSpec.Comparator})
} }
tmplData := mongoFindTemplateData{ tmplData := mongoFindTemplateData{
EntityType: g.StructModel.Name, EntityType: g.StructModel.Name,
QuerySpec: querySpec{
Operator: operation.Query.Operator,
Predicates: predicates, Predicates: predicates,
},
} }
if operation.Mode == spec.QueryModeOne { if operation.Mode == spec.QueryModeOne {

View file

@ -134,6 +134,18 @@ func TestGenerateMongoRepository(t *testing.T) {
code.SimpleType("error"), code.SimpleType("error"),
}, },
}, },
{
Name: "FindByGenderOrAgeLessThan",
Params: []code.Param{
{Name: "ctx", Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Name: "gender", Type: code.SimpleType("Gender")},
{Name: "age", Type: code.SimpleType("int")},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.SimpleType("error"),
},
},
}, },
} }
@ -277,6 +289,23 @@ func (r *UserRepositoryMongo) FindByAgeGreaterThanEqual(ctx context.Context, arg
} }
return entities, nil return entities, nil
} }
func (r *UserRepositoryMongo) FindByGenderOrAgeLessThan(ctx context.Context, arg0 Gender, arg1 int) ([]*UserModel, error) {
cursor, err := r.collection.Find(ctx, bson.M{
"$or": []bson.M{
{"gender": arg0},
{"age": bson.M{"$lt": arg1}},
},
})
if err != nil {
return nil, err
}
var entities []*UserModel
if err := cursor.All(ctx, &entities); err != nil {
return nil, err
}
return entities, nil
}
` `
expectedCodeLines := strings.Split(expectedCode, "\n") expectedCodeLines := strings.Split(expectedCode, "\n")
actualCodeLines := strings.Split(code, "\n") actualCodeLines := strings.Split(code, "\n")

View file

@ -2,28 +2,56 @@ package mongo
import ( import (
"fmt" "fmt"
"strings"
"github.com/sunboyy/repogen/internal/spec" "github.com/sunboyy/repogen/internal/spec"
) )
type querySpec struct {
Operator spec.Operator
Predicates []predicate
}
func (q querySpec) Code() string {
var predicateCodes []string
for i, predicate := range q.Predicates {
predicateCodes = append(predicateCodes, predicate.Code(i))
}
var lines []string
switch q.Operator {
case spec.OperatorOr:
lines = append(lines, ` "$or": []bson.M{`)
for _, predicateCode := range predicateCodes {
lines = append(lines, fmt.Sprintf(` {%s},`, predicateCode))
}
lines = append(lines, ` },`)
default:
for _, predicateCode := range predicateCodes {
lines = append(lines, fmt.Sprintf(` %s,`, predicateCode))
}
}
return strings.Join(lines, "\n")
}
type predicate struct { type predicate struct {
Field string Field string
Operator spec.Operator Comparator spec.Comparator
} }
func (p predicate) Code(argIndex int) string { func (p predicate) Code(argIndex int) string {
switch p.Operator { switch p.Comparator {
case spec.OperatorEqual: case spec.ComparatorEqual:
return fmt.Sprintf(`"%s": arg%d`, p.Field, argIndex) return fmt.Sprintf(`"%s": arg%d`, p.Field, argIndex)
case spec.OperatorNot: case spec.ComparatorNot:
return fmt.Sprintf(`"%s": bson.M{"$ne": arg%d}`, p.Field, argIndex) return fmt.Sprintf(`"%s": bson.M{"$ne": arg%d}`, p.Field, argIndex)
case spec.OperatorLessThan: case spec.ComparatorLessThan:
return fmt.Sprintf(`"%s": bson.M{"$lt": arg%d}`, p.Field, argIndex) return fmt.Sprintf(`"%s": bson.M{"$lt": arg%d}`, p.Field, argIndex)
case spec.OperatorLessThanEqual: case spec.ComparatorLessThanEqual:
return fmt.Sprintf(`"%s": bson.M{"$lte": arg%d}`, p.Field, argIndex) return fmt.Sprintf(`"%s": bson.M{"$lte": arg%d}`, p.Field, argIndex)
case spec.OperatorGreaterThan: case spec.ComparatorGreaterThan:
return fmt.Sprintf(`"%s": bson.M{"$gt": arg%d}`, p.Field, argIndex) return fmt.Sprintf(`"%s": bson.M{"$gt": arg%d}`, p.Field, argIndex)
case spec.OperatorGreaterThanEqual: case spec.ComparatorGreaterThanEqual:
return fmt.Sprintf(`"%s": bson.M{"$gte": arg%d}`, p.Field, argIndex) return fmt.Sprintf(`"%s": bson.M{"$gte": arg%d}`, p.Field, argIndex)
} }
return "" return ""

View file

@ -75,7 +75,7 @@ func (data mongoMethodTemplateData) Returns() string {
const findOneTemplate = ` var entity {{.EntityType}} const findOneTemplate = ` var entity {{.EntityType}}
if err := r.collection.FindOne(ctx, bson.M{ if err := r.collection.FindOne(ctx, bson.M{
{{range $index, $field := .Predicates}} {{$field.Code $index}}, {{range $index, $field := .QuerySpec.Predicates}} {{$field.Code $index}},
{{end}} }).Decode(&entity); err != nil { {{end}} }).Decode(&entity); err != nil {
return nil, err return nil, err
} }
@ -83,12 +83,12 @@ const findOneTemplate = ` var entity {{.EntityType}}
type mongoFindTemplateData struct { type mongoFindTemplateData struct {
EntityType string EntityType string
Predicates []predicate QuerySpec querySpec
} }
const findManyTemplate = ` cursor, err := r.collection.Find(ctx, bson.M{ const findManyTemplate = ` cursor, err := r.collection.Find(ctx, bson.M{
{{range $index, $field := .Predicates}} {{$field.Code $index}}, {{.QuerySpec.Code}}
{{end}} }) })
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -41,6 +41,7 @@ type FindOperation struct {
// QuerySpec is a set of conditions of querying the database // QuerySpec is a set of conditions of querying the database
type QuerySpec struct { type QuerySpec struct {
Operator Operator
Predicates []Predicate Predicates []Predicate
} }
@ -49,42 +50,51 @@ func (q QuerySpec) NumberOfArguments() int {
return len(q.Predicates) return len(q.Predicates)
} }
// Operator is an operator of the condition to query the data // Operator is a boolean operator for merging conditions
type Operator string type Operator string
// operator constants // boolean operator types
const ( const (
OperatorEqual Operator = "EQUAL" OperatorAnd Operator = "AND"
OperatorNot Operator = "NOT" OperatorOr Operator = "OR"
OperatorLessThan Operator = "LESS_THAN" )
OperatorLessThanEqual Operator = "LESS_THAN_EQUAL"
OperatorGreaterThan Operator = "GREATER_THAN" // Comparator is a comparison operator of the condition to query the data
OperatorGreaterThanEqual Operator = "GREATER_THAN_EQUAL" type Comparator string
// comparator types
const (
ComparatorNot Comparator = "NOT"
ComparatorEqual Comparator = "EQUAL"
ComparatorLessThan Comparator = "LESS_THAN"
ComparatorLessThanEqual Comparator = "LESS_THAN_EQUAL"
ComparatorGreaterThan Comparator = "GREATER_THAN"
ComparatorGreaterThanEqual Comparator = "GREATER_THAN_EQUAL"
) )
// Predicate is a criteria for querying a field // Predicate is a criteria for querying a field
type Predicate struct { type Predicate struct {
Field string Field string
Operator Operator Comparator Comparator
} }
type predicateToken []string type predicateToken []string
func (t predicateToken) ToPredicate() Predicate { func (t predicateToken) ToPredicate() Predicate {
if len(t) > 1 && t[len(t)-1] == "Not" { if len(t) > 1 && t[len(t)-1] == "Not" {
return Predicate{Field: strings.Join(t[:len(t)-1], ""), Operator: OperatorNot} return Predicate{Field: strings.Join(t[:len(t)-1], ""), Comparator: ComparatorNot}
} }
if len(t) > 2 && t[len(t)-2] == "Less" && t[len(t)-1] == "Than" { if len(t) > 2 && t[len(t)-2] == "Less" && t[len(t)-1] == "Than" {
return Predicate{Field: strings.Join(t[:len(t)-2], ""), Operator: OperatorLessThan} return Predicate{Field: strings.Join(t[:len(t)-2], ""), Comparator: ComparatorLessThan}
} }
if len(t) > 3 && t[len(t)-3] == "Less" && t[len(t)-2] == "Than" && t[len(t)-1] == "Equal" { if len(t) > 3 && t[len(t)-3] == "Less" && t[len(t)-2] == "Than" && t[len(t)-1] == "Equal" {
return Predicate{Field: strings.Join(t[:len(t)-3], ""), Operator: OperatorLessThanEqual} return Predicate{Field: strings.Join(t[:len(t)-3], ""), Comparator: ComparatorLessThanEqual}
} }
if len(t) > 2 && t[len(t)-2] == "Greater" && t[len(t)-1] == "Than" { if len(t) > 2 && t[len(t)-2] == "Greater" && t[len(t)-1] == "Than" {
return Predicate{Field: strings.Join(t[:len(t)-2], ""), Operator: OperatorGreaterThan} return Predicate{Field: strings.Join(t[:len(t)-2], ""), Comparator: ComparatorGreaterThan}
} }
if len(t) > 3 && t[len(t)-3] == "Greater" && t[len(t)-2] == "Than" && t[len(t)-1] == "Equal" { if len(t) > 3 && t[len(t)-3] == "Greater" && t[len(t)-2] == "Than" && t[len(t)-1] == "Equal" {
return Predicate{Field: strings.Join(t[:len(t)-3], ""), Operator: OperatorGreaterThanEqual} return Predicate{Field: strings.Join(t[:len(t)-3], ""), Comparator: ComparatorGreaterThanEqual}
} }
return Predicate{Field: strings.Join(t, ""), Operator: OperatorEqual} return Predicate{Field: strings.Join(t, ""), Comparator: ComparatorEqual}
} }

View file

@ -127,18 +127,26 @@ func (p repositoryInterfaceParser) parseQuery(tokens []string) (QuerySpec, error
tokens = tokens[1:] tokens = tokens[1:]
} }
if tokens[0] == "And" { if tokens[0] == "And" || tokens[0] == "Or" {
return QuerySpec{}, errors.New("method name not supported") return QuerySpec{}, errors.New("method name not supported")
} }
var operator Operator
var predicates []Predicate var predicates []Predicate
var aggregatedToken predicateToken var aggregatedToken predicateToken
for _, token := range tokens { for _, token := range tokens {
if token != "And" { if token != "And" && token != "Or" {
aggregatedToken = append(aggregatedToken, token) aggregatedToken = append(aggregatedToken, token)
} else { } else if token == "And" && operator != OperatorOr {
operator = OperatorAnd
predicates = append(predicates, aggregatedToken.ToPredicate()) predicates = append(predicates, aggregatedToken.ToPredicate())
aggregatedToken = predicateToken{} aggregatedToken = predicateToken{}
} else if token == "Or" && operator != OperatorAnd {
operator = OperatorOr
predicates = append(predicates, aggregatedToken.ToPredicate())
aggregatedToken = predicateToken{}
} else {
return QuerySpec{}, errors.New("method name contains ambiguous query")
} }
} }
if len(aggregatedToken) == 0 { if len(aggregatedToken) == 0 {
@ -146,5 +154,5 @@ func (p repositoryInterfaceParser) parseQuery(tokens []string) (QuerySpec, error
} }
predicates = append(predicates, aggregatedToken.ToPredicate()) predicates = append(predicates, aggregatedToken.ToPredicate())
return QuerySpec{Predicates: predicates}, nil return QuerySpec{Operator: operator, Predicates: predicates}, nil
} }

View file

@ -59,7 +59,7 @@ func TestParseRepositoryInterface(t *testing.T) {
Operation: spec.FindOperation{ Operation: spec.FindOperation{
Mode: spec.QueryModeOne, Mode: spec.QueryModeOne,
Query: spec.QuerySpec{Predicates: []spec.Predicate{ Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "ID", Operator: spec.OperatorEqual}, {Field: "ID", Comparator: spec.ComparatorEqual},
}}, }},
}, },
}, },
@ -100,7 +100,7 @@ func TestParseRepositoryInterface(t *testing.T) {
Operation: spec.FindOperation{ Operation: spec.FindOperation{
Mode: spec.QueryModeOne, Mode: spec.QueryModeOne,
Query: spec.QuerySpec{Predicates: []spec.Predicate{ Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "PhoneNumber", Operator: spec.OperatorEqual}, {Field: "PhoneNumber", Comparator: spec.ComparatorEqual},
}}, }},
}, },
}, },
@ -141,7 +141,7 @@ func TestParseRepositoryInterface(t *testing.T) {
Operation: spec.FindOperation{ Operation: spec.FindOperation{
Mode: spec.QueryModeMany, Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{ Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "City", Operator: spec.OperatorEqual}, {Field: "City", Comparator: spec.ComparatorEqual},
}}, }},
}, },
}, },
@ -219,10 +219,60 @@ func TestParseRepositoryInterface(t *testing.T) {
}, },
Operation: spec.FindOperation{ Operation: spec.FindOperation{
Mode: spec.QueryModeMany, Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{ Query: spec.QuerySpec{
{Field: "City", Operator: spec.OperatorEqual}, Operator: spec.OperatorAnd,
{Field: "Gender", Operator: spec.OperatorEqual}, Predicates: []spec.Predicate{
}}, {Field: "City", Comparator: spec.ComparatorEqual},
{Field: "Gender", Comparator: spec.ComparatorEqual},
},
},
},
},
},
},
},
{
Name: "FindByArgOrArg method",
Interface: code.Interface{
Name: "UserRepository",
Methods: []code.Method{
{
Name: "FindByCityOrGender",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("string")},
{Type: code.SimpleType("Gender")},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.SimpleType("error"),
},
},
},
},
ExpectedOutput: spec.RepositorySpec{
InterfaceName: "UserRepository",
Methods: []spec.MethodSpec{
{
Name: "FindByCityOrGender",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("string")},
{Type: code.SimpleType("Gender")},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.SimpleType("error"),
},
Operation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{
Operator: spec.OperatorOr,
Predicates: []spec.Predicate{
{Field: "City", Comparator: spec.ComparatorEqual},
{Field: "Gender", Comparator: spec.ComparatorEqual},
},
},
}, },
}, },
}, },
@ -262,7 +312,7 @@ func TestParseRepositoryInterface(t *testing.T) {
Operation: spec.FindOperation{ Operation: spec.FindOperation{
Mode: spec.QueryModeMany, Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{ Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "City", Operator: spec.OperatorNot}, {Field: "City", Comparator: spec.ComparatorNot},
}}, }},
}, },
}, },
@ -303,7 +353,7 @@ func TestParseRepositoryInterface(t *testing.T) {
Operation: spec.FindOperation{ Operation: spec.FindOperation{
Mode: spec.QueryModeMany, Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{ Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "Age", Operator: spec.OperatorLessThan}, {Field: "Age", Comparator: spec.ComparatorLessThan},
}}, }},
}, },
}, },
@ -344,7 +394,7 @@ func TestParseRepositoryInterface(t *testing.T) {
Operation: spec.FindOperation{ Operation: spec.FindOperation{
Mode: spec.QueryModeMany, Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{ Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "Age", Operator: spec.OperatorLessThanEqual}, {Field: "Age", Comparator: spec.ComparatorLessThanEqual},
}}, }},
}, },
}, },
@ -385,7 +435,7 @@ func TestParseRepositoryInterface(t *testing.T) {
Operation: spec.FindOperation{ Operation: spec.FindOperation{
Mode: spec.QueryModeMany, Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{ Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "Age", Operator: spec.OperatorGreaterThan}, {Field: "Age", Comparator: spec.ComparatorGreaterThan},
}}, }},
}, },
}, },
@ -426,7 +476,7 @@ func TestParseRepositoryInterface(t *testing.T) {
Operation: spec.FindOperation{ Operation: spec.FindOperation{
Mode: spec.QueryModeMany, Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{ Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "Age", Operator: spec.OperatorGreaterThanEqual}, {Field: "Age", Comparator: spec.ComparatorGreaterThanEqual},
}}, }},
}, },
}, },