diff --git a/README.md b/README.md
index 8bdc515..6e6ddee 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,9 @@
+
+
+
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},
}},
},
},