Add push update operator
This commit is contained in:
parent
b3d9bf3f8f
commit
295f457e64
14 changed files with 382 additions and 57 deletions
|
@ -19,9 +19,11 @@ func (err ParsingError) Error() string {
|
|||
case InvalidParamError:
|
||||
return "parameters do not match the query"
|
||||
case InvalidUpdateFieldsError:
|
||||
return "update fields is invalid"
|
||||
return "update fields are invalid"
|
||||
case ContextParamRequiredError:
|
||||
return "context parameter is required"
|
||||
case PushNonArrayError:
|
||||
return "cannot use push operation in a non-array type"
|
||||
}
|
||||
return string(err)
|
||||
}
|
||||
|
@ -33,6 +35,7 @@ const (
|
|||
InvalidParamError ParsingError = "ERROR_INVALID_PARAM"
|
||||
InvalidUpdateFieldsError ParsingError = "ERROR_INVALID_UPDATE_FIELDS"
|
||||
ContextParamRequiredError ParsingError = "ERROR_CONTEXT_PARAM_REQUIRED"
|
||||
PushNonArrayError ParsingError = "ERROR_PUSH_NON_ARRAY"
|
||||
)
|
||||
|
||||
// NewInvalidQueryError creates invalidQueryError
|
||||
|
|
|
@ -48,7 +48,7 @@ func TestError(t *testing.T) {
|
|||
for _, testCase := range testTable {
|
||||
t.Run(testCase.Name, func(t *testing.T) {
|
||||
if testCase.Error.Error() != testCase.ExpectedString {
|
||||
t.Errorf("Expected = %v\nReceived = %v", testCase.ExpectedString, testCase.Error.Error())
|
||||
t.Errorf("Expected = %+v\nReceived = %+v", testCase.ExpectedString, testCase.Error.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ func TestOperationName(t *testing.T) {
|
|||
for _, testCase := range testTable {
|
||||
t.Run(testCase.ExpectedName, func(t *testing.T) {
|
||||
if testCase.Operation.Name() != testCase.ExpectedName {
|
||||
t.Errorf("Expected = %v\nReceived = %v", testCase.ExpectedName, testCase.Operation.Name())
|
||||
t.Errorf("Expected = %+v\nReceived = %+v", testCase.ExpectedName, testCase.Operation.Name())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -49,6 +49,10 @@ var (
|
|||
Name: "Enabled",
|
||||
Type: code.SimpleType("bool"),
|
||||
}
|
||||
consentHistoryField = code.StructField{
|
||||
Name: "ConsentHistory",
|
||||
Type: code.ArrayType{ContainedType: code.SimpleType("ConsentHistoryItem")},
|
||||
}
|
||||
|
||||
firstNameField = code.StructField{
|
||||
Name: "First",
|
||||
|
@ -81,6 +85,7 @@ var (
|
|||
contactField,
|
||||
referrerField,
|
||||
defaultPaymentField,
|
||||
consentHistoryField,
|
||||
enabledField,
|
||||
},
|
||||
}
|
||||
|
@ -149,7 +154,7 @@ func TestParseInterfaceMethod_Insert(t *testing.T) {
|
|||
Operation: testCase.ExpectedOperation,
|
||||
}
|
||||
if !reflect.DeepEqual(actualSpec, expectedOutput) {
|
||||
t.Errorf("Expected = %v\nReceived = %v", expectedOutput, actualSpec)
|
||||
t.Errorf("Expected = %+v\nReceived = %+v", expectedOutput, actualSpec)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -654,7 +659,7 @@ func TestParseInterfaceMethod_Find(t *testing.T) {
|
|||
Operation: testCase.ExpectedOperation,
|
||||
}
|
||||
if !reflect.DeepEqual(actualSpec, expectedOutput) {
|
||||
t.Errorf("Expected = %v\nReceived = %v", expectedOutput, actualSpec)
|
||||
t.Errorf("Expected = %+v\nReceived = %+v", expectedOutput, actualSpec)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -700,7 +705,7 @@ func TestParseInterfaceMethod_Update(t *testing.T) {
|
|||
},
|
||||
ExpectedOperation: spec.UpdateOperation{
|
||||
Update: spec.UpdateFields{
|
||||
{FieldReference: spec.FieldReference{genderField}, ParamIndex: 1},
|
||||
{FieldReference: spec.FieldReference{genderField}, ParamIndex: 1, Operator: spec.UpdateOperatorSet},
|
||||
},
|
||||
Mode: spec.QueryModeOne,
|
||||
Query: spec.QuerySpec{Predicates: []spec.Predicate{
|
||||
|
@ -724,7 +729,7 @@ func TestParseInterfaceMethod_Update(t *testing.T) {
|
|||
},
|
||||
ExpectedOperation: spec.UpdateOperation{
|
||||
Update: spec.UpdateFields{
|
||||
{FieldReference: spec.FieldReference{genderField}, ParamIndex: 1},
|
||||
{FieldReference: spec.FieldReference{genderField}, ParamIndex: 1, Operator: spec.UpdateOperatorSet},
|
||||
},
|
||||
Mode: spec.QueryModeMany,
|
||||
Query: spec.QuerySpec{Predicates: []spec.Predicate{
|
||||
|
@ -748,7 +753,7 @@ func TestParseInterfaceMethod_Update(t *testing.T) {
|
|||
},
|
||||
ExpectedOperation: spec.UpdateOperation{
|
||||
Update: spec.UpdateFields{
|
||||
{FieldReference: spec.FieldReference{nameField, firstNameField}, ParamIndex: 1},
|
||||
{FieldReference: spec.FieldReference{nameField, firstNameField}, ParamIndex: 1, Operator: spec.UpdateOperatorSet},
|
||||
},
|
||||
Mode: spec.QueryModeOne,
|
||||
Query: spec.QuerySpec{Predicates: []spec.Predicate{
|
||||
|
@ -773,8 +778,58 @@ func TestParseInterfaceMethod_Update(t *testing.T) {
|
|||
},
|
||||
ExpectedOperation: spec.UpdateOperation{
|
||||
Update: spec.UpdateFields{
|
||||
{FieldReference: spec.FieldReference{genderField}, ParamIndex: 1},
|
||||
{FieldReference: spec.FieldReference{cityField}, ParamIndex: 2},
|
||||
{FieldReference: spec.FieldReference{genderField}, ParamIndex: 1, Operator: spec.UpdateOperatorSet},
|
||||
{FieldReference: spec.FieldReference{cityField}, ParamIndex: 2, Operator: spec.UpdateOperatorSet},
|
||||
},
|
||||
Mode: spec.QueryModeMany,
|
||||
Query: spec.QuerySpec{Predicates: []spec.Predicate{
|
||||
{FieldReference: spec.FieldReference{idField}, Comparator: spec.ComparatorEqual, ParamIndex: 3},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "UpdateArgPushByArg method",
|
||||
Method: code.Method{
|
||||
Name: "UpdateConsentHistoryPushByID",
|
||||
Params: []code.Param{
|
||||
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
|
||||
{Type: code.SimpleType("ConsentHistoryItem")},
|
||||
{Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}},
|
||||
},
|
||||
Returns: []code.Type{
|
||||
code.SimpleType("int"),
|
||||
code.SimpleType("error"),
|
||||
},
|
||||
},
|
||||
ExpectedOperation: spec.UpdateOperation{
|
||||
Update: spec.UpdateFields{
|
||||
{FieldReference: spec.FieldReference{consentHistoryField}, ParamIndex: 1, Operator: spec.UpdateOperatorPush},
|
||||
},
|
||||
Mode: spec.QueryModeMany,
|
||||
Query: spec.QuerySpec{Predicates: []spec.Predicate{
|
||||
{FieldReference: spec.FieldReference{idField}, Comparator: spec.ComparatorEqual, ParamIndex: 2},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "UpdateArgAndArgPushByArg method",
|
||||
Method: code.Method{
|
||||
Name: "UpdateEnabledAndConsentHistoryPushByID",
|
||||
Params: []code.Param{
|
||||
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
|
||||
{Type: code.SimpleType("bool")},
|
||||
{Type: code.SimpleType("ConsentHistoryItem")},
|
||||
{Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}},
|
||||
},
|
||||
Returns: []code.Type{
|
||||
code.SimpleType("int"),
|
||||
code.SimpleType("error"),
|
||||
},
|
||||
},
|
||||
ExpectedOperation: spec.UpdateOperation{
|
||||
Update: spec.UpdateFields{
|
||||
{FieldReference: spec.FieldReference{enabledField}, ParamIndex: 1, Operator: spec.UpdateOperatorSet},
|
||||
{FieldReference: spec.FieldReference{consentHistoryField}, ParamIndex: 2, Operator: spec.UpdateOperatorPush},
|
||||
},
|
||||
Mode: spec.QueryModeMany,
|
||||
Query: spec.QuerySpec{Predicates: []spec.Predicate{
|
||||
|
@ -798,7 +853,7 @@ func TestParseInterfaceMethod_Update(t *testing.T) {
|
|||
Operation: testCase.ExpectedOperation,
|
||||
}
|
||||
if !reflect.DeepEqual(actualSpec, expectedOutput) {
|
||||
t.Errorf("Expected = %v\nReceived = %v", expectedOutput, actualSpec)
|
||||
t.Errorf("Expected = %+v\nReceived = %+v", expectedOutput, actualSpec)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1089,7 +1144,7 @@ func TestParseInterfaceMethod_Delete(t *testing.T) {
|
|||
Operation: testCase.ExpectedOperation,
|
||||
}
|
||||
if !reflect.DeepEqual(actualSpec, expectedOutput) {
|
||||
t.Errorf("Expected = %v\nReceived = %v", expectedOutput, actualSpec)
|
||||
t.Errorf("Expected = %+v\nReceived = %+v", expectedOutput, actualSpec)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1150,7 +1205,7 @@ func TestParseInterfaceMethod_Count(t *testing.T) {
|
|||
Operation: testCase.ExpectedOperation,
|
||||
}
|
||||
if !reflect.DeepEqual(actualSpec, expectedOutput) {
|
||||
t.Errorf("Expected = %v\nReceived = %v", expectedOutput, actualSpec)
|
||||
t.Errorf("Expected = %+v\nReceived = %+v", expectedOutput, actualSpec)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1169,7 +1224,7 @@ func TestParseInterfaceMethod_Invalid(t *testing.T) {
|
|||
|
||||
expectedError := spec.NewUnknownOperationError("Search")
|
||||
if err != expectedError {
|
||||
t.Errorf("\nExpected = %v\nReceived = %v", expectedError, err)
|
||||
t.Errorf("\nExpected = %+v\nReceived = %+v", expectedError, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1275,7 +1330,7 @@ func TestParseInterfaceMethod_Insert_Invalid(t *testing.T) {
|
|||
_, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method)
|
||||
|
||||
if err != testCase.ExpectedError {
|
||||
t.Errorf("\nExpected = %v\nReceived = %v", testCase.ExpectedError, err)
|
||||
t.Errorf("\nExpected = %+v\nReceived = %+v", testCase.ExpectedError, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1577,7 +1632,7 @@ func TestParseInterfaceMethod_Find_Invalid(t *testing.T) {
|
|||
_, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method)
|
||||
|
||||
if err.Error() != testCase.ExpectedError.Error() {
|
||||
t.Errorf("\nExpected = %v\nReceived = %v", testCase.ExpectedError.Error(), err.Error())
|
||||
t.Errorf("\nExpected = %+v\nReceived = %+v", testCase.ExpectedError.Error(), err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1647,6 +1702,22 @@ func TestParseInterfaceMethod_Update_Invalid(t *testing.T) {
|
|||
},
|
||||
ExpectedError: spec.InvalidUpdateFieldsError,
|
||||
},
|
||||
{
|
||||
Name: "push operator in non-array field",
|
||||
Method: code.Method{
|
||||
Name: "UpdateGenderPushByID",
|
||||
Params: []code.Param{
|
||||
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
|
||||
{Type: code.SimpleType("Gender")},
|
||||
{Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}},
|
||||
},
|
||||
Returns: []code.Type{
|
||||
code.SimpleType("bool"),
|
||||
code.SimpleType("error"),
|
||||
},
|
||||
},
|
||||
ExpectedError: spec.PushNonArrayError,
|
||||
},
|
||||
{
|
||||
Name: "update method without query",
|
||||
Method: code.Method{
|
||||
|
@ -1677,6 +1748,37 @@ func TestParseInterfaceMethod_Update_Invalid(t *testing.T) {
|
|||
},
|
||||
ExpectedError: spec.NewInvalidQueryError([]string{"ID", "And", "Username", "Or", "Gender"}),
|
||||
},
|
||||
{
|
||||
Name: "parameters for push operator is not array's contained type",
|
||||
Method: code.Method{
|
||||
Name: "UpdateConsentHistoryPushByID",
|
||||
Params: []code.Param{
|
||||
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
|
||||
{Type: code.ArrayType{ContainedType: code.SimpleType("ConsentHistoryItem")}},
|
||||
{Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}},
|
||||
},
|
||||
Returns: []code.Type{
|
||||
code.SimpleType("int"),
|
||||
code.SimpleType("error"),
|
||||
},
|
||||
},
|
||||
ExpectedError: spec.InvalidUpdateFieldsError,
|
||||
},
|
||||
{
|
||||
Name: "insufficient function parameters",
|
||||
Method: code.Method{
|
||||
Name: "UpdateEnabledAll",
|
||||
Params: []code.Param{
|
||||
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
|
||||
// {Type: code.SimpleType("Enabled")},
|
||||
},
|
||||
Returns: []code.Type{
|
||||
code.SimpleType("int"),
|
||||
code.SimpleType("error"),
|
||||
},
|
||||
},
|
||||
ExpectedError: spec.InvalidUpdateFieldsError,
|
||||
},
|
||||
{
|
||||
Name: "update model with invalid parameter",
|
||||
Method: code.Method{
|
||||
|
@ -1762,7 +1864,7 @@ func TestParseInterfaceMethod_Update_Invalid(t *testing.T) {
|
|||
_, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method)
|
||||
|
||||
if err != testCase.ExpectedError {
|
||||
t.Errorf("\nExpected = %v\nReceived = %v", testCase.ExpectedError, err)
|
||||
t.Errorf("\nExpected = %+v\nReceived = %+v", testCase.ExpectedError, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1941,7 +2043,7 @@ func TestParseInterfaceMethod_Delete_Invalid(t *testing.T) {
|
|||
_, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method)
|
||||
|
||||
if err != testCase.ExpectedError {
|
||||
t.Errorf("\nExpected = %v\nReceived = %v", testCase.ExpectedError, err)
|
||||
t.Errorf("\nExpected = %+v\nReceived = %+v", testCase.ExpectedError, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -2072,7 +2174,7 @@ func TestParseInterfaceMethod_Count_Invalid(t *testing.T) {
|
|||
_, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method)
|
||||
|
||||
if err != testCase.ExpectedError {
|
||||
t.Errorf("\nExpected = %v\nReceived = %v", testCase.ExpectedError, err)
|
||||
t.Errorf("\nExpected = %+v\nReceived = %+v", testCase.ExpectedError, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -51,6 +51,35 @@ func (u UpdateFields) NumberOfArguments() int {
|
|||
type UpdateField struct {
|
||||
FieldReference FieldReference
|
||||
ParamIndex int
|
||||
Operator UpdateOperator
|
||||
}
|
||||
|
||||
// UpdateOperator is a custom type that declares update operator to be used in an update operation
|
||||
type UpdateOperator string
|
||||
|
||||
// UpdateOperator constants
|
||||
const (
|
||||
UpdateOperatorSet UpdateOperator = "SET"
|
||||
UpdateOperatorPush UpdateOperator = "PUSH"
|
||||
)
|
||||
|
||||
// NumberOfArguments returns number of arguments required to perform an update operation
|
||||
func (o UpdateOperator) NumberOfArguments() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
// ArgumentType returns type that is required for function parameter
|
||||
func (o UpdateOperator) ArgumentType(fieldType code.Type) (code.Type, error) {
|
||||
switch o {
|
||||
case UpdateOperatorPush:
|
||||
arrayType, ok := fieldType.(code.ArrayType)
|
||||
if !ok {
|
||||
return nil, PushNonArrayError
|
||||
}
|
||||
return arrayType.ContainedType, nil
|
||||
default:
|
||||
return fieldType, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (p interfaceMethodParser) parseUpdateOperation(tokens []string) (Operation, error) {
|
||||
|
@ -104,24 +133,51 @@ func (p interfaceMethodParser) parseUpdate(tokens []string) (Update, error) {
|
|||
|
||||
paramIndex := 1
|
||||
for _, updateFieldToken := range updateFieldTokens {
|
||||
updateFieldReference, ok := p.fieldResolver.ResolveStructField(p.StructModel, updateFieldToken)
|
||||
if !ok {
|
||||
return nil, NewStructFieldNotFoundError(updateFieldToken)
|
||||
updateField, err := p.parseUpdateField(updateFieldToken, paramIndex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updateFields = append(updateFields, UpdateField{
|
||||
FieldReference: updateFieldReference,
|
||||
ParamIndex: paramIndex,
|
||||
})
|
||||
paramIndex++
|
||||
updateFields = append(updateFields, updateField)
|
||||
paramIndex += updateField.Operator.NumberOfArguments()
|
||||
}
|
||||
|
||||
for _, field := range updateFields {
|
||||
if len(p.Method.Params) <= field.ParamIndex ||
|
||||
field.FieldReference.ReferencedField().Type != p.Method.Params[field.ParamIndex].Type {
|
||||
if len(p.Method.Params) < field.ParamIndex+field.Operator.NumberOfArguments() {
|
||||
return nil, InvalidUpdateFieldsError
|
||||
}
|
||||
|
||||
requiredType, err := field.Operator.ArgumentType(field.FieldReference.ReferencedField().Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := 0; i < field.Operator.NumberOfArguments(); i++ {
|
||||
if requiredType != p.Method.Params[field.ParamIndex+i].Type {
|
||||
return nil, InvalidUpdateFieldsError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return updateFields, nil
|
||||
}
|
||||
|
||||
func (p interfaceMethodParser) parseUpdateField(t []string, paramIndex int) (UpdateField, error) {
|
||||
if len(t) > 1 && t[len(t)-1] == "Push" {
|
||||
return p.createUpdateField(t[:len(t)-1], UpdateOperatorPush, paramIndex)
|
||||
}
|
||||
return p.createUpdateField(t, UpdateOperatorSet, paramIndex)
|
||||
}
|
||||
|
||||
func (p interfaceMethodParser) createUpdateField(t []string, operator UpdateOperator, paramIndex int) (UpdateField, error) {
|
||||
fieldReference, ok := p.fieldResolver.ResolveStructField(p.StructModel, t)
|
||||
if !ok {
|
||||
return UpdateField{}, NewStructFieldNotFoundError(t)
|
||||
}
|
||||
|
||||
return UpdateField{
|
||||
FieldReference: fieldReference,
|
||||
ParamIndex: paramIndex,
|
||||
Operator: operator,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ func TestUpdateTypeName(t *testing.T) {
|
|||
for _, testCase := range testTable {
|
||||
t.Run(testCase.ExpectedName, func(t *testing.T) {
|
||||
if testCase.Update.Name() != testCase.ExpectedName {
|
||||
t.Errorf("Expected = %v\nReceived = %v", testCase.ExpectedName, testCase.Update.Name())
|
||||
t.Errorf("Expected = %+v\nReceived = %+v", testCase.ExpectedName, testCase.Update.Name())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue