Skip to content
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

Merged
merged 18 commits into from
Aug 30, 2017

Conversation

hhayano
Copy link

@hhayano hhayano commented Aug 16, 2017

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.

xibz
xibz previously requested changes Aug 17, 2017
// TODO consider AST instead of string in the future
switch expr.Expression[i+1] {
case 'p':
if index.name >= len(en.names) {
Copy link
Contributor

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)
Copy link
Contributor

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 {
Copy link
Contributor

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.

https://play.golang.org/p/txrOvLujQW

// 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 {
Copy link
Contributor

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)
Copy link
Contributor

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) {
Copy link
Contributor

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 {
Copy link
Contributor

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 {}

Copy link
Contributor

@jasdel jasdel left a 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 vs fmt.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)
Copy link
Contributor

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"
}
Copy link
Contributor

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 "
Copy link
Contributor

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
Copy link
Contributor

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()
Copy link
Contributor

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]
Copy link
Contributor

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)
Copy link
Contributor

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)
Copy link
Contributor

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.

Copy link
Contributor

@jasdel jasdel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@jasdel jasdel dismissed xibz’s stale review August 30, 2017 17:06

reviewed by jasdel

@jasdel jasdel merged commit ce236ed into aws:dynamodb-abstraction Aug 30, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants