-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Separate idea of Expression from Operand #1476
Conversation
// TODO consider AST instead of string in the future | ||
switch expr.Expression[i+1] { | ||
case 'p': | ||
if index.name >= len(en.names) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about putting each of these cases in their own functions?
for _, expr := range list { | ||
for alias, name := range expr.Names { | ||
if ret.Names == nil { | ||
ret.Names = make(map[string]*string) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor, but this could be ret.Names = map[string]*string{}
// ExpressionAttributeValues: factory.Values(), | ||
// TableName: aws.String("SomeTable"), | ||
// } | ||
func (factory Factory) Projection() *string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest instead of using method for each expression type use a single getter taking the expression type as input.
// expression, err := condition.BuildExpression() // Used to make an Expression | ||
func BeginsWith(p PathBuilder, s string) ConditionBuilder { | ||
// factoryBuilder := Condition(condition) // Used to make an FactoryBuilder | ||
func BeginsWith(nameBuilder NameBuilder, substr string) ConditionBuilder { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggest substr
be prefix
type OperandBuilder interface { | ||
BuildOperand() (ExprNode, error) | ||
BuildNode() (ExprNode, error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest this be a BuildOperand
which returns a Operand
type which could be something like the following.
type Operand struct {
Name string
Value dynamodb.AttributeValue
Expression string
}
This will allow you to make ExprNode
private,
// BuildTree() method will only be called recursively by the functions | ||
// BuildFactory and buildChildTrees. This function should not be called by the | ||
// users. | ||
func (conditionBuilder ConditionBuilder) BuildTree() (ExprNode, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be private since TreeBuilder
can be a private internal interface.
// ExpressionAttributeValues: factory.Values(), | ||
// TableName: aws.String("SomeTable"), | ||
// } | ||
type FactoryBuilder struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest instead of a factor builder being constructed by things like Condition
switch it around and define an ExpressionBuilder
which as helper methods to add Condition
, Update
ect.
type Builder struct {}
func NewBuilder() Builder {}
func (e Builder) WithCondition(builder ConditionBuilder) ExpressionBuilder {}
func (e Builder) WithUpdate(builder UpdateBuilder) ExpressionBuilder {}
...
func (e Builder) Build() Expression {}
type Expression struct {}
func (e Expression) Names() map[string]*string {}
func (e Expression) Values() map[string]*dynamodb.AttributeValue {}
func (e Expression) Get(expType Type) *string {}
// or
func (e Expression) Condition() *string {}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Few general comments
- Error handling: inconsistent experience for predefined
awserr.Error
vsfmt.Errorf
errors. Should be consistent. - Error wording: the function the error occurred in generally is not as important to the user as the error that occurred. The wording should be more specific from the user's point of view. e.g.
build condition failed, unknown condition mode xzy
default: | ||
return ExprNode{}, fmt.Errorf("buildCondition error: unsupported mode: %v", cond.mode) | ||
return exprNode{}, fmt.Errorf("buildTree error: unsupported mode: %v", cb.mode) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be consistent with errors that are returned. in the unsetCond
case a awserr.Error
is used but generic error
is used here.
For errors can consider what action the receiver is expected to take, or do with the error. In most cases (all?) these errors are fatal, and inform the user that their using invalid input. Because of this the awserr.Error may be overly onerous.
Also consider that the buildTree
part of the error message isn't helpful to a user. What about error messages something like, "build condition failed, unsupported mode %v", cb.mode
case greaterThanCond: | ||
node.fmtExpr = "$c > $c" | ||
case greaterThanEqualCond: | ||
node.fmtExpr = "$c >= $c" | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probably want error default
case for known condition mode. This will also help adding additional cases in the future.
// create a string with escaped characters to substitute them with proper | ||
// aliases during runtime | ||
var mode string | ||
switch c.mode { | ||
switch conditionBuilder.mode { | ||
case andCond: | ||
mode = " AND " | ||
case orCond: | ||
mode = " OR " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probably want default case also.
|
||
// Condition will return the *string corresponding to the Condition Expression | ||
// of the argument Expression. This method is used to satisfy the members of | ||
// DynamoDB input structs. If the argument Expression does not have a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit, Expression
here generally isn't talked about as argument
. In this context it is more similar to a type that has methods on it. e.g If the Expression does not have a condition expression this method will return nil.
// TableName: aws.String("SomeTable"), | ||
// } | ||
func (e Expression) Names() map[string]*string { | ||
aliasList, _ := e.buildChildTrees() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be done inside of Builder.Build
so that any errors can be surfaced to the user. In addition, this will reduce the duplicate logic that is occurring in each getter. I think it makes sense to see the Expression
type as a read only view into the already built/constructed data created by Builder
|
||
valuesMap := map[string]*dynamodb.AttributeValue{} | ||
for i := 0; i < len(aliasList.valuesList); i++ { | ||
valuesMap[fmt.Sprintf(":%v", i)] = &aliasList.valuesList[i] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic should be apart of the Builder.Build
method.
|
||
namesMap := map[string]*string{} | ||
for ind, val := range aliasList.namesList { | ||
namesMap[fmt.Sprintf("#%v", ind)] = aws.String(val) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic should be apart of the Builder.Build
method.
// PathBuilder represents a path to either a top level item attribute or a | ||
// nested attribute. It will implement the OperandBuilder interface. It will | ||
// NameBuilder represents a name of a top level item attribute or a nested | ||
// attribute. It will implement the OperandBuilder interface. It will | ||
// have various methods corresponding to the operations supported by DynamoDB | ||
// operations. (i.e. AND, BETWEEN, EQUALS) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should state that a Name
is a DynamoDB Operand
to create that clear relationship.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
For changes to files under the
/model/
folder, and manual edits to autogenerated code (e.g./service/s3/api.go
) please create an Issue instead of a PR for those type of changes.If there is an existing bug or feature this PR is answers please reference it here.