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