1package expression
2
3import (
4	"fmt"
5	"sort"
6	"strings"
7)
8
9// operationMode specifies the types of update operations that the
10// updateBuilder is going to represent. The const is in a string to use the
11// const value as a map key and as a string when creating the formatted
12// expression for the exprNodes.
13type operationMode string
14
15const (
16	setOperation    operationMode = "SET"
17	removeOperation               = "REMOVE"
18	addOperation                  = "ADD"
19	deleteOperation               = "DELETE"
20)
21
22// Implementing the Sort interface
23type modeList []operationMode
24
25func (ml modeList) Len() int {
26	return len(ml)
27}
28
29func (ml modeList) Less(i, j int) bool {
30	return string(ml[i]) < string(ml[j])
31}
32
33func (ml modeList) Swap(i, j int) {
34	ml[i], ml[j] = ml[j], ml[i]
35}
36
37// UpdateBuilder represents Update Expressions in DynamoDB. UpdateBuilders
38// are the building blocks of the Builder struct. Note that there are different
39// update operations in DynamoDB and an UpdateBuilder can represent multiple
40// update operations.
41// More Information at: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html
42type UpdateBuilder struct {
43	operationList map[operationMode][]operationBuilder
44}
45
46// operationBuilder represents specific update actions (SET, REMOVE, ADD,
47// DELETE). The mode specifies what type of update action the
48// operationBuilder represents.
49type operationBuilder struct {
50	name  NameBuilder
51	value OperandBuilder
52	mode  operationMode
53}
54
55// buildOperation builds an exprNode from an operationBuilder. buildOperation
56// is called recursively by buildTree in order to create a tree structure
57// of exprNodes representing the parent/child relationships between
58// UpdateBuilders and operationBuilders.
59func (ob operationBuilder) buildOperation() (exprNode, error) {
60	pathChild, err := ob.name.BuildOperand()
61	if err != nil {
62		return exprNode{}, err
63	}
64
65	node := exprNode{
66		children: []exprNode{pathChild.exprNode},
67		fmtExpr:  "$c",
68	}
69
70	if ob.mode == removeOperation {
71		return node, nil
72	}
73
74	valueChild, err := ob.value.BuildOperand()
75	if err != nil {
76		return exprNode{}, err
77	}
78	node.children = append(node.children, valueChild.exprNode)
79
80	switch ob.mode {
81	case setOperation:
82		node.fmtExpr += " = $c"
83	case addOperation, deleteOperation:
84		node.fmtExpr += " $c"
85	default:
86		return exprNode{}, fmt.Errorf("build update error: build operation error: unsupported mode: %v", ob.mode)
87	}
88
89	return node, nil
90}
91
92// Delete returns an UpdateBuilder representing one Delete operation for
93// DynamoDB Update Expressions. The argument name should specify the item
94// attribute and the argument value should specify the value to be deleted. The
95// resulting UpdateBuilder can be used as an argument to the WithUpdate() method
96// for the Builder struct.
97//
98// Example:
99//
100//     // update represents the delete operation to delete the string value
101//     // "subsetToDelete" from the item attribute "pathToList"
102//     update := expression.Delete(expression.Name("pathToList"), expression.Value("subsetToDelete"))
103//
104//     // Adding more update methods
105//     anotherUpdate := update.Remove(expression.Name("someName"))
106//     // Creating a Builder
107//     builder := Update(update)
108//
109// Expression Equivalent:
110//
111//     expression.Delete(expression.Name("pathToList"), expression.Value("subsetToDelete"))
112//     // let :del be an ExpressionAttributeValue representing the value
113//     // "subsetToDelete"
114//     "DELETE pathToList :del"
115func Delete(name NameBuilder, value ValueBuilder) UpdateBuilder {
116	emptyUpdateBuilder := UpdateBuilder{}
117	return emptyUpdateBuilder.Delete(name, value)
118}
119
120// Delete adds a Delete operation to the argument UpdateBuilder. The
121// argument name should specify the item attribute and the argument value should
122// specify the value to be deleted. The resulting UpdateBuilder can be used as
123// an argument to the WithUpdate() method for the Builder struct.
124//
125// Example:
126//
127//     // Let update represent an already existing update expression. Delete()
128//     // adds the operation to delete the value "subsetToDelete" from the item
129//     // attribute "pathToList"
130//     update := update.Delete(expression.Name("pathToList"), expression.Value("subsetToDelete"))
131//
132//     // Adding more update methods
133//     anotherUpdate := update.Remove(expression.Name("someName"))
134//     // Creating a Builder
135//     builder := Update(update)
136//
137// Expression Equivalent:
138//
139//     Delete(expression.Name("pathToList"), expression.Value("subsetToDelete"))
140//     // let :del be an ExpressionAttributeValue representing the value
141//     // "subsetToDelete"
142//     "DELETE pathToList :del"
143func (ub UpdateBuilder) Delete(name NameBuilder, value ValueBuilder) UpdateBuilder {
144	if ub.operationList == nil {
145		ub.operationList = map[operationMode][]operationBuilder{}
146	}
147	ub.operationList[deleteOperation] = append(ub.operationList[deleteOperation], operationBuilder{
148		name:  name,
149		value: value,
150		mode:  deleteOperation,
151	})
152	return ub
153}
154
155// Add returns an UpdateBuilder representing the Add operation for DynamoDB
156// Update Expressions. The argument name should specify the item attribute and
157// the argument value should specify the value to be added. The resulting
158// UpdateBuilder can be used as an argument to the WithUpdate() method for the
159// Builder struct.
160//
161// Example:
162//
163//     // update represents the add operation to add the value 5 to the item
164//     // attribute "aPath"
165//     update := expression.Add(expression.Name("aPath"), expression.Value(5))
166//
167//     // Adding more update methods
168//     anotherUpdate := update.Remove(expression.Name("someName"))
169//     // Creating a Builder
170//     builder := Update(update)
171//
172// Expression Equivalent:
173//
174//     expression.Add(expression.Name("aPath"), expression.Value(5))
175//     // Let :five be an ExpressionAttributeValue representing the value 5
176//     "ADD aPath :5"
177func Add(name NameBuilder, value ValueBuilder) UpdateBuilder {
178	emptyUpdateBuilder := UpdateBuilder{}
179	return emptyUpdateBuilder.Add(name, value)
180}
181
182// Add adds an Add operation to the argument UpdateBuilder. The argument
183// name should specify the item attribute and the argument value should specify
184// the value to be added. The resulting UpdateBuilder can be used as an argument
185// to the WithUpdate() method for the Builder struct.
186//
187// Example:
188//
189//     // Let update represent an already existing update expression. Add() adds
190//     // the operation to add the value 5 to the item attribute "aPath"
191//     update := update.Add(expression.Name("aPath"), expression.Value(5))
192//
193//     // Adding more update methods
194//     anotherUpdate := update.Remove(expression.Name("someName"))
195//     // Creating a Builder
196//     builder := Update(update)
197//
198// Expression Equivalent:
199//
200//     Add(expression.Name("aPath"), expression.Value(5))
201//     // Let :five be an ExpressionAttributeValue representing the value 5
202//     "ADD aPath :5"
203func (ub UpdateBuilder) Add(name NameBuilder, value ValueBuilder) UpdateBuilder {
204	if ub.operationList == nil {
205		ub.operationList = map[operationMode][]operationBuilder{}
206	}
207	ub.operationList[addOperation] = append(ub.operationList[addOperation], operationBuilder{
208		name:  name,
209		value: value,
210		mode:  addOperation,
211	})
212	return ub
213}
214
215// Remove returns an UpdateBuilder representing the Remove operation for
216// DynamoDB Update Expressions. The argument name should specify the item
217// attribute to delete. The resulting UpdateBuilder can be used as an argument
218// to the WithUpdate() method for the Builder struct.
219//
220// Example:
221//
222//     // update represents the remove operation to remove the item attribute
223//     // "itemToRemove"
224//     update := expression.Remove(expression.Name("itemToRemove"))
225//
226//     // Adding more update methods
227//     anotherUpdate := update.Remove(expression.Name("someName"))
228//     // Creating a Builder
229//     builder := Update(update)
230//
231// Expression Equivalent:
232//
233//     expression.Remove(expression.Name("itemToRemove"))
234//     "REMOVE itemToRemove"
235func Remove(name NameBuilder) UpdateBuilder {
236	emptyUpdateBuilder := UpdateBuilder{}
237	return emptyUpdateBuilder.Remove(name)
238}
239
240// Remove adds a Remove operation to the argument UpdateBuilder. The
241// argument name should specify the item attribute to delete. The resulting
242// UpdateBuilder can be used as an argument to the WithUpdate() method for the
243// Builder struct.
244//
245// Example:
246//
247//     // Let update represent an already existing update expression. Remove()
248//     // adds the operation to remove the item attribute "itemToRemove"
249//     update := update.Remove(expression.Name("itemToRemove"))
250//
251//     // Adding more update methods
252//     anotherUpdate := update.Remove(expression.Name("someName"))
253//     // Creating a Builder
254//     builder := Update(update)
255//
256// Expression Equivalent:
257//
258//     Remove(expression.Name("itemToRemove"))
259//     "REMOVE itemToRemove"
260func (ub UpdateBuilder) Remove(name NameBuilder) UpdateBuilder {
261	if ub.operationList == nil {
262		ub.operationList = map[operationMode][]operationBuilder{}
263	}
264	ub.operationList[removeOperation] = append(ub.operationList[removeOperation], operationBuilder{
265		name: name,
266		mode: removeOperation,
267	})
268	return ub
269}
270
271// Set returns an UpdateBuilder representing the Set operation for DynamoDB
272// Update Expressions. The argument name should specify the item attribute to
273// modify. The argument OperandBuilder should specify the value to modify the
274// the item attribute to. The resulting UpdateBuilder can be used as an argument
275// to the WithUpdate() method for the Builder struct.
276//
277// Example:
278//
279//     // update represents the set operation to set the item attribute
280//     // "itemToSet" to the value "setValue" if the item attribute does not
281//     // exist yet. (conditional write)
282//     update := expression.Set(expression.Name("itemToSet"), expression.IfNotExists(expression.Name("itemToSet"), expression.Value("setValue")))
283//
284//     // Adding more update methods
285//     anotherUpdate := update.Remove(expression.Name("someName"))
286//     // Creating a Builder
287//     builder := Update(update)
288//
289// Expression Equivalent:
290//
291//     expression.Set(expression.Name("itemToSet"), expression.IfNotExists(expression.Name("itemToSet"), expression.Value("setValue")))
292//     // Let :val be an ExpressionAttributeValue representing the value
293//     // "setValue"
294//     "SET itemToSet = :val"
295func Set(name NameBuilder, operandBuilder OperandBuilder) UpdateBuilder {
296	emptyUpdateBuilder := UpdateBuilder{}
297	return emptyUpdateBuilder.Set(name, operandBuilder)
298}
299
300// Set adds a Set operation to the argument UpdateBuilder. The argument name
301// should specify the item attribute to modify. The argument OperandBuilder
302// should specify the value to modify the the item attribute to. The resulting
303// UpdateBuilder can be used as an argument to the WithUpdate() method for the
304// Builder struct.
305//
306// Example:
307//
308//     // Let update represent an already existing update expression. Set() adds
309//     // the operation to to set the item attribute "itemToSet" to the value
310//     // "setValue" if the item attribute does not exist yet. (conditional
311//     // write)
312//     update := update.Set(expression.Name("itemToSet"), expression.IfNotExists(expression.Name("itemToSet"), expression.Value("setValue")))
313//
314//     // Adding more update methods
315//     anotherUpdate := update.Remove(expression.Name("someName"))
316//     // Creating a Builder
317//     builder := Update(update)
318//
319// Expression Equivalent:
320//
321//     Set(expression.Name("itemToSet"), expression.IfNotExists(expression.Name("itemToSet"), expression.Value("setValue")))
322//     // Let :val be an ExpressionAttributeValue representing the value
323//     // "setValue"
324//     "SET itemToSet = :val"
325func (ub UpdateBuilder) Set(name NameBuilder, operandBuilder OperandBuilder) UpdateBuilder {
326	if ub.operationList == nil {
327		ub.operationList = map[operationMode][]operationBuilder{}
328	}
329	ub.operationList[setOperation] = append(ub.operationList[setOperation], operationBuilder{
330		name:  name,
331		value: operandBuilder,
332		mode:  setOperation,
333	})
334	return ub
335}
336
337// buildTree builds a tree structure of exprNodes based on the tree
338// structure of the input UpdateBuilder's child UpdateBuilders/Operands.
339// buildTree() satisfies the TreeBuilder interface so ProjectionBuilder can be a
340// part of Expression struct.
341func (ub UpdateBuilder) buildTree() (exprNode, error) {
342	if ub.operationList == nil {
343		return exprNode{}, newUnsetParameterError("buildTree", "UpdateBuilder")
344	}
345	ret := exprNode{
346		children: []exprNode{},
347	}
348
349	modes := modeList{}
350
351	for mode := range ub.operationList {
352		modes = append(modes, mode)
353	}
354
355	sort.Sort(modes)
356
357	for _, key := range modes {
358		ret.fmtExpr += string(key) + " $c\n"
359
360		childNode, err := buildChildNodes(ub.operationList[key])
361		if err != nil {
362			return exprNode{}, err
363		}
364
365		ret.children = append(ret.children, childNode)
366	}
367
368	return ret, nil
369}
370
371// buildChildNodes creates the list of the child exprNodes.
372func buildChildNodes(operationBuilderList []operationBuilder) (exprNode, error) {
373	if len(operationBuilderList) == 0 {
374		return exprNode{}, fmt.Errorf("buildChildNodes error: operationBuilder list is empty")
375	}
376
377	node := exprNode{
378		children: make([]exprNode, 0, len(operationBuilderList)),
379		fmtExpr:  "$c" + strings.Repeat(", $c", len(operationBuilderList)-1),
380	}
381
382	for _, val := range operationBuilderList {
383		valNode, err := val.buildOperation()
384		if err != nil {
385			return exprNode{}, err
386		}
387		node.children = append(node.children, valNode)
388	}
389
390	return node, nil
391}
392