diff --git a/README.md b/README.md index 8bdc515..6e6ddee 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,9 @@ build status badge + + + Repogen is a code generator for database repository in Golang. (WIP) diff --git a/internal/mongo/generator.go b/internal/mongo/generator.go index 3b9e705..234e5ab 100644 --- a/internal/mongo/generator.go +++ b/internal/mongo/generator.go @@ -132,12 +132,15 @@ func (g mongoRepositoryGenerator) generateFindImplementation(operation spec.Find 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{ EntityType: g.StructModel.Name, - Predicates: predicates, + QuerySpec: querySpec{ + Operator: operation.Query.Operator, + Predicates: predicates, + }, } if operation.Mode == spec.QueryModeOne { diff --git a/internal/mongo/generator_test.go b/internal/mongo/generator_test.go index 87e1486..7f28719 100644 --- a/internal/mongo/generator_test.go +++ b/internal/mongo/generator_test.go @@ -134,6 +134,18 @@ func TestGenerateMongoRepository(t *testing.T) { 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 } + +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") actualCodeLines := strings.Split(code, "\n") diff --git a/internal/mongo/models.go b/internal/mongo/models.go index ca07421..959daba 100644 --- a/internal/mongo/models.go +++ b/internal/mongo/models.go @@ -2,28 +2,56 @@ package mongo import ( "fmt" + "strings" "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 { - Field string - Operator spec.Operator + Field string + Comparator spec.Comparator } func (p predicate) Code(argIndex int) string { - switch p.Operator { - case spec.OperatorEqual: + switch p.Comparator { + case spec.ComparatorEqual: 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) - case spec.OperatorLessThan: + case spec.ComparatorLessThan: 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) - case spec.OperatorGreaterThan: + case spec.ComparatorGreaterThan: 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 "" diff --git a/internal/mongo/templates.go b/internal/mongo/templates.go index e421a61..4f73e02 100644 --- a/internal/mongo/templates.go +++ b/internal/mongo/templates.go @@ -75,7 +75,7 @@ func (data mongoMethodTemplateData) Returns() string { const findOneTemplate = ` var entity {{.EntityType}} 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 { return nil, err } @@ -83,12 +83,12 @@ const findOneTemplate = ` var entity {{.EntityType}} type mongoFindTemplateData struct { EntityType string - Predicates []predicate + QuerySpec querySpec } const findManyTemplate = ` cursor, err := r.collection.Find(ctx, bson.M{ -{{range $index, $field := .Predicates}} {{$field.Code $index}}, -{{end}} }) +{{.QuerySpec.Code}} + }) if err != nil { return nil, err } diff --git a/internal/spec/models.go b/internal/spec/models.go index 15b4696..e0143f1 100644 --- a/internal/spec/models.go +++ b/internal/spec/models.go @@ -41,6 +41,7 @@ type FindOperation struct { // QuerySpec is a set of conditions of querying the database type QuerySpec struct { + Operator Operator Predicates []Predicate } @@ -49,42 +50,51 @@ func (q QuerySpec) NumberOfArguments() int { 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 -// operator constants +// boolean operator types const ( - OperatorEqual Operator = "EQUAL" - OperatorNot Operator = "NOT" - OperatorLessThan Operator = "LESS_THAN" - OperatorLessThanEqual Operator = "LESS_THAN_EQUAL" - OperatorGreaterThan Operator = "GREATER_THAN" - OperatorGreaterThanEqual Operator = "GREATER_THAN_EQUAL" + OperatorAnd Operator = "AND" + OperatorOr Operator = "OR" +) + +// Comparator is a comparison operator of the condition to query the data +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 type Predicate struct { - Field string - Operator Operator + Field string + Comparator Comparator } type predicateToken []string func (t predicateToken) ToPredicate() Predicate { 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" { - 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" { - 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" { - 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" { - 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} } diff --git a/internal/spec/parser.go b/internal/spec/parser.go index ce6afd0..fa83714 100644 --- a/internal/spec/parser.go +++ b/internal/spec/parser.go @@ -127,18 +127,26 @@ func (p repositoryInterfaceParser) parseQuery(tokens []string) (QuerySpec, error tokens = tokens[1:] } - if tokens[0] == "And" { + if tokens[0] == "And" || tokens[0] == "Or" { return QuerySpec{}, errors.New("method name not supported") } + var operator Operator var predicates []Predicate var aggregatedToken predicateToken for _, token := range tokens { - if token != "And" { + if token != "And" && token != "Or" { aggregatedToken = append(aggregatedToken, token) - } else { + } else if token == "And" && operator != OperatorOr { + operator = OperatorAnd predicates = append(predicates, aggregatedToken.ToPredicate()) 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 { @@ -146,5 +154,5 @@ func (p repositoryInterfaceParser) parseQuery(tokens []string) (QuerySpec, error } predicates = append(predicates, aggregatedToken.ToPredicate()) - return QuerySpec{Predicates: predicates}, nil + return QuerySpec{Operator: operator, Predicates: predicates}, nil } diff --git a/internal/spec/parser_test.go b/internal/spec/parser_test.go index 0aeba9a..f365525 100644 --- a/internal/spec/parser_test.go +++ b/internal/spec/parser_test.go @@ -59,7 +59,7 @@ func TestParseRepositoryInterface(t *testing.T) { Operation: spec.FindOperation{ Mode: spec.QueryModeOne, 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{ Mode: spec.QueryModeOne, 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{ Mode: spec.QueryModeMany, 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{ Mode: spec.QueryModeMany, - Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "City", Operator: spec.OperatorEqual}, - {Field: "Gender", Operator: spec.OperatorEqual}, - }}, + Query: spec.QuerySpec{ + Operator: spec.OperatorAnd, + 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{ Mode: spec.QueryModeMany, 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{ Mode: spec.QueryModeMany, 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{ Mode: spec.QueryModeMany, 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{ Mode: spec.QueryModeMany, 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{ Mode: spec.QueryModeMany, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "Age", Operator: spec.OperatorGreaterThanEqual}, + {Field: "Age", Comparator: spec.ComparatorGreaterThanEqual}, }}, }, },