github.com/aavshr/aws-sdk-go@v1.41.3/service/dynamodb/expression/expression.go (about)

     1  package expression
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  
     7  	"github.com/aavshr/aws-sdk-go/aws"
     8  	"github.com/aavshr/aws-sdk-go/service/dynamodb"
     9  )
    10  
    11  // expressionType specifies the type of Expression. Declaring this type is used
    12  // to eliminate magic strings
    13  type expressionType string
    14  
    15  const (
    16  	projection   expressionType = "projection"
    17  	keyCondition                = "keyCondition"
    18  	condition                   = "condition"
    19  	filter                      = "filter"
    20  	update                      = "update"
    21  )
    22  
    23  // Implement the Sort interface
    24  type typeList []expressionType
    25  
    26  func (l typeList) Len() int {
    27  	return len(l)
    28  }
    29  
    30  func (l typeList) Less(i, j int) bool {
    31  	return string(l[i]) < string(l[j])
    32  }
    33  
    34  func (l typeList) Swap(i, j int) {
    35  	l[i], l[j] = l[j], l[i]
    36  }
    37  
    38  // Builder represents the struct that builds the Expression struct. Methods such
    39  // as WithProjection() and WithCondition() can add different kinds of DynamoDB
    40  // Expressions to the Builder. The method Build() creates an Expression struct
    41  // with the specified types of DynamoDB Expressions.
    42  //
    43  // Example:
    44  //
    45  //     keyCond := expression.Key("someKey").Equal(expression.Value("someValue"))
    46  //     proj := expression.NamesList(expression.Name("aName"), expression.Name("anotherName"), expression.Name("oneOtherName"))
    47  //
    48  //     builder := expression.NewBuilder().WithKeyCondition(keyCond).WithProjection(proj)
    49  //     expr := builder.Build()
    50  //
    51  //     queryInput := dynamodb.QueryInput{
    52  //       KeyConditionExpression:    expr.KeyCondition(),
    53  //       ProjectionExpression:      expr.Projection(),
    54  //       ExpressionAttributeNames:  expr.Names(),
    55  //       ExpressionAttributeValues: expr.Values(),
    56  //       TableName: aws.String("SomeTable"),
    57  //     }
    58  type Builder struct {
    59  	expressionMap map[expressionType]treeBuilder
    60  }
    61  
    62  // NewBuilder returns an empty Builder struct. Methods such as WithProjection()
    63  // and WithCondition() can add different kinds of DynamoDB Expressions to the
    64  // Builder. The method Build() creates an Expression struct with the specified
    65  // types of DynamoDB Expressions.
    66  //
    67  // Example:
    68  //
    69  //     keyCond := expression.Key("someKey").Equal(expression.Value("someValue"))
    70  //     proj := expression.NamesList(expression.Name("aName"), expression.Name("anotherName"), expression.Name("oneOtherName"))
    71  //     builder := expression.NewBuilder().WithKeyCondition(keyCond).WithProjection(proj)
    72  func NewBuilder() Builder {
    73  	return Builder{}
    74  }
    75  
    76  // Build builds an Expression struct representing multiple types of DynamoDB
    77  // Expressions. Getter methods on the resulting Expression struct returns the
    78  // DynamoDB Expression strings as well as the maps that correspond to
    79  // ExpressionAttributeNames and ExpressionAttributeValues. Calling Build() on an
    80  // empty Builder returns the typed error EmptyParameterError.
    81  //
    82  // Example:
    83  //
    84  //     // keyCond represents the Key Condition Expression
    85  //     keyCond := expression.Key("someKey").Equal(expression.Value("someValue"))
    86  //     // proj represents the Projection Expression
    87  //     proj := expression.NamesList(expression.Name("aName"), expression.Name("anotherName"), expression.Name("oneOtherName"))
    88  //
    89  //     // Add keyCond and proj to builder as a Key Condition and Projection
    90  //     // respectively
    91  //     builder := expression.NewBuilder().WithKeyCondition(keyCond).WithProjection(proj)
    92  //     expr := builder.Build()
    93  //
    94  //     queryInput := dynamodb.QueryInput{
    95  //       KeyConditionExpression:    expr.KeyCondition(),
    96  //       ProjectionExpression:      expr.Projection(),
    97  //       ExpressionAttributeNames:  expr.Names(),
    98  //       ExpressionAttributeValues: expr.Values(),
    99  //       TableName: aws.String("SomeTable"),
   100  //     }
   101  func (b Builder) Build() (Expression, error) {
   102  	if b.expressionMap == nil {
   103  		return Expression{}, newUnsetParameterError("Build", "Builder")
   104  	}
   105  
   106  	aliasList, expressionMap, err := b.buildChildTrees()
   107  	if err != nil {
   108  		return Expression{}, err
   109  	}
   110  
   111  	expression := Expression{
   112  		expressionMap: expressionMap,
   113  	}
   114  
   115  	if len(aliasList.namesList) != 0 {
   116  		namesMap := map[string]*string{}
   117  		for ind, val := range aliasList.namesList {
   118  			namesMap[fmt.Sprintf("#%v", ind)] = aws.String(val)
   119  		}
   120  		expression.namesMap = namesMap
   121  	}
   122  
   123  	if len(aliasList.valuesList) != 0 {
   124  		valuesMap := map[string]*dynamodb.AttributeValue{}
   125  		for i := 0; i < len(aliasList.valuesList); i++ {
   126  			valuesMap[fmt.Sprintf(":%v", i)] = &aliasList.valuesList[i]
   127  		}
   128  		expression.valuesMap = valuesMap
   129  	}
   130  
   131  	return expression, nil
   132  }
   133  
   134  // buildChildTrees compiles the list of treeBuilders that are the children of
   135  // the argument Builder. The returned aliasList represents all the alias tokens
   136  // used in the expression strings. The returned map[string]string maps the type
   137  // of expression (i.e. "condition", "update") to the appropriate expression
   138  // string.
   139  func (b Builder) buildChildTrees() (aliasList, map[expressionType]string, error) {
   140  	aList := aliasList{}
   141  	formattedExpressions := map[expressionType]string{}
   142  	keys := typeList{}
   143  
   144  	for expressionType := range b.expressionMap {
   145  		keys = append(keys, expressionType)
   146  	}
   147  
   148  	sort.Sort(keys)
   149  
   150  	for _, key := range keys {
   151  		node, err := b.expressionMap[key].buildTree()
   152  		if err != nil {
   153  			return aliasList{}, nil, err
   154  		}
   155  		formattedExpression, err := node.buildExpressionString(&aList)
   156  		if err != nil {
   157  			return aliasList{}, nil, err
   158  		}
   159  		formattedExpressions[key] = formattedExpression
   160  	}
   161  
   162  	return aList, formattedExpressions, nil
   163  }
   164  
   165  // WithCondition method adds the argument ConditionBuilder as a Condition
   166  // Expression to the argument Builder. If the argument Builder already has a
   167  // ConditionBuilder representing a Condition Expression, WithCondition()
   168  // overwrites the existing ConditionBuilder.
   169  //
   170  // Example:
   171  //
   172  //     // let builder be an existing Builder{} and cond be an existing
   173  //     // ConditionBuilder{}
   174  //     builder = builder.WithCondition(cond)
   175  //
   176  //     // add other DynamoDB Expressions to the builder. let proj be an already
   177  //     // existing ProjectionBuilder
   178  //     builder = builder.WithProjection(proj)
   179  //     // create an Expression struct
   180  //     expr := builder.Build()
   181  func (b Builder) WithCondition(conditionBuilder ConditionBuilder) Builder {
   182  	if b.expressionMap == nil {
   183  		b.expressionMap = map[expressionType]treeBuilder{}
   184  	}
   185  	b.expressionMap[condition] = conditionBuilder
   186  	return b
   187  }
   188  
   189  // WithProjection method adds the argument ProjectionBuilder as a Projection
   190  // Expression to the argument Builder. If the argument Builder already has a
   191  // ProjectionBuilder representing a Projection Expression, WithProjection()
   192  // overwrites the existing ProjectionBuilder.
   193  //
   194  // Example:
   195  //
   196  //     // let builder be an existing Builder{} and proj be an existing
   197  //     // ProjectionBuilder{}
   198  //     builder = builder.WithProjection(proj)
   199  //
   200  //     // add other DynamoDB Expressions to the builder. let cond be an already
   201  //     // existing ConditionBuilder
   202  //     builder = builder.WithCondition(cond)
   203  //     // create an Expression struct
   204  //     expr := builder.Build()
   205  func (b Builder) WithProjection(projectionBuilder ProjectionBuilder) Builder {
   206  	if b.expressionMap == nil {
   207  		b.expressionMap = map[expressionType]treeBuilder{}
   208  	}
   209  	b.expressionMap[projection] = projectionBuilder
   210  	return b
   211  }
   212  
   213  // WithKeyCondition method adds the argument KeyConditionBuilder as a Key
   214  // Condition Expression to the argument Builder. If the argument Builder already
   215  // has a KeyConditionBuilder representing a Key Condition Expression,
   216  // WithKeyCondition() overwrites the existing KeyConditionBuilder.
   217  //
   218  // Example:
   219  //
   220  //     // let builder be an existing Builder{} and keyCond be an existing
   221  //     // KeyConditionBuilder{}
   222  //     builder = builder.WithKeyCondition(keyCond)
   223  //
   224  //     // add other DynamoDB Expressions to the builder. let cond be an already
   225  //     // existing ConditionBuilder
   226  //     builder = builder.WithCondition(cond)
   227  //     // create an Expression struct
   228  //     expr := builder.Build()
   229  func (b Builder) WithKeyCondition(keyConditionBuilder KeyConditionBuilder) Builder {
   230  	if b.expressionMap == nil {
   231  		b.expressionMap = map[expressionType]treeBuilder{}
   232  	}
   233  	b.expressionMap[keyCondition] = keyConditionBuilder
   234  	return b
   235  }
   236  
   237  // WithFilter method adds the argument ConditionBuilder as a Filter Expression
   238  // to the argument Builder. If the argument Builder already has a
   239  // ConditionBuilder representing a Filter Expression, WithFilter()
   240  // overwrites the existing ConditionBuilder.
   241  //
   242  // Example:
   243  //
   244  //     // let builder be an existing Builder{} and filt be an existing
   245  //     // ConditionBuilder{}
   246  //     builder = builder.WithFilter(filt)
   247  //
   248  //     // add other DynamoDB Expressions to the builder. let cond be an already
   249  //     // existing ConditionBuilder
   250  //     builder = builder.WithCondition(cond)
   251  //     // create an Expression struct
   252  //     expr := builder.Build()
   253  func (b Builder) WithFilter(filterBuilder ConditionBuilder) Builder {
   254  	if b.expressionMap == nil {
   255  		b.expressionMap = map[expressionType]treeBuilder{}
   256  	}
   257  	b.expressionMap[filter] = filterBuilder
   258  	return b
   259  }
   260  
   261  // WithUpdate method adds the argument UpdateBuilder as an Update Expression
   262  // to the argument Builder. If the argument Builder already has a UpdateBuilder
   263  // representing a Update Expression, WithUpdate() overwrites the existing
   264  // UpdateBuilder.
   265  //
   266  // Example:
   267  //
   268  //     // let builder be an existing Builder{} and update be an existing
   269  //     // UpdateBuilder{}
   270  //     builder = builder.WithUpdate(update)
   271  //
   272  //     // add other DynamoDB Expressions to the builder. let cond be an already
   273  //     // existing ConditionBuilder
   274  //     builder = builder.WithCondition(cond)
   275  //     // create an Expression struct
   276  //     expr := builder.Build()
   277  func (b Builder) WithUpdate(updateBuilder UpdateBuilder) Builder {
   278  	if b.expressionMap == nil {
   279  		b.expressionMap = map[expressionType]treeBuilder{}
   280  	}
   281  	b.expressionMap[update] = updateBuilder
   282  	return b
   283  }
   284  
   285  // Expression represents a collection of DynamoDB Expressions. The getter
   286  // methods of the Expression struct retrieves the formatted DynamoDB
   287  // Expressions, ExpressionAttributeNames, and ExpressionAttributeValues.
   288  //
   289  // Example:
   290  //
   291  //     // keyCond represents the Key Condition Expression
   292  //     keyCond := expression.Key("someKey").Equal(expression.Value("someValue"))
   293  //     // proj represents the Projection Expression
   294  //     proj := expression.NamesList(expression.Name("aName"), expression.Name("anotherName"), expression.Name("oneOtherName"))
   295  //
   296  //     // Add keyCond and proj to builder as a Key Condition and Projection
   297  //     // respectively
   298  //     builder := expression.NewBuilder().WithKeyCondition(keyCond).WithProjection(proj)
   299  //     expr := builder.Build()
   300  //
   301  //     queryInput := dynamodb.QueryInput{
   302  //       KeyConditionExpression:    expr.KeyCondition(),
   303  //       ProjectionExpression:      expr.Projection(),
   304  //       ExpressionAttributeNames:  expr.Names(),
   305  //       ExpressionAttributeValues: expr.Values(),
   306  //       TableName: aws.String("SomeTable"),
   307  //     }
   308  type Expression struct {
   309  	expressionMap map[expressionType]string
   310  	namesMap      map[string]*string
   311  	valuesMap     map[string]*dynamodb.AttributeValue
   312  }
   313  
   314  // treeBuilder interface is fulfilled by builder structs that represent
   315  // different types of Expressions.
   316  type treeBuilder interface {
   317  	// buildTree creates the tree structure of exprNodes. The tree structure
   318  	// of exprNodes are traversed in order to build the string representing
   319  	// different types of Expressions as well as the maps that represent
   320  	// ExpressionAttributeNames and ExpressionAttributeValues.
   321  	buildTree() (exprNode, error)
   322  }
   323  
   324  // Condition returns the *string corresponding to the Condition Expression
   325  // of the argument Expression. This method is used to satisfy the members of
   326  // DynamoDB input structs. If the Expression does not have a condition
   327  // expression this method returns nil.
   328  //
   329  // Example:
   330  //
   331  //     // let expression be an instance of Expression{}
   332  //
   333  //     deleteInput := dynamodb.DeleteItemInput{
   334  //       ConditionExpression:       expression.Condition(),
   335  //       ExpressionAttributeNames:  expression.Names(),
   336  //       ExpressionAttributeValues: expression.Values(),
   337  //       Key: map[string]*dynamodb.AttributeValue{
   338  //         "PartitionKey": &dynamodb.AttributeValue{
   339  //           S: aws.String("SomeKey"),
   340  //         },
   341  //       },
   342  //       TableName: aws.String("SomeTable"),
   343  //     }
   344  func (e Expression) Condition() *string {
   345  	return e.returnExpression(condition)
   346  }
   347  
   348  // Filter returns the *string corresponding to the Filter Expression of the
   349  // argument Expression. This method is used to satisfy the members of DynamoDB
   350  // input structs. If the Expression does not have a filter expression this
   351  // method returns nil.
   352  //
   353  // Example:
   354  //
   355  //     // let expression be an instance of Expression{}
   356  //
   357  //     queryInput := dynamodb.QueryInput{
   358  //       KeyConditionExpression:    expression.KeyCondition(),
   359  //       FilterExpression:          expression.Filter(),
   360  //       ExpressionAttributeNames:  expression.Names(),
   361  //       ExpressionAttributeValues: expression.Values(),
   362  //       TableName: aws.String("SomeTable"),
   363  //     }
   364  func (e Expression) Filter() *string {
   365  	return e.returnExpression(filter)
   366  }
   367  
   368  // Projection returns the *string corresponding to the Projection Expression
   369  // of the argument Expression. This method is used to satisfy the members of
   370  // DynamoDB input structs. If the Expression does not have a projection
   371  // expression this method returns nil.
   372  //
   373  // Example:
   374  //
   375  //     // let expression be an instance of Expression{}
   376  //
   377  //     queryInput := dynamodb.QueryInput{
   378  //       KeyConditionExpression:    expression.KeyCondition(),
   379  //       ProjectionExpression:      expression.Projection(),
   380  //       ExpressionAttributeNames:  expression.Names(),
   381  //       ExpressionAttributeValues: expression.Values(),
   382  //       TableName: aws.String("SomeTable"),
   383  //     }
   384  func (e Expression) Projection() *string {
   385  	return e.returnExpression(projection)
   386  }
   387  
   388  // KeyCondition returns the *string corresponding to the Key Condition
   389  // Expression of the argument Expression. This method is used to satisfy the
   390  // members of DynamoDB input structs. If the argument Expression does not have a
   391  // KeyConditionExpression, KeyCondition() returns nil.
   392  //
   393  // Example:
   394  //
   395  //     // let expression be an instance of Expression{}
   396  //
   397  //     queryInput := dynamodb.QueryInput{
   398  //       KeyConditionExpression:    expression.KeyCondition(),
   399  //       ProjectionExpression:      expression.Projection(),
   400  //       ExpressionAttributeNames:  expression.Names(),
   401  //       ExpressionAttributeValues: expression.Values(),
   402  //       TableName: aws.String("SomeTable"),
   403  //     }
   404  func (e Expression) KeyCondition() *string {
   405  	return e.returnExpression(keyCondition)
   406  }
   407  
   408  // Update returns the *string corresponding to the Update Expression of the
   409  // argument Expression. This method is used to satisfy the members of DynamoDB
   410  // input structs. If the argument Expression does not have a UpdateExpression,
   411  // Update() returns nil.
   412  //
   413  // Example:
   414  //
   415  //     // let expression be an instance of Expression{}
   416  //
   417  //     updateInput := dynamodb.UpdateInput{
   418  //       Key: map[string]*dynamodb.AttributeValue{
   419  //         "PartitionKey": {
   420  //           S: aws.String("someKey"),
   421  //         },
   422  //       },
   423  //       UpdateExpression:          expression.Update(),
   424  //       ExpressionAttributeNames:  expression.Names(),
   425  //       ExpressionAttributeValues: expression.Values(),
   426  //       TableName: aws.String("SomeTable"),
   427  //     }
   428  func (e Expression) Update() *string {
   429  	return e.returnExpression(update)
   430  }
   431  
   432  // Names returns the map[string]*string corresponding to the
   433  // ExpressionAttributeNames of the argument Expression. This method is used to
   434  // satisfy the members of DynamoDB input structs. If Expression does not use
   435  // ExpressionAttributeNames, this method returns nil. The
   436  // ExpressionAttributeNames and ExpressionAttributeValues member of the input
   437  // struct must always be assigned when using the Expression struct since all
   438  // item attribute names and values are aliased. That means that if the
   439  // ExpressionAttributeNames and ExpressionAttributeValues member is not assigned
   440  // with the corresponding Names() and Values() methods, the DynamoDB operation
   441  // will run into a logic error.
   442  //
   443  // Example:
   444  //
   445  //     // let expression be an instance of Expression{}
   446  //
   447  //     queryInput := dynamodb.QueryInput{
   448  //       KeyConditionExpression:    expression.KeyCondition(),
   449  //       ProjectionExpression:      expression.Projection(),
   450  //       ExpressionAttributeNames:  expression.Names(),
   451  //       ExpressionAttributeValues: expression.Values(),
   452  //       TableName: aws.String("SomeTable"),
   453  //     }
   454  func (e Expression) Names() map[string]*string {
   455  	return e.namesMap
   456  }
   457  
   458  // Values returns the map[string]*dynamodb.AttributeValue corresponding to
   459  // the ExpressionAttributeValues of the argument Expression. This method is used
   460  // to satisfy the members of DynamoDB input structs. If Expression does not use
   461  // ExpressionAttributeValues, this method returns nil. The
   462  // ExpressionAttributeNames and ExpressionAttributeValues member of the input
   463  // struct must always be assigned when using the Expression struct since all
   464  // item attribute names and values are aliased. That means that if the
   465  // ExpressionAttributeNames and ExpressionAttributeValues member is not assigned
   466  // with the corresponding Names() and Values() methods, the DynamoDB operation
   467  // will run into a logic error.
   468  //
   469  // Example:
   470  //
   471  //     // let expression be an instance of Expression{}
   472  //
   473  //     queryInput := dynamodb.QueryInput{
   474  //       KeyConditionExpression:    expression.KeyCondition(),
   475  //       ProjectionExpression:      expression.Projection(),
   476  //       ExpressionAttributeNames:  expression.Names(),
   477  //       ExpressionAttributeValues: expression.Values(),
   478  //       TableName: aws.String("SomeTable"),
   479  //     }
   480  func (e Expression) Values() map[string]*dynamodb.AttributeValue {
   481  	return e.valuesMap
   482  }
   483  
   484  // returnExpression returns *string corresponding to the type of Expression
   485  // string specified by the expressionType. If there is no corresponding
   486  // expression available in Expression, the method returns nil
   487  func (e Expression) returnExpression(expressionType expressionType) *string {
   488  	if e.expressionMap == nil {
   489  		return nil
   490  	}
   491  	if s, exists := e.expressionMap[expressionType]; exists {
   492  		return &s
   493  	}
   494  	return nil
   495  }
   496  
   497  // exprNode are the generic nodes that represents both Operands and
   498  // Conditions. The purpose of exprNode is to be able to call an generic
   499  // recursive function on the top level exprNode to be able to determine a root
   500  // node in order to deduplicate name aliases.
   501  // fmtExpr is a string that has escaped characters to refer to
   502  // names/values/children which needs to be aliased at runtime in order to avoid
   503  // duplicate values. The rules are as follows:
   504  //     $n: Indicates that an alias of a name needs to be inserted. The
   505  //         corresponding name to be alias is in the []names slice.
   506  //     $v: Indicates that an alias of a value needs to be inserted. The
   507  //         corresponding value to be alias is in the []values slice.
   508  //     $c: Indicates that the fmtExpr of a child exprNode needs to be inserted.
   509  //         The corresponding child node is in the []children slice.
   510  type exprNode struct {
   511  	names    []string
   512  	values   []dynamodb.AttributeValue
   513  	children []exprNode
   514  	fmtExpr  string
   515  }
   516  
   517  // aliasList keeps track of all the names we need to alias in the nested
   518  // struct of conditions and operands. This allows each alias to be unique.
   519  // aliasList is passed in as a pointer when buildChildTrees is called in
   520  // order to deduplicate all names within the tree strcuture of the exprNodes.
   521  type aliasList struct {
   522  	namesList  []string
   523  	valuesList []dynamodb.AttributeValue
   524  }
   525  
   526  // buildExpressionString returns a string with aliasing for names/values
   527  // specified by aliasList. The string corresponds to the expression that the
   528  // exprNode tree represents.
   529  func (en exprNode) buildExpressionString(aliasList *aliasList) (string, error) {
   530  	// Since each exprNode contains a slice of names, values, and children that
   531  	// correspond to the escaped characters, we an index to traverse the slices
   532  	index := struct {
   533  		name, value, children int
   534  	}{}
   535  
   536  	formattedExpression := en.fmtExpr
   537  
   538  	for i := 0; i < len(formattedExpression); {
   539  		if formattedExpression[i] != '$' {
   540  			i++
   541  			continue
   542  		}
   543  
   544  		if i == len(formattedExpression)-1 {
   545  			return "", fmt.Errorf("buildexprNode error: invalid escape character")
   546  		}
   547  
   548  		var alias string
   549  		var err error
   550  		// if an escaped character is found, substitute it with the proper alias
   551  		// TODO consider AST instead of string in the future
   552  		switch formattedExpression[i+1] {
   553  		case 'n':
   554  			alias, err = substitutePath(index.name, en, aliasList)
   555  			if err != nil {
   556  				return "", err
   557  			}
   558  			index.name++
   559  
   560  		case 'v':
   561  			alias, err = substituteValue(index.value, en, aliasList)
   562  			if err != nil {
   563  				return "", err
   564  			}
   565  			index.value++
   566  
   567  		case 'c':
   568  			alias, err = substituteChild(index.children, en, aliasList)
   569  			if err != nil {
   570  				return "", err
   571  			}
   572  			index.children++
   573  
   574  		default:
   575  			return "", fmt.Errorf("buildexprNode error: invalid escape rune %#v", formattedExpression[i+1])
   576  		}
   577  		formattedExpression = formattedExpression[:i] + alias + formattedExpression[i+2:]
   578  		i += len(alias)
   579  	}
   580  
   581  	return formattedExpression, nil
   582  }
   583  
   584  // substitutePath substitutes the escaped character $n with the appropriate
   585  // alias.
   586  func substitutePath(index int, node exprNode, aliasList *aliasList) (string, error) {
   587  	if index >= len(node.names) {
   588  		return "", fmt.Errorf("substitutePath error: exprNode []names out of range")
   589  	}
   590  	str, err := aliasList.aliasPath(node.names[index])
   591  	if err != nil {
   592  		return "", err
   593  	}
   594  	return str, nil
   595  }
   596  
   597  // substituteValue substitutes the escaped character $v with the appropriate
   598  // alias.
   599  func substituteValue(index int, node exprNode, aliasList *aliasList) (string, error) {
   600  	if index >= len(node.values) {
   601  		return "", fmt.Errorf("substituteValue error: exprNode []values out of range")
   602  	}
   603  	str, err := aliasList.aliasValue(node.values[index])
   604  	if err != nil {
   605  		return "", err
   606  	}
   607  	return str, nil
   608  }
   609  
   610  // substituteChild substitutes the escaped character $c with the appropriate
   611  // alias.
   612  func substituteChild(index int, node exprNode, aliasList *aliasList) (string, error) {
   613  	if index >= len(node.children) {
   614  		return "", fmt.Errorf("substituteChild error: exprNode []children out of range")
   615  	}
   616  	return node.children[index].buildExpressionString(aliasList)
   617  }
   618  
   619  // aliasValue returns the corresponding alias to the dav value argument. Since
   620  // values are not deduplicated as of now, all values are just appended to the
   621  // aliasList and given the index as the alias.
   622  func (al *aliasList) aliasValue(dav dynamodb.AttributeValue) (string, error) {
   623  	al.valuesList = append(al.valuesList, dav)
   624  	return fmt.Sprintf(":%d", len(al.valuesList)-1), nil
   625  }
   626  
   627  // aliasPath returns the corresponding alias to the argument string. The
   628  // argument is checked against all existing aliasList names in order to avoid
   629  // duplicate strings getting two different aliases.
   630  func (al *aliasList) aliasPath(nm string) (string, error) {
   631  	for ind, name := range al.namesList {
   632  		if nm == name {
   633  			return fmt.Sprintf("#%d", ind), nil
   634  		}
   635  	}
   636  	al.namesList = append(al.namesList, nm)
   637  	return fmt.Sprintf("#%d", len(al.namesList)-1), nil
   638  }