github.com/aldelo/common@v1.5.1/wrapper/dynamodb/crud.go (about) 1 package dynamodb 2 3 /* 4 * Copyright 2020-2023 Aldelo, LP 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 import ( 20 "context" 21 "encoding/base64" 22 "encoding/json" 23 "fmt" 24 util "github.com/aldelo/common" 25 "github.com/aldelo/common/wrapper/aws/awsregion" 26 "github.com/aws/aws-sdk-go/aws" 27 ddb "github.com/aws/aws-sdk-go/service/dynamodb" 28 "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" 29 "strings" 30 "time" 31 ) 32 33 type Crud struct { 34 _ddb *DynamoDB 35 _timeout uint 36 _actionRetries uint 37 } 38 39 type ConnectionConfig struct { 40 Region string 41 TableName string 42 UseDax bool 43 DaxUrl string 44 45 TimeoutSeconds uint 46 ActionRetries uint 47 } 48 49 type QueryExpression struct { 50 PKName string 51 PKValue string 52 53 UseSK bool 54 SKName string 55 SKIsNumber bool 56 SKCompareSymbol string // valid symbols: = <= >= < > BETWEEN begins_with (note = not equal symbol is not allowed) 57 SKValue string 58 SKValue2 string // used only if SKComparerSymbol is BETWEEN 59 60 IndexName string 61 } 62 63 type PkSkValuePair struct { 64 PKValue string 65 SKValue string 66 } 67 68 type AttributeValue struct { 69 Name string 70 71 Value string // string value or string value representing number 72 IsN bool // treats Value as number, and ListValue as number slice 73 IsBool bool // treats Value as boolean, does not work with ListValue 74 ListValue []string // honors IsN to treat ListValue as either list of string or list of numbers 75 76 ComplexMap interface{} // for map of custom type or custom type slice 77 ComplexList interface{} // for custom type slice 78 ComplexObject interface{} // for custom type object 79 } 80 81 type GlobalTableInfo struct { 82 TableName string 83 Regions []awsregion.AWSRegion 84 } 85 86 var cachedGlobalTableSupportedRegions []string 87 88 // Open will establish connection to the target dynamodb table as defined in config.yaml 89 func (c *Crud) Open(cfg *ConnectionConfig) error { 90 if cfg == nil { 91 return fmt.Errorf("Config is Required") 92 } 93 94 c._ddb = &DynamoDB{ 95 AwsRegion: awsregion.GetAwsRegion(cfg.Region), 96 SkipDax: !cfg.UseDax, 97 DaxEndpoint: cfg.DaxUrl, 98 TableName: cfg.TableName, 99 PKName: "PK", 100 SKName: "SK", 101 } 102 103 if err := c._ddb.Connect(); err != nil { 104 return err 105 } else { 106 if cfg.UseDax { 107 if err = c._ddb.EnableDax(); err != nil { 108 return err 109 } 110 } 111 112 c._timeout = cfg.TimeoutSeconds 113 c._actionRetries = cfg.ActionRetries 114 115 return nil 116 } 117 } 118 119 // Close will reset and clean up connection to dynamodb table 120 func (c *Crud) Close() { 121 if c._ddb != nil { 122 c._ddb.DisableDax() 123 c._ddb = nil 124 c._timeout = 5 125 c._actionRetries = 4 126 } 127 } 128 129 // CreatePKValue generates composite pk values from configured app and service name, along with parameterized pk values 130 func (c *Crud) CreatePKValue(pkApp string, pkService string, pkScope string, pkIdentifier string, values ...string) (pkValue string, err error) { 131 pkValue = fmt.Sprintf("%s#%s#%s#%s", pkApp, pkService, pkScope, pkIdentifier) 132 133 for _, v := range values { 134 if util.LenTrim(v) > 0 { 135 if util.LenTrim(pkValue) > 0 { 136 pkValue += "#" 137 } 138 139 pkValue += v 140 } 141 } 142 143 if util.LenTrim(pkValue) > 0 { 144 return pkValue, nil 145 } else { 146 return "", fmt.Errorf("Create PK Value Failed: %s", err.Error()) 147 } 148 } 149 150 // Get retrieves data from dynamodb table with given pk and sk values, 151 // resultDataPtr refers to pointer to struct of the target dynamodb table record 152 // 153 // result struct contains PK, SK, and attributes, with struct tags for json and dynamodbav 154 // 155 // warning: projectedAttributes = if specified, MUST include PartitionKey (Hash Key) typically "PK" as the first projected attribute, regardless if used or not 156 func (c *Crud) Get(pkValue string, skValue string, resultDataPtr interface{}, consistentRead bool, projectedAttributes ...string) (err error) { 157 if c._ddb == nil { 158 return fmt.Errorf("Get From Data Store Failed: (Validater 1) Connection Not Established") 159 } 160 161 if util.LenTrim(pkValue) == 0 { 162 return fmt.Errorf("Get From Data Store Failed: (Validater 2) PK Value is Required") 163 } 164 165 if util.LenTrim(skValue) == 0 { 166 return fmt.Errorf("Get From Data Store Failed: (Validater 3) SK Value is Required") 167 } 168 169 if resultDataPtr == nil { 170 return fmt.Errorf("Get From Data Store Failed: (Validater 4) Result Var Requires Ptr") 171 } 172 173 if e := c._ddb.GetItemWithRetry(c._actionRetries, resultDataPtr, pkValue, skValue, c._ddb.TimeOutDuration(c._timeout), util.BoolPtr(consistentRead), projectedAttributes...); e != nil { 174 // get error 175 return fmt.Errorf("Get From Data Store Failed: (GetItem) %s", e.Error()) 176 } else { 177 // get success 178 return nil 179 } 180 } 181 182 // BatchGet executes get against up to 100 PK SK search keys, 183 // results populated into resultDataSlicePtr (each slice element is struct of underlying dynamodb table record attributes definition) 184 // 185 // warning: projectedAttributes = if specified, MUST include PartitionKey (Hash Key) typically "PK" as the first projected attribute, regardless if used or not 186 func (c *Crud) BatchGet(searchKeys []PkSkValuePair, resultDataSlicePtr interface{}, consistentRead bool, projectedAttributes ...string) (found bool, err error) { 187 if c._ddb == nil { 188 return false, fmt.Errorf("BatchGet From Data Store Failed: (Validater 1) Connection Not Established") 189 } 190 191 if resultDataSlicePtr == nil { 192 return false, fmt.Errorf("BatchGet From Data Store Failed: (Validater 2) Result Data Slice Missing Ptr") 193 } 194 195 if len(searchKeys) == 0 { 196 return false, fmt.Errorf("BatchGet From Data Store Failed: (Validater 3) Search Keys Missing Values") 197 } 198 199 ddbSearchKeys := []DynamoDBTableKeys{} 200 201 for _, v := range searchKeys { 202 ddbSearchKeys = append(ddbSearchKeys, DynamoDBTableKeys{ 203 PK: v.PKValue, 204 SK: v.SKValue, 205 }) 206 } 207 208 if notFound, e := c._ddb.BatchGetItemsWithRetry(c._actionRetries, resultDataSlicePtr, ddbSearchKeys, c._ddb.TimeOutDuration(c._timeout), util.BoolPtr(consistentRead), projectedAttributes...); e != nil { 209 // error 210 return false, fmt.Errorf("BatchGet From Data Store Failed: (BatchGetItems) %s" + e.Error()) 211 } else { 212 // success 213 return !notFound, nil 214 } 215 } 216 217 // TransactionGet retrieves records from dynamodb table(s), based on given PK SK, 218 // action results will be passed to caller via transReads' ResultItemPtr and ResultError fields 219 func (c *Crud) TransactionGet(transReads ...*DynamoDBTransactionReads) (successCount int, err error) { 220 if c._ddb == nil { 221 return 0, fmt.Errorf("TransactionGet From Data Store Failed: (Validater 1) Connection Not Established") 222 } 223 224 if transReads == nil { 225 return 0, fmt.Errorf("TransactionGet From Data Store Failed: (Validater 2) Transaction Keys Missing") 226 } 227 228 if success, e := c._ddb.TransactionGetItemsWithRetry(c._actionRetries, c._ddb.TimeOutDuration(c._timeout), transReads...); e != nil { 229 // error 230 return 0, fmt.Errorf("TransactionGet From Data Store Failed: (TransactionGetItems) %s", e.Error()) 231 } else { 232 // success 233 return success, nil 234 } 235 } 236 237 // Set persists data to dynamodb table with given pointer struct that represents the target dynamodb table record, 238 // pk value within pointer struct is created using CreatePKValue func 239 // dataPtr refers to pointer to struct of the target dynamodb table record 240 // 241 // data struct contains PK, SK, and attributes, with struct tags for json and dynamodbav 242 func (c *Crud) Set(dataPtr interface{}) (err error) { 243 if c._ddb == nil { 244 return fmt.Errorf("Set To Data Store Failed: (Validater 1) Connection Not Established") 245 } 246 247 if dataPtr == nil { 248 return fmt.Errorf("Set To Data Store Failed: (Validater 2) Data Var Requires Ptr") 249 } 250 251 if e := c._ddb.PutItemWithRetry(c._actionRetries, dataPtr, c._ddb.TimeOutDuration(c._timeout)); e != nil { 252 // set error 253 return fmt.Errorf("Set To Data Store Failed: (PutItem) %s", e.Error()) 254 } else { 255 // set success 256 return nil 257 } 258 } 259 260 // BatchSet executes put and delete against up to 25 grouped records combined, 261 // putDataSlice = []dataStruct for the put items (make sure not passing in as Ptr) 262 // deleteKeys = PK SK pairs slice to delete against 263 // failedPuts & failedDeletes = PK SK pairs slices for the failed action attempts 264 func (c *Crud) BatchSet(putDataSlice interface{}, deleteKeys []PkSkValuePair) (successCount int, failedPuts []PkSkValuePair, failedDeletes []PkSkValuePair, err error) { 265 if c._ddb == nil { 266 return 0, nil, nil, fmt.Errorf("BatchSet To Data Store Failed: (Validater 1) Connection Not Established") 267 } 268 269 ddbDeleteKeys := []DynamoDBTableKeys{} 270 271 for _, v := range deleteKeys { 272 ddbDeleteKeys = append(ddbDeleteKeys, DynamoDBTableKeys{ 273 PK: v.PKValue, 274 SK: v.SKValue, 275 }) 276 } 277 278 if len(ddbDeleteKeys) == 0 { 279 ddbDeleteKeys = nil 280 } 281 282 if success, unprocessed, e := c._ddb.BatchWriteItemsWithRetry(c._actionRetries, putDataSlice, ddbDeleteKeys, c._ddb.TimeOutDuration(c._timeout)); e != nil { 283 // error 284 return 0, nil, nil, fmt.Errorf("BatchSet To Data Store Failed: (BatchWriteItems) %s" + e.Error()) 285 } else { 286 // success (may contain unprocessed) 287 if unprocessed != nil { 288 if unprocessed.PutItems != nil { 289 for _, v := range unprocessed.PutItems { 290 if v != nil { 291 failedPuts = append(failedPuts, PkSkValuePair{PKValue: aws.StringValue(v["PK"].S), SKValue: aws.StringValue(v["SK"].S)}) 292 } 293 } 294 295 if len(failedPuts) == 0 { 296 failedPuts = nil 297 } 298 } 299 300 if unprocessed.DeleteKeys != nil { 301 for _, v := range unprocessed.DeleteKeys { 302 if v != nil { 303 failedDeletes = append(failedDeletes, PkSkValuePair{PKValue: v.PK, SKValue: v.SK}) 304 } 305 } 306 307 if len(failedDeletes) == 0 { 308 failedDeletes = nil 309 } 310 } 311 } 312 313 return success, failedPuts, failedDeletes, nil 314 } 315 } 316 317 // TransactionSet puts, updates, deletes records against dynamodb table, with option to override table name, 318 func (c *Crud) TransactionSet(transWrites ...*DynamoDBTransactionWrites) (success bool, err error) { 319 if c._ddb == nil { 320 return false, fmt.Errorf("TransactionSet To Data Store Failed: (Validater 1) Connection Not Established") 321 } 322 323 if transWrites == nil { 324 return false, fmt.Errorf("TransactionSet To Data Store Failed: (Validater 2) Transaction Data Missing") 325 } 326 327 if ok, e := c._ddb.TransactionWriteItemsWithRetry(c._actionRetries, c._ddb.TimeOutDuration(c._timeout), transWrites...); e != nil { 328 // error 329 return false, fmt.Errorf("TransactionSet To Data Store Failed: (TransactionWriteItems) %s", e.Error()) 330 } else { 331 // success 332 return ok, nil 333 } 334 } 335 336 // Query retrieves data from dynamodb table with given pk and sk values, or via LSI / GSI using index name, 337 // pagedDataPtrSlice refers to pointer slice of data struct pointers for use during paged query, that each data struct represents the underlying dynamodb table record, 338 // 339 // &[]*xyz{} 340 // 341 // resultDataPtrSlice refers to pointer slice of data struct pointers to contain the paged query results (this is the working variable, not the returning result), 342 // 343 // &[]*xyz{} 344 // 345 // both pagedDataPtrSlice and resultDataPtrSlice have the same data types, but they will be contained in separate slice ptr vars, 346 // 347 // data struct contains PK, SK, and attributes, with struct tags for json and dynamodbav, ie: &[]*exampleDataStruct 348 // 349 // responseDataPtrSlice, is the slice ptr result to caller, expects caller to assert to target slice ptr objects, ie: results.([]*xyz) 350 func (c *Crud) Query(keyExpression *QueryExpression, pagedDataPtrSlice interface{}, resultDataPtrSlice interface{}) (responseDataPtrSlice interface{}, err error) { 351 if c._ddb == nil { 352 return nil, fmt.Errorf("Query From Data Store Failed: (Validater 1) Connection Not Established") 353 } 354 355 if keyExpression == nil { 356 return nil, fmt.Errorf("Query From Data Store Failed: (Validater 2) Key Expression is Required") 357 } 358 359 if util.LenTrim(keyExpression.PKName) == 0 { 360 return nil, fmt.Errorf("Query From Data Store Failed: (Validater 3) Key Expression Missing PK Name") 361 } 362 363 if util.LenTrim(keyExpression.PKValue) == 0 { 364 return nil, fmt.Errorf("Query From Data Store Failed: (Validater 4) Key Expression Missing PK Value") 365 } 366 367 if keyExpression.UseSK { 368 if util.LenTrim(keyExpression.SKName) == 0 { 369 return nil, fmt.Errorf("Query From Data Store Failed: (Validater 5) Key Expression Missing SK Name") 370 } 371 372 if util.LenTrim(keyExpression.SKCompareSymbol) == 0 && keyExpression.SKIsNumber { 373 return nil, fmt.Errorf("Query From Data Store Failed: (Validater 6) Key Expression Missing SK Comparer") 374 } 375 376 if util.LenTrim(keyExpression.SKValue) == 0 { 377 return nil, fmt.Errorf("Query From Data Store Failed: (Validater 7) Key Expression Missing SK Value") 378 } 379 } 380 381 if pagedDataPtrSlice == nil { 382 return nil, fmt.Errorf("Query From Data Store Failed: (Validater 8) Paged Data Slice Missing Ptr") 383 } 384 385 if resultDataPtrSlice == nil { 386 return nil, fmt.Errorf("Query From Data Store Failed: (Validater 9) Result Data Slice Missing Ptr") 387 } 388 389 keyValues := map[string]*ddb.AttributeValue{} 390 391 keyCondition := keyExpression.PKName + "=:" + keyExpression.PKName 392 keyValues[":"+keyExpression.PKName] = &ddb.AttributeValue{ 393 S: aws.String(keyExpression.PKValue), 394 } 395 396 if keyExpression.UseSK { 397 if util.LenTrim(keyExpression.SKCompareSymbol) == 0 { 398 keyExpression.SKCompareSymbol = "=" 399 } 400 401 keyCondition += " AND " 402 var isBetween bool 403 404 switch strings.TrimSpace(strings.ToUpper(keyExpression.SKCompareSymbol)) { 405 case "BETWEEN": 406 keyCondition += fmt.Sprintf("%s BETWEEN %s AND %s", keyExpression.SKName, ":"+keyExpression.SKName, ":"+keyExpression.SKName+"2") 407 isBetween = true 408 case "BEGINS_WITH": 409 keyCondition += fmt.Sprintf("begins_with(%s, %s)", keyExpression.SKName, ":"+keyExpression.SKName) 410 default: 411 keyCondition += keyExpression.SKName + keyExpression.SKCompareSymbol + ":" + keyExpression.SKName 412 } 413 414 if !keyExpression.SKIsNumber { 415 keyValues[":"+keyExpression.SKName] = &ddb.AttributeValue{ 416 S: aws.String(keyExpression.SKValue), 417 } 418 419 if isBetween { 420 keyValues[":"+keyExpression.SKName+"2"] = &ddb.AttributeValue{ 421 S: aws.String(keyExpression.SKValue2), 422 } 423 } 424 } else { 425 keyValues[":"+keyExpression.SKName] = &ddb.AttributeValue{ 426 N: aws.String(keyExpression.SKValue), 427 } 428 429 if isBetween { 430 keyValues[":"+keyExpression.SKName+"2"] = &ddb.AttributeValue{ 431 N: aws.String(keyExpression.SKValue2), 432 } 433 } 434 } 435 } 436 437 // query against dynamodb table 438 if dataList, e := c._ddb.QueryPagedItemsWithRetry(c._actionRetries, pagedDataPtrSlice, resultDataPtrSlice, 439 c._ddb.TimeOutDuration(c._timeout), keyExpression.IndexName, 440 keyCondition, keyValues, nil); e != nil { 441 // query error 442 return nil, fmt.Errorf("Query From Data Store Failed: (QueryPaged) %s", e.Error()) 443 } else { 444 // query success 445 return dataList, nil 446 } 447 } 448 449 // lastEvalKeyToBase64 serializes last evaluated key to base 64 string 450 func (c *Crud) lastEvalKeyToBase64(key map[string]*ddb.AttributeValue) (string, error) { 451 if key != nil { 452 lastEvalKey := map[string]interface{}{} 453 454 if err := dynamodbattribute.UnmarshalMap(key, &lastEvalKey); err != nil { 455 return "", fmt.Errorf("Base64 Encode LastEvalKey Failed: (Unmarshal Map Error) %s", err.Error()) 456 } else { 457 if keyOutput, e := json.Marshal(lastEvalKey); e != nil { 458 return "", fmt.Errorf("Base64 Encode LastEvalKey Failed: (Json Marshal Error) %s", e.Error()) 459 } else { 460 return base64.StdEncoding.EncodeToString(keyOutput), nil 461 } 462 } 463 } else { 464 return "", nil 465 } 466 } 467 468 // exclusiveStartKeyFromBase64 de-serializes last evaluated key base 64 string into map[string]*dynamodb.Attribute object 469 func (c *Crud) exclusiveStartKeyFromBase64(key string) (map[string]*ddb.AttributeValue, error) { 470 if util.LenTrim(key) > 0 { 471 if byteJson, err := base64.StdEncoding.DecodeString(key); err != nil { 472 return nil, fmt.Errorf("Base64 Decode ExclusiveStartKey Failed: (Base64 DecodeString Error) %s", err.Error()) 473 } else { 474 outputJson := map[string]interface{}{} 475 476 if err = json.Unmarshal(byteJson, &outputJson); err != nil { 477 return nil, fmt.Errorf("Base64 Decode ExclusiveStartKey Failed: (Json Unmarshal Error) %s", err.Error()) 478 } else { 479 var outputKey map[string]*ddb.AttributeValue 480 481 if outputKey, err = dynamodbattribute.MarshalMap(outputJson); err != nil { 482 return nil, fmt.Errorf("Base64 Decode ExclusiveStartKey Failed: (Marshal Map Error) %s", err.Error()) 483 } else { 484 return outputKey, nil 485 } 486 } 487 } 488 } else { 489 return nil, nil 490 } 491 } 492 493 // QueryByPage retrieves data from dynamodb table with given pk and sk values, or via LSI / GSI using index name on per page basis 494 // 495 // Parameters: 496 // 497 // itemsPerPage = indicates total number of items per page to return in query, defaults to 10 if set to 0; max limit is 500 498 // exclusiveStartKey = if this is new query, set to ""; if this is continuation query (pagination), set the prior query's prevEvalKey in base64 string format 499 // keyExpression = query expression object 500 // pagedDataPtrSlice = refers to pointer slice of data struct pointers for use during paged query, that each data struct represents the underlying dynamodb table record 501 // 502 // &[]*xyz{} 503 // 504 // data struct contains PK, SK, and attributes, with struct tags for json and dynamodbav, ie: &[]*exampleDataStruct 505 // 506 // responseDataPtrSlice, is the slice ptr result to caller, expects caller to assert to target slice ptr objects, ie: results.([]*xyz) 507 func (c *Crud) QueryByPage(itemsPerPage int64, exclusiveStartKey string, keyExpression *QueryExpression, pagedDataPtrSlice interface{}) (responseDataPtrSlice interface{}, prevEvalKey string, err error) { 508 if c._ddb == nil { 509 return nil, "", fmt.Errorf("QueryByPage From Data Store Failed: (Validater 1) Connection Not Established") 510 } 511 512 if keyExpression == nil { 513 return nil, "", fmt.Errorf("QueryByPage From Data Store Failed: (Validater 2) Key Expression is Required") 514 } 515 516 if util.LenTrim(keyExpression.PKName) == 0 { 517 return nil, "", fmt.Errorf("QueryByPage From Data Store Failed: (Validater 3) Key Expression Missing PK Name") 518 } 519 520 if util.LenTrim(keyExpression.PKValue) == 0 { 521 return nil, "", fmt.Errorf("QueryByPage From Data Store Failed: (Validater 4) Key Expression Missing PK Value") 522 } 523 524 if keyExpression.UseSK { 525 if util.LenTrim(keyExpression.SKName) == 0 { 526 return nil, "", fmt.Errorf("QueryByPage From Data Store Failed: (Validater 5) Key Expression Missing SK Name") 527 } 528 529 if util.LenTrim(keyExpression.SKCompareSymbol) == 0 && keyExpression.SKIsNumber { 530 return nil, "", fmt.Errorf("QueryByPage From Data Store Failed: (Validater 6) Key Expression Missing SK Comparer") 531 } 532 533 if util.LenTrim(keyExpression.SKValue) == 0 { 534 return nil, "", fmt.Errorf("QueryByPage From Data Store Failed: (Validater 7) Key Expression Missing SK Value") 535 } 536 } 537 538 if pagedDataPtrSlice == nil { 539 return nil, "", fmt.Errorf("QueryByPage From Data Store Failed: (Validater 8) Paged Data Slice Missing Ptr") 540 } 541 542 if itemsPerPage < 0 { 543 itemsPerPage = 10 544 } else if itemsPerPage > 500 { 545 itemsPerPage = 500 546 } 547 548 keyValues := map[string]*ddb.AttributeValue{} 549 550 keyCondition := keyExpression.PKName + "=:" + keyExpression.PKName 551 keyValues[":"+keyExpression.PKName] = &ddb.AttributeValue{ 552 S: aws.String(keyExpression.PKValue), 553 } 554 555 if keyExpression.UseSK { 556 if util.LenTrim(keyExpression.SKCompareSymbol) == 0 { 557 keyExpression.SKCompareSymbol = "=" 558 } 559 560 keyCondition += " AND " 561 var isBetween bool 562 563 switch strings.TrimSpace(strings.ToUpper(keyExpression.SKCompareSymbol)) { 564 case "BETWEEN": 565 keyCondition += fmt.Sprintf("%s BETWEEN %s AND %s", keyExpression.SKName, ":"+keyExpression.SKName, ":"+keyExpression.SKName+"2") 566 isBetween = true 567 case "BEGINS_WITH": 568 keyCondition += fmt.Sprintf("begins_with(%s, %s)", keyExpression.SKName, ":"+keyExpression.SKName) 569 default: 570 keyCondition += keyExpression.SKName + keyExpression.SKCompareSymbol + ":" + keyExpression.SKName 571 } 572 573 if !keyExpression.SKIsNumber { 574 keyValues[":"+keyExpression.SKName] = &ddb.AttributeValue{ 575 S: aws.String(keyExpression.SKValue), 576 } 577 578 if isBetween { 579 keyValues[":"+keyExpression.SKName+"2"] = &ddb.AttributeValue{ 580 S: aws.String(keyExpression.SKValue2), 581 } 582 } 583 } else { 584 keyValues[":"+keyExpression.SKName] = &ddb.AttributeValue{ 585 N: aws.String(keyExpression.SKValue), 586 } 587 588 if isBetween { 589 keyValues[":"+keyExpression.SKName+"2"] = &ddb.AttributeValue{ 590 N: aws.String(keyExpression.SKValue2), 591 } 592 } 593 } 594 } 595 596 // query by page against dynamodb table 597 var esk map[string]*ddb.AttributeValue 598 599 esk, err = c.exclusiveStartKeyFromBase64(exclusiveStartKey) 600 601 if err != nil { 602 return nil, "", fmt.Errorf("QueryByPage From Data Store Failed: (ESK From Base64 Error) %s", err.Error()) 603 } 604 605 if dataList, prevKey, e := c._ddb.QueryPerPageItemsWithRetry(c._actionRetries, itemsPerPage, esk, pagedDataPtrSlice, 606 c._ddb.TimeOutDuration(c._timeout), keyExpression.IndexName, 607 keyCondition, keyValues, nil); e != nil { 608 // query error 609 return nil, "", fmt.Errorf("QueryByPage From Data Store Failed: (QueryPaged) %s", e.Error()) 610 } else { 611 // query success 612 var lek string 613 614 if lek, err = c.lastEvalKeyToBase64(prevKey); err != nil { 615 return nil, "", fmt.Errorf("QueryByPage From Data Store Failed: (LEK To Base64 Error) %s", err.Error()) 616 } else { 617 return dataList, lek, nil 618 } 619 } 620 } 621 622 // QueryPaginationData returns pagination slice to be used for paging 623 // 624 // if paginationData is nil or zero length, then this is single page 625 // 626 // if paginationData is 1 or more elements, then element 0 (first element) is always page 1 and value is nil, 627 // page 2 will be on element 1 and contains the exclusiveStartKey, and so on. 628 // 629 // each element contains base64 encoded value of exclusiveStartkey, therefore page 1 exclusiveStartKey is nil. 630 // 631 // for page 1 use exclusiveStartKey as nil 632 // for page 2 and more use the exclusiveStartKey from paginationData slice 633 func (c *Crud) QueryPaginationData(itemsPerPage int64, keyExpression *QueryExpression) (paginationData []string, err error) { 634 if c._ddb == nil { 635 return nil, fmt.Errorf("QueryPaginationData From Data Store Failed: (Validater 1) Connection Not Established") 636 } 637 638 if keyExpression == nil { 639 return nil, fmt.Errorf("QueryPaginationData From Data Store Failed: (Validater 2) Key Expression is Required") 640 } 641 642 if util.LenTrim(keyExpression.PKName) == 0 { 643 return nil, fmt.Errorf("QueryPaginationData From Data Store Failed: (Validater 3) Key Expression Missing PK Name") 644 } 645 646 if util.LenTrim(keyExpression.PKValue) == 0 { 647 return nil, fmt.Errorf("QueryPaginationData From Data Store Failed: (Validater 4) Key Expression Missing PK Value") 648 } 649 650 if keyExpression.UseSK { 651 if util.LenTrim(keyExpression.SKName) == 0 { 652 return nil, fmt.Errorf("QueryPaginationData From Data Store Failed: (Validater 5) Key Expression Missing SK Name") 653 } 654 655 if util.LenTrim(keyExpression.SKCompareSymbol) == 0 && keyExpression.SKIsNumber { 656 return nil, fmt.Errorf("QueryPaginationData From Data Store Failed: (Validater 6) Key Expression Missing SK Comparer") 657 } 658 659 if util.LenTrim(keyExpression.SKValue) == 0 { 660 return nil, fmt.Errorf("QueryPaginationData From Data Store Failed: (Validater 7) Key Expression Missing SK Value") 661 } 662 } 663 664 if itemsPerPage <= 0 { 665 itemsPerPage = 10 666 } else if itemsPerPage > 500 { 667 itemsPerPage = 500 668 } 669 670 keyValues := map[string]*ddb.AttributeValue{} 671 672 keyCondition := keyExpression.PKName + "=:" + keyExpression.PKName 673 keyValues[":"+keyExpression.PKName] = &ddb.AttributeValue{ 674 S: aws.String(keyExpression.PKValue), 675 } 676 677 if keyExpression.UseSK { 678 if util.LenTrim(keyExpression.SKCompareSymbol) == 0 { 679 keyExpression.SKCompareSymbol = "=" 680 } 681 682 keyCondition += " AND " 683 var isBetween bool 684 685 switch strings.TrimSpace(strings.ToUpper(keyExpression.SKCompareSymbol)) { 686 case "BETWEEN": 687 keyCondition += fmt.Sprintf("%s BETWEEN %s AND %s", keyExpression.SKName, ":"+keyExpression.SKName, ":"+keyExpression.SKName+"2") 688 isBetween = true 689 case "BEGINS_WITH": 690 keyCondition += fmt.Sprintf("begins_with(%s, %s)", keyExpression.SKName, ":"+keyExpression.SKName) 691 default: 692 keyCondition += keyExpression.SKName + keyExpression.SKCompareSymbol + ":" + keyExpression.SKName 693 } 694 695 if !keyExpression.SKIsNumber { 696 keyValues[":"+keyExpression.SKName] = &ddb.AttributeValue{ 697 S: aws.String(keyExpression.SKValue), 698 } 699 700 if isBetween { 701 keyValues[":"+keyExpression.SKName+"2"] = &ddb.AttributeValue{ 702 S: aws.String(keyExpression.SKValue2), 703 } 704 } 705 } else { 706 keyValues[":"+keyExpression.SKName] = &ddb.AttributeValue{ 707 N: aws.String(keyExpression.SKValue), 708 } 709 710 if isBetween { 711 keyValues[":"+keyExpression.SKName+"2"] = &ddb.AttributeValue{ 712 N: aws.String(keyExpression.SKValue2), 713 } 714 } 715 } 716 } 717 718 // query pagination data against dynamodb table 719 if pData, e := c._ddb.QueryPaginationDataWithRetry(c._actionRetries, c._ddb.TimeOutDuration(c._timeout), util.StringPtr(keyExpression.IndexName), itemsPerPage, keyCondition, nil, keyValues); e != nil { 720 // query error 721 return nil, fmt.Errorf("QueryPaginationData From Data Store Failed: (QueryPaged) %s", e.Error()) 722 } else { 723 // query success 724 if pData != nil && len(pData) > 0 { 725 paginationData = make([]string, 1) 726 727 for _, v := range pData { 728 if v != nil { 729 if lek, e := c.lastEvalKeyToBase64(v); e != nil { 730 return nil, fmt.Errorf("QueryPaginationData From Data Store Failed: (LEK To Base64 Error) %s", e.Error()) 731 } else { 732 paginationData = append(paginationData, lek) 733 } 734 } 735 } 736 737 return paginationData, nil 738 } else { 739 // single page 740 return make([]string, 1), nil 741 } 742 } 743 } 744 745 // Update will update a specific dynamodb record based on PK and SK, with given update expression, condition, and attribute values, 746 // attribute values controls the actual values going to be updated into the record 747 // 748 // updateExpression = required, ATTRIBUTES ARE CASE SENSITIVE; set remove add or delete action expression, see Rules URL for full detail 749 // Rules: 750 // 1) https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html 751 // Usage Syntax: 752 // 1) Action Keywords are: set, add, remove, delete 753 // 2) Each Action Keyword May Appear in UpdateExpression Only Once 754 // 3) Each Action Keyword Grouping May Contain One or More Actions, Such as 'set price=:p, age=:age, etc' (each action separated by comma) 755 // 4) Each Action Keyword Always Begin with Action Keyword itself, such as 'set ...', 'add ...', etc 756 // 5) If Attribute is Numeric, Action Can Perform + or - Operation in Expression, such as 'set age=age-:newAge, price=price+:price, etc' 757 // 6) If Attribute is Slice, Action Can Perform Slice Element Operation in Expression, such as 'set age[2]=:newData, etc' 758 // 7) When Attribute Name is Reserved Keyword, Use ExpressionAttributeNames to Define #xyz to Alias 759 // a) Use the #xyz in the KeyConditionExpression such as #yr = :year (:year is Defined ExpressionAttributeValue) 760 // 8) When Attribute is a List, Use list_append(a, b, ...) in Expression to append elements (list_append() is case sensitive) 761 // a) set #ri = list_append(#ri, :vals) where :vals represents one or more of elements to add as in L 762 // 9) if_not_exists(path, value) 763 // a) Avoids existing attribute if already exists 764 // b) set price = if_not_exists(price, :p) 765 // c) if_not_exists is case sensitive; path is the existing attribute to check 766 // 10) Action Type Purposes 767 // a) SET = add one or more attributes to an item; overrides existing attributes in item with new values; if attribute is number, able to perform + or - operations 768 // b) REMOVE = remove one or more attributes from an item, to remove multiple attributes, separate by comma; remove element from list use xyz[1] index notation 769 // c) ADD = adds a new attribute and its values to an item; if attribute is number and already exists, value will add up or subtract 770 // d) DELETE = supports only on set data types; deletes one or more elements from a set, such as 'delete color :c' 771 // 11) Example 772 // a) set age=:age, name=:name, etc 773 // b) set age=age-:age, num=num+:num, etc 774 // 775 // conditionExpress = optional, ATTRIBUTES ARE CASE SENSITIVE; sets conditions for this condition expression, set to blank if not used 776 // Usage Syntax: 777 // 1) "size(info.actors) >= :num" 778 // a) When Length of Actors Attribute Value is Equal or Greater Than :num, ONLY THEN UpdateExpression is Performed 779 func (c *Crud) Update(pkValue string, skValue string, updateExpression string, conditionExpression string, attributeValues []*AttributeValue) (err error) { 780 if c._ddb == nil { 781 return fmt.Errorf("Update To Data Store Failed: (Validater 1) Connection Not Established") 782 } 783 784 if util.LenTrim(pkValue) == 0 { 785 return fmt.Errorf("Update To Data Store Failed: (Validater 2) PK Value is Missing") 786 } 787 788 if util.LenTrim(skValue) == 0 { 789 return fmt.Errorf("Update To Data Store Failed: (Validater 3) SK Value is Missing") 790 } 791 792 if util.LenTrim(updateExpression) == 0 { 793 return fmt.Errorf("Update To Data Store Failed: (Validater 4) Update Expression is Missing") 794 } 795 796 // extract set and remove expressions from update expression 797 setExpression := "" 798 removeExpression := "" 799 upUTCExpression := "" 800 801 if pos := strings.Index(strings.ToLower(updateExpression), ", uputc="); pos > 0 { 802 upUTCExpression = util.Trim(util.Right(updateExpression, util.LenTrim(updateExpression)-pos)) 803 updateExpression = util.Trim(util.Left(updateExpression, pos)) 804 } 805 806 if strings.ToLower(util.Left(updateExpression, 4)) == "set " { 807 if strings.Contains(strings.ToLower(updateExpression), " remove ") { 808 pos := strings.Index(strings.ToLower(updateExpression), " remove ") 809 810 if pos > 0 { 811 setExpression = util.Trim(util.Left(updateExpression, pos)) + upUTCExpression 812 removeExpression = util.Trim(util.Right(updateExpression, util.LenTrim(updateExpression)-pos)) 813 } else { 814 setExpression = util.Trim(updateExpression) + upUTCExpression 815 } 816 } else { 817 setExpression = util.Trim(updateExpression) + upUTCExpression 818 } 819 } else if strings.ToLower(util.Left(updateExpression, 7)) == "remove " { 820 removeExpression = util.Trim(updateExpression) 821 } 822 823 if util.LenTrim(setExpression) > 0 { 824 if attributeValues == nil { 825 return fmt.Errorf("Update To Data Store Failed: (Validater 5) Attribute Values Not Defined and is Required When Set Expression is Used") 826 } 827 828 if len(attributeValues) == 0 { 829 return fmt.Errorf("Update To Data Store Failed: (Validater 6) Attribute Values is Required When Set Expression is Used") 830 } 831 } 832 833 // prepare and execute set expression action 834 if util.LenTrim(setExpression) > 0 { 835 expressionAttributeValues := map[string]*ddb.AttributeValue{} 836 837 for _, v := range attributeValues { 838 if v != nil { 839 if v.IsN { 840 if len(v.ListValue) == 0 { 841 if !util.IsNumericFloat64(v.Value, false) { 842 v.Value = "0" 843 } 844 845 expressionAttributeValues[v.Name] = &ddb.AttributeValue{ 846 N: aws.String(v.Value), 847 } 848 } else { 849 expressionAttributeValues[v.Name] = &ddb.AttributeValue{ 850 NS: aws.StringSlice(v.ListValue), 851 } 852 } 853 } else if v.IsBool { 854 b, _ := util.ParseBool(v.Value) 855 expressionAttributeValues[v.Name] = &ddb.AttributeValue{ 856 BOOL: aws.Bool(b), 857 } 858 } else { 859 if len(v.ListValue) == 0 { 860 if v.ComplexMap == nil && v.ComplexList == nil && v.ComplexObject == nil { 861 // string value 862 expressionAttributeValues[v.Name] = &ddb.AttributeValue{ 863 S: aws.String(v.Value), 864 } 865 } else if v.ComplexMap != nil { 866 // map[string]*ddb.AttributeValue 867 if complexMap, err := dynamodbattribute.MarshalMap(v.ComplexMap); err != nil { 868 return fmt.Errorf("Update To Data Store Failed: (MarshalMap on ComplexMap) %s", err.Error()) 869 } else { 870 expressionAttributeValues[v.Name] = &ddb.AttributeValue{ 871 M: complexMap, 872 } 873 } 874 } else if v.ComplexList != nil { 875 // []*ddb.AttributeValue 876 if complexList, err := dynamodbattribute.MarshalList(v.ComplexList); err != nil { 877 return fmt.Errorf("Update To Data Store Failed: (MarshalList on ComplexList) %s", err.Error()) 878 } else { 879 expressionAttributeValues[v.Name] = &ddb.AttributeValue{ 880 L: complexList, 881 } 882 } 883 } else if v.ComplexObject != nil { 884 // *ddb.AttributeValue 885 if complexObject, err := dynamodbattribute.Marshal(v.ComplexObject); err != nil { 886 return fmt.Errorf("Update To Data Store Failed: (MarshalObject on ComplexObject) %s", err.Error()) 887 } else { 888 expressionAttributeValues[v.Name] = complexObject 889 } 890 } 891 } else { 892 expressionAttributeValues[v.Name] = &ddb.AttributeValue{ 893 SS: aws.StringSlice(v.ListValue), 894 } 895 } 896 } 897 } 898 } 899 900 if e := c._ddb.UpdateItemWithRetry(c._actionRetries, pkValue, skValue, setExpression, conditionExpression, nil, expressionAttributeValues, c._ddb.TimeOutDuration(c._timeout)); e != nil { 901 // update item error 902 return fmt.Errorf("Update To Data Store Failed: (UpdateItem) %s", e.Error()) 903 } 904 } 905 906 // prepare and execute remove expression action 907 if util.LenTrim(removeExpression) > 0 { 908 if e := c._ddb.RemoveItemAttributeWithRetry(c._actionRetries, pkValue, skValue, removeExpression, c._ddb.TimeOutDuration(c._timeout)); err != nil { 909 // remove item attribute error 910 return fmt.Errorf("Update To Data Store Failed: (RemoveItemAttribute) %s", e.Error()) 911 } 912 } 913 914 // success 915 return nil 916 } 917 918 // Delete removes data from dynamodb table with given pk and sk values 919 func (c *Crud) Delete(pkValue string, skValue string) (err error) { 920 if c._ddb == nil { 921 return fmt.Errorf("Delete From Data Store Failed: (Validater 1) Connection Not Established") 922 } 923 924 if util.LenTrim(pkValue) == 0 { 925 return fmt.Errorf("Delete From Data Store Failed: (Validater 2) PK Value is Required") 926 } 927 928 if util.LenTrim(skValue) == 0 { 929 return fmt.Errorf("Delete From Data Store Failed: (Validater 3) SK Value is Required") 930 } 931 932 if e := c._ddb.DeleteItemWithRetry(c._actionRetries, pkValue, skValue, c._ddb.TimeOutDuration(c._timeout)); e != nil { 933 // delete error 934 return fmt.Errorf("Delete From Data Store Failed: (DeleteItem) %s", e.Error()) 935 } else { 936 // delete success 937 return nil 938 } 939 } 940 941 // BatchDelete removes one or more record from dynamodb table based on the PK SK pairs 942 func (c *Crud) BatchDelete(deleteKeys ...PkSkValuePair) (successCount int, failedDeletes []PkSkValuePair, err error) { 943 if c._ddb == nil { 944 return 0, nil, fmt.Errorf("BatchDelete From Data Store Failed: (Validater 1) Connection Not Established") 945 } 946 947 if deleteKeys == nil { 948 return 0, nil, fmt.Errorf("BatchDelete From Data Store Failed: (Validater 2) Delete Keys Missing") 949 } 950 951 ddbDeleteKeys := []*DynamoDBTableKeys{} 952 953 for _, v := range deleteKeys { 954 ddbDeleteKeys = append(ddbDeleteKeys, &DynamoDBTableKeys{ 955 PK: v.PKValue, 956 SK: v.SKValue, 957 }) 958 } 959 960 if failed, e := c._ddb.BatchDeleteItemsWithRetry(c._actionRetries, c._ddb.TimeOutDuration(c._timeout), ddbDeleteKeys...); e != nil { 961 return 0, nil, fmt.Errorf("BatchDelete From Data Store Failed: (Validater 2) %s", e.Error()) 962 } else { 963 successCount = len(deleteKeys) 964 965 if failed != nil { 966 for _, v := range failed { 967 if v != nil { 968 failedDeletes = append(failedDeletes, PkSkValuePair{PKValue: v.PK, SKValue: v.SK}) 969 } 970 } 971 972 if len(failedDeletes) == 0 { 973 failedDeletes = nil 974 } else { 975 successCount -= len(failedDeletes) 976 } 977 } 978 979 return successCount, failedDeletes, nil 980 } 981 } 982 983 // CreateTable will create a new dynamodb table based on input parameter values 984 // 985 // onDemand = sets billing to "PAY_PER_REQUEST", required if creating global table 986 // rcu / wcu = defaults to 2 if value is 0 987 // attributes = PK and SK are inserted automatically, only need to specify non PK SK attributes 988 func (c *Crud) CreateTable(tableName string, 989 onDemand bool, 990 rcu int64, wcu int64, 991 sse *ddb.SSESpecification, 992 enableStream bool, 993 lsi []*ddb.LocalSecondaryIndex, 994 gsi []*ddb.GlobalSecondaryIndex, 995 attributes []*ddb.AttributeDefinition, 996 customDynamoDBConnection ...*DynamoDB) error { 997 998 // check for custom object 999 var ddbObj *DynamoDB 1000 1001 if len(customDynamoDBConnection) > 0 { 1002 ddbObj = customDynamoDBConnection[0] 1003 } else { 1004 ddbObj = c._ddb 1005 } 1006 1007 // validate 1008 if ddbObj == nil { 1009 return fmt.Errorf("CreateTable Failed: (Validater 1) Connection Not Established") 1010 } 1011 1012 if util.LenTrim(tableName) == 0 { 1013 return fmt.Errorf("CreateTable Failed: (Validater 2) Table Name is Required") 1014 } 1015 1016 if sse != nil { 1017 if aws.BoolValue(sse.Enabled) { 1018 if sse.SSEType == nil { 1019 sse.SSEType = aws.String("KMS") 1020 } 1021 } 1022 } 1023 1024 if rcu <= 0 { 1025 rcu = 2 1026 } 1027 1028 if wcu <= 0 { 1029 wcu = 2 1030 } 1031 1032 billing := "PROVISIONED" 1033 1034 if onDemand { 1035 billing = "PAY_PER_REQUEST" 1036 } 1037 1038 // prepare 1039 input := &ddb.CreateTableInput{ 1040 TableName: aws.String(tableName), 1041 KeySchema: []*ddb.KeySchemaElement{ 1042 { 1043 AttributeName: aws.String("PK"), 1044 KeyType: aws.String("HASH"), 1045 }, 1046 { 1047 AttributeName: aws.String("SK"), 1048 KeyType: aws.String("RANGE"), 1049 }, 1050 }, 1051 TableClass: aws.String("STANDARD"), 1052 BillingMode: aws.String(billing), 1053 } 1054 1055 if !onDemand { 1056 input.ProvisionedThroughput = &ddb.ProvisionedThroughput{ 1057 ReadCapacityUnits: aws.Int64(rcu), 1058 WriteCapacityUnits: aws.Int64(wcu), 1059 } 1060 } 1061 1062 if sse != nil { 1063 input.SSESpecification = sse 1064 } 1065 1066 if enableStream { 1067 input.StreamSpecification = &ddb.StreamSpecification{ 1068 StreamEnabled: aws.Bool(true), 1069 StreamViewType: aws.String("NEW_AND_OLD_IMAGES"), 1070 } 1071 } 1072 1073 if lsi != nil && len(lsi) > 0 { 1074 input.LocalSecondaryIndexes = lsi 1075 } 1076 1077 if gsi != nil && len(gsi) > 0 { 1078 input.GlobalSecondaryIndexes = gsi 1079 } 1080 1081 if attributes == nil { 1082 attributes = []*ddb.AttributeDefinition{} 1083 } 1084 1085 attributes = append(attributes, &ddb.AttributeDefinition{ 1086 AttributeName: aws.String("PK"), 1087 AttributeType: aws.String("S"), 1088 }, &ddb.AttributeDefinition{ 1089 AttributeName: aws.String("SK"), 1090 AttributeType: aws.String("S"), 1091 }) 1092 1093 if attributes != nil && len(attributes) > 0 { 1094 input.AttributeDefinitions = attributes 1095 } 1096 1097 // execute 1098 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 1099 defer cancel() 1100 1101 if output, err := ddbObj.CreateTable(input, ctx); err != nil { 1102 return fmt.Errorf("CreateTable on %s Failed: (Exec 1) %s", ddbObj.AwsRegion.Key(), err.Error()) 1103 } else { 1104 if output == nil { 1105 return fmt.Errorf("CreateTable on %s Failed: (Exec 2) %s", ddbObj.AwsRegion.Key(), "Output Response is Nil") 1106 } else { 1107 return nil 1108 } 1109 } 1110 } 1111 1112 // UpdateTable will update an existing dynamodb table based on input parameter values 1113 // 1114 // tableName = (required) the name of dynamodb table to be updated 1115 // rcu / wcu = if > 0, corresponding update is affected to the provisioned throughput; if to be updated, both must be set 1116 // gsi = contains slice of global secondary index updates (create / delete / update ... of gsi) 1117 // attributes = attributes involved for the table (does not pre-load PK or SK in this function call) 1118 func (c *Crud) UpdateTable(tableName string, rcu int64, wcu int64, 1119 gsi []*ddb.GlobalSecondaryIndexUpdate, 1120 attributes []*ddb.AttributeDefinition) error { 1121 1122 // validate 1123 if c._ddb == nil { 1124 return fmt.Errorf("UpdateTable Failed: (Validater 1) Connection Not Established") 1125 } 1126 1127 if util.LenTrim(tableName) == 0 { 1128 return fmt.Errorf("UpdateTable Failed: (Validater 2) Table Name is Required") 1129 } 1130 1131 if (rcu > 0 || wcu > 0) && (rcu <= 0 || wcu <= 0) { 1132 return fmt.Errorf("UpdateTable Failed: (Validater 3) Capacity Update Requires Both RCU and WCU Provided") 1133 } 1134 1135 var hasUpdates bool 1136 1137 // prepare 1138 input := &ddb.UpdateTableInput{ 1139 TableName: aws.String(tableName), 1140 } 1141 1142 if rcu > 0 && wcu > 0 { 1143 input.ProvisionedThroughput = &ddb.ProvisionedThroughput{ 1144 ReadCapacityUnits: aws.Int64(rcu), 1145 WriteCapacityUnits: aws.Int64(wcu), 1146 } 1147 hasUpdates = true 1148 } 1149 1150 if gsi != nil && len(gsi) > 0 { 1151 input.GlobalSecondaryIndexUpdates = gsi 1152 hasUpdates = true 1153 } 1154 1155 if attributes != nil && len(attributes) > 0 { 1156 input.AttributeDefinitions = attributes 1157 hasUpdates = true 1158 } 1159 1160 if !hasUpdates { 1161 return fmt.Errorf("UpdateTable Failed: (Validater 4) No Update Parameter Inputs Provided") 1162 } 1163 1164 // execute 1165 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 1166 defer cancel() 1167 1168 if output, err := c._ddb.UpdateTable(input, ctx); err != nil { 1169 return fmt.Errorf("UpdateTable Failed: (Exec 1) %s", err.Error()) 1170 } else { 1171 if output == nil { 1172 return fmt.Errorf("UpdateTable Failed: (Exec 2) %s", "Output Response is Nil") 1173 } else { 1174 return nil 1175 } 1176 } 1177 } 1178 1179 // DeleteTable will delete the target dynamodb table given by input parameter values 1180 func (c *Crud) DeleteTable(tableName string, region awsregion.AWSRegion) error { 1181 // validate 1182 if c._ddb == nil { 1183 return fmt.Errorf("DeleteTable Failed: (Validater 1) Connection Not Established") 1184 } 1185 1186 if !region.Valid() && region != awsregion.UNKNOWN { 1187 return fmt.Errorf("DeleteTable Failed: (Validater 2) Region is Required") 1188 } 1189 1190 // * 1191 // * get dynamodb object 1192 // * 1193 var ddbObj *DynamoDB 1194 1195 if c._ddb.AwsRegion == region { 1196 ddbObj = c._ddb 1197 } else { 1198 d := &DynamoDB{ 1199 AwsRegion: region, 1200 TableName: tableName, 1201 PKName: "PK", 1202 SKName: "SK", 1203 HttpOptions: c._ddb.HttpOptions, 1204 SkipDax: true, 1205 DaxEndpoint: "", 1206 } 1207 1208 if err := d.connectInternal(); err != nil { 1209 return fmt.Errorf("DeleteTable Failed: (Validater 3) Delete Regional Replica from %s Table %s Error, %s", region.Key(), tableName, err.Error()) 1210 } 1211 1212 ddbObj = d 1213 } 1214 1215 // prepare 1216 input := &ddb.DeleteTableInput{ 1217 TableName: aws.String(tableName), 1218 } 1219 1220 // execute 1221 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 1222 defer cancel() 1223 1224 if output, err := ddbObj.DeleteTable(input, ctx); err != nil { 1225 return fmt.Errorf("DeleteTable Failed: (Exec 1) %s", err.Error()) 1226 } else { 1227 if output == nil { 1228 return fmt.Errorf("DeleteTable Failed: (Exec 2) %s", "Output Response is Nil") 1229 } else { 1230 return nil 1231 } 1232 } 1233 } 1234 1235 // ListTables will return list of all dynamodb table names 1236 func (c *Crud) ListTables() ([]string, error) { 1237 outputData := new([]string) 1238 1239 if err := c.listTablesInternal(nil, outputData); err != nil { 1240 return []string{}, err 1241 } else { 1242 return *outputData, nil 1243 } 1244 } 1245 1246 func (c *Crud) listTablesInternal(exclusiveStartTableName *string, outputData *[]string) error { 1247 // validate 1248 if c._ddb == nil { 1249 return fmt.Errorf("listTablesInternal Failed: (Validater 1) Connection Not Established") 1250 } 1251 1252 // prepare 1253 input := &ddb.ListTablesInput{ 1254 ExclusiveStartTableName: exclusiveStartTableName, 1255 Limit: aws.Int64(100), 1256 } 1257 1258 if outputData == nil { 1259 outputData = new([]string) 1260 } 1261 1262 // execute 1263 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 1264 defer cancel() 1265 1266 if output, err := c._ddb.ListTables(input, ctx); err != nil { 1267 return fmt.Errorf("listTablesInternal Failed: (Exec 1) %s", err.Error()) 1268 } else { 1269 if output == nil { 1270 return fmt.Errorf("listTablesInternal Failed: (Exec 2) %s", "Output Response is Nil") 1271 } 1272 1273 for _, v := range output.TableNames { 1274 *outputData = append(*outputData, aws.StringValue(v)) 1275 } 1276 1277 if util.LenTrim(aws.StringValue(output.LastEvaluatedTableName)) > 0 { 1278 // more to query 1279 if err := c.listTablesInternal(output.LastEvaluatedTableName, outputData); err != nil { 1280 return err 1281 } else { 1282 return nil 1283 } 1284 } else { 1285 // no more query 1286 return nil 1287 } 1288 } 1289 } 1290 1291 // DescribeTable will describe the dynamodb table info based on input parameter values 1292 func (c *Crud) DescribeTable(tableName string) (*ddb.TableDescription, error) { 1293 // validate 1294 if c._ddb == nil { 1295 return nil, fmt.Errorf("DescribeTable Failed: (Validater 1) Connection Not Established") 1296 } 1297 1298 if util.LenTrim(tableName) == 0 { 1299 return nil, fmt.Errorf("DescribeTable Failed: (Validater 2) Table Name is Required") 1300 } 1301 1302 // prepare 1303 input := &ddb.DescribeTableInput{ 1304 TableName: aws.String(tableName), 1305 } 1306 1307 // execute 1308 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 1309 defer cancel() 1310 1311 if output, err := c._ddb.DescribeTable(input, ctx); err != nil { 1312 return nil, fmt.Errorf("DescribeTable Failed: (Exec 1) %s", err.Error()) 1313 } else { 1314 if output == nil { 1315 return nil, fmt.Errorf("DescribeTable Failed: (Exec 2) %s", "Output Response is Nil") 1316 } else { 1317 if output.Table == nil { 1318 return nil, fmt.Errorf("DescribeTable Failed: (Exec 3) %s", "Table Description From Output is Nil") 1319 } else { 1320 return output.Table, nil 1321 } 1322 } 1323 } 1324 } 1325 1326 // supportGlobalTable checks if input parameter supports dynamodb global table 1327 func (c *Crud) supportGlobalTable(region awsregion.AWSRegion) bool { 1328 if !region.Valid() && region != awsregion.UNKNOWN { 1329 return false 1330 } 1331 1332 if len(cachedGlobalTableSupportedRegions) == 0 { 1333 cachedGlobalTableSupportedRegions = []string{ 1334 awsregion.AWS_us_east_1_nvirginia.Key(), 1335 awsregion.AWS_us_west_2_oregon.Key(), 1336 awsregion.AWS_ap_southeast_1_singapore.Key(), 1337 awsregion.AWS_ap_northeast_1_tokyo.Key(), 1338 awsregion.AWS_ap_southeast_2_sydney.Key(), 1339 awsregion.AWS_eu_central_1_frankfurt.Key(), 1340 awsregion.AWS_eu_west_2_london.Key(), 1341 } 1342 } 1343 1344 return util.StringSliceContains(&cachedGlobalTableSupportedRegions, region.Key()) 1345 } 1346 1347 // CreateGlobalTable will create a new dynamodb global table based on input parameter values, 1348 // this function first creates the primary table in the current default region, 1349 // then this function creates the same table on replicaRegions identified. 1350 // 1351 // billing = default to PAY_PER_REQUEST (onDemand) 1352 // stream = enabled, with old and new images 1353 // 1354 // global table supported regions: 1355 // 1356 // us-east-1 (nvirginia), us-east-2 (ohio), us-west-1 (california), us-west-2 (oregon) 1357 // eu-west-2 (london), eu-central-1 (frankfurt), eu-west-1 (ireland) 1358 // ap-southeast-1 (singapore), ap-southeast-2 (sydney), ap-northeast-1 (tokyo), ap-northeast-2 (seoul) 1359 // 1360 // warning: do not first create the original table, this function creates the primary automatically 1361 func (c *Crud) CreateGlobalTable(tableName string, 1362 sse *ddb.SSESpecification, 1363 lsi []*ddb.LocalSecondaryIndex, 1364 gsi []*ddb.GlobalSecondaryIndex, 1365 attributes []*ddb.AttributeDefinition, 1366 replicaRegions []awsregion.AWSRegion) error { 1367 1368 // validate 1369 if c._ddb == nil { 1370 return fmt.Errorf("CreateGlobalTable Failed: (Validater 1) Connection Not Established") 1371 } 1372 1373 if util.LenTrim(tableName) == 0 { 1374 return fmt.Errorf("CreateGlobalTable Failed: (Validater 2) Global Table Name is Required") 1375 } 1376 1377 if !c.supportGlobalTable(c._ddb.AwsRegion) { 1378 return fmt.Errorf("CreateGlobalTable Failed: (Validater 3-1) Region %s Not Support Global Table", c._ddb.AwsRegion.Key()) 1379 } 1380 1381 for _, r := range replicaRegions { 1382 if r.Valid() && r != awsregion.UNKNOWN && !c.supportGlobalTable(r) { 1383 return fmt.Errorf("CreateGlobalTable Failed: (Validater 3-2) Region %s Not Support Global Table", r.Key()) 1384 } 1385 } 1386 1387 if sse != nil { 1388 if aws.BoolValue(sse.Enabled) { 1389 if sse.SSEType == nil { 1390 sse.SSEType = aws.String("KMS") 1391 } 1392 } 1393 } 1394 1395 if replicaRegions == nil { 1396 return fmt.Errorf("CreateGlobalTable Failed: (Validater 4) Regions List is Required") 1397 } 1398 1399 if len(replicaRegions) == 0 { 1400 return fmt.Errorf("CreateGlobalTable Failed: (Validater 5) Regions List is Required") 1401 } 1402 1403 // prepare 1404 input := &ddb.CreateGlobalTableInput{ 1405 GlobalTableName: aws.String(tableName), 1406 } 1407 1408 replicas := []*ddb.Replica{ 1409 { 1410 RegionName: aws.String(c._ddb.AwsRegion.Key()), 1411 }, 1412 } 1413 1414 for _, v := range replicaRegions { 1415 if v.Valid() && v != awsregion.UNKNOWN { 1416 replicas = append(replicas, &ddb.Replica{ 1417 RegionName: aws.String(v.Key()), 1418 }) 1419 } 1420 } 1421 1422 if len(replicas) == 0 { 1423 return fmt.Errorf("CreateGlobalTable Failed: (Validater 6) Replicas' Region List is Required") 1424 } 1425 1426 input.ReplicationGroup = replicas 1427 1428 // * 1429 // * create replica region tables before creating global table 1430 // * 1431 if err := c.CreateTable(tableName, true, 0, 0, sse, true, lsi, gsi, attributes); err != nil { 1432 return fmt.Errorf("CreateGlobalTable Failed: (Validater 7) Create Regional Primary Table Error, " + err.Error()) 1433 } 1434 1435 for _, r := range replicaRegions { 1436 if r.Valid() && r != awsregion.UNKNOWN && c._ddb.AwsRegion.Key() != r.Key() { 1437 d := &DynamoDB{ 1438 AwsRegion: r, 1439 TableName: tableName, 1440 PKName: "PK", 1441 SKName: "SK", 1442 HttpOptions: c._ddb.HttpOptions, 1443 SkipDax: true, 1444 DaxEndpoint: "", 1445 } 1446 1447 if err := d.connectInternal(); err != nil { 1448 return fmt.Errorf("CreateGlobalTable Failed: (Validater 8) Create Regional Replica to %s Table %s Error, %s", r.Key(), tableName, err.Error()) 1449 } 1450 1451 if err := c.CreateTable(tableName, true, 0, 0, sse, true, lsi, gsi, attributes, d); err != nil { 1452 return fmt.Errorf("CreateGlobalTable Failed: (Validater 9) Create Regional Replica to %s to Table %s Error, %s", r.Key(), tableName, err.Error()) 1453 } 1454 } 1455 } 1456 1457 // execute 1458 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 1459 defer cancel() 1460 1461 if output, err := c._ddb.CreateGlobalTable(input, ctx); err != nil { 1462 return fmt.Errorf("CreateGlobalTable Failed: (Exec 1) %s", err.Error()) 1463 } else { 1464 if output == nil { 1465 return fmt.Errorf("CreateGlobalTable Failed: (Exec 2) %s", "Output Response is Nil") 1466 } else { 1467 return nil 1468 } 1469 } 1470 } 1471 1472 // UpdateGlobalTable creates or deletes global table replicas 1473 // 1474 // if update is to create new global table regional replicas, the regional tables will auto create based on given table name, 1475 // then associate to global table 1476 // 1477 // if update is to delete existing global table regional replicas, the regional table will be removed from global replication, and actual table deleted 1478 // 1479 // global table supported regions: 1480 // 1481 // us-east-1 (nvirginia), us-east-2 (ohio), us-west-1 (california), us-west-2 (oregon) 1482 // eu-west-2 (london), eu-central-1 (frankfurt), eu-west-1 (ireland) 1483 // ap-southeast-1 (singapore), ap-southeast-2 (sydney), ap-northeast-1 (tokyo), ap-northeast-2 (seoul) 1484 // 1485 // warning: do not first create the new replica table when adding to global table, this function creates all the new replica tables automatically 1486 func (c *Crud) UpdateGlobalTable(tableName string, createRegions []awsregion.AWSRegion, deleteRegions []awsregion.AWSRegion) error { 1487 // validate 1488 if c._ddb == nil { 1489 return fmt.Errorf("UpdateGlobalTable Failed: (Validater 1) Connection Not Established") 1490 } 1491 1492 if util.LenTrim(tableName) == 0 { 1493 return fmt.Errorf("UpdateGlobalTable Failed: (Validater 2) Global Table Name is Required") 1494 } 1495 1496 if createRegions == nil && deleteRegions == nil { 1497 return fmt.Errorf("UpdateGlobalTable Failed: (Validater 3) Either Create Regions or Delete Regions List is Required") 1498 } 1499 1500 if len(createRegions) == 0 && len(deleteRegions) == 0 { 1501 return fmt.Errorf("UpdateGlobalTable Failed: (Validater 4) Either Create Regions or Delete Regions List is Required") 1502 } 1503 1504 if createRegions != nil && len(createRegions) > 0 { 1505 for _, r := range createRegions { 1506 if r.Valid() && r != awsregion.UNKNOWN && !c.supportGlobalTable(r) { 1507 return fmt.Errorf("UpdateGlobalTable Failed: (Validater 5) Region %s Not Support Global Table", r.Key()) 1508 } 1509 } 1510 } 1511 1512 // * 1513 // * create new regions 1514 // * 1515 if createRegions != nil && len(createRegions) > 0 { 1516 // load current region table description 1517 tblDesc, err := c.DescribeTable(tableName) 1518 1519 if err != nil { 1520 return fmt.Errorf("UpdateGlobalTable Failed: (Validater 6) Describe Current Region %s Table %s Failed, %s", c._ddb.AwsRegion.Key(), tableName, err.Error()) 1521 } 1522 1523 if tblDesc == nil { 1524 return fmt.Errorf("UpdateGlobalTable Failed: (Validater 7) Describe Current Region %s Table %s Failed, %s", c._ddb.AwsRegion.Key(), tableName, "Received Table Description is Nil") 1525 } 1526 1527 // create new tables in target regions based on tblDesc 1528 var sse *ddb.SSESpecification 1529 1530 if tblDesc.SSEDescription != nil { 1531 if aws.StringValue(tblDesc.SSEDescription.Status) == "ENABLED" { 1532 sse = new(ddb.SSESpecification) 1533 sse.Enabled = aws.Bool(true) 1534 1535 sse.SSEType = tblDesc.SSEDescription.SSEType 1536 sse.KMSMasterKeyId = tblDesc.SSEDescription.KMSMasterKeyArn 1537 } 1538 } 1539 1540 var lsi []*ddb.LocalSecondaryIndex 1541 1542 if tblDesc.LocalSecondaryIndexes != nil && len(tblDesc.LocalSecondaryIndexes) > 0 { 1543 for _, v := range tblDesc.LocalSecondaryIndexes { 1544 if v != nil { 1545 lsi = append(lsi, &ddb.LocalSecondaryIndex{ 1546 IndexName: v.IndexName, 1547 KeySchema: v.KeySchema, 1548 Projection: v.Projection, 1549 }) 1550 } 1551 } 1552 } 1553 1554 var gsi []*ddb.GlobalSecondaryIndex 1555 1556 if tblDesc.GlobalSecondaryIndexes != nil && len(tblDesc.GlobalSecondaryIndexes) > 0 { 1557 for _, v := range tblDesc.GlobalSecondaryIndexes { 1558 if v != nil { 1559 gsi = append(gsi, &ddb.GlobalSecondaryIndex{ 1560 IndexName: v.IndexName, 1561 KeySchema: v.KeySchema, 1562 Projection: v.Projection, 1563 }) 1564 } 1565 } 1566 } 1567 1568 var attributes []*ddb.AttributeDefinition 1569 1570 if tblDesc.AttributeDefinitions != nil && len(tblDesc.AttributeDefinitions) > 0 { 1571 for _, v := range tblDesc.AttributeDefinitions { 1572 if v != nil && strings.ToUpper(aws.StringValue(v.AttributeName)) != "PK" && strings.ToUpper(aws.StringValue(v.AttributeName)) != "SK" { 1573 attributes = append(attributes, &ddb.AttributeDefinition{ 1574 AttributeName: v.AttributeName, 1575 AttributeType: v.AttributeType, 1576 }) 1577 } 1578 } 1579 } 1580 1581 for _, r := range createRegions { 1582 if r.Valid() && r != awsregion.UNKNOWN && c._ddb.AwsRegion.Key() != r.Key() { 1583 d := &DynamoDB{ 1584 AwsRegion: r, 1585 TableName: tableName, 1586 PKName: "PK", 1587 SKName: "SK", 1588 HttpOptions: c._ddb.HttpOptions, 1589 SkipDax: true, 1590 DaxEndpoint: "", 1591 } 1592 1593 if err := d.connectInternal(); err != nil { 1594 return fmt.Errorf("UpdateGlobalTable Failed: (Validater 8) Create Regional Replica to %s Table %s Error, %s", r.Key(), tableName, err.Error()) 1595 } 1596 1597 if err := c.CreateTable(tableName, true, 0, 0, sse, true, lsi, gsi, attributes, d); err != nil { 1598 return fmt.Errorf("UpdateGlobalTable Failed: (Validater 9) Create Regional Replica to %s to Table %s Error, %s", r.Key(), tableName, err.Error()) 1599 } 1600 } 1601 } 1602 } 1603 1604 // * 1605 // * construct replicaUpdates slice 1606 // * 1607 updates := []*ddb.ReplicaUpdate{} 1608 1609 if createRegions != nil && len(createRegions) > 0 { 1610 for _, r := range createRegions { 1611 if r.Valid() && r != awsregion.UNKNOWN { 1612 updates = append(updates, &ddb.ReplicaUpdate{ 1613 Create: &ddb.CreateReplicaAction{ 1614 RegionName: aws.String(r.Key()), 1615 }, 1616 }) 1617 } 1618 } 1619 } 1620 1621 if deleteRegions != nil && len(deleteRegions) > 0 { 1622 for _, r := range deleteRegions { 1623 if r.Valid() && r != awsregion.UNKNOWN { 1624 updates = append(updates, &ddb.ReplicaUpdate{ 1625 Delete: &ddb.DeleteReplicaAction{ 1626 RegionName: aws.String(r.Key()), 1627 }, 1628 }) 1629 } 1630 } 1631 } 1632 1633 // prepare 1634 input := &ddb.UpdateGlobalTableInput{ 1635 GlobalTableName: aws.String(tableName), 1636 ReplicaUpdates: updates, 1637 } 1638 1639 // execute 1640 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 1641 defer cancel() 1642 1643 if output, err := c._ddb.UpdateGlobalTable(input, ctx); err != nil { 1644 return fmt.Errorf("UpdateGlobalTable Failed: (Exec 1) %s", err.Error()) 1645 } else { 1646 if output == nil { 1647 return fmt.Errorf("UpdateGlobalTable Failed: (Exec 2) %s", "Output Response is Nil") 1648 } else { 1649 // * 1650 // * if there are replica deletes, delete source tables here 1651 // * 1652 m := "" 1653 1654 if deleteRegions != nil && len(deleteRegions) > 0 { 1655 for _, r := range deleteRegions { 1656 if r.Valid() && r != awsregion.UNKNOWN { 1657 if err := c.DeleteTable(tableName, r); err != nil { 1658 if util.LenTrim(m) > 0 { 1659 m += "; " 1660 } 1661 1662 m += fmt.Sprintf("Delete Regional Replica Table %s From %s Failed (%s)", tableName, r.Key(), err.Error()) 1663 } 1664 } 1665 } 1666 } 1667 1668 if util.LenTrim(m) > 0 { 1669 m = "UpdateGlobalTable Needs Clean Up;" + m + "; Clean Up By Manual Delete From AWS DynamoDB Console" 1670 return fmt.Errorf(m) 1671 } else { 1672 return nil 1673 } 1674 } 1675 } 1676 } 1677 1678 // ListGlobalTables will return list of all dynamodb global table names 1679 // 1680 // global table supported regions: 1681 // 1682 // us-east-1 (nvirginia), us-east-2 (ohio), us-west-1 (california), us-west-2 (oregon) 1683 // eu-west-2 (london), eu-central-1 (frankfurt), eu-west-1 (ireland) 1684 // ap-southeast-1 (singapore), ap-southeast-2 (sydney), ap-northeast-1 (tokyo), ap-northeast-2 (seoul) 1685 func (c *Crud) ListGlobalTables(filterRegion ...awsregion.AWSRegion) ([]*GlobalTableInfo, error) { 1686 outputData := new([]*GlobalTableInfo) 1687 1688 region := awsregion.UNKNOWN 1689 1690 if len(filterRegion) > 0 { 1691 region = filterRegion[0] 1692 } 1693 1694 if region.Valid() && region != awsregion.UNKNOWN { 1695 if !c.supportGlobalTable(region) { 1696 return []*GlobalTableInfo{}, fmt.Errorf("ListGlobalTables Failed: (Validater 1) Region %s Not Support Global Table", region.Key()) 1697 } 1698 } 1699 1700 if err := c.listGlobalTablesInternal(region, nil, outputData); err != nil { 1701 return []*GlobalTableInfo{}, err 1702 } else { 1703 return *outputData, nil 1704 } 1705 } 1706 1707 func (c *Crud) listGlobalTablesInternal(filterRegion awsregion.AWSRegion, exclusiveStartGlobalTableName *string, outputData *[]*GlobalTableInfo) error { 1708 // validate 1709 if c._ddb == nil { 1710 return fmt.Errorf("listGlobalTablesInternal Failed: (Validater 1) Connection Not Established") 1711 } 1712 1713 if outputData == nil { 1714 outputData = new([]*GlobalTableInfo) 1715 } 1716 1717 // prepare 1718 input := &ddb.ListGlobalTablesInput{ 1719 ExclusiveStartGlobalTableName: exclusiveStartGlobalTableName, 1720 Limit: aws.Int64(100), 1721 } 1722 1723 if filterRegion.Valid() && filterRegion != awsregion.UNKNOWN { 1724 input.RegionName = aws.String(filterRegion.Key()) 1725 } 1726 1727 // execute 1728 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 1729 defer cancel() 1730 1731 if output, err := c._ddb.ListGlobalTables(input, ctx); err != nil { 1732 return fmt.Errorf("listGlobalTablesInternal Failed: (Exec 1) %s", err.Error()) 1733 } else { 1734 if output == nil { 1735 return fmt.Errorf("listGlobalTablesInternal Failed: (Exec 2) %s", "Output Response is Nil") 1736 } 1737 1738 for _, v := range output.GlobalTables { 1739 if v != nil { 1740 g := &GlobalTableInfo{TableName: aws.StringValue(v.GlobalTableName)} 1741 1742 for _, r := range v.ReplicationGroup { 1743 if r != nil && r.RegionName != nil { 1744 if rv := awsregion.GetAwsRegion(aws.StringValue(r.RegionName)); rv.Valid() && rv != awsregion.UNKNOWN { 1745 g.Regions = append(g.Regions, rv) 1746 } 1747 } 1748 } 1749 1750 *outputData = append(*outputData, g) 1751 } 1752 } 1753 1754 if util.LenTrim(aws.StringValue(output.LastEvaluatedGlobalTableName)) > 0 { 1755 // more to query 1756 if err := c.listGlobalTablesInternal(filterRegion, output.LastEvaluatedGlobalTableName, outputData); err != nil { 1757 return err 1758 } else { 1759 return nil 1760 } 1761 } else { 1762 // no more query 1763 return nil 1764 } 1765 } 1766 } 1767 1768 // DescribeGlobalTable will describe the dynamodb global table info based on input parameter values 1769 func (c *Crud) DescribeGlobalTable(tableName string) (*ddb.GlobalTableDescription, error) { 1770 // validate 1771 if c._ddb == nil { 1772 return nil, fmt.Errorf("DescribeGlobalTable Failed: (Validater 1) Connection Not Established") 1773 } 1774 1775 if util.LenTrim(tableName) == 0 { 1776 return nil, fmt.Errorf("DescribeGlobalTable Failed: (Validater 2) Global Table Name is Required") 1777 } 1778 1779 // prepare 1780 input := &ddb.DescribeGlobalTableInput{ 1781 GlobalTableName: aws.String(tableName), 1782 } 1783 1784 // execute 1785 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 1786 defer cancel() 1787 1788 if output, err := c._ddb.DescribeGlobalTable(input, ctx); err != nil { 1789 return nil, fmt.Errorf("DescribeGlobalTable Failed: (Exec 1) %s", err.Error()) 1790 } else { 1791 if output == nil { 1792 return nil, fmt.Errorf("DescribeGlobalTable Failed: (Exec 2) %s", "Output Response is Nil") 1793 } else { 1794 if output.GlobalTableDescription == nil { 1795 return nil, fmt.Errorf("DescribeGlobalTable Failed: (Exec 3) %s", "Global Table Description From Output is Nil") 1796 } else { 1797 return output.GlobalTableDescription, nil 1798 } 1799 } 1800 } 1801 } 1802 1803 // CreateBackup creates dynamodb backup based on the given input parameter 1804 func (c *Crud) CreateBackup(tableName string, backupName string) (backupArn string, err error) { 1805 // validate 1806 if c._ddb == nil { 1807 return "", fmt.Errorf("CreateBackup Failed: (Validater 1) Connection Not Established") 1808 } 1809 1810 if util.LenTrim(tableName) == 0 { 1811 return "", fmt.Errorf("CreateBackup Failed: (Validater 3) Table Name is Required") 1812 } 1813 1814 if util.LenTrim(backupName) == 0 { 1815 return "", fmt.Errorf("CreateBackup Failed: (Validater 3) Backup Name is Required") 1816 } 1817 1818 // prepare 1819 input := &ddb.CreateBackupInput{ 1820 TableName: aws.String(tableName), 1821 BackupName: aws.String(backupName), 1822 } 1823 1824 // execute 1825 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 1826 defer cancel() 1827 1828 if output, err := c._ddb.CreateBackup(input, ctx); err != nil { 1829 return "", fmt.Errorf("CreateBackup Failed: (Exec 1) %s", err.Error()) 1830 } else { 1831 if output == nil { 1832 return "", fmt.Errorf("CreateBackup Failed: (Exec 2) %s", "Output Response is Nil") 1833 } else if output.BackupDetails == nil { 1834 return "", fmt.Errorf("CreateBackup Failed: (Exec 3) %s", "Backup Details in Output Response is Nil") 1835 } else { 1836 return aws.StringValue(output.BackupDetails.BackupArn), nil 1837 } 1838 } 1839 } 1840 1841 // DeleteBackup deletes dynamodb backup based on the given input parameter 1842 func (c *Crud) DeleteBackup(backupArn string) error { 1843 // validate 1844 if c._ddb == nil { 1845 return fmt.Errorf("DeleteBackup Failed: (Validater 1) Connection Not Established") 1846 } 1847 1848 if util.LenTrim(backupArn) == 0 { 1849 return fmt.Errorf("DeleteBackup Failed: (Validater 2) BackupArn is Required") 1850 } 1851 1852 // prepare 1853 input := &ddb.DeleteBackupInput{ 1854 BackupArn: aws.String(backupArn), 1855 } 1856 1857 // execute 1858 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 1859 defer cancel() 1860 1861 if output, err := c._ddb.DeleteBackup(input, ctx); err != nil { 1862 return fmt.Errorf("DeleteBackup Failed: (Exec 1) %s", err.Error()) 1863 } else { 1864 if output == nil { 1865 return fmt.Errorf("DeleteBackup Failed: (Exec 2) %s", "Output Response is Nil") 1866 } else { 1867 return nil 1868 } 1869 } 1870 } 1871 1872 // ListBackups lists dynamodb backups based on the given input parameter 1873 func (c *Crud) ListBackups(tableNameFilter string, fromTime *time.Time, toTime *time.Time) ([]*ddb.BackupSummary, error) { 1874 outputData := new([]*ddb.BackupSummary) 1875 1876 var tableName *string 1877 1878 if util.LenTrim(tableNameFilter) > 0 { 1879 tableName = aws.String(tableNameFilter) 1880 } 1881 1882 if err := c.listBackupsInternal(tableName, fromTime, toTime, nil, outputData); err != nil { 1883 return []*ddb.BackupSummary{}, err 1884 } else { 1885 return *outputData, nil 1886 } 1887 } 1888 1889 // listBackupsInternal handles dynamodb backups listing internal logic 1890 func (c *Crud) listBackupsInternal(tableNameFilter *string, fromTime *time.Time, toTime *time.Time, 1891 exclusiveStartBackupArn *string, outputData *[]*ddb.BackupSummary) error { 1892 1893 // validate 1894 if c._ddb == nil { 1895 return fmt.Errorf("listBackupsInternal Failed: (Validater 1) Connection Not Established") 1896 } 1897 1898 // prepare 1899 input := &ddb.ListBackupsInput{ 1900 BackupType: aws.String("ALL"), 1901 Limit: aws.Int64(25), 1902 TableName: tableNameFilter, 1903 TimeRangeLowerBound: fromTime, 1904 TimeRangeUpperBound: toTime, 1905 ExclusiveStartBackupArn: exclusiveStartBackupArn, 1906 } 1907 1908 // execute 1909 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 1910 defer cancel() 1911 1912 if output, err := c._ddb.ListBackups(input, ctx); err != nil { 1913 return fmt.Errorf("listBackupsInternal Failed: (Exec 1) %s", err.Error()) 1914 } else { 1915 if output == nil { 1916 return fmt.Errorf("listBackupsInternal Failed: (Exec 2) %s", "Output Response is Nil") 1917 } 1918 1919 for _, v := range output.BackupSummaries { 1920 if v != nil { 1921 *outputData = append(*outputData, v) 1922 } 1923 } 1924 1925 if util.LenTrim(aws.StringValue(output.LastEvaluatedBackupArn)) > 0 { 1926 // more to query 1927 if err := c.listBackupsInternal(tableNameFilter, fromTime, toTime, output.LastEvaluatedBackupArn, outputData); err != nil { 1928 return err 1929 } else { 1930 return nil 1931 } 1932 } else { 1933 // no more query 1934 return nil 1935 } 1936 } 1937 } 1938 1939 // DescribeBackup describes a given dynamodb backup info 1940 func (c *Crud) DescribeBackup(backupArn string) (*ddb.BackupDescription, error) { 1941 // validate 1942 if c._ddb == nil { 1943 return nil, fmt.Errorf("DescribeBackup Failed: (Validater 1) Connection Not Established") 1944 } 1945 1946 if util.LenTrim(backupArn) == 0 { 1947 return nil, fmt.Errorf("DescribeBackup Failed: (Validater 2) BackupArn is Required") 1948 } 1949 1950 // prepare 1951 input := &ddb.DescribeBackupInput{ 1952 BackupArn: aws.String(backupArn), 1953 } 1954 1955 // execute 1956 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 1957 defer cancel() 1958 1959 if output, err := c._ddb.DescribeBackup(input, ctx); err != nil { 1960 return nil, fmt.Errorf("DescribeBackup Failed: (Exec 1) %s", err.Error()) 1961 } else { 1962 if output == nil { 1963 return nil, fmt.Errorf("DescribeBackup Failed: (Exec 2) %s", "Output Response is Nil") 1964 } else { 1965 if output.BackupDescription == nil { 1966 return nil, fmt.Errorf("DescribeBackup Failed: (Exec 3) %s", "Backup Description From Output is Nil") 1967 } else { 1968 return output.BackupDescription, nil 1969 } 1970 } 1971 } 1972 } 1973 1974 // UpdatePointInTimeBackup updates dynamodb continuous backup options (point in time recovery) based on the given input parameter 1975 func (c *Crud) UpdatePointInTimeBackup(tableName string, pointInTimeRecoveryEnabled bool) error { 1976 // validate 1977 if c._ddb == nil { 1978 return fmt.Errorf("UpdatePointInTimeBackup Failed: (Validater 1) Connection Not Established") 1979 } 1980 1981 if util.LenTrim(tableName) == 0 { 1982 return fmt.Errorf("UpdatePointInTimeBackup Failed: (Validater 2) Table Name is Required") 1983 } 1984 1985 // prepare 1986 input := &ddb.UpdateContinuousBackupsInput{ 1987 TableName: aws.String(tableName), 1988 PointInTimeRecoverySpecification: &ddb.PointInTimeRecoverySpecification{ 1989 PointInTimeRecoveryEnabled: aws.Bool(pointInTimeRecoveryEnabled), 1990 }, 1991 } 1992 1993 // execute 1994 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 1995 defer cancel() 1996 1997 if output, err := c._ddb.UpdatePointInTimeBackup(input, ctx); err != nil { 1998 return fmt.Errorf("UpdatePointInTimeBackup Failed: (Exec 1) %s", err.Error()) 1999 } else { 2000 if output == nil { 2001 return fmt.Errorf("UpdatePointInTimeBackup Failed: (Exec 2) %s", "Output Response is Nil") 2002 } else if output.ContinuousBackupsDescription == nil { 2003 return fmt.Errorf("UpdatePointInTimeBackup Failed: (Exec 3) %s", "Continuous Backup Description in Output Response is Nil") 2004 } else { 2005 return nil 2006 } 2007 } 2008 }