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  }