github.com/aldelo/common@v1.5.1/wrapper/dynamodb/dynamodb.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  // =================================================================================================================
    20  // AWS CREDENTIAL:
    21  //		use $> aws configure (to set aws access key and secret to target machine)
    22  //		Store AWS Access ID and Secret Key into Default Profile Using '$ aws configure' cli
    23  //
    24  // To Install & Setup AWS CLI on Host:
    25  //		1) https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2-linux.html
    26  //				On Ubuntu, if host does not have zip and unzip:
    27  //					$> sudo apt install zip
    28  //					$> sudo apt install unzip
    29  //				On Ubuntu, to install AWS CLI v2:
    30  //					$> curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
    31  //					$> unzip awscliv2.zip
    32  //					$> sudo ./aws/install
    33  //		2) $> aws configure set region awsRegionName --profile default
    34  // 		3) $> aws configure
    35  //				follow prompts to enter Access ID and Secret Key
    36  //
    37  // AWS Region Name Reference:
    38  //		us-west-2, us-east-1, ap-northeast-1, etc
    39  //		See: https://docs.aws.amazon.com/general/latest/gr/rande.html
    40  // =================================================================================================================
    41  
    42  import (
    43  	"context"
    44  	"errors"
    45  	"fmt"
    46  	util "github.com/aldelo/common"
    47  	awshttp2 "github.com/aldelo/common/wrapper/aws"
    48  	"github.com/aldelo/common/wrapper/aws/awsregion"
    49  	"github.com/aldelo/common/wrapper/xray"
    50  	"github.com/aws/aws-dax-go/dax"
    51  	"github.com/aws/aws-sdk-go/aws"
    52  	"github.com/aws/aws-sdk-go/aws/awserr"
    53  	"github.com/aws/aws-sdk-go/aws/session"
    54  	"github.com/aws/aws-sdk-go/service/dynamodb"
    55  	"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
    56  	"github.com/aws/aws-sdk-go/service/dynamodb/expression"
    57  	awsxray "github.com/aws/aws-xray-sdk-go/xray"
    58  	"log"
    59  	"net/http"
    60  	"reflect"
    61  	"time"
    62  )
    63  
    64  // ================================================================================================================
    65  // STRUCTS
    66  // ================================================================================================================
    67  
    68  // DynamoDB struct encapsulates the AWS DynamoDB access functionality
    69  //
    70  // Notes:
    71  //  1. to use dax, must be within vpc with dax cluster subnet pointing to private ip subnet of the vpc
    72  //  2. dax is not accessible outside of vpc
    73  //  3. on ec2 or container within vpc, also need aws credential via aws cli too = aws configure
    74  type DynamoDB struct {
    75  	// define the AWS region that dynamodb is serviced from
    76  	AwsRegion awsregion.AWSRegion
    77  
    78  	// custom http2 client options
    79  	HttpOptions *awshttp2.HttpClientSettings
    80  
    81  	// define the Dax Endpoint (required if using DAX)
    82  	DaxEndpoint string
    83  
    84  	// dynamodb connection object
    85  	cn *dynamodb.DynamoDB
    86  
    87  	// dax connection object
    88  	cnDax *dax.Dax
    89  
    90  	// if dax is enabled, skip dax will skip dax and route direct to DynamoDB
    91  	// if dax is not enabled, skip dax true or not will always route to DynamoDB
    92  	SkipDax bool
    93  
    94  	// operating table
    95  	TableName string
    96  	PKName    string
    97  	SKName    string
    98  
    99  	// last execute param string
   100  	LastExecuteParamsPayload string
   101  
   102  	_parentSegment *xray.XRayParentSegment
   103  }
   104  
   105  // DynamoDBError struct contains special status info including error and retry advise
   106  type DynamoDBError struct {
   107  	ErrorMessage  string
   108  	SuppressError bool
   109  
   110  	AllowRetry        bool
   111  	RetryNeedsBackOff bool
   112  }
   113  
   114  // Error returns error string of the struct object
   115  func (e *DynamoDBError) Error() string {
   116  	return e.ErrorMessage
   117  }
   118  
   119  // DynamoDBTableKeys struct defines the PK and SK fields to be used in key search (Always PK and SK)
   120  //
   121  // important
   122  //
   123  //	if dynamodb table is defined as PK and SK together, then to search, MUST use PK and SK together or error will trigger
   124  //
   125  // ResultItemPtr = optional, used with TransactionGetItems() to denote output unmarshal object target
   126  type DynamoDBTableKeys struct {
   127  	PK string
   128  	SK string
   129  
   130  	ResultItemPtr interface{} `dynamodbav:"-"`
   131  	ResultError   error       `dynamodbav:"-"`
   132  
   133  	resultProcessed bool
   134  }
   135  
   136  // DynamoDBUnprocessedItemsAndKeys defines struct to slices of items and keys
   137  type DynamoDBUnprocessedItemsAndKeys struct {
   138  	PutItems   []map[string]*dynamodb.AttributeValue
   139  	DeleteKeys []*DynamoDBTableKeys
   140  }
   141  
   142  // UnmarshalPutItems will convert struct's PutItems into target slice of struct objects
   143  //
   144  // notes:
   145  //
   146  //	resultItemsPtr interface{} = Input is Slice of Actual Struct Objects
   147  func (u *DynamoDBUnprocessedItemsAndKeys) UnmarshalPutItems(resultItemsPtr interface{}) error {
   148  	if u == nil {
   149  		return errors.New("UnmarshalPutItems Failed: (Validate) " + "DynamoDBUnprocessedItemsAndKeys Object Nil")
   150  	}
   151  
   152  	if resultItemsPtr == nil {
   153  		return errors.New("UnmarshalPutItems Failed: (Validate) " + "ResultItems Object Nil")
   154  	}
   155  
   156  	if err := dynamodbattribute.UnmarshalListOfMaps(u.PutItems, resultItemsPtr); err != nil {
   157  		return errors.New("UnmarshalPutItems Failed: (Unmarshal) " + err.Error())
   158  	} else {
   159  		// success
   160  		return nil
   161  	}
   162  }
   163  
   164  // DynamoDBUpdateItemInput defines a single item update instruction
   165  //
   166  // important
   167  //
   168  //	if dynamodb table is defined as PK and SK together, then to search, MUST use PK and SK together or error will trigger
   169  //
   170  // parameters:
   171  //
   172  //	pkValue = required, value of partition key to seek
   173  //	skValue = optional, value of sort key to seek; set to blank if value not provided
   174  //
   175  //	updateExpression = required, ATTRIBUTES ARE CASE SENSITIVE; set remove add or delete action expression, see Rules URL for full detail
   176  //		Rules:
   177  //			1) https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html
   178  //		Usage Syntax:
   179  //			1) Action Keywords are: set, add, remove, delete
   180  //			2) Each Action Keyword May Appear in UpdateExpression Only Once
   181  //			3) Each Action Keyword Grouping May Contain One or More Actions, Such as 'set price=:p, age=:age, etc' (each action separated by comma)
   182  //			4) Each Action Keyword Always Begin with Action Keyword itself, such as 'set ...', 'add ...', etc
   183  //			5) If Attribute is Numeric, Action Can Perform + or - Operation in Expression, such as 'set age=age-:newAge, price=price+:price, etc'
   184  //			6) If Attribute is Slice, Action Can Perform Slice Element Operation in Expression, such as 'set age[2]=:newData, etc'
   185  //			7) When Attribute Name is Reserved Keyword, Use ExpressionAttributeNames to Define #xyz to Alias
   186  //				a) Use the #xyz in the KeyConditionExpression such as #yr = :year (:year is Defined ExpressionAttributeValue)
   187  //			8) When Attribute is a List, Use list_append(a, b, ...) in Expression to append elements (list_append() is case sensitive)
   188  //				a) set #ri = list_append(#ri, :vals) where :vals represents one or more of elements to add as in L
   189  //			9) if_not_exists(path, value)
   190  //				a) Avoids existing attribute if already exists
   191  //				b) set price = if_not_exists(price, :p)
   192  //				c) if_not_exists is case sensitive; path is the existing attribute to check
   193  //			10) Action Type Purposes
   194  //				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
   195  //				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
   196  //				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
   197  //				d) DELETE = supports only on set data types; deletes one or more elements from a set, such as 'delete color :c'
   198  //			11) Example
   199  //				a) set age=:age, name=:name, etc
   200  //				b) set age=age-:age, num=num+:num, etc
   201  //
   202  //	conditionExpress = optional, ATTRIBUTES ARE CASE SENSITIVE; sets conditions for this condition expression, set to blank if not used
   203  //			Usage Syntax:
   204  //				1) "size(info.actors) >= :num"
   205  //					a) When Length of Actors Attribute Value is Equal or Greater Than :num, ONLY THEN UpdateExpression is Performed
   206  //				2) ExpressionAttributeName and ExpressionAttributeValue is Still Defined within ExpressionAttributeNames and ExpressionAttributeValues Where Applicable
   207  //
   208  //	expressionAttributeNames = optional, ATTRIBUTES ARE CASE SENSITIVE; set nil if not used, must define for attribute names that are reserved keywords such as year, data etc. using #xyz
   209  //		Usage Syntax:
   210  //			1) map[string]*string: where string is the #xyz, and *string is the original xyz attribute name
   211  //				a) map[string]*string { "#xyz": aws.String("Xyz"), }
   212  //			2) Add to Map
   213  //				a) m := make(map[string]*string)
   214  //				b) m["#xyz"] = aws.String("Xyz")
   215  //
   216  //	expressionAttributeValues = required, ATTRIBUTES ARE CASE SENSITIVE; sets the value token and value actual to be used within the keyConditionExpression; this sets both compare token and compare value
   217  //		Usage Syntax:
   218  //			1) map[string]*dynamodb.AttributeValue: where string is the :xyz, and *dynamodb.AttributeValue is { S: aws.String("abc"), },
   219  //				a) map[string]*dynamodb.AttributeValue { ":xyz" : { S: aws.String("abc"), }, ":xyy" : { N: aws.String("123"), }, }
   220  //			2) Add to Map
   221  //				a) m := make(map[string]*dynamodb.AttributeValue)
   222  //				b) m[":xyz"] = &dynamodb.AttributeValue{ S: aws.String("xyz") }
   223  //			3) Slice of Strings -> CONVERT To Slice of *dynamodb.AttributeValue = []string -> []*dynamodb.AttributeValue
   224  //				a) av, err := dynamodbattribute.MarshalList(xyzSlice)
   225  //				b) ExpressionAttributeValue, Use 'L' To Represent the List for av defined in 3.a above
   226  type DynamoDBUpdateItemInput struct {
   227  	PK                        string
   228  	SK                        string
   229  	UpdateExpression          string
   230  	ConditionExpression       string
   231  	ExpressionAttributeNames  map[string]*string
   232  	ExpressionAttributeValues map[string]*dynamodb.AttributeValue
   233  }
   234  
   235  // DynamoDBTransactionWrites defines one or more items to put, update or delete
   236  //
   237  // notes
   238  //
   239  //	PutItems interface{} = is Slice of PutItems: []Xyz
   240  //		a) We use interface{} because []interface{} will require each element conversion (instead we will handle conversion by internal code)
   241  //		b) PutItems ALWAYS Slice of Struct (Value), NOT pointers to Structs
   242  type DynamoDBTransactionWrites struct {
   243  	PutItems          interface{}
   244  	UpdateItems       []*DynamoDBUpdateItemInput
   245  	DeleteItems       []*DynamoDBTableKeys
   246  	TableNameOverride string
   247  }
   248  
   249  // MarshalPutItems will marshal dynamodb transaction writes' put items into []map[string]*dynamodb.AttributeValue
   250  func (w *DynamoDBTransactionWrites) MarshalPutItems() (result []map[string]*dynamodb.AttributeValue, err error) {
   251  	if w == nil {
   252  		return nil, errors.New("MarshalPutItems Failed: (Validate) " + "DynamoDBTransactionWrites Object Nil")
   253  	}
   254  
   255  	// validate
   256  	if w.PutItems == nil {
   257  		// no PutItems
   258  		return nil, nil
   259  	}
   260  
   261  	// get []interface{}
   262  	itemsIf := util.SliceObjectsToSliceInterface(w.PutItems)
   263  
   264  	if itemsIf == nil {
   265  		// no PutItems
   266  		return nil, errors.New("MarshalPutItems Failed: (Slice Convert) " + "Interface Slice Nil")
   267  	}
   268  
   269  	if len(itemsIf) <= 0 {
   270  		// no PutItems
   271  		return nil, nil
   272  	}
   273  
   274  	// loop thru each put item to marshal
   275  	for _, v := range itemsIf {
   276  		if m, e := dynamodbattribute.MarshalMap(v); e != nil {
   277  			return nil, errors.New("MarshalPutItems Failed: (Marshal) " + e.Error())
   278  		} else {
   279  			if m != nil {
   280  				result = append(result, m)
   281  			} else {
   282  				return nil, errors.New("MarshalPutItems Failed: (Marshal) " + "Marshaled Result Nil")
   283  			}
   284  		}
   285  	}
   286  
   287  	// return result
   288  	return result, nil
   289  }
   290  
   291  // DynamoDBTransactionReads defines one or more items to get by PK / SK
   292  type DynamoDBTransactionReads struct {
   293  	Keys              []*DynamoDBTableKeys
   294  	TableNameOverride string
   295  }
   296  
   297  // ================================================================================================================
   298  // STRUCTS FUNCTIONS
   299  // ================================================================================================================
   300  
   301  // ----------------------------------------------------------------------------------------------------------------
   302  // utility functions
   303  // ----------------------------------------------------------------------------------------------------------------
   304  
   305  // handleError is an internal helper method to evaluate dynamodb error,
   306  // and to advise if retry, immediate retry, suppress error etc error handling advisory
   307  //
   308  // notes:
   309  //
   310  //	RetryNeedsBackOff = true indicates when doing retry, must wait an arbitrary time duration before retry; false indicates immediate is ok
   311  func (d *DynamoDB) handleError(err error, errorPrefix ...string) *DynamoDBError {
   312  	if err != nil {
   313  		prefix := ""
   314  
   315  		if len(errorPrefix) > 0 {
   316  			prefix = errorPrefix[0] + " "
   317  		}
   318  
   319  		prefixType := ""
   320  		origError := ""
   321  
   322  		if aerr, ok := err.(awserr.Error); ok {
   323  			// aws errors
   324  			prefixType = "[AWS] "
   325  
   326  			if aerr.OrigErr() != nil {
   327  				origError = "OrigErr = " + aerr.OrigErr().Error()
   328  			} else {
   329  				origError = "OrigErr = Nil"
   330  			}
   331  
   332  			switch aerr.Code() {
   333  			case dynamodb.ErrCodeConditionalCheckFailedException:
   334  				fallthrough
   335  			case dynamodb.ErrCodeResourceInUseException:
   336  				fallthrough
   337  			case dynamodb.ErrCodeResourceNotFoundException:
   338  				fallthrough
   339  			case dynamodb.ErrCodeIdempotentParameterMismatchException:
   340  				fallthrough
   341  			case dynamodb.ErrCodeBackupInUseException:
   342  				fallthrough
   343  			case dynamodb.ErrCodeBackupNotFoundException:
   344  				fallthrough
   345  			case dynamodb.ErrCodeContinuousBackupsUnavailableException:
   346  				fallthrough
   347  			case dynamodb.ErrCodeGlobalTableAlreadyExistsException:
   348  				fallthrough
   349  			case dynamodb.ErrCodeGlobalTableNotFoundException:
   350  				fallthrough
   351  			case dynamodb.ErrCodeIndexNotFoundException:
   352  				fallthrough
   353  			case dynamodb.ErrCodeInvalidRestoreTimeException:
   354  				fallthrough
   355  			case dynamodb.ErrCodePointInTimeRecoveryUnavailableException:
   356  				fallthrough
   357  			case dynamodb.ErrCodeReplicaAlreadyExistsException:
   358  				fallthrough
   359  			case dynamodb.ErrCodeReplicaNotFoundException:
   360  				fallthrough
   361  			case dynamodb.ErrCodeTableAlreadyExistsException:
   362  				fallthrough
   363  			case dynamodb.ErrCodeTableInUseException:
   364  				fallthrough
   365  			case dynamodb.ErrCodeTableNotFoundException:
   366  				fallthrough
   367  			case dynamodb.ErrCodeTransactionCanceledException:
   368  				fallthrough
   369  			case dynamodb.ErrCodeTransactionConflictException:
   370  				fallthrough
   371  			case dynamodb.ErrCodeTransactionInProgressException:
   372  				// show error + no retry
   373  				return &DynamoDBError{
   374  					ErrorMessage:      prefix + prefixType + aerr.Code() + " - " + aerr.Message() + " - " + origError,
   375  					SuppressError:     false,
   376  					AllowRetry:        false,
   377  					RetryNeedsBackOff: false,
   378  				}
   379  
   380  			case dynamodb.ErrCodeItemCollectionSizeLimitExceededException:
   381  				fallthrough
   382  			case dynamodb.ErrCodeLimitExceededException:
   383  				// show error + allow retry with backoff
   384  				return &DynamoDBError{
   385  					ErrorMessage:      prefix + prefixType + aerr.Code() + " - " + aerr.Message() + " - " + origError,
   386  					SuppressError:     false,
   387  					AllowRetry:        true,
   388  					RetryNeedsBackOff: true,
   389  				}
   390  
   391  			case dynamodb.ErrCodeProvisionedThroughputExceededException:
   392  				fallthrough
   393  			case dynamodb.ErrCodeRequestLimitExceeded:
   394  				// no error + allow retry with backoff
   395  				return &DynamoDBError{
   396  					ErrorMessage:      prefix + prefixType + aerr.Code() + " - " + aerr.Message() + " - " + origError,
   397  					SuppressError:     true,
   398  					AllowRetry:        true,
   399  					RetryNeedsBackOff: true,
   400  				}
   401  
   402  			case dynamodb.ErrCodeInternalServerError:
   403  				// no error + allow auto retry without backoff
   404  				return &DynamoDBError{
   405  					ErrorMessage:      prefix + prefixType + aerr.Code() + " - " + aerr.Message() + " - " + origError,
   406  					SuppressError:     true,
   407  					AllowRetry:        true,
   408  					RetryNeedsBackOff: false,
   409  				}
   410  
   411  			default:
   412  				return &DynamoDBError{
   413  					ErrorMessage:      prefix + prefixType + aerr.Code() + " - " + aerr.Message() + " - " + origError,
   414  					SuppressError:     false,
   415  					AllowRetry:        false,
   416  					RetryNeedsBackOff: false,
   417  				}
   418  			}
   419  		} else {
   420  			// other errors
   421  			prefixType = "[General] "
   422  
   423  			return &DynamoDBError{
   424  				ErrorMessage:      prefix + prefixType + err.Error(),
   425  				SuppressError:     false,
   426  				AllowRetry:        false,
   427  				RetryNeedsBackOff: false,
   428  			}
   429  		}
   430  	} else {
   431  		// no error
   432  		return nil
   433  	}
   434  }
   435  
   436  // Connect will establish a connection to the dynamodb service
   437  func (d *DynamoDB) Connect(parentSegment ...*xray.XRayParentSegment) (err error) {
   438  	if xray.XRayServiceOn() {
   439  		if len(parentSegment) > 0 {
   440  			d._parentSegment = parentSegment[0]
   441  		}
   442  
   443  		seg := xray.NewSegment("DynamoDB-Connect", d._parentSegment)
   444  		defer seg.Close()
   445  		defer func() {
   446  			_ = seg.Seg.AddMetadata("DynamoDB-AWS-Region", d.AwsRegion)
   447  			_ = seg.Seg.AddMetadata("DynamoDB-Table-Name", d.TableName)
   448  
   449  			if err != nil {
   450  				_ = seg.Seg.AddError(err)
   451  			}
   452  		}()
   453  
   454  		err = d.connectInternal()
   455  
   456  		if err == nil {
   457  			awsxray.AWS(d.cn.Client)
   458  		}
   459  
   460  		return err
   461  	} else {
   462  		return d.connectInternal()
   463  	}
   464  }
   465  
   466  // Connect will establish a connection to the dynamodb service
   467  func (d *DynamoDB) connectInternal() error {
   468  	// clean up prior cn reference
   469  	d.cn = nil
   470  	d.SkipDax = false
   471  
   472  	if !d.AwsRegion.Valid() || d.AwsRegion == awsregion.UNKNOWN {
   473  		return errors.New("Connect To DynamoDB Failed: (AWS Session Error) " + "Region is Required")
   474  	}
   475  
   476  	// create custom http2 client if needed
   477  	var httpCli *http.Client
   478  	var httpErr error
   479  
   480  	if d.HttpOptions == nil {
   481  		d.HttpOptions = new(awshttp2.HttpClientSettings)
   482  	}
   483  
   484  	// use custom http2 client
   485  	h2 := &awshttp2.AwsHttp2Client{
   486  		Options: d.HttpOptions,
   487  	}
   488  
   489  	if httpCli, httpErr = h2.NewHttp2Client(); httpErr != nil {
   490  		return errors.New("Connect to DynamoDB Failed: (AWS Session Error) " + "Create Custom Http2 Client Errored = " + httpErr.Error())
   491  	}
   492  
   493  	// establish aws session connection and connect to dynamodb service
   494  	if sess, err := session.NewSession(
   495  		&aws.Config{
   496  			Region:     aws.String(d.AwsRegion.Key()),
   497  			HTTPClient: httpCli,
   498  		}); err != nil {
   499  		// aws session error
   500  		return errors.New("Connect To DynamoDB Failed: (AWS Session Error) " + err.Error())
   501  	} else {
   502  		// aws session obtained
   503  		d.cn = dynamodb.New(sess)
   504  
   505  		if d.cn == nil {
   506  			return errors.New("Connect To DynamoDB Failed: (New DynamoDB Connection) " + "Connection Object Nil")
   507  		}
   508  
   509  		// successfully connected to dynamodb service
   510  		return nil
   511  	}
   512  }
   513  
   514  // EnableDax will enable dax service for this dynamodb session
   515  func (d *DynamoDB) EnableDax() (err error) {
   516  	// get new xray segment for tracing
   517  	seg := xray.NewSegmentNullable("DynamoDB-EnableDax", d._parentSegment)
   518  
   519  	if seg != nil {
   520  		defer seg.Close()
   521  		defer func() {
   522  			_ = seg.Seg.AddMetadata("DynamoDB-Dax-Endpoint", d.DaxEndpoint)
   523  
   524  			if err != nil {
   525  				_ = seg.Seg.AddError(err)
   526  			}
   527  		}()
   528  
   529  		err = d.enableDaxInternal()
   530  		return err
   531  	} else {
   532  		return d.enableDaxInternal()
   533  	}
   534  }
   535  
   536  // EnableDax will enable dax service for this dynamodb session
   537  func (d *DynamoDB) enableDaxInternal() error {
   538  	if d.cn == nil {
   539  		return errors.New("Enable Dax Failed: " + "DynamoDB Not Yet Connected")
   540  	}
   541  
   542  	cfg := dax.DefaultConfig()
   543  	cfg.HostPorts = []string{d.DaxEndpoint}
   544  	cfg.Region = d.AwsRegion.Key()
   545  
   546  	var err error
   547  
   548  	d.cnDax, err = dax.New(cfg)
   549  
   550  	if err != nil {
   551  		d.cnDax = nil
   552  		return errors.New("Enable Dax Failed: " + err.Error())
   553  	}
   554  
   555  	// default skip dax to false
   556  	d.SkipDax = false
   557  
   558  	// success
   559  	return nil
   560  }
   561  
   562  // DisableDax will disable dax service for this dynamodb session
   563  func (d *DynamoDB) DisableDax() {
   564  	if d.cnDax != nil {
   565  		_ = d.cnDax.Close()
   566  		d.cnDax = nil
   567  		d.SkipDax = false
   568  	}
   569  }
   570  
   571  // UpdateParentSegment updates this struct's xray parent segment, if no parent segment, set nil
   572  func (d *DynamoDB) UpdateParentSegment(parentSegment *xray.XRayParentSegment) {
   573  	d._parentSegment = parentSegment
   574  }
   575  
   576  // do_PutItem is a helper that calls either dax or dynamodb based on dax availability
   577  func (d *DynamoDB) do_PutItem(input *dynamodb.PutItemInput, ctx ...aws.Context) (output *dynamodb.PutItemOutput, err error) {
   578  	if d.cnDax != nil && !d.SkipDax {
   579  		// dax
   580  		if len(ctx) <= 0 {
   581  			return d.cnDax.PutItem(input)
   582  		} else {
   583  			return d.cnDax.PutItemWithContext(ctx[0], input)
   584  		}
   585  	} else if d.cn != nil {
   586  		// dynamodb
   587  		if len(ctx) <= 0 {
   588  			return d.cn.PutItem(input)
   589  		} else {
   590  			return d.cn.PutItemWithContext(ctx[0], input)
   591  		}
   592  	} else {
   593  		// connection error
   594  		return nil, errors.New("DynamoDB PutItem Failed: " + "No DynamoDB or Dax Connection Available")
   595  	}
   596  }
   597  
   598  // do_UpdateItem is a helper that calls either dax or dynamodb based on dax availability
   599  func (d *DynamoDB) do_UpdateItem(input *dynamodb.UpdateItemInput, ctx ...aws.Context) (output *dynamodb.UpdateItemOutput, err error) {
   600  	if d.cnDax != nil && !d.SkipDax {
   601  		// dax
   602  		if len(ctx) <= 0 {
   603  			return d.cnDax.UpdateItem(input)
   604  		} else {
   605  			return d.cnDax.UpdateItemWithContext(ctx[0], input)
   606  		}
   607  	} else if d.cn != nil {
   608  		// dynamodb
   609  		if len(ctx) <= 0 {
   610  			return d.cn.UpdateItem(input)
   611  		} else {
   612  			return d.cn.UpdateItemWithContext(ctx[0], input)
   613  		}
   614  	} else {
   615  		// connection error
   616  		return nil, errors.New("DynamoDB UpdateItem Failed: " + "No DynamoDB or Dax Connection Available")
   617  	}
   618  }
   619  
   620  // do_DeleteItem is a helper that calls either dax or dynamodb based on dax availability
   621  func (d *DynamoDB) do_DeleteItem(input *dynamodb.DeleteItemInput, ctx ...aws.Context) (output *dynamodb.DeleteItemOutput, err error) {
   622  	if d.cnDax != nil && !d.SkipDax {
   623  		// dax
   624  		if len(ctx) <= 0 {
   625  			return d.cnDax.DeleteItem(input)
   626  		} else {
   627  			return d.cnDax.DeleteItemWithContext(ctx[0], input)
   628  		}
   629  	} else if d.cn != nil {
   630  		// dynamodb
   631  		if len(ctx) <= 0 {
   632  			return d.cn.DeleteItem(input)
   633  		} else {
   634  			return d.cn.DeleteItemWithContext(ctx[0], input)
   635  		}
   636  	} else {
   637  		// connection error
   638  		return nil, errors.New("DynamoDB DeleteItem Failed: " + "No DynamoDB or Dax Connection Available")
   639  	}
   640  }
   641  
   642  // do_GetItem is a helper that calls either dax or dynamodb based on dax availability
   643  func (d *DynamoDB) do_GetItem(input *dynamodb.GetItemInput, ctx ...aws.Context) (output *dynamodb.GetItemOutput, err error) {
   644  	if d.cnDax != nil && !d.SkipDax {
   645  		// dax
   646  		if len(ctx) <= 0 {
   647  			return d.cnDax.GetItem(input)
   648  		} else {
   649  			return d.cnDax.GetItemWithContext(ctx[0], input)
   650  		}
   651  	} else if d.cn != nil {
   652  		// dynamodb
   653  		if len(ctx) <= 0 {
   654  			return d.cn.GetItem(input)
   655  		} else {
   656  			return d.cn.GetItemWithContext(ctx[0], input)
   657  		}
   658  	} else {
   659  		// connection error
   660  		return nil, errors.New("DynamoDB GetItem Failed: " + "No DynamoDB or Dax Connection Available")
   661  	}
   662  }
   663  
   664  // do_Query_Pagination_Data is a helper that calls either dax or dynamodb based on dax availability, to get pagination data for the given query filter
   665  func (d *DynamoDB) do_Query_Pagination_Data(input *dynamodb.QueryInput, ctx ...aws.Context) (paginationData []map[string]*dynamodb.AttributeValue, err error) {
   666  	paginationData = make([]map[string]*dynamodb.AttributeValue, 0)
   667  
   668  	if d.cnDax != nil && !d.SkipDax {
   669  		// dax
   670  		fn := func(pageOutput *dynamodb.QueryOutput, lastPage bool) bool {
   671  			if pageOutput != nil {
   672  				if pageOutput.Items != nil && len(pageOutput.Items) > 0 {
   673  					if pageOutput.LastEvaluatedKey != nil {
   674  						paginationData = append(paginationData, pageOutput.LastEvaluatedKey)
   675  					}
   676  				}
   677  			}
   678  
   679  			return !lastPage
   680  		}
   681  
   682  		if len(ctx) <= 0 {
   683  			err = d.cnDax.QueryPages(input, fn)
   684  		} else {
   685  			err = d.cnDax.QueryPagesWithContext(ctx[0], input, fn)
   686  		}
   687  
   688  		return paginationData, err
   689  
   690  	} else if d.cn != nil {
   691  		// dynamodb
   692  		fn := func(pageOutput *dynamodb.QueryOutput, lastPage bool) bool {
   693  			if pageOutput != nil {
   694  				if pageOutput.Items != nil && len(pageOutput.Items) > 0 {
   695  					if pageOutput.LastEvaluatedKey != nil {
   696  						paginationData = append(paginationData, pageOutput.LastEvaluatedKey)
   697  					}
   698  				}
   699  			}
   700  
   701  			return !lastPage
   702  		}
   703  
   704  		if len(ctx) <= 0 {
   705  			err = d.cn.QueryPages(input, fn)
   706  		} else {
   707  			err = d.cn.QueryPagesWithContext(ctx[0], input, fn)
   708  		}
   709  
   710  		return paginationData, err
   711  
   712  	} else {
   713  		// connection error
   714  		return nil, errors.New("DynamoDB QueryPaginationData Failed: " + "No DynamoDB or Dax Connection Available")
   715  	}
   716  }
   717  
   718  // do_Query is a helper that calls either dax or dynamodb based on dax availability
   719  func (d *DynamoDB) do_Query(input *dynamodb.QueryInput, pagedQuery bool, pagedQueryPageCountLimit *int64, ctx ...aws.Context) (output *dynamodb.QueryOutput, err error) {
   720  	if d.cnDax != nil && !d.SkipDax {
   721  		// dax
   722  		if !pagedQuery {
   723  			//
   724  			// not paged query
   725  			//
   726  			if len(ctx) <= 0 {
   727  				return d.cnDax.Query(input)
   728  			} else {
   729  				return d.cnDax.QueryWithContext(ctx[0], input)
   730  			}
   731  		} else {
   732  			//
   733  			// paged query
   734  			//
   735  			pageCount := int64(0)
   736  
   737  			fn := func(pageOutput *dynamodb.QueryOutput, lastPage bool) bool {
   738  				if pageOutput != nil {
   739  					if pageOutput.Items != nil && len(pageOutput.Items) > 0 {
   740  						pageCount++
   741  
   742  						if output == nil {
   743  							output = new(dynamodb.QueryOutput)
   744  						}
   745  
   746  						output.SetCount(aws.Int64Value(output.Count) + aws.Int64Value(pageOutput.Count))
   747  						output.SetScannedCount(aws.Int64Value(output.ScannedCount) + aws.Int64Value(pageOutput.ScannedCount))
   748  						output.SetLastEvaluatedKey(pageOutput.LastEvaluatedKey)
   749  
   750  						for _, v := range pageOutput.Items {
   751  							output.Items = append(output.Items, v)
   752  						}
   753  
   754  						// check if ok to stop
   755  						if pagedQueryPageCountLimit != nil && *pagedQueryPageCountLimit > 0 {
   756  							if pageCount >= *pagedQueryPageCountLimit {
   757  								return false
   758  							}
   759  						}
   760  					}
   761  				}
   762  
   763  				return !lastPage
   764  			}
   765  
   766  			if len(ctx) <= 0 {
   767  				err = d.cnDax.QueryPages(input, fn)
   768  			} else {
   769  				err = d.cnDax.QueryPagesWithContext(ctx[0], input, fn)
   770  			}
   771  
   772  			return output, err
   773  		}
   774  	} else if d.cn != nil {
   775  		// dynamodb
   776  		if !pagedQuery {
   777  			//
   778  			// not paged query
   779  			//
   780  			if len(ctx) <= 0 {
   781  				return d.cn.Query(input)
   782  			} else {
   783  				return d.cn.QueryWithContext(ctx[0], input)
   784  			}
   785  		} else {
   786  			//
   787  			// paged query
   788  			//
   789  			pageCount := int64(0)
   790  
   791  			fn := func(pageOutput *dynamodb.QueryOutput, lastPage bool) bool {
   792  				if pageOutput != nil {
   793  					if pageOutput.Items != nil && len(pageOutput.Items) > 0 {
   794  						pageCount++
   795  
   796  						if output == nil {
   797  							output = new(dynamodb.QueryOutput)
   798  						}
   799  
   800  						output.SetCount(aws.Int64Value(output.Count) + aws.Int64Value(pageOutput.Count))
   801  						output.SetScannedCount(aws.Int64Value(output.ScannedCount) + aws.Int64Value(pageOutput.ScannedCount))
   802  						output.SetLastEvaluatedKey(pageOutput.LastEvaluatedKey)
   803  
   804  						for _, v := range pageOutput.Items {
   805  							output.Items = append(output.Items, v)
   806  						}
   807  
   808  						// check if ok to stop
   809  						if pagedQueryPageCountLimit != nil && *pagedQueryPageCountLimit > 0 {
   810  							if pageCount >= *pagedQueryPageCountLimit {
   811  								return false
   812  							}
   813  						}
   814  					}
   815  				}
   816  
   817  				return !lastPage
   818  			}
   819  
   820  			if len(ctx) <= 0 {
   821  				err = d.cn.QueryPages(input, fn)
   822  			} else {
   823  				err = d.cn.QueryPagesWithContext(ctx[0], input, fn)
   824  			}
   825  
   826  			return output, err
   827  		}
   828  	} else {
   829  		// connection error
   830  		return nil, errors.New("DynamoDB QueryItems Failed: " + "No DynamoDB or Dax Connection Available")
   831  	}
   832  }
   833  
   834  // do_Scan is a helper that calls either dax or dynamodb based on dax availability
   835  func (d *DynamoDB) do_Scan(input *dynamodb.ScanInput, pagedQuery bool, pagedQueryPageCountLimit *int64, ctx ...aws.Context) (output *dynamodb.ScanOutput, err error) {
   836  	if d.cnDax != nil && !d.SkipDax {
   837  		// dax
   838  		if !pagedQuery {
   839  			//
   840  			// not paged query
   841  			//
   842  			if len(ctx) <= 0 {
   843  				return d.cnDax.Scan(input)
   844  			} else {
   845  				return d.cnDax.ScanWithContext(ctx[0], input)
   846  			}
   847  		} else {
   848  			//
   849  			// paged query
   850  			//
   851  			pageCount := int64(0)
   852  
   853  			fn := func(pageOutput *dynamodb.ScanOutput, lastPage bool) bool {
   854  				if pageOutput != nil {
   855  					if pageOutput.Items != nil && len(pageOutput.Items) > 0 {
   856  						pageCount++
   857  
   858  						if output == nil {
   859  							output = new(dynamodb.ScanOutput)
   860  						}
   861  
   862  						output.SetCount(aws.Int64Value(output.Count) + aws.Int64Value(pageOutput.Count))
   863  						output.SetScannedCount(aws.Int64Value(output.ScannedCount) + aws.Int64Value(pageOutput.ScannedCount))
   864  						output.SetLastEvaluatedKey(pageOutput.LastEvaluatedKey)
   865  
   866  						for _, v := range pageOutput.Items {
   867  							output.Items = append(output.Items, v)
   868  						}
   869  
   870  						if pagedQueryPageCountLimit != nil && *pagedQueryPageCountLimit > 0 {
   871  							if pageCount >= *pagedQueryPageCountLimit {
   872  								return false
   873  							}
   874  						}
   875  					}
   876  				}
   877  
   878  				return !lastPage
   879  			}
   880  
   881  			if len(ctx) <= 0 {
   882  				err = d.cnDax.ScanPages(input, fn)
   883  			} else {
   884  				err = d.cnDax.ScanPagesWithContext(ctx[0], input, fn)
   885  			}
   886  
   887  			return output, err
   888  		}
   889  	} else if d.cn != nil {
   890  		// dynamodb
   891  		if !pagedQuery {
   892  			//
   893  			// not paged query
   894  			//
   895  			if len(ctx) <= 0 {
   896  				return d.cn.Scan(input)
   897  			} else {
   898  				return d.cn.ScanWithContext(ctx[0], input)
   899  			}
   900  		} else {
   901  			//
   902  			// paged query
   903  			//
   904  			pageCount := int64(0)
   905  
   906  			fn := func(pageOutput *dynamodb.ScanOutput, lastPage bool) bool {
   907  				if pageOutput != nil {
   908  					if pageOutput.Items != nil && len(pageOutput.Items) > 0 {
   909  						pageCount++
   910  
   911  						if output == nil {
   912  							output = new(dynamodb.ScanOutput)
   913  						}
   914  
   915  						output.SetCount(aws.Int64Value(output.Count) + aws.Int64Value(pageOutput.Count))
   916  						output.SetScannedCount(aws.Int64Value(output.ScannedCount) + aws.Int64Value(pageOutput.ScannedCount))
   917  						output.SetLastEvaluatedKey(pageOutput.LastEvaluatedKey)
   918  
   919  						for _, v := range pageOutput.Items {
   920  							output.Items = append(output.Items, v)
   921  						}
   922  
   923  						if pagedQueryPageCountLimit != nil && *pagedQueryPageCountLimit > 0 {
   924  							if pageCount >= *pagedQueryPageCountLimit {
   925  								return false
   926  							}
   927  						}
   928  					}
   929  				}
   930  
   931  				return !lastPage
   932  			}
   933  
   934  			if len(ctx) <= 0 {
   935  				err = d.cn.ScanPages(input, fn)
   936  			} else {
   937  				err = d.cn.ScanPagesWithContext(ctx[0], input, fn)
   938  			}
   939  
   940  			return output, err
   941  		}
   942  	} else {
   943  		// connection error
   944  		return nil, errors.New("DynamoDB ScanItems Failed: " + "No DynamoDB or Dax Connection Available")
   945  	}
   946  }
   947  
   948  // do_BatchWriteItem is a helper that calls either dax or dynamodb based on dax availability
   949  func (d *DynamoDB) do_BatchWriteItem(input *dynamodb.BatchWriteItemInput, ctx ...aws.Context) (output *dynamodb.BatchWriteItemOutput, err error) {
   950  	if d.cnDax != nil && !d.SkipDax {
   951  		// dax
   952  		if len(ctx) <= 0 {
   953  			return d.cnDax.BatchWriteItem(input)
   954  		} else {
   955  			return d.cnDax.BatchWriteItemWithContext(ctx[0], input)
   956  		}
   957  	} else if d.cn != nil {
   958  		// dynamodb
   959  		if len(ctx) <= 0 {
   960  			return d.cn.BatchWriteItem(input)
   961  		} else {
   962  			return d.cn.BatchWriteItemWithContext(ctx[0], input)
   963  		}
   964  	} else {
   965  		// connection error
   966  		return nil, errors.New("DynamoDB BatchWriteItem Failed: " + "No DynamoDB or Dax Connection Available")
   967  	}
   968  }
   969  
   970  // do_BatchGetItem is a helper that calls either dax or dynamodb based on dax availability
   971  func (d *DynamoDB) do_BatchGetItem(input *dynamodb.BatchGetItemInput, ctx ...aws.Context) (output *dynamodb.BatchGetItemOutput, err error) {
   972  	if d.cnDax != nil && !d.SkipDax {
   973  		// dax
   974  		if len(ctx) <= 0 {
   975  			return d.cnDax.BatchGetItem(input)
   976  		} else {
   977  			return d.cnDax.BatchGetItemWithContext(ctx[0], input)
   978  		}
   979  	} else if d.cn != nil {
   980  		// dynamodb
   981  		if len(ctx) <= 0 {
   982  			return d.cn.BatchGetItem(input)
   983  		} else {
   984  			return d.cn.BatchGetItemWithContext(ctx[0], input)
   985  		}
   986  	} else {
   987  		// connection error
   988  		return nil, errors.New("DynamoDB BatchGetItem Failed: " + "No DynamoDB or Dax Connection Available")
   989  	}
   990  }
   991  
   992  // do_TransactWriteItems is a helper that calls either dax or dynamodb based on dax availability
   993  func (d *DynamoDB) do_TransactWriteItems(input *dynamodb.TransactWriteItemsInput, ctx ...aws.Context) (output *dynamodb.TransactWriteItemsOutput, err error) {
   994  	if d.cnDax != nil && !d.SkipDax {
   995  		// dax
   996  		if len(ctx) <= 0 {
   997  			return d.cnDax.TransactWriteItems(input)
   998  		} else {
   999  			return d.cnDax.TransactWriteItemsWithContext(ctx[0], input)
  1000  		}
  1001  	} else if d.cn != nil {
  1002  		// dynamodb
  1003  		if len(ctx) <= 0 {
  1004  			return d.cn.TransactWriteItems(input)
  1005  		} else {
  1006  			return d.cn.TransactWriteItemsWithContext(ctx[0], input)
  1007  		}
  1008  	} else {
  1009  		// connection error
  1010  		return nil, errors.New("DynamoDB TransactionWriteItems Failed: " + "No DynamoDB or Dax Connection Available")
  1011  	}
  1012  }
  1013  
  1014  // do_TransactGetItems is a helper that calls either dax or dynamodb based on dax availability
  1015  func (d *DynamoDB) do_TransactGetItems(input *dynamodb.TransactGetItemsInput, ctx ...aws.Context) (output *dynamodb.TransactGetItemsOutput, err error) {
  1016  	if d.cnDax != nil && !d.SkipDax {
  1017  		// dax
  1018  		if len(ctx) <= 0 {
  1019  			return d.cnDax.TransactGetItems(input)
  1020  		} else {
  1021  			return d.cnDax.TransactGetItemsWithContext(ctx[0], input)
  1022  		}
  1023  	} else if d.cn != nil {
  1024  		// dynamodb
  1025  		if len(ctx) <= 0 {
  1026  			return d.cn.TransactGetItems(input)
  1027  		} else {
  1028  			return d.cn.TransactGetItemsWithContext(ctx[0], input)
  1029  		}
  1030  	} else {
  1031  		// connection error
  1032  		return nil, errors.New("DynamoDB TransactionGetItems Failed: " + "No DynamoDB or Dax Connection Available")
  1033  	}
  1034  }
  1035  
  1036  func (d *DynamoDB) TimeOutDuration(timeOutSeconds uint) *time.Duration {
  1037  	if timeOutSeconds == 0 {
  1038  		return nil
  1039  	} else {
  1040  		return util.DurationPtr(time.Duration(timeOutSeconds) * time.Second)
  1041  	}
  1042  }
  1043  
  1044  // PutItem will add or update a new item into dynamodb table
  1045  //
  1046  // parameters:
  1047  //
  1048  //	item = required, must be a struct object; ALWAYS SINGLE STRUCT OBJECT, NEVER SLICE
  1049  //		   must start with fields 'pk string', 'sk string', and 'data string' before any other attributes
  1050  //	timeOutDuration = optional, timeout duration sent via context to scan method; nil if not using timeout duration
  1051  //
  1052  // notes:
  1053  //
  1054  //	item struct tags
  1055  //		use `json:"" dynamodbav:""`
  1056  //			json = sets the name used in json
  1057  //			dynamodbav = sets the name used in dynamodb
  1058  //		reference child element
  1059  //			if struct has field with complex type (another struct), to reference it in code, use the parent struct field dot child field notation
  1060  //				Info in parent struct with struct tag as info; to reach child element: info.xyz
  1061  func (d *DynamoDB) PutItem(item interface{}, timeOutDuration *time.Duration) (ddbErr *DynamoDBError) {
  1062  	if xray.XRayServiceOn() {
  1063  		return d.putItemWithTrace(item, timeOutDuration)
  1064  	} else {
  1065  		return d.putItemNormal(item, timeOutDuration)
  1066  	}
  1067  }
  1068  
  1069  func (d *DynamoDB) putItemWithTrace(item interface{}, timeOutDuration *time.Duration) (ddbErr *DynamoDBError) {
  1070  	trace := xray.NewSegment("DynamoDB-PutItem", d._parentSegment)
  1071  	defer trace.Close()
  1072  	defer func() {
  1073  		if ddbErr != nil {
  1074  			_ = trace.Seg.AddError(fmt.Errorf(ddbErr.ErrorMessage))
  1075  		}
  1076  	}()
  1077  
  1078  	if d.cn == nil {
  1079  		ddbErr = d.handleError(errors.New("DynamoDB Connection is Required"))
  1080  		return ddbErr
  1081  	}
  1082  
  1083  	if util.LenTrim(d.TableName) <= 0 {
  1084  		ddbErr = d.handleError(errors.New("DynamoDB Table Name is Required"))
  1085  		return ddbErr
  1086  	}
  1087  
  1088  	if item == nil {
  1089  		ddbErr = d.handleError(errors.New("DynamoDB PutItem Failed: " + "Input Item Object is Nil"))
  1090  		return ddbErr
  1091  	}
  1092  
  1093  	trace.Capture("PutItem", func() error {
  1094  		if av, err := dynamodbattribute.MarshalMap(item); err != nil {
  1095  			ddbErr = d.handleError(err, "DynamoDB PutItem Failed: (MarshalMap)")
  1096  			return fmt.Errorf(ddbErr.ErrorMessage)
  1097  		} else {
  1098  			input := &dynamodb.PutItemInput{
  1099  				Item:      av,
  1100  				TableName: aws.String(d.TableName),
  1101  			}
  1102  
  1103  			// record params payload
  1104  			d.LastExecuteParamsPayload = "PutItem = " + input.String()
  1105  
  1106  			subTrace := trace.NewSubSegment("PutItem_Do")
  1107  			defer subTrace.Close()
  1108  
  1109  			// save into dynamodb table
  1110  			if timeOutDuration != nil {
  1111  				ctx, cancel := context.WithTimeout(subTrace.Ctx, *timeOutDuration)
  1112  				defer cancel()
  1113  				_, err = d.do_PutItem(input, ctx)
  1114  			} else {
  1115  				_, err = d.do_PutItem(input, subTrace.Ctx)
  1116  			}
  1117  
  1118  			if err != nil {
  1119  				ddbErr = d.handleError(err, "DynamoDB PutItem Failed: (PutItem)")
  1120  				return fmt.Errorf(ddbErr.ErrorMessage)
  1121  			} else {
  1122  				return nil
  1123  			}
  1124  		}
  1125  	}, &xray.XTraceData{
  1126  		Meta: map[string]interface{}{
  1127  			"TableName": d.TableName,
  1128  			"ItemInfo":  item,
  1129  		},
  1130  	})
  1131  
  1132  	// put item was successful
  1133  	return ddbErr
  1134  }
  1135  
  1136  func (d *DynamoDB) putItemNormal(item interface{}, timeOutDuration *time.Duration) (ddbErr *DynamoDBError) {
  1137  	if d.cn == nil {
  1138  		return d.handleError(errors.New("DynamoDB Connection is Required"))
  1139  	}
  1140  
  1141  	if util.LenTrim(d.TableName) <= 0 {
  1142  		return d.handleError(errors.New("DynamoDB Table Name is Required"))
  1143  	}
  1144  
  1145  	if item == nil {
  1146  		return d.handleError(errors.New("DynamoDB PutItem Failed: " + "Input Item Object is Nil"))
  1147  	}
  1148  
  1149  	if av, err := dynamodbattribute.MarshalMap(item); err != nil {
  1150  		ddbErr = d.handleError(err, "DynamoDB PutItem Failed: (MarshalMap)")
  1151  	} else {
  1152  		input := &dynamodb.PutItemInput{
  1153  			Item:      av,
  1154  			TableName: aws.String(d.TableName),
  1155  		}
  1156  
  1157  		// record params payload
  1158  		d.LastExecuteParamsPayload = "PutItem = " + input.String()
  1159  
  1160  		// save into dynamodb table
  1161  		if timeOutDuration != nil {
  1162  			ctx, cancel := context.WithTimeout(context.Background(), *timeOutDuration)
  1163  			defer cancel()
  1164  			_, err = d.do_PutItem(input, ctx)
  1165  		} else {
  1166  			_, err = d.do_PutItem(input)
  1167  		}
  1168  
  1169  		if err != nil {
  1170  			ddbErr = d.handleError(err, "DynamoDB PutItem Failed: (PutItem)")
  1171  		} else {
  1172  			ddbErr = nil
  1173  		}
  1174  	}
  1175  
  1176  	// put item was successful
  1177  	return ddbErr
  1178  }
  1179  
  1180  // PutItemWithRetry add or updates, and handles dynamodb retries in case action temporarily fails
  1181  func (d *DynamoDB) PutItemWithRetry(maxRetries uint, item interface{}, timeOutDuration *time.Duration) *DynamoDBError {
  1182  	if maxRetries > 10 {
  1183  		maxRetries = 10
  1184  	}
  1185  
  1186  	timeout := 5 * time.Second
  1187  
  1188  	if timeOutDuration != nil {
  1189  		timeout = *timeOutDuration
  1190  	}
  1191  
  1192  	if timeout < 5*time.Second {
  1193  		timeout = 5 * time.Second
  1194  	} else if timeout > 15*time.Second {
  1195  		timeout = 15 * time.Second
  1196  	}
  1197  
  1198  	if err := d.PutItem(item, util.DurationPtr(timeout)); err != nil {
  1199  		// has error
  1200  		if maxRetries > 0 {
  1201  			if err.AllowRetry {
  1202  				if err.RetryNeedsBackOff {
  1203  					time.Sleep(500 * time.Millisecond)
  1204  				} else {
  1205  					time.Sleep(100 * time.Millisecond)
  1206  				}
  1207  
  1208  				log.Println("PutItemWithRetry Failed: " + err.ErrorMessage)
  1209  				return d.PutItemWithRetry(maxRetries-1, item, util.DurationPtr(timeout))
  1210  			} else {
  1211  				if err.SuppressError {
  1212  					log.Println("PutItemWithRetry DynamoDB Error Suppressed, Returning Error Nil (MaxRetries = " + util.UintToStr(maxRetries) + ")")
  1213  					return nil
  1214  				} else {
  1215  					return &DynamoDBError{
  1216  						ErrorMessage:      "PutItemWithRetry Failed: " + err.ErrorMessage,
  1217  						SuppressError:     false,
  1218  						AllowRetry:        false,
  1219  						RetryNeedsBackOff: false,
  1220  					}
  1221  				}
  1222  			}
  1223  		} else {
  1224  			if err.SuppressError {
  1225  				log.Println("PutItemWithRetry DynamoDB Error Suppressed, Returning Error Nil (MaxRetries = 0)")
  1226  				return nil
  1227  			} else {
  1228  				return &DynamoDBError{
  1229  					ErrorMessage:      "PutItemWithRetry Failed: (MaxRetries = 0) " + err.ErrorMessage,
  1230  					SuppressError:     false,
  1231  					AllowRetry:        false,
  1232  					RetryNeedsBackOff: false,
  1233  				}
  1234  			}
  1235  		}
  1236  	} else {
  1237  		// no error
  1238  		return nil
  1239  	}
  1240  }
  1241  
  1242  // UpdateItem will update dynamodb item in given table using primary key (PK, SK), and set specific attributes with new value and persists
  1243  // UpdateItem requires using Primary Key attributes, and limited to TWO key attributes in condition maximum;
  1244  //
  1245  // important
  1246  //
  1247  //	if dynamodb table is defined as PK and SK together, then to search, MUST use PK and SK together or error will trigger
  1248  //
  1249  // parameters:
  1250  //
  1251  //	pkValue = required, value of partition key to seek
  1252  //	skValue = optional, value of sort key to seek; set to blank if value not provided
  1253  //
  1254  //	updateExpression = required, ATTRIBUTES ARE CASE SENSITIVE; set remove add or delete action expression, see Rules URL for full detail
  1255  //		Rules:
  1256  //			1) https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html
  1257  //		Usage Syntax:
  1258  //			1) Action Keywords are: set, add, remove, delete
  1259  //			2) Each Action Keyword May Appear in UpdateExpression Only Once
  1260  //			3) Each Action Keyword Grouping May Contain One or More Actions, Such as 'set price=:p, age=:age, etc' (each action separated by comma)
  1261  //			4) Each Action Keyword Always Begin with Action Keyword itself, such as 'set ...', 'add ...', etc
  1262  //			5) If Attribute is Numeric, Action Can Perform + or - Operation in Expression, such as 'set age=age-:newAge, price=price+:price, etc'
  1263  //			6) If Attribute is Slice, Action Can Perform Slice Element Operation in Expression, such as 'set age[2]=:newData, etc'
  1264  //			7) When Attribute Name is Reserved Keyword, Use ExpressionAttributeNames to Define #xyz to Alias
  1265  //				a) Use the #xyz in the KeyConditionExpression such as #yr = :year (:year is Defined ExpressionAttributeValue)
  1266  //			8) When Attribute is a List, Use list_append(a, b, ...) in Expression to append elements (list_append() is case sensitive)
  1267  //				a) set #ri = list_append(#ri, :vals) where :vals represents one or more of elements to add as in L
  1268  //			9) if_not_exists(path, value)
  1269  //				a) Avoids existing attribute if already exists
  1270  //				b) set price = if_not_exists(price, :p)
  1271  //				c) if_not_exists is case sensitive; path is the existing attribute to check
  1272  //			10) Action Type Purposes
  1273  //				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
  1274  //				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
  1275  //				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
  1276  //				d) DELETE = supports only on set data types; deletes one or more elements from a set, such as 'delete color :c'
  1277  //			11) Example
  1278  //				a) set age=:age, name=:name, etc
  1279  //				b) set age=age-:age, num=num+:num, etc
  1280  //
  1281  //	conditionExpress = optional, ATTRIBUTES ARE CASE SENSITIVE; sets conditions for this condition expression, set to blank if not used
  1282  //			Usage Syntax:
  1283  //				1) "size(info.actors) >= :num"
  1284  //					a) When Length of Actors Attribute Value is Equal or Greater Than :num, ONLY THEN UpdateExpression is Performed
  1285  //				2) ExpressionAttributeName and ExpressionAttributeValue is Still Defined within ExpressionAttributeNames and ExpressionAttributeValues Where Applicable
  1286  //
  1287  //	expressionAttributeNames = optional, ATTRIBUTES ARE CASE SENSITIVE; set nil if not used, must define for attribute names that are reserved keywords such as year, data etc. using #xyz
  1288  //		Usage Syntax:
  1289  //			1) map[string]*string: where string is the #xyz, and *string is the original xyz attribute name
  1290  //				a) map[string]*string { "#xyz": aws.String("Xyz"), }
  1291  //			2) Add to Map
  1292  //				a) m := make(map[string]*string)
  1293  //				b) m["#xyz"] = aws.String("Xyz")
  1294  //
  1295  //	expressionAttributeValues = required, ATTRIBUTES ARE CASE SENSITIVE; sets the value token and value actual to be used within the keyConditionExpression; this sets both compare token and compare value
  1296  //		Usage Syntax:
  1297  //			1) map[string]*dynamodb.AttributeValue: where string is the :xyz, and *dynamodb.AttributeValue is { S: aws.String("abc"), },
  1298  //				a) map[string]*dynamodb.AttributeValue { ":xyz" : { S: aws.String("abc"), }, ":xyy" : { N: aws.String("123"), }, }
  1299  //			2) Add to Map
  1300  //				a) m := make(map[string]*dynamodb.AttributeValue)
  1301  //				b) m[":xyz"] = &dynamodb.AttributeValue{ S: aws.String("xyz") }
  1302  //			3) Slice of Strings -> CONVERT To Slice of *dynamodb.AttributeValue = []string -> []*dynamodb.AttributeValue
  1303  //				a) av, err := dynamodbattribute.MarshalList(xyzSlice)
  1304  //				b) ExpressionAttributeValue, Use 'L' To Represent the List for av defined in 3.a above
  1305  //
  1306  //	timeOutDuration = optional, timeout duration sent via context to scan method; nil if not using timeout duration
  1307  //
  1308  // notes:
  1309  //
  1310  //	item struct tags
  1311  //		use `json:"" dynamodbav:""`
  1312  //			json = sets the name used in json
  1313  //			dynamodbav = sets the name used in dynamodb
  1314  //		reference child element
  1315  //			if struct has field with complex type (another struct), to reference it in code, use the parent struct field dot child field notation
  1316  //				Info in parent struct with struct tag as info; to reach child element: info.xyz
  1317  func (d *DynamoDB) UpdateItem(pkValue string, skValue string,
  1318  	updateExpression string,
  1319  	conditionExpression string,
  1320  	expressionAttributeNames map[string]*string,
  1321  	expressionAttributeValues map[string]*dynamodb.AttributeValue,
  1322  	timeOutDuration *time.Duration) (ddbErr *DynamoDBError) {
  1323  
  1324  	if xray.XRayServiceOn() {
  1325  		return d.updateItemWithTrace(pkValue, skValue, updateExpression, conditionExpression, expressionAttributeNames, expressionAttributeValues, timeOutDuration)
  1326  	} else {
  1327  		return d.updateItemNormal(pkValue, skValue, updateExpression, conditionExpression, expressionAttributeNames, expressionAttributeValues, timeOutDuration)
  1328  	}
  1329  }
  1330  
  1331  func (d *DynamoDB) updateItemWithTrace(pkValue string, skValue string,
  1332  	updateExpression string,
  1333  	conditionExpression string,
  1334  	expressionAttributeNames map[string]*string,
  1335  	expressionAttributeValues map[string]*dynamodb.AttributeValue,
  1336  	timeOutDuration *time.Duration) (ddbErr *DynamoDBError) {
  1337  
  1338  	trace := xray.NewSegment("DynamoDB-UpdateItem", d._parentSegment)
  1339  	defer trace.Close()
  1340  	defer func() {
  1341  		if ddbErr != nil {
  1342  			_ = trace.Seg.AddError(fmt.Errorf(ddbErr.ErrorMessage))
  1343  		}
  1344  	}()
  1345  
  1346  	if d.cn == nil {
  1347  		ddbErr = d.handleError(errors.New("DynamoDB Connection is Required"))
  1348  		return ddbErr
  1349  	}
  1350  
  1351  	if util.LenTrim(d.TableName) <= 0 {
  1352  		ddbErr = d.handleError(errors.New("DynamoDB Table Name is Required"))
  1353  		return ddbErr
  1354  	}
  1355  
  1356  	// validate input parameters
  1357  	if util.LenTrim(d.PKName) <= 0 {
  1358  		ddbErr = d.handleError(errors.New("DynamoDB UpdateItem Failed: " + "PK Name is Required"))
  1359  		return ddbErr
  1360  	}
  1361  
  1362  	if util.LenTrim(pkValue) <= 0 {
  1363  		ddbErr = d.handleError(errors.New("DynamoDB UpdateItem Failed: " + "PK Value is Required"))
  1364  		return ddbErr
  1365  	}
  1366  
  1367  	if util.LenTrim(skValue) > 0 {
  1368  		if util.LenTrim(d.SKName) <= 0 {
  1369  			ddbErr = d.handleError(errors.New("DynamoDB UpdateItem Failed: " + "SK Name is Required"))
  1370  			return ddbErr
  1371  		}
  1372  	}
  1373  
  1374  	if util.LenTrim(updateExpression) <= 0 {
  1375  		ddbErr = d.handleError(errors.New("DynamoDB UpdateItem Failed: " + "UpdateExpression is Required"))
  1376  		return ddbErr
  1377  	}
  1378  
  1379  	if expressionAttributeValues == nil {
  1380  		ddbErr = d.handleError(errors.New("DynamoDB UpdateItem Failed: " + "ExpressionAttributeValues is Required"))
  1381  		return ddbErr
  1382  	}
  1383  
  1384  	trace.Capture("UpdateItem", func() error {
  1385  		// define key
  1386  		m := make(map[string]*dynamodb.AttributeValue)
  1387  
  1388  		m[d.PKName] = &dynamodb.AttributeValue{S: aws.String(pkValue)}
  1389  
  1390  		if util.LenTrim(skValue) > 0 {
  1391  			m[d.SKName] = &dynamodb.AttributeValue{S: aws.String(skValue)}
  1392  		}
  1393  
  1394  		// build update item input params
  1395  		params := &dynamodb.UpdateItemInput{
  1396  			TableName:                 aws.String(d.TableName),
  1397  			Key:                       m,
  1398  			UpdateExpression:          aws.String(updateExpression),
  1399  			ExpressionAttributeValues: expressionAttributeValues,
  1400  			ReturnValues:              aws.String(dynamodb.ReturnValueAllNew),
  1401  		}
  1402  
  1403  		if util.LenTrim(conditionExpression) > 0 {
  1404  			params.ConditionExpression = aws.String(conditionExpression)
  1405  		}
  1406  
  1407  		if expressionAttributeNames != nil {
  1408  			params.ExpressionAttributeNames = expressionAttributeNames
  1409  		}
  1410  
  1411  		// record params payload
  1412  		d.LastExecuteParamsPayload = "UpdateItem = " + params.String()
  1413  
  1414  		// execute dynamodb service
  1415  		var err error
  1416  
  1417  		subTrace := trace.NewSubSegment("UpdateItem_Do")
  1418  		defer subTrace.Close()
  1419  
  1420  		// create timeout context
  1421  		if timeOutDuration != nil {
  1422  			ctx, cancel := context.WithTimeout(subTrace.Ctx, *timeOutDuration)
  1423  			defer cancel()
  1424  			_, err = d.do_UpdateItem(params, ctx)
  1425  		} else {
  1426  			_, err = d.do_UpdateItem(params, subTrace.Ctx)
  1427  		}
  1428  
  1429  		if err != nil {
  1430  			ddbErr = d.handleError(err, "DynamoDB UpdateItem Failed: (UpdateItem)")
  1431  			return fmt.Errorf(ddbErr.ErrorMessage)
  1432  		} else {
  1433  			return nil
  1434  		}
  1435  	}, &xray.XTraceData{
  1436  		Meta: map[string]interface{}{
  1437  			"TableName":                 d.TableName,
  1438  			"PK":                        pkValue,
  1439  			"SK":                        skValue,
  1440  			"UpdateExpression":          updateExpression,
  1441  			"ConditionExpress":          conditionExpression,
  1442  			"ExpressionAttributeNames":  expressionAttributeNames,
  1443  			"ExpressionAttributeValues": expressionAttributeValues,
  1444  		},
  1445  	})
  1446  
  1447  	// update item successful
  1448  	return ddbErr
  1449  }
  1450  
  1451  func (d *DynamoDB) updateItemNormal(pkValue string, skValue string,
  1452  	updateExpression string,
  1453  	conditionExpression string,
  1454  	expressionAttributeNames map[string]*string,
  1455  	expressionAttributeValues map[string]*dynamodb.AttributeValue,
  1456  	timeOutDuration *time.Duration) (ddbErr *DynamoDBError) {
  1457  
  1458  	if d.cn == nil {
  1459  		return d.handleError(errors.New("DynamoDB Connection is Required"))
  1460  	}
  1461  
  1462  	if util.LenTrim(d.TableName) <= 0 {
  1463  		return d.handleError(errors.New("DynamoDB Table Name is Required"))
  1464  	}
  1465  
  1466  	// validate input parameters
  1467  	if util.LenTrim(d.PKName) <= 0 {
  1468  		return d.handleError(errors.New("DynamoDB UpdateItem Failed: " + "PK Name is Required"))
  1469  	}
  1470  
  1471  	if util.LenTrim(pkValue) <= 0 {
  1472  		return d.handleError(errors.New("DynamoDB UpdateItem Failed: " + "PK Value is Required"))
  1473  	}
  1474  
  1475  	if util.LenTrim(skValue) > 0 {
  1476  		if util.LenTrim(d.SKName) <= 0 {
  1477  			return d.handleError(errors.New("DynamoDB UpdateItem Failed: " + "SK Name is Required"))
  1478  		}
  1479  	}
  1480  
  1481  	if util.LenTrim(updateExpression) <= 0 {
  1482  		return d.handleError(errors.New("DynamoDB UpdateItem Failed: " + "UpdateExpression is Required"))
  1483  	}
  1484  
  1485  	if expressionAttributeValues == nil {
  1486  		return d.handleError(errors.New("DynamoDB UpdateItem Failed: " + "ExpressionAttributeValues is Required"))
  1487  	}
  1488  
  1489  	// define key
  1490  	m := make(map[string]*dynamodb.AttributeValue)
  1491  
  1492  	m[d.PKName] = &dynamodb.AttributeValue{S: aws.String(pkValue)}
  1493  
  1494  	if util.LenTrim(skValue) > 0 {
  1495  		m[d.SKName] = &dynamodb.AttributeValue{S: aws.String(skValue)}
  1496  	}
  1497  
  1498  	// build update item input params
  1499  	params := &dynamodb.UpdateItemInput{
  1500  		TableName:                 aws.String(d.TableName),
  1501  		Key:                       m,
  1502  		UpdateExpression:          aws.String(updateExpression),
  1503  		ExpressionAttributeValues: expressionAttributeValues,
  1504  		ReturnValues:              aws.String(dynamodb.ReturnValueAllNew),
  1505  	}
  1506  
  1507  	if util.LenTrim(conditionExpression) > 0 {
  1508  		params.ConditionExpression = aws.String(conditionExpression)
  1509  	}
  1510  
  1511  	if expressionAttributeNames != nil {
  1512  		params.ExpressionAttributeNames = expressionAttributeNames
  1513  	}
  1514  
  1515  	// record params payload
  1516  	d.LastExecuteParamsPayload = "UpdateItem = " + params.String()
  1517  
  1518  	// execute dynamodb service
  1519  	var err error
  1520  
  1521  	// create timeout context
  1522  	if timeOutDuration != nil {
  1523  		ctx, cancel := context.WithTimeout(context.Background(), *timeOutDuration)
  1524  		defer cancel()
  1525  		_, err = d.do_UpdateItem(params, ctx)
  1526  	} else {
  1527  		_, err = d.do_UpdateItem(params)
  1528  	}
  1529  
  1530  	if err != nil {
  1531  		ddbErr = d.handleError(err, "DynamoDB UpdateItem Failed: (UpdateItem)")
  1532  	} else {
  1533  		ddbErr = nil
  1534  	}
  1535  
  1536  	// update item successful
  1537  	return ddbErr
  1538  }
  1539  
  1540  // RemoveItemAttribute will remove attribute from dynamodb item in given table using primary key (PK, SK)
  1541  func (d *DynamoDB) RemoveItemAttribute(pkValue string, skValue string, removeExpression string, timeOutDuration *time.Duration) (ddbErr *DynamoDBError) {
  1542  	if xray.XRayServiceOn() {
  1543  		return d.removeItemAttributeWithTrace(pkValue, skValue, removeExpression, timeOutDuration)
  1544  	} else {
  1545  		return d.removeItemAttributeNormal(pkValue, skValue, removeExpression, timeOutDuration)
  1546  	}
  1547  }
  1548  
  1549  func (d *DynamoDB) removeItemAttributeWithTrace(pkValue string, skValue string, removeExpression string, timeOutDuration *time.Duration) (ddbErr *DynamoDBError) {
  1550  	trace := xray.NewSegment("DynamoDB-RemoveItemAttribute", d._parentSegment)
  1551  	defer trace.Close()
  1552  	defer func() {
  1553  		if ddbErr != nil {
  1554  			_ = trace.Seg.AddError(fmt.Errorf(ddbErr.ErrorMessage))
  1555  		}
  1556  	}()
  1557  
  1558  	if d.cn == nil {
  1559  		ddbErr = d.handleError(errors.New("DynamoDB Connection is Required"))
  1560  		return ddbErr
  1561  	}
  1562  
  1563  	if util.LenTrim(d.TableName) <= 0 {
  1564  		ddbErr = d.handleError(errors.New("DynamoDB Table Name is Required"))
  1565  		return ddbErr
  1566  	}
  1567  
  1568  	// validate input parameters
  1569  	if util.LenTrim(d.PKName) <= 0 {
  1570  		ddbErr = d.handleError(errors.New("DynamoDB RemoveItemAttribute Failed: " + "PK Name is Required"))
  1571  		return ddbErr
  1572  	}
  1573  
  1574  	if util.LenTrim(pkValue) <= 0 {
  1575  		ddbErr = d.handleError(errors.New("DynamoDB RemoveItemAttribute Failed: " + "PK Value is Required"))
  1576  		return ddbErr
  1577  	}
  1578  
  1579  	if util.LenTrim(skValue) > 0 {
  1580  		if util.LenTrim(d.SKName) <= 0 {
  1581  			ddbErr = d.handleError(errors.New("DynamoDB RemoveItemAttribute Failed: " + "SK Name is Required"))
  1582  			return ddbErr
  1583  		}
  1584  	}
  1585  
  1586  	if util.LenTrim(removeExpression) <= 0 {
  1587  		ddbErr = d.handleError(errors.New("DynamoDB RemoveItemAttribute Failed: " + "RemoveExpression is Required"))
  1588  		return ddbErr
  1589  	}
  1590  
  1591  	trace.Capture("RemoveItemAttribute", func() error {
  1592  		// define key
  1593  		m := make(map[string]*dynamodb.AttributeValue)
  1594  
  1595  		m[d.PKName] = &dynamodb.AttributeValue{S: aws.String(pkValue)}
  1596  
  1597  		if util.LenTrim(skValue) > 0 {
  1598  			m[d.SKName] = &dynamodb.AttributeValue{S: aws.String(skValue)}
  1599  		}
  1600  
  1601  		// build update item input params for remove item attribute action
  1602  		params := &dynamodb.UpdateItemInput{
  1603  			TableName:        aws.String(d.TableName),
  1604  			Key:              m,
  1605  			UpdateExpression: aws.String(removeExpression),
  1606  			ReturnValues:     aws.String(dynamodb.ReturnValueAllNew),
  1607  		}
  1608  
  1609  		// record params payload
  1610  		d.LastExecuteParamsPayload = "RemoveItemAttribute = " + params.String()
  1611  
  1612  		// execute dynamodb service
  1613  		var err error
  1614  
  1615  		subTrace := trace.NewSubSegment("RemoveItemAttribute_Do")
  1616  		defer subTrace.Close()
  1617  
  1618  		// create timeout context
  1619  		if timeOutDuration != nil {
  1620  			ctx, cancel := context.WithTimeout(subTrace.Ctx, *timeOutDuration)
  1621  			defer cancel()
  1622  			_, err = d.do_UpdateItem(params, ctx)
  1623  		} else {
  1624  			_, err = d.do_UpdateItem(params, subTrace.Ctx)
  1625  		}
  1626  
  1627  		if err != nil {
  1628  			ddbErr = d.handleError(err, "DynamoDB RemoveItemAttribute Failed: (UpdateItem to RemoveItemAttribute)")
  1629  			return fmt.Errorf(ddbErr.ErrorMessage)
  1630  		} else {
  1631  			return nil
  1632  		}
  1633  	}, &xray.XTraceData{
  1634  		Meta: map[string]interface{}{
  1635  			"TableName":        d.TableName,
  1636  			"PK":               pkValue,
  1637  			"SK":               skValue,
  1638  			"RemoveExpression": removeExpression,
  1639  		},
  1640  	})
  1641  
  1642  	// remove item attribute successful
  1643  	return ddbErr
  1644  }
  1645  
  1646  func (d *DynamoDB) removeItemAttributeNormal(pkValue string, skValue string, removeExpression string, timeOutDuration *time.Duration) (ddbErr *DynamoDBError) {
  1647  	if d.cn == nil {
  1648  		return d.handleError(errors.New("DynamoDB Connection is Required"))
  1649  	}
  1650  
  1651  	if util.LenTrim(d.TableName) <= 0 {
  1652  		return d.handleError(errors.New("DynamoDB Table Name is Required"))
  1653  	}
  1654  
  1655  	// validate input parameters
  1656  	if util.LenTrim(d.PKName) <= 0 {
  1657  		return d.handleError(errors.New("DynamoDB RemoveItemAttribute Failed: " + "PK Name is Required"))
  1658  	}
  1659  
  1660  	if util.LenTrim(pkValue) <= 0 {
  1661  		return d.handleError(errors.New("DynamoDB RemoveItemAttribute Failed: " + "PK Value is Required"))
  1662  	}
  1663  
  1664  	if util.LenTrim(skValue) > 0 {
  1665  		if util.LenTrim(d.SKName) <= 0 {
  1666  			return d.handleError(errors.New("DynamoDB RemoveItemAttribute Failed: " + "SK Name is Required"))
  1667  		}
  1668  	}
  1669  
  1670  	if util.LenTrim(removeExpression) <= 0 {
  1671  		return d.handleError(errors.New("DynamoDB RemoveItemAttribute Failed: " + "RemoveExpression is Required"))
  1672  	}
  1673  
  1674  	// define key
  1675  	m := make(map[string]*dynamodb.AttributeValue)
  1676  
  1677  	m[d.PKName] = &dynamodb.AttributeValue{S: aws.String(pkValue)}
  1678  
  1679  	if util.LenTrim(skValue) > 0 {
  1680  		m[d.SKName] = &dynamodb.AttributeValue{S: aws.String(skValue)}
  1681  	}
  1682  
  1683  	// build update item input params
  1684  	params := &dynamodb.UpdateItemInput{
  1685  		TableName:        aws.String(d.TableName),
  1686  		Key:              m,
  1687  		UpdateExpression: aws.String(removeExpression),
  1688  		ReturnValues:     aws.String(dynamodb.ReturnValueAllNew),
  1689  	}
  1690  
  1691  	// record params payload
  1692  	d.LastExecuteParamsPayload = "RemoveItemAttribute = " + params.String()
  1693  
  1694  	// execute dynamodb service
  1695  	var err error
  1696  
  1697  	// create timeout context
  1698  	if timeOutDuration != nil {
  1699  		ctx, cancel := context.WithTimeout(context.Background(), *timeOutDuration)
  1700  		defer cancel()
  1701  		_, err = d.do_UpdateItem(params, ctx)
  1702  	} else {
  1703  		_, err = d.do_UpdateItem(params)
  1704  	}
  1705  
  1706  	if err != nil {
  1707  		ddbErr = d.handleError(err, "DynamoDB RemoveItemAttribute Failed: (UpdateItem to RemoveItemAttribute)")
  1708  	} else {
  1709  		ddbErr = nil
  1710  	}
  1711  
  1712  	// remove item attribute successful
  1713  	return ddbErr
  1714  }
  1715  
  1716  // UpdateItemWithRetry handles dynamodb retries in case action temporarily fails
  1717  func (d *DynamoDB) UpdateItemWithRetry(maxRetries uint,
  1718  	pkValue string, skValue string,
  1719  	updateExpression string,
  1720  	conditionExpression string,
  1721  	expressionAttributeNames map[string]*string,
  1722  	expressionAttributeValues map[string]*dynamodb.AttributeValue,
  1723  	timeOutDuration *time.Duration) *DynamoDBError {
  1724  	if maxRetries > 10 {
  1725  		maxRetries = 10
  1726  	}
  1727  
  1728  	timeout := 10 * time.Second
  1729  
  1730  	if timeOutDuration != nil {
  1731  		timeout = *timeOutDuration
  1732  	}
  1733  
  1734  	if timeout < 10*time.Second {
  1735  		timeout = 10 * time.Second
  1736  	} else if timeout > 30*time.Second {
  1737  		timeout = 30 * time.Second
  1738  	}
  1739  
  1740  	if err := d.UpdateItem(pkValue, skValue, updateExpression, conditionExpression, expressionAttributeNames, expressionAttributeValues, util.DurationPtr(timeout)); err != nil {
  1741  		// has error
  1742  		if maxRetries > 0 {
  1743  			if err.AllowRetry {
  1744  				if err.RetryNeedsBackOff {
  1745  					time.Sleep(500 * time.Millisecond)
  1746  				} else {
  1747  					time.Sleep(100 * time.Millisecond)
  1748  				}
  1749  
  1750  				log.Println("UpdateItemWithRetry Failed: " + err.ErrorMessage)
  1751  				return d.UpdateItemWithRetry(maxRetries-1, pkValue, skValue, updateExpression, conditionExpression, expressionAttributeNames, expressionAttributeValues, util.DurationPtr(timeout))
  1752  			} else {
  1753  				if err.SuppressError {
  1754  					log.Println("UpdateItemWithRetry DynamoDB Error Suppressed, Returning Error Nil (MaxRetries = " + util.UintToStr(maxRetries) + ")")
  1755  					return nil
  1756  				} else {
  1757  					return &DynamoDBError{
  1758  						ErrorMessage:      "UpdateItemWithRetry Failed: " + err.ErrorMessage,
  1759  						SuppressError:     false,
  1760  						AllowRetry:        false,
  1761  						RetryNeedsBackOff: false,
  1762  					}
  1763  				}
  1764  			}
  1765  		} else {
  1766  			if err.SuppressError {
  1767  				log.Println("UpdateItemWithRetry DynamoDB Error Suppressed, Returning Error Nil (MaxRetries = 0)")
  1768  				return nil
  1769  			} else {
  1770  				return &DynamoDBError{
  1771  					ErrorMessage:      "UpdateItemWithRetry Failed: (MaxRetries = 0) " + err.ErrorMessage,
  1772  					SuppressError:     false,
  1773  					AllowRetry:        false,
  1774  					RetryNeedsBackOff: false,
  1775  				}
  1776  			}
  1777  		}
  1778  	} else {
  1779  		// no error
  1780  		return nil
  1781  	}
  1782  }
  1783  
  1784  // RemoveItemAttributeWithRetry handles dynamodb retries in case action temporarily fails
  1785  func (d *DynamoDB) RemoveItemAttributeWithRetry(maxRetries uint, pkValue string, skValue string, removeExpression string, timeOutDuration *time.Duration) *DynamoDBError {
  1786  	if maxRetries > 10 {
  1787  		maxRetries = 10
  1788  	}
  1789  
  1790  	timeout := 10 * time.Second
  1791  
  1792  	if timeOutDuration != nil {
  1793  		timeout = *timeOutDuration
  1794  	}
  1795  
  1796  	if timeout < 10*time.Second {
  1797  		timeout = 10 * time.Second
  1798  	} else if timeout > 30*time.Second {
  1799  		timeout = 30 * time.Second
  1800  	}
  1801  
  1802  	if err := d.RemoveItemAttribute(pkValue, skValue, removeExpression, util.DurationPtr(timeout)); err != nil {
  1803  		// has error
  1804  		if maxRetries > 0 {
  1805  			if err.AllowRetry {
  1806  				if err.RetryNeedsBackOff {
  1807  					time.Sleep(500 * time.Millisecond)
  1808  				} else {
  1809  					time.Sleep(100 * time.Millisecond)
  1810  				}
  1811  
  1812  				log.Println("RemoveItemAttributeWithRetry Failed: " + err.ErrorMessage)
  1813  				return d.RemoveItemAttributeWithRetry(maxRetries-1, pkValue, skValue, removeExpression, util.DurationPtr(timeout))
  1814  			} else {
  1815  				if err.SuppressError {
  1816  					log.Println("RemoveItemAttributeWithRetry DynamoDB Error Suppressed, Returning Error Nil (MaxRetries = " + util.UintToStr(maxRetries) + ")")
  1817  					return nil
  1818  				} else {
  1819  					return &DynamoDBError{
  1820  						ErrorMessage:      "RemoveItemAttributeWithRetry Failed: " + err.ErrorMessage,
  1821  						SuppressError:     false,
  1822  						AllowRetry:        false,
  1823  						RetryNeedsBackOff: false,
  1824  					}
  1825  				}
  1826  			}
  1827  		} else {
  1828  			if err.SuppressError {
  1829  				log.Println("RemoveItemAttributeWithRetry DynamoDB Error Suppressed, Returning Error Nil (MaxRetries = 0)")
  1830  				return nil
  1831  			} else {
  1832  				return &DynamoDBError{
  1833  					ErrorMessage:      "RemoveItemAttributeWithRetry Failed: (MaxRetries = 0) " + err.ErrorMessage,
  1834  					SuppressError:     false,
  1835  					AllowRetry:        false,
  1836  					RetryNeedsBackOff: false,
  1837  				}
  1838  			}
  1839  		}
  1840  	} else {
  1841  		// no error
  1842  		return nil
  1843  	}
  1844  }
  1845  
  1846  // DeleteItem will delete an existing item from dynamodb table, using primary key values (PK and SK)
  1847  //
  1848  // important
  1849  //
  1850  //	if dynamodb table is defined as PK and SK together, then to search, MUST use PK and SK together or error will trigger
  1851  //
  1852  // parameters:
  1853  //
  1854  //	pkValue = required, value of partition key to seek
  1855  //	skValue = optional, value of sort key to seek; set to blank if value not provided
  1856  //	timeOutDuration = optional, timeout duration sent via context to scan method; nil if not using timeout duration
  1857  func (d *DynamoDB) DeleteItem(pkValue string, skValue string, timeOutDuration *time.Duration) (ddbErr *DynamoDBError) {
  1858  	if xray.XRayServiceOn() {
  1859  		return d.deleteItemWithTrace(pkValue, skValue, timeOutDuration)
  1860  	} else {
  1861  		return d.deleteItemNormal(pkValue, skValue, timeOutDuration)
  1862  	}
  1863  }
  1864  
  1865  func (d *DynamoDB) deleteItemWithTrace(pkValue string, skValue string, timeOutDuration *time.Duration) (ddbErr *DynamoDBError) {
  1866  	trace := xray.NewSegment("DynamoDB-DeleteItem", d._parentSegment)
  1867  	defer trace.Close()
  1868  	defer func() {
  1869  		if ddbErr != nil {
  1870  			_ = trace.Seg.AddError(fmt.Errorf(ddbErr.ErrorMessage))
  1871  		}
  1872  	}()
  1873  
  1874  	if d.cn == nil {
  1875  		ddbErr = d.handleError(errors.New("DynamoDB Connection is Required"))
  1876  		return ddbErr
  1877  	}
  1878  
  1879  	if util.LenTrim(d.TableName) <= 0 {
  1880  		ddbErr = d.handleError(errors.New("DynamoDB Table Name is Required"))
  1881  		return ddbErr
  1882  	}
  1883  
  1884  	if util.LenTrim(d.PKName) <= 0 {
  1885  		ddbErr = d.handleError(errors.New("DynamoDB DeleteItem Failed: " + "PK Name is Required"))
  1886  		return ddbErr
  1887  	}
  1888  
  1889  	if util.LenTrim(pkValue) <= 0 {
  1890  		ddbErr = d.handleError(errors.New("DynamoDB DeleteItem Failed: " + "PK Value is Required"))
  1891  		return ddbErr
  1892  	}
  1893  
  1894  	if util.LenTrim(skValue) > 0 {
  1895  		if util.LenTrim(d.SKName) <= 0 {
  1896  			ddbErr = d.handleError(errors.New("DynamoDB DeleteItem Failed: " + "SK Name is Required"))
  1897  			return ddbErr
  1898  		}
  1899  	}
  1900  
  1901  	trace.Capture("DeleteItem", func() error {
  1902  		m := make(map[string]*dynamodb.AttributeValue)
  1903  
  1904  		m[d.PKName] = &dynamodb.AttributeValue{S: aws.String(pkValue)}
  1905  
  1906  		if util.LenTrim(skValue) > 0 {
  1907  			m[d.SKName] = &dynamodb.AttributeValue{S: aws.String(skValue)}
  1908  		}
  1909  
  1910  		params := &dynamodb.DeleteItemInput{
  1911  			TableName: aws.String(d.TableName),
  1912  			Key:       m,
  1913  		}
  1914  
  1915  		// record params payload
  1916  		d.LastExecuteParamsPayload = "DeleteItem = " + params.String()
  1917  
  1918  		var err error
  1919  
  1920  		subTrace := trace.NewSubSegment("DeleteItem_Do")
  1921  		defer subTrace.Close()
  1922  
  1923  		if timeOutDuration != nil {
  1924  			ctx, cancel := context.WithTimeout(subTrace.Ctx, *timeOutDuration)
  1925  			defer cancel()
  1926  			_, err = d.do_DeleteItem(params, ctx)
  1927  		} else {
  1928  			_, err = d.do_DeleteItem(params, subTrace.Ctx)
  1929  		}
  1930  
  1931  		if err != nil {
  1932  			ddbErr = d.handleError(err, "DynamoDB DeleteItem Failed: (DeleteItem)")
  1933  			return fmt.Errorf(ddbErr.ErrorMessage)
  1934  		} else {
  1935  			return nil
  1936  		}
  1937  	}, &xray.XTraceData{
  1938  		Meta: map[string]interface{}{
  1939  			"TableName": d.TableName,
  1940  			"PK":        pkValue,
  1941  			"SK":        skValue,
  1942  		},
  1943  	})
  1944  
  1945  	// delete item was successful
  1946  	return ddbErr
  1947  }
  1948  
  1949  func (d *DynamoDB) deleteItemNormal(pkValue string, skValue string, timeOutDuration *time.Duration) (ddbErr *DynamoDBError) {
  1950  	if d.cn == nil {
  1951  		return d.handleError(errors.New("DynamoDB Connection is Required"))
  1952  	}
  1953  
  1954  	if util.LenTrim(d.TableName) <= 0 {
  1955  		return d.handleError(errors.New("DynamoDB Table Name is Required"))
  1956  	}
  1957  
  1958  	if util.LenTrim(d.PKName) <= 0 {
  1959  		return d.handleError(errors.New("DynamoDB DeleteItem Failed: " + "PK Name is Required"))
  1960  	}
  1961  
  1962  	if util.LenTrim(pkValue) <= 0 {
  1963  		return d.handleError(errors.New("DynamoDB DeleteItem Failed: " + "PK Value is Required"))
  1964  	}
  1965  
  1966  	if util.LenTrim(skValue) > 0 {
  1967  		if util.LenTrim(d.SKName) <= 0 {
  1968  			return d.handleError(errors.New("DynamoDB DeleteItem Failed: " + "SK Name is Required"))
  1969  		}
  1970  	}
  1971  
  1972  	m := make(map[string]*dynamodb.AttributeValue)
  1973  
  1974  	m[d.PKName] = &dynamodb.AttributeValue{S: aws.String(pkValue)}
  1975  
  1976  	if util.LenTrim(skValue) > 0 {
  1977  		m[d.SKName] = &dynamodb.AttributeValue{S: aws.String(skValue)}
  1978  	}
  1979  
  1980  	params := &dynamodb.DeleteItemInput{
  1981  		TableName: aws.String(d.TableName),
  1982  		Key:       m,
  1983  	}
  1984  
  1985  	// record params payload
  1986  	d.LastExecuteParamsPayload = "DeleteItem = " + params.String()
  1987  
  1988  	var err error
  1989  
  1990  	if timeOutDuration != nil {
  1991  		ctx, cancel := context.WithTimeout(context.Background(), *timeOutDuration)
  1992  		defer cancel()
  1993  		_, err = d.do_DeleteItem(params, ctx)
  1994  	} else {
  1995  		_, err = d.do_DeleteItem(params)
  1996  	}
  1997  
  1998  	if err != nil {
  1999  		ddbErr = d.handleError(err, "DynamoDB DeleteItem Failed: (DeleteItem)")
  2000  	} else {
  2001  		ddbErr = nil
  2002  	}
  2003  
  2004  	// delete item was successful
  2005  	return ddbErr
  2006  }
  2007  
  2008  // DeleteItemWithRetry handles dynamodb retries in case action temporarily fails
  2009  func (d *DynamoDB) DeleteItemWithRetry(maxRetries uint, pkValue string, skValue string, timeOutDuration *time.Duration) *DynamoDBError {
  2010  	if maxRetries > 10 {
  2011  		maxRetries = 10
  2012  	}
  2013  
  2014  	timeout := 5 * time.Second
  2015  
  2016  	if timeOutDuration != nil {
  2017  		timeout = *timeOutDuration
  2018  	}
  2019  
  2020  	if timeout < 5*time.Second {
  2021  		timeout = 5 * time.Second
  2022  	} else if timeout > 15*time.Second {
  2023  		timeout = 15 * time.Second
  2024  	}
  2025  
  2026  	if err := d.DeleteItem(pkValue, skValue, util.DurationPtr(timeout)); err != nil {
  2027  		// has error
  2028  		if maxRetries > 0 {
  2029  			if err.AllowRetry {
  2030  				if err.RetryNeedsBackOff {
  2031  					time.Sleep(500 * time.Millisecond)
  2032  				} else {
  2033  					time.Sleep(100 * time.Millisecond)
  2034  				}
  2035  
  2036  				log.Println("DeleteItemWithRetry Failed: " + err.ErrorMessage)
  2037  				return d.DeleteItemWithRetry(maxRetries-1, pkValue, skValue, util.DurationPtr(timeout))
  2038  			} else {
  2039  				if err.SuppressError {
  2040  					log.Println("DeleteItemWithRetry DynamoDB Error Suppressed, Returning Error Nil (MaxRetries = " + util.UintToStr(maxRetries) + ")")
  2041  					return nil
  2042  				} else {
  2043  					return &DynamoDBError{
  2044  						ErrorMessage:      "DeleteItemWithRetry Failed: " + err.ErrorMessage,
  2045  						SuppressError:     false,
  2046  						AllowRetry:        false,
  2047  						RetryNeedsBackOff: false,
  2048  					}
  2049  				}
  2050  			}
  2051  		} else {
  2052  			if err.SuppressError {
  2053  				log.Println("DeleteItemWithRetry DynamoDB Error Suppressed, Returning Error Nil (MaxRetries = 0)")
  2054  				return nil
  2055  			} else {
  2056  				return &DynamoDBError{
  2057  					ErrorMessage:      "DeleteItemWithRetry Failed: (MaxRetries = 0) " + err.ErrorMessage,
  2058  					SuppressError:     false,
  2059  					AllowRetry:        false,
  2060  					RetryNeedsBackOff: false,
  2061  				}
  2062  			}
  2063  		}
  2064  	} else {
  2065  		// no error
  2066  		return nil
  2067  	}
  2068  }
  2069  
  2070  // GetItem will find an existing item from dynamodb table
  2071  //
  2072  // important
  2073  //
  2074  //	if dynamodb table is defined as PK and SK together, then to search, MUST use PK and SK together or error will trigger
  2075  //
  2076  // warning
  2077  //
  2078  //	projectedAttributes = if specified, must include PartitionKey (Hash key) typically "PK" as the first attribute in projected attributes
  2079  //
  2080  // parameters:
  2081  //
  2082  //	resultItemPtr = required, pointer to item object for return value to unmarshal into; if projected attributes less than struct fields, unmatched is defaulted
  2083  //		a) MUST BE STRUCT OBJECT; NEVER A SLICE
  2084  //	pkValue = required, value of partition key to seek
  2085  //	skValue = optional, value of sort key to seek; set to blank if value not provided
  2086  //	timeOutDuration = optional, timeout duration sent via context to scan method; nil if not using timeout duration
  2087  //	consistentRead = optional, scan uses consistent read or eventual consistent read, default is eventual consistent read
  2088  //	projectedAttributes = optional; ATTRIBUTES ARE CASE SENSITIVE; variadic list of attribute names that this query will project into result items;
  2089  //					      attribute names must match struct field name or struct tag's json / dynamodbav tag values,
  2090  //						  if specified, must include PartitionKey (Hash key) typically "PK" as the first attribute in projected attributes
  2091  //
  2092  // notes:
  2093  //
  2094  //	item struct tags
  2095  //		use `json:"" dynamodbav:""`
  2096  //			json = sets the name used in json
  2097  //			dynamodbav = sets the name used in dynamodb
  2098  //		reference child element
  2099  //			if struct has field with complex type (another struct), to reference it in code, use the parent struct field dot child field notation
  2100  //				Info in parent struct with struct tag as info; to reach child element: info.xyz
  2101  func (d *DynamoDB) GetItem(resultItemPtr interface{},
  2102  	pkValue string, skValue string,
  2103  	timeOutDuration *time.Duration, consistentRead *bool, projectedAttributes ...string) (ddbErr *DynamoDBError) {
  2104  	if xray.XRayServiceOn() {
  2105  		return d.getItemWithTrace(resultItemPtr, pkValue, skValue, timeOutDuration, consistentRead, projectedAttributes...)
  2106  	} else {
  2107  		return d.getItemNormal(resultItemPtr, pkValue, skValue, timeOutDuration, consistentRead, projectedAttributes...)
  2108  	}
  2109  }
  2110  
  2111  func (d *DynamoDB) getItemWithTrace(resultItemPtr interface{},
  2112  	pkValue string, skValue string,
  2113  	timeOutDuration *time.Duration, consistentRead *bool, projectedAttributes ...string) (ddbErr *DynamoDBError) {
  2114  	trace := xray.NewSegment("DynamoDB-GetItem", d._parentSegment)
  2115  	defer trace.Close()
  2116  	defer func() {
  2117  		if ddbErr != nil {
  2118  			_ = trace.Seg.AddError(fmt.Errorf(ddbErr.ErrorMessage))
  2119  		}
  2120  	}()
  2121  
  2122  	if d.cn == nil {
  2123  		ddbErr = d.handleError(errors.New("DynamoDB Connection is Required"))
  2124  		return ddbErr
  2125  	}
  2126  
  2127  	if util.LenTrim(d.TableName) <= 0 {
  2128  		ddbErr = d.handleError(errors.New("DynamoDB Table Name is Required"))
  2129  		return ddbErr
  2130  	}
  2131  
  2132  	// validate input parameters
  2133  	if resultItemPtr == nil {
  2134  		ddbErr = d.handleError(errors.New("DynamoDB GetItem Failed: " + "ResultItemPtr Must Initialize First"))
  2135  		return ddbErr
  2136  	}
  2137  
  2138  	if util.LenTrim(d.PKName) <= 0 {
  2139  		ddbErr = d.handleError(errors.New("DynamoDB GetItem Failed: " + "PK Name is Required"))
  2140  		return ddbErr
  2141  	}
  2142  
  2143  	if util.LenTrim(pkValue) <= 0 {
  2144  		ddbErr = d.handleError(errors.New("DynamoDB GetItem Failed: " + "PK Value is Required"))
  2145  		return ddbErr
  2146  	}
  2147  
  2148  	if util.LenTrim(skValue) > 0 {
  2149  		if util.LenTrim(d.SKName) <= 0 {
  2150  			ddbErr = d.handleError(errors.New("DynamoDB GetItem Failed: " + "SK Name is Required"))
  2151  			return ddbErr
  2152  		}
  2153  	}
  2154  
  2155  	trace.Capture("GetItem", func() error {
  2156  		// define key filter
  2157  		m := make(map[string]*dynamodb.AttributeValue)
  2158  
  2159  		m[d.PKName] = &dynamodb.AttributeValue{S: aws.String(pkValue)}
  2160  
  2161  		if util.LenTrim(skValue) > 0 {
  2162  			m[d.SKName] = &dynamodb.AttributeValue{S: aws.String(skValue)}
  2163  		}
  2164  
  2165  		// define projected attributes
  2166  		var proj expression.ProjectionBuilder
  2167  		projSet := false
  2168  
  2169  		if len(projectedAttributes) > 0 {
  2170  			// compose projected attributes if specified
  2171  			firstProjectedAttribute := expression.Name(projectedAttributes[0])
  2172  			moreProjectedAttributes := []expression.NameBuilder{}
  2173  
  2174  			if len(projectedAttributes) > 1 {
  2175  				firstAttribute := true
  2176  
  2177  				for _, v := range projectedAttributes {
  2178  					if !firstAttribute {
  2179  						moreProjectedAttributes = append(moreProjectedAttributes, expression.Name(v))
  2180  					} else {
  2181  						firstAttribute = false
  2182  					}
  2183  				}
  2184  			}
  2185  
  2186  			if len(moreProjectedAttributes) > 0 {
  2187  				proj = expression.NamesList(firstProjectedAttribute, moreProjectedAttributes...)
  2188  			} else {
  2189  				proj = expression.NamesList(firstProjectedAttribute)
  2190  			}
  2191  
  2192  			projSet = true
  2193  		}
  2194  
  2195  		// compose filter expression and projection if applicable
  2196  		var expr expression.Expression
  2197  		var err error
  2198  
  2199  		if projSet {
  2200  			if expr, err = expression.NewBuilder().WithProjection(proj).Build(); err != nil {
  2201  				ddbErr = d.handleError(err, "DynamoDB GetItem Failed: (GetItem)")
  2202  				return fmt.Errorf(ddbErr.ErrorMessage)
  2203  			}
  2204  		}
  2205  
  2206  		// set params
  2207  		params := &dynamodb.GetItemInput{
  2208  			TableName: aws.String(d.TableName),
  2209  			Key:       m,
  2210  		}
  2211  
  2212  		if projSet {
  2213  			params.ProjectionExpression = expr.Projection()
  2214  			params.ExpressionAttributeNames = expr.Names()
  2215  		}
  2216  
  2217  		if consistentRead != nil {
  2218  			if *consistentRead {
  2219  				params.ConsistentRead = consistentRead
  2220  			}
  2221  		}
  2222  
  2223  		// record params payload
  2224  		d.LastExecuteParamsPayload = "GetItem = " + params.String()
  2225  
  2226  		// execute get item action
  2227  		var result *dynamodb.GetItemOutput
  2228  
  2229  		subTrace := trace.NewSubSegment("GetItem_Do")
  2230  		defer subTrace.Close()
  2231  
  2232  		if timeOutDuration != nil {
  2233  			ctx, cancel := context.WithTimeout(subTrace.Ctx, *timeOutDuration)
  2234  			defer cancel()
  2235  			result, err = d.do_GetItem(params, ctx)
  2236  		} else {
  2237  			result, err = d.do_GetItem(params, subTrace.Ctx)
  2238  		}
  2239  
  2240  		// evaluate result
  2241  		if err != nil {
  2242  			ddbErr = d.handleError(err, "DynamoDB GetItem Failed: (GetItem)")
  2243  			return fmt.Errorf(ddbErr.ErrorMessage)
  2244  		}
  2245  
  2246  		if result == nil {
  2247  			ddbErr = d.handleError(errors.New("DynamoDB GetItem Failed: " + "Result Object Nil"))
  2248  			return fmt.Errorf(ddbErr.ErrorMessage)
  2249  		}
  2250  
  2251  		if err = dynamodbattribute.UnmarshalMap(result.Item, resultItemPtr); err != nil {
  2252  			ddbErr = d.handleError(err, "DynamoDB GetItem Failed: (Unmarshal)")
  2253  			return fmt.Errorf(ddbErr.ErrorMessage)
  2254  		} else {
  2255  			return nil
  2256  		}
  2257  	}, &xray.XTraceData{
  2258  		Meta: map[string]interface{}{
  2259  			"TableName": d.TableName,
  2260  			"PK":        pkValue,
  2261  			"SK":        skValue,
  2262  		},
  2263  	})
  2264  
  2265  	// get item was successful
  2266  	return ddbErr
  2267  }
  2268  
  2269  func (d *DynamoDB) getItemNormal(resultItemPtr interface{},
  2270  	pkValue string, skValue string,
  2271  	timeOutDuration *time.Duration, consistentRead *bool, projectedAttributes ...string) (ddbErr *DynamoDBError) {
  2272  	if d.cn == nil {
  2273  		return d.handleError(errors.New("DynamoDB Connection is Required"))
  2274  	}
  2275  
  2276  	if util.LenTrim(d.TableName) <= 0 {
  2277  		return d.handleError(errors.New("DynamoDB Table Name is Required"))
  2278  	}
  2279  
  2280  	// validate input parameters
  2281  	if resultItemPtr == nil {
  2282  		return d.handleError(errors.New("DynamoDB GetItem Failed: " + "ResultItemPtr Must Initialize First"))
  2283  	}
  2284  
  2285  	if util.LenTrim(d.PKName) <= 0 {
  2286  		return d.handleError(errors.New("DynamoDB GetItem Failed: " + "PK Name is Required"))
  2287  	}
  2288  
  2289  	if util.LenTrim(pkValue) <= 0 {
  2290  		return d.handleError(errors.New("DynamoDB GetItem Failed: " + "PK Value is Required"))
  2291  	}
  2292  
  2293  	if util.LenTrim(skValue) > 0 {
  2294  		if util.LenTrim(d.SKName) <= 0 {
  2295  			return d.handleError(errors.New("DynamoDB GetItem Failed: " + "SK Name is Required"))
  2296  		}
  2297  	}
  2298  
  2299  	// define key filter
  2300  	m := make(map[string]*dynamodb.AttributeValue)
  2301  
  2302  	m[d.PKName] = &dynamodb.AttributeValue{S: aws.String(pkValue)}
  2303  
  2304  	if util.LenTrim(skValue) > 0 {
  2305  		m[d.SKName] = &dynamodb.AttributeValue{S: aws.String(skValue)}
  2306  	}
  2307  
  2308  	// define projected attributes
  2309  	var proj expression.ProjectionBuilder
  2310  	projSet := false
  2311  
  2312  	if len(projectedAttributes) > 0 {
  2313  		// compose projected attributes if specified
  2314  		firstProjectedAttribute := expression.Name(projectedAttributes[0])
  2315  		moreProjectedAttributes := []expression.NameBuilder{}
  2316  
  2317  		if len(projectedAttributes) > 1 {
  2318  			firstAttribute := true
  2319  
  2320  			for _, v := range projectedAttributes {
  2321  				if !firstAttribute {
  2322  					moreProjectedAttributes = append(moreProjectedAttributes, expression.Name(v))
  2323  				} else {
  2324  					firstAttribute = false
  2325  				}
  2326  			}
  2327  		}
  2328  
  2329  		if len(moreProjectedAttributes) > 0 {
  2330  			proj = expression.NamesList(firstProjectedAttribute, moreProjectedAttributes...)
  2331  		} else {
  2332  			proj = expression.NamesList(firstProjectedAttribute)
  2333  		}
  2334  
  2335  		projSet = true
  2336  	}
  2337  
  2338  	// compose filter expression and projection if applicable
  2339  	var expr expression.Expression
  2340  	var err error
  2341  
  2342  	if projSet {
  2343  		if expr, err = expression.NewBuilder().WithProjection(proj).Build(); err != nil {
  2344  			return d.handleError(err, "DynamoDB GetItem Failed: (GetItem)")
  2345  		}
  2346  	}
  2347  
  2348  	// set params
  2349  	params := &dynamodb.GetItemInput{
  2350  		TableName: aws.String(d.TableName),
  2351  		Key:       m,
  2352  	}
  2353  
  2354  	if projSet {
  2355  		params.ProjectionExpression = expr.Projection()
  2356  		params.ExpressionAttributeNames = expr.Names()
  2357  	}
  2358  
  2359  	if consistentRead != nil {
  2360  		if *consistentRead {
  2361  			params.ConsistentRead = consistentRead
  2362  		}
  2363  	}
  2364  
  2365  	// record params payload
  2366  	d.LastExecuteParamsPayload = "GetItem = " + params.String()
  2367  
  2368  	// execute get item action
  2369  	var result *dynamodb.GetItemOutput
  2370  
  2371  	if timeOutDuration != nil {
  2372  		ctx, cancel := context.WithTimeout(context.Background(), *timeOutDuration)
  2373  		defer cancel()
  2374  		result, err = d.do_GetItem(params, ctx)
  2375  	} else {
  2376  		result, err = d.do_GetItem(params)
  2377  	}
  2378  
  2379  	// evaluate result
  2380  	if err != nil {
  2381  		return d.handleError(err, "DynamoDB GetItem Failed: (GetItem)")
  2382  	}
  2383  
  2384  	if result == nil {
  2385  		return d.handleError(errors.New("DynamoDB GetItem Failed: " + "Result Object Nil"))
  2386  	}
  2387  
  2388  	if err = dynamodbattribute.UnmarshalMap(result.Item, resultItemPtr); err != nil {
  2389  		ddbErr = d.handleError(err, "DynamoDB GetItem Failed: (Unmarshal)")
  2390  	} else {
  2391  		ddbErr = nil
  2392  	}
  2393  
  2394  	// get item was successful
  2395  	return ddbErr
  2396  }
  2397  
  2398  // GetItemWithRetry handles dynamodb retries in case action temporarily fails
  2399  //
  2400  // warning
  2401  //
  2402  //	projectedAttributes = if specified, must include PartitionKey (Hash key) typically "PK" as the first attribute in projected attributes
  2403  func (d *DynamoDB) GetItemWithRetry(maxRetries uint,
  2404  	resultItemPtr interface{}, pkValue string, skValue string,
  2405  	timeOutDuration *time.Duration, consistentRead *bool, projectedAttributes ...string) *DynamoDBError {
  2406  	if maxRetries > 10 {
  2407  		maxRetries = 10
  2408  	}
  2409  
  2410  	timeout := 5 * time.Second
  2411  
  2412  	if timeOutDuration != nil {
  2413  		timeout = *timeOutDuration
  2414  	}
  2415  
  2416  	if timeout < 5*time.Second {
  2417  		timeout = 5 * time.Second
  2418  	} else if timeout > 15*time.Second {
  2419  		timeout = 15 * time.Second
  2420  	}
  2421  
  2422  	if err := d.GetItem(resultItemPtr, pkValue, skValue, util.DurationPtr(timeout), consistentRead, projectedAttributes...); err != nil {
  2423  		// has error
  2424  		if maxRetries > 0 {
  2425  			if err.AllowRetry {
  2426  				if err.RetryNeedsBackOff {
  2427  					time.Sleep(500 * time.Millisecond)
  2428  				} else {
  2429  					time.Sleep(100 * time.Millisecond)
  2430  				}
  2431  
  2432  				log.Println("GetItemWithRetry Failed: " + err.ErrorMessage)
  2433  				return d.GetItemWithRetry(maxRetries-1, resultItemPtr, pkValue, skValue, util.DurationPtr(timeout), consistentRead, projectedAttributes...)
  2434  			} else {
  2435  				if err.SuppressError {
  2436  					log.Println("GetItemWithRetry DynamoDB Error Suppressed, Returning Error Nil (MaxRetries = " + util.UintToStr(maxRetries) + ")")
  2437  					return nil
  2438  				} else {
  2439  					return &DynamoDBError{
  2440  						ErrorMessage:      "GetItemWithRetry Failed: " + err.ErrorMessage,
  2441  						SuppressError:     false,
  2442  						AllowRetry:        false,
  2443  						RetryNeedsBackOff: false,
  2444  					}
  2445  				}
  2446  			}
  2447  		} else {
  2448  			if err.SuppressError {
  2449  				log.Println("GetItemWithRetry DynamoDB Error Suppressed, Returning Error Nil (MaxRetries = 0)")
  2450  				return nil
  2451  			} else {
  2452  				return &DynamoDBError{
  2453  					ErrorMessage:      "GetItemWithRetry Failed: (MaxRetries = 0) " + err.ErrorMessage,
  2454  					SuppressError:     false,
  2455  					AllowRetry:        false,
  2456  					RetryNeedsBackOff: false,
  2457  				}
  2458  			}
  2459  		}
  2460  	} else {
  2461  		// no error
  2462  		return nil
  2463  	}
  2464  }
  2465  
  2466  // QueryPaginationDataWithRetry returns slice of ExclusiveStartKeys,
  2467  // with first element always a nil to represent no exclusiveStartKey,
  2468  // and each subsequent element starts from page 2 with its own exclusiveStartKey.
  2469  //
  2470  // if slice is nil or zero element, then it also indicates single page,
  2471  // same as if slice is single element with nil indicating single page.
  2472  //
  2473  // Caller can use this info to pre-build the pagination buttons, so that clicking page 1 simply query using no exclusiveStartKey,
  2474  // where as query page 2 uses the exclusiveStartKey from element 1 of the slice, and so on.
  2475  func (d *DynamoDB) QueryPaginationDataWithRetry(
  2476  	maxRetries uint,
  2477  	timeOutDuration *time.Duration,
  2478  	indexName *string,
  2479  	itemsPerPage int64,
  2480  	keyConditionExpression string,
  2481  	expressionAttributeNames map[string]*string,
  2482  	expressionAttributeValues map[string]*dynamodb.AttributeValue) (paginationData []map[string]*dynamodb.AttributeValue, ddbErr *DynamoDBError) {
  2483  
  2484  	if maxRetries > 10 {
  2485  		maxRetries = 10
  2486  	}
  2487  
  2488  	timeout := 5 * time.Second
  2489  
  2490  	if timeOutDuration != nil {
  2491  		timeout = *timeOutDuration
  2492  	}
  2493  
  2494  	if timeout < 5*time.Second {
  2495  		timeout = 5 * time.Second
  2496  	} else if timeout > 15*time.Second {
  2497  		timeout = 15 * time.Second
  2498  	}
  2499  
  2500  	if paginationData, ddbErr = d.queryPaginationDataWrapper(util.DurationPtr(timeout), indexName, itemsPerPage, keyConditionExpression, expressionAttributeNames, expressionAttributeValues); ddbErr != nil {
  2501  		// has error
  2502  		if maxRetries > 0 {
  2503  			if ddbErr.AllowRetry {
  2504  				if ddbErr.RetryNeedsBackOff {
  2505  					time.Sleep(500 * time.Millisecond)
  2506  				} else {
  2507  					time.Sleep(100 * time.Millisecond)
  2508  				}
  2509  
  2510  				log.Println("QueryPaginationDataWithRetry Failed: " + ddbErr.ErrorMessage)
  2511  				return d.QueryPaginationDataWithRetry(maxRetries-1, util.DurationPtr(timeout), indexName, itemsPerPage, keyConditionExpression, expressionAttributeNames, expressionAttributeValues)
  2512  			} else {
  2513  				if ddbErr.SuppressError {
  2514  					log.Println("QueryPaginationDataWithRetry DynamoDB Error Suppressed, Returning Error Nil (MaxRetries = " + util.UintToStr(maxRetries) + ")")
  2515  					return nil, nil
  2516  				} else {
  2517  					return nil, &DynamoDBError{
  2518  						ErrorMessage:      "QueryPaginationDataWithRetry Failed: " + ddbErr.ErrorMessage,
  2519  						SuppressError:     false,
  2520  						AllowRetry:        false,
  2521  						RetryNeedsBackOff: false,
  2522  					}
  2523  				}
  2524  			}
  2525  		} else {
  2526  			if ddbErr.SuppressError {
  2527  				log.Println("QueryPaginationDataWithRetry DynamoDB Error Suppressed, Returning Error Nil (MaxRetries = 0)")
  2528  				return nil, nil
  2529  			} else {
  2530  				return nil, &DynamoDBError{
  2531  					ErrorMessage:      "QueryPaginationDataWithRetry Failed: (MaxRetries = 0) " + ddbErr.ErrorMessage,
  2532  					SuppressError:     false,
  2533  					AllowRetry:        false,
  2534  					RetryNeedsBackOff: false,
  2535  				}
  2536  			}
  2537  		}
  2538  	} else {
  2539  		// no error
  2540  		if paginationData == nil {
  2541  			paginationData = make([]map[string]*dynamodb.AttributeValue, 1)
  2542  		} else {
  2543  			paginationData = append([]map[string]*dynamodb.AttributeValue{nil}, paginationData...)
  2544  		}
  2545  
  2546  		return paginationData, nil
  2547  	}
  2548  }
  2549  
  2550  func (d *DynamoDB) queryPaginationDataWrapper(
  2551  	timeOutDuration *time.Duration,
  2552  	indexName *string,
  2553  	itemsPerPage int64,
  2554  	keyConditionExpression string,
  2555  	expressionAttributeNames map[string]*string,
  2556  	expressionAttributeValues map[string]*dynamodb.AttributeValue) (paginationData []map[string]*dynamodb.AttributeValue, ddbErr *DynamoDBError) {
  2557  
  2558  	if xray.XRayServiceOn() {
  2559  		return d.queryPaginationDataWithTrace(timeOutDuration, indexName, itemsPerPage, keyConditionExpression, expressionAttributeNames, expressionAttributeValues)
  2560  	} else {
  2561  		return d.queryPaginationDataNormal(timeOutDuration, indexName, itemsPerPage, keyConditionExpression, expressionAttributeNames, expressionAttributeValues)
  2562  	}
  2563  }
  2564  
  2565  func (d *DynamoDB) queryPaginationDataWithTrace(
  2566  	timeOutDuration *time.Duration,
  2567  	indexName *string,
  2568  	itemsPerPage int64,
  2569  	keyConditionExpression string,
  2570  	expressionAttributeNames map[string]*string,
  2571  	expressionAttributeValues map[string]*dynamodb.AttributeValue) (paginationData []map[string]*dynamodb.AttributeValue, ddbErr *DynamoDBError) {
  2572  
  2573  	trace := xray.NewSegment("DynamoDB-QueryPaginationDataWithTrace", d._parentSegment)
  2574  	defer trace.Close()
  2575  	defer func() {
  2576  		if ddbErr != nil {
  2577  			_ = trace.Seg.AddError(fmt.Errorf(ddbErr.ErrorMessage))
  2578  		}
  2579  	}()
  2580  
  2581  	if d.cn == nil {
  2582  		ddbErr = d.handleError(errors.New("QueryPaginationDataWithTrace Failed: DynamoDB Connection is Required"))
  2583  		return nil, ddbErr
  2584  	}
  2585  
  2586  	if util.LenTrim(d.TableName) <= 0 {
  2587  		ddbErr = d.handleError(errors.New("QueryPaginationDataWithTrace Failed: DynamoDB Table Name is Required"))
  2588  		return nil, ddbErr
  2589  	}
  2590  
  2591  	// validate additional input parameters
  2592  	if util.LenTrim(keyConditionExpression) <= 0 {
  2593  		ddbErr = d.handleError(errors.New("QueryPaginationDataWithTrace Failed: KeyConditionExpress is Required"))
  2594  		return nil, ddbErr
  2595  	}
  2596  
  2597  	if expressionAttributeValues == nil {
  2598  		ddbErr = d.handleError(errors.New("QueryPaginationDataWithTrace Failed: ExpressionAttributeValues is Required"))
  2599  		return nil, ddbErr
  2600  	}
  2601  
  2602  	trace.Capture("QueryPaginationDataWithTrace", func() error {
  2603  		// compose filter expression and projection if applicable
  2604  		expr, err := expression.NewBuilder().WithProjection(expression.NamesList(expression.Name("PK"))).Build()
  2605  
  2606  		if err != nil {
  2607  			ddbErr = d.handleError(err, "QueryPaginationDataWithTrace Failed: (Filter/Projection Expression Build)")
  2608  			return fmt.Errorf(ddbErr.ErrorMessage)
  2609  		}
  2610  
  2611  		// build query input params
  2612  		params := &dynamodb.QueryInput{
  2613  			TableName:                 aws.String(d.TableName),
  2614  			KeyConditionExpression:    aws.String(keyConditionExpression),
  2615  			ExpressionAttributeValues: expressionAttributeValues,
  2616  		}
  2617  
  2618  		if expressionAttributeNames != nil {
  2619  			params.ExpressionAttributeNames = expressionAttributeNames
  2620  		}
  2621  
  2622  		params.FilterExpression = expr.Filter()
  2623  
  2624  		if params.ExpressionAttributeNames == nil {
  2625  			params.ExpressionAttributeNames = make(map[string]*string)
  2626  		}
  2627  
  2628  		for k, v := range expr.Names() {
  2629  			params.ExpressionAttributeNames[k] = v
  2630  		}
  2631  
  2632  		for k, v := range expr.Values() {
  2633  			params.ExpressionAttributeValues[k] = v
  2634  		}
  2635  
  2636  		params.ProjectionExpression = expr.Projection()
  2637  
  2638  		if params.ExpressionAttributeNames == nil {
  2639  			params.ExpressionAttributeNames = expr.Names()
  2640  		} else {
  2641  			for k1, v1 := range expr.Names() {
  2642  				params.ExpressionAttributeNames[k1] = v1
  2643  			}
  2644  		}
  2645  
  2646  		if indexName != nil && util.LenTrim(*indexName) > 0 {
  2647  			params.IndexName = indexName
  2648  		}
  2649  
  2650  		params.Limit = aws.Int64(itemsPerPage)
  2651  
  2652  		// record params payload
  2653  		d.LastExecuteParamsPayload = "QueryPaginationDataWithTrace = " + params.String()
  2654  
  2655  		subTrace := trace.NewSubSegment("QueryPaginationDataWithTrace_Do")
  2656  		defer subTrace.Close()
  2657  
  2658  		if timeOutDuration != nil {
  2659  			ctx, cancel := context.WithTimeout(subTrace.Ctx, *timeOutDuration)
  2660  			defer cancel()
  2661  			paginationData, err = d.do_Query_Pagination_Data(params, ctx)
  2662  		} else {
  2663  			paginationData, err = d.do_Query_Pagination_Data(params, subTrace.Ctx)
  2664  		}
  2665  
  2666  		if err != nil {
  2667  			ddbErr = d.handleError(err, "QueryPaginationDataWithTrace Failed: (QueryPaginationDataWithTrace)")
  2668  			return fmt.Errorf(ddbErr.ErrorMessage)
  2669  		}
  2670  
  2671  		return nil
  2672  
  2673  	}, &xray.XTraceData{
  2674  		Meta: map[string]interface{}{
  2675  			"TableName":                 d.TableName,
  2676  			"IndexName":                 aws.StringValue(indexName),
  2677  			"KeyConditionExpression":    keyConditionExpression,
  2678  			"ExpressionAttributeNames":  expressionAttributeNames,
  2679  			"ExpressionAttributeValues": expressionAttributeValues,
  2680  		},
  2681  	})
  2682  
  2683  	// query items successful
  2684  	return paginationData, ddbErr
  2685  }
  2686  
  2687  func (d *DynamoDB) queryPaginationDataNormal(
  2688  	timeOutDuration *time.Duration,
  2689  	indexName *string,
  2690  	itemsPerPage int64,
  2691  	keyConditionExpression string,
  2692  	expressionAttributeNames map[string]*string,
  2693  	expressionAttributeValues map[string]*dynamodb.AttributeValue) (paginationData []map[string]*dynamodb.AttributeValue, ddbErr *DynamoDBError) {
  2694  
  2695  	if d.cn == nil {
  2696  		return nil, d.handleError(errors.New("QueryPaginationDataNormal Failed: DynamoDB Connection is Required"))
  2697  	}
  2698  
  2699  	if util.LenTrim(d.TableName) <= 0 {
  2700  		return nil, d.handleError(errors.New("QueryPaginationDataNormal Failed: DynamoDB Table Name is Required"))
  2701  	}
  2702  
  2703  	// validate additional input parameters
  2704  	if util.LenTrim(keyConditionExpression) <= 0 {
  2705  		return nil, d.handleError(errors.New("QueryPaginationDataNormal Failed: KeyConditionExpress is Required"))
  2706  	}
  2707  
  2708  	if expressionAttributeValues == nil {
  2709  		return nil, d.handleError(errors.New("QueryPaginationDataNormal Failed: ExpressionAttributeValues is Required"))
  2710  	}
  2711  
  2712  	// compose filter expression and projection if applicable
  2713  	expr, err := expression.NewBuilder().WithProjection(expression.NamesList(expression.Name("PK"))).Build()
  2714  
  2715  	if err != nil {
  2716  		return nil, d.handleError(err, "QueryPaginationDataNormal Failed: (Filter/Projection Expression Build)")
  2717  	}
  2718  
  2719  	// build query input params
  2720  	params := &dynamodb.QueryInput{
  2721  		TableName:                 aws.String(d.TableName),
  2722  		KeyConditionExpression:    aws.String(keyConditionExpression),
  2723  		ExpressionAttributeValues: expressionAttributeValues,
  2724  	}
  2725  
  2726  	if expressionAttributeNames != nil {
  2727  		params.ExpressionAttributeNames = expressionAttributeNames
  2728  	}
  2729  
  2730  	params.FilterExpression = expr.Filter()
  2731  
  2732  	if params.ExpressionAttributeNames == nil {
  2733  		params.ExpressionAttributeNames = make(map[string]*string)
  2734  	}
  2735  
  2736  	for k, v := range expr.Names() {
  2737  		params.ExpressionAttributeNames[k] = v
  2738  	}
  2739  
  2740  	for k, v := range expr.Values() {
  2741  		params.ExpressionAttributeValues[k] = v
  2742  	}
  2743  
  2744  	params.ProjectionExpression = expr.Projection()
  2745  
  2746  	if params.ExpressionAttributeNames == nil {
  2747  		params.ExpressionAttributeNames = expr.Names()
  2748  	} else {
  2749  		for k1, v1 := range expr.Names() {
  2750  			params.ExpressionAttributeNames[k1] = v1
  2751  		}
  2752  	}
  2753  
  2754  	if indexName != nil && util.LenTrim(*indexName) > 0 {
  2755  		params.IndexName = indexName
  2756  	}
  2757  
  2758  	params.Limit = aws.Int64(itemsPerPage)
  2759  
  2760  	// record params payload
  2761  	d.LastExecuteParamsPayload = "QueryPaginationDataNormal = " + params.String()
  2762  
  2763  	if timeOutDuration != nil {
  2764  		ctx, cancel := context.WithTimeout(context.Background(), *timeOutDuration)
  2765  		defer cancel()
  2766  		paginationData, err = d.do_Query_Pagination_Data(params, ctx)
  2767  	} else {
  2768  		paginationData, err = d.do_Query_Pagination_Data(params)
  2769  	}
  2770  
  2771  	if err != nil {
  2772  		return nil, d.handleError(err, "QueryPaginationDataNormal Failed: (QueryPaginationDataNormal)")
  2773  	}
  2774  
  2775  	if paginationData == nil {
  2776  		return nil, d.handleError(err, "QueryPaginationDataNormal Failed: (QueryPaginationDataNormal)")
  2777  	}
  2778  
  2779  	// query pagination data successful
  2780  	return paginationData, nil
  2781  }
  2782  
  2783  // QueryItems will query dynamodb items in given table using primary key (PK, SK for example), or one of Global/Local Secondary Keys (indexName must be defined if using GSI)
  2784  // To query against non-key attributes, use Scan (bad for performance however)
  2785  // QueryItems requires using Key attributes, and limited to TWO key attributes in condition maximum;
  2786  //
  2787  // important
  2788  //
  2789  //	if dynamodb table is defined as PK and SK together, then to search without GSI/LSI, MUST use PK and SK together or error will trigger
  2790  //
  2791  // warning
  2792  //
  2793  //	projectedAttributes = if specified, must include PartitionKey (Hash key) typically "PK" as the first attribute in projected attributes
  2794  //
  2795  // parameters:
  2796  //
  2797  //	resultItemsPtr = required, pointer to items list struct to contain queried result; i.e. []Item{} where Item is struct; if projected attributes less than struct fields, unmatched is defaulted
  2798  //	timeOutDuration = optional, timeout duration sent via context to scan method; nil if not using timeout duration
  2799  //	consistentRead = optional, scan uses consistent read or eventual consistent read, default is eventual consistent read
  2800  //	indexName = optional, global secondary index or local secondary index name to help in query operation
  2801  //	pageLimit = optional, scan page limit if set, this limits number of items examined per page during scan operation, allowing scan to work better for RCU
  2802  //	pagedQuery = optional, indicates if query is page based or not; if true, query will be performed via pages, this helps overcome 1 MB limit of each query result
  2803  //	pagedQueryPageCountLimit = optional, indicates how many pages to query during paged query action
  2804  //	exclusiveStartKey = optional, if using pagedQuery and starting the query from prior results
  2805  //
  2806  //	keyConditionExpression = required, ATTRIBUTES ARE CASE SENSITIVE; either the primary key (PK SK for example) or global secondary index (SK Data for example) or another secondary index (secondary index must be named)
  2807  //		Usage Syntax:
  2808  //			1) Max 2 Attribute Fields
  2809  //			2) First Field must be Partition Key (Must Evaluate to True or False)
  2810  //				a) = ONLY
  2811  //			3) Second Field is Sort Key (May Evaluate to True or False or Range)
  2812  //				a) =, <, <=, >, >=, BETWEEN, begins_with()
  2813  //			4) Combine Two Fields with AND
  2814  //			5) When Attribute Name is Reserved Keyword, Use ExpressionAttributeNames to Define #xyz to Alias
  2815  //				a) Use the #xyz in the KeyConditionExpression such as #yr = :year (:year is Defined ExpressionAttributeValue)
  2816  //			6) Example
  2817  //				a) partitionKeyName = :partitionKeyVal
  2818  //				b) partitionKeyName = :partitionKeyVal AND sortKeyName = :sortKeyVal
  2819  //				c) #yr = :year
  2820  //			7) If Using GSI / Local Index
  2821  //				a) When Using, Must Specify the IndexName
  2822  //				b) First Field is the GSI's Partition Key, such as SK (Evals to True/False), While Second Field is the GSI's SortKey (Range)
  2823  //
  2824  //	expressionAttributeNames = optional, ATTRIBUTES ARE CASE SENSITIVE; set nil if not used, must define for attribute names that are reserved keywords such as year, data etc. using #xyz
  2825  //		Usage Syntax:
  2826  //			1) map[string]*string: where string is the #xyz, and *string is the original xyz attribute name
  2827  //				a) map[string]*string { "#xyz": aws.String("Xyz"), }
  2828  //			2) Add to Map
  2829  //				a) m := make(map[string]*string)
  2830  //				b) m["#xyz"] = aws.String("Xyz")
  2831  //
  2832  //	expressionAttributeValues = required, ATTRIBUTES ARE CASE SENSITIVE; sets the value token and value actual to be used within the keyConditionExpression; this sets both compare token and compare value
  2833  //		Usage Syntax:
  2834  //			1) map[string]*dynamodb.AttributeValue: where string is the :xyz, and *dynamodb.AttributeValue is { S: aws.String("abc"), },
  2835  //				a) map[string]*dynamodb.AttributeValue { ":xyz" : { S: aws.String("abc"), }, ":xyy" : { N: aws.String("123"), }, }
  2836  //			2) Add to Map
  2837  //				a) m := make(map[string]*dynamodb.AttributeValue)
  2838  //				b) m[":xyz"] = &dynamodb.AttributeValue{ S: aws.String("xyz") }
  2839  //			3) Slice of Strings -> CONVERT To Slice of *dynamodb.AttributeValue = []string -> []*dynamodb.AttributeValue
  2840  //				a) av, err := dynamodbattribute.MarshalList(xyzSlice)
  2841  //				b) ExpressionAttributeValue, Use 'L' To Represent the List for av defined in 3.a above
  2842  //
  2843  //	filterConditionExpression = optional; ATTRIBUTES ARE CASE SENSITIVE; once query on key conditions returned, this filter condition further restricts return data before output to caller;
  2844  //		Usage Syntax:
  2845  //			1) &expression.Name(xyz).Equals(expression.Value(abc))
  2846  //			2) &expression.Name(xyz).Equals(expression.Value(abc)).And(...)
  2847  //
  2848  //	projectedAttributes = optional; ATTRIBUTES ARE CASE SENSITIVE; variadic list of attribute names that this query will project into result items;
  2849  //					      attribute names must match struct field name or struct tag's json / dynamodbav tag values
  2850  //
  2851  // Return Values:
  2852  //
  2853  //	prevEvalKey = if paged query, the last evaluate key returned, to be used in subsequent query via exclusiveStartKey; otherwise always nil is returned
  2854  //				  prevEvalkey map is set into exclusiveStartKey field if more data to load
  2855  //
  2856  // notes:
  2857  //
  2858  //	item struct tags
  2859  //		use `json:"" dynamodbav:""`
  2860  //			json = sets the name used in json
  2861  //			dynamodbav = sets the name used in dynamodb
  2862  //		reference child element
  2863  //			if struct has field with complex type (another struct), to reference it in code, use the parent struct field dot child field notation
  2864  //				Info in parent struct with struct tag as info; to reach child element: info.xyz
  2865  func (d *DynamoDB) QueryItems(resultItemsPtr interface{},
  2866  	timeOutDuration *time.Duration,
  2867  	consistentRead *bool,
  2868  	indexName *string,
  2869  	pageLimit *int64,
  2870  	pagedQuery bool,
  2871  	pagedQueryPageCountLimit *int64,
  2872  	exclusiveStartKey map[string]*dynamodb.AttributeValue,
  2873  	keyConditionExpression string,
  2874  	expressionAttributeNames map[string]*string,
  2875  	expressionAttributeValues map[string]*dynamodb.AttributeValue,
  2876  	filterConditionExpression *expression.ConditionBuilder,
  2877  	projectedAttributes ...string) (prevEvalKey map[string]*dynamodb.AttributeValue, ddbErr *DynamoDBError) {
  2878  	if xray.XRayServiceOn() {
  2879  		return d.queryItemsWithTrace(resultItemsPtr, timeOutDuration, consistentRead, indexName, pageLimit, pagedQuery, pagedQueryPageCountLimit, exclusiveStartKey,
  2880  			keyConditionExpression, expressionAttributeNames, expressionAttributeValues, filterConditionExpression, projectedAttributes...)
  2881  	} else {
  2882  		return d.queryItemsNormal(resultItemsPtr, timeOutDuration, consistentRead, indexName, pageLimit, pagedQuery, pagedQueryPageCountLimit, exclusiveStartKey,
  2883  			keyConditionExpression, expressionAttributeNames, expressionAttributeValues, filterConditionExpression, projectedAttributes...)
  2884  	}
  2885  }
  2886  
  2887  func (d *DynamoDB) queryItemsWithTrace(resultItemsPtr interface{},
  2888  	timeOutDuration *time.Duration,
  2889  	consistentRead *bool,
  2890  	indexName *string,
  2891  	pageLimit *int64,
  2892  	pagedQuery bool,
  2893  	pagedQueryPageCountLimit *int64,
  2894  	exclusiveStartKey map[string]*dynamodb.AttributeValue,
  2895  	keyConditionExpression string,
  2896  	expressionAttributeNames map[string]*string,
  2897  	expressionAttributeValues map[string]*dynamodb.AttributeValue,
  2898  	filterConditionExpression *expression.ConditionBuilder,
  2899  	projectedAttributes ...string) (prevEvalKey map[string]*dynamodb.AttributeValue, ddbErr *DynamoDBError) {
  2900  	trace := xray.NewSegment("DynamoDB-QueryItems", d._parentSegment)
  2901  	defer trace.Close()
  2902  	defer func() {
  2903  		if ddbErr != nil {
  2904  			_ = trace.Seg.AddError(fmt.Errorf(ddbErr.ErrorMessage))
  2905  		}
  2906  	}()
  2907  
  2908  	if d.cn == nil {
  2909  		ddbErr = d.handleError(errors.New("DynamoDB Connection is Required"))
  2910  		return nil, ddbErr
  2911  	}
  2912  
  2913  	if util.LenTrim(d.TableName) <= 0 {
  2914  		ddbErr = d.handleError(errors.New("DynamoDB Table Name is Required"))
  2915  		return nil, ddbErr
  2916  	}
  2917  
  2918  	// result items pointer must be set
  2919  	if resultItemsPtr == nil {
  2920  		ddbErr = d.handleError(errors.New("DynamoDB QueryItems Failed: " + "ResultItems is Nil"))
  2921  		return nil, ddbErr
  2922  	}
  2923  
  2924  	// validate additional input parameters
  2925  	if util.LenTrim(keyConditionExpression) <= 0 {
  2926  		ddbErr = d.handleError(errors.New("DynamoDB QueryItems Failed: " + "KeyConditionExpress is Required"))
  2927  		return nil, ddbErr
  2928  	}
  2929  
  2930  	if expressionAttributeValues == nil {
  2931  		ddbErr = d.handleError(errors.New("DynamoDB QueryItems Failed: " + "ExpressionAttributeValues is Required"))
  2932  		return nil, ddbErr
  2933  	}
  2934  
  2935  	// execute dynamodb service
  2936  	var result *dynamodb.QueryOutput
  2937  
  2938  	trace.Capture("QueryItems", func() error {
  2939  		// gather projection attributes
  2940  		var proj expression.ProjectionBuilder
  2941  		projSet := false
  2942  
  2943  		if len(projectedAttributes) > 0 {
  2944  			// compose projected attributes if specified
  2945  			firstProjectedAttribute := expression.Name(projectedAttributes[0])
  2946  			moreProjectedAttributes := []expression.NameBuilder{}
  2947  
  2948  			if len(projectedAttributes) > 1 {
  2949  				firstAttribute := true
  2950  
  2951  				for _, v := range projectedAttributes {
  2952  					if !firstAttribute {
  2953  						moreProjectedAttributes = append(moreProjectedAttributes, expression.Name(v))
  2954  					} else {
  2955  						firstAttribute = false
  2956  					}
  2957  				}
  2958  			}
  2959  
  2960  			if len(moreProjectedAttributes) > 0 {
  2961  				proj = expression.NamesList(firstProjectedAttribute, moreProjectedAttributes...)
  2962  			} else {
  2963  				proj = expression.NamesList(firstProjectedAttribute)
  2964  			}
  2965  
  2966  			projSet = true
  2967  		}
  2968  
  2969  		// compose filter expression and projection if applicable
  2970  		var expr expression.Expression
  2971  		var err error
  2972  
  2973  		filterSet := false
  2974  
  2975  		if filterConditionExpression != nil && projSet {
  2976  			expr, err = expression.NewBuilder().WithFilter(*filterConditionExpression).WithProjection(proj).Build()
  2977  			filterSet = true
  2978  			projSet = true
  2979  		} else if filterConditionExpression != nil {
  2980  			expr, err = expression.NewBuilder().WithFilter(*filterConditionExpression).Build()
  2981  			filterSet = true
  2982  			projSet = false
  2983  		} else if projSet {
  2984  			expr, err = expression.NewBuilder().WithProjection(proj).Build()
  2985  			filterSet = false
  2986  			projSet = true
  2987  		}
  2988  
  2989  		if err != nil {
  2990  			ddbErr = d.handleError(err, "DynamoDB QueryItems Failed (Filter/Projection Expression Build)")
  2991  			return fmt.Errorf(ddbErr.ErrorMessage)
  2992  		}
  2993  
  2994  		// build query input params
  2995  		params := &dynamodb.QueryInput{
  2996  			TableName:                 aws.String(d.TableName),
  2997  			KeyConditionExpression:    aws.String(keyConditionExpression),
  2998  			ExpressionAttributeValues: expressionAttributeValues,
  2999  		}
  3000  
  3001  		if expressionAttributeNames != nil {
  3002  			params.ExpressionAttributeNames = expressionAttributeNames
  3003  		}
  3004  
  3005  		if filterSet {
  3006  			params.FilterExpression = expr.Filter()
  3007  
  3008  			if params.ExpressionAttributeNames == nil {
  3009  				params.ExpressionAttributeNames = make(map[string]*string)
  3010  			}
  3011  
  3012  			for k, v := range expr.Names() {
  3013  				params.ExpressionAttributeNames[k] = v
  3014  			}
  3015  
  3016  			for k, v := range expr.Values() {
  3017  				params.ExpressionAttributeValues[k] = v
  3018  			}
  3019  		}
  3020  
  3021  		if projSet {
  3022  			params.ProjectionExpression = expr.Projection()
  3023  
  3024  			if params.ExpressionAttributeNames == nil {
  3025  				params.ExpressionAttributeNames = expr.Names()
  3026  			} else {
  3027  				for k1, v1 := range expr.Names() {
  3028  					params.ExpressionAttributeNames[k1] = v1
  3029  				}
  3030  			}
  3031  		}
  3032  
  3033  		if consistentRead != nil {
  3034  			if *consistentRead {
  3035  				if len(*indexName) > 0 {
  3036  					// gsi not valid for consistent read, turn off consistent read
  3037  					*consistentRead = false
  3038  				}
  3039  			}
  3040  
  3041  			params.ConsistentRead = consistentRead
  3042  		}
  3043  
  3044  		if indexName != nil && util.LenTrim(*indexName) > 0 {
  3045  			params.IndexName = indexName
  3046  		}
  3047  
  3048  		if pageLimit != nil {
  3049  			params.Limit = pageLimit
  3050  		}
  3051  
  3052  		if exclusiveStartKey != nil {
  3053  			params.ExclusiveStartKey = exclusiveStartKey
  3054  		}
  3055  
  3056  		// record params payload
  3057  		d.LastExecuteParamsPayload = "QueryItems = " + params.String()
  3058  
  3059  		subTrace := trace.NewSubSegment("QueryItems_Do")
  3060  		defer subTrace.Close()
  3061  
  3062  		if timeOutDuration != nil {
  3063  			ctx, cancel := context.WithTimeout(subTrace.Ctx, *timeOutDuration)
  3064  			defer cancel()
  3065  			result, err = d.do_Query(params, pagedQuery, pagedQueryPageCountLimit, ctx)
  3066  		} else {
  3067  			result, err = d.do_Query(params, pagedQuery, pagedQueryPageCountLimit, subTrace.Ctx)
  3068  		}
  3069  
  3070  		if err != nil {
  3071  			ddbErr = d.handleError(err, "DynamoDB QueryItems Failed: (QueryItems)")
  3072  			return fmt.Errorf(ddbErr.ErrorMessage)
  3073  		}
  3074  
  3075  		if result == nil {
  3076  			return nil
  3077  		}
  3078  
  3079  		// unmarshal result items to target object map
  3080  		if err = dynamodbattribute.UnmarshalListOfMaps(result.Items, resultItemsPtr); err != nil {
  3081  			ddbErr = d.handleError(err, "Dynamo QueryItems Failed: (Unmarshal Result Items)")
  3082  			return fmt.Errorf(ddbErr.ErrorMessage)
  3083  		} else {
  3084  			return nil
  3085  		}
  3086  	}, &xray.XTraceData{
  3087  		Meta: map[string]interface{}{
  3088  			"TableName":                 d.TableName,
  3089  			"IndexName":                 aws.StringValue(indexName),
  3090  			"ExclusiveStartKey":         exclusiveStartKey,
  3091  			"KeyConditionExpression":    keyConditionExpression,
  3092  			"ExpressionAttributeNames":  expressionAttributeNames,
  3093  			"ExpressionAttributeValues": expressionAttributeValues,
  3094  			"FilterConditionExpression": filterConditionExpression,
  3095  		},
  3096  	})
  3097  
  3098  	// query items successful
  3099  	if result != nil {
  3100  		return result.LastEvaluatedKey, ddbErr
  3101  	} else {
  3102  		return nil, ddbErr
  3103  	}
  3104  }
  3105  
  3106  func (d *DynamoDB) queryItemsNormal(resultItemsPtr interface{},
  3107  	timeOutDuration *time.Duration,
  3108  	consistentRead *bool,
  3109  	indexName *string,
  3110  	pageLimit *int64,
  3111  	pagedQuery bool,
  3112  	pagedQueryPageCountLimit *int64,
  3113  	exclusiveStartKey map[string]*dynamodb.AttributeValue,
  3114  	keyConditionExpression string,
  3115  	expressionAttributeNames map[string]*string,
  3116  	expressionAttributeValues map[string]*dynamodb.AttributeValue,
  3117  	filterConditionExpression *expression.ConditionBuilder,
  3118  	projectedAttributes ...string) (prevEvalKey map[string]*dynamodb.AttributeValue, ddbErr *DynamoDBError) {
  3119  	if d.cn == nil {
  3120  		return nil, d.handleError(errors.New("DynamoDB Connection is Required"))
  3121  	}
  3122  
  3123  	if util.LenTrim(d.TableName) <= 0 {
  3124  		return nil, d.handleError(errors.New("DynamoDB Table Name is Required"))
  3125  	}
  3126  
  3127  	// result items pointer must be set
  3128  	if resultItemsPtr == nil {
  3129  		return nil, d.handleError(errors.New("DynamoDB QueryItems Failed: " + "ResultItems is Nil"))
  3130  	}
  3131  
  3132  	// validate additional input parameters
  3133  	if util.LenTrim(keyConditionExpression) <= 0 {
  3134  		return nil, d.handleError(errors.New("DynamoDB QueryItems Failed: " + "KeyConditionExpress is Required"))
  3135  	}
  3136  
  3137  	if expressionAttributeValues == nil {
  3138  		return nil, d.handleError(errors.New("DynamoDB QueryItems Failed: " + "ExpressionAttributeValues is Required"))
  3139  	}
  3140  
  3141  	// execute dynamodb service
  3142  	var result *dynamodb.QueryOutput
  3143  
  3144  	// gather projection attributes
  3145  	var proj expression.ProjectionBuilder
  3146  	projSet := false
  3147  
  3148  	if len(projectedAttributes) > 0 {
  3149  		// compose projected attributes if specified
  3150  		firstProjectedAttribute := expression.Name(projectedAttributes[0])
  3151  		moreProjectedAttributes := []expression.NameBuilder{}
  3152  
  3153  		if len(projectedAttributes) > 1 {
  3154  			firstAttribute := true
  3155  
  3156  			for _, v := range projectedAttributes {
  3157  				if !firstAttribute {
  3158  					moreProjectedAttributes = append(moreProjectedAttributes, expression.Name(v))
  3159  				} else {
  3160  					firstAttribute = false
  3161  				}
  3162  			}
  3163  		}
  3164  
  3165  		if len(moreProjectedAttributes) > 0 {
  3166  			proj = expression.NamesList(firstProjectedAttribute, moreProjectedAttributes...)
  3167  		} else {
  3168  			proj = expression.NamesList(firstProjectedAttribute)
  3169  		}
  3170  
  3171  		projSet = true
  3172  	}
  3173  
  3174  	// compose filter expression and projection if applicable
  3175  	var expr expression.Expression
  3176  	var err error
  3177  
  3178  	filterSet := false
  3179  
  3180  	if filterConditionExpression != nil && projSet {
  3181  		expr, err = expression.NewBuilder().WithFilter(*filterConditionExpression).WithProjection(proj).Build()
  3182  		filterSet = true
  3183  		projSet = true
  3184  	} else if filterConditionExpression != nil {
  3185  		expr, err = expression.NewBuilder().WithFilter(*filterConditionExpression).Build()
  3186  		filterSet = true
  3187  		projSet = false
  3188  	} else if projSet {
  3189  		expr, err = expression.NewBuilder().WithProjection(proj).Build()
  3190  		filterSet = false
  3191  		projSet = true
  3192  	}
  3193  
  3194  	if err != nil {
  3195  		return nil, d.handleError(err, "DynamoDB QueryItems Failed (Filter/Projection Expression Build)")
  3196  	}
  3197  
  3198  	// build query input params
  3199  	params := &dynamodb.QueryInput{
  3200  		TableName:                 aws.String(d.TableName),
  3201  		KeyConditionExpression:    aws.String(keyConditionExpression),
  3202  		ExpressionAttributeValues: expressionAttributeValues,
  3203  	}
  3204  
  3205  	if expressionAttributeNames != nil {
  3206  		params.ExpressionAttributeNames = expressionAttributeNames
  3207  	}
  3208  
  3209  	if filterSet {
  3210  		params.FilterExpression = expr.Filter()
  3211  
  3212  		if params.ExpressionAttributeNames == nil {
  3213  			params.ExpressionAttributeNames = make(map[string]*string)
  3214  		}
  3215  
  3216  		for k, v := range expr.Names() {
  3217  			params.ExpressionAttributeNames[k] = v
  3218  		}
  3219  
  3220  		for k, v := range expr.Values() {
  3221  			params.ExpressionAttributeValues[k] = v
  3222  		}
  3223  	}
  3224  
  3225  	if projSet {
  3226  		params.ProjectionExpression = expr.Projection()
  3227  
  3228  		if params.ExpressionAttributeNames == nil {
  3229  			params.ExpressionAttributeNames = expr.Names()
  3230  		} else {
  3231  			for k1, v1 := range expr.Names() {
  3232  				params.ExpressionAttributeNames[k1] = v1
  3233  			}
  3234  		}
  3235  	}
  3236  
  3237  	if consistentRead != nil {
  3238  		if *consistentRead {
  3239  			if len(*indexName) > 0 {
  3240  				// gsi not valid for consistent read, turn off consistent read
  3241  				*consistentRead = false
  3242  			}
  3243  		}
  3244  
  3245  		params.ConsistentRead = consistentRead
  3246  	}
  3247  
  3248  	if indexName != nil && util.LenTrim(*indexName) > 0 {
  3249  		params.IndexName = indexName
  3250  	}
  3251  
  3252  	if pageLimit != nil {
  3253  		params.Limit = pageLimit
  3254  	}
  3255  
  3256  	if exclusiveStartKey != nil {
  3257  		params.ExclusiveStartKey = exclusiveStartKey
  3258  	}
  3259  
  3260  	// record params payload
  3261  	d.LastExecuteParamsPayload = "QueryItems = " + params.String()
  3262  
  3263  	if timeOutDuration != nil {
  3264  		ctx, cancel := context.WithTimeout(context.Background(), *timeOutDuration)
  3265  		defer cancel()
  3266  		result, err = d.do_Query(params, pagedQuery, pagedQueryPageCountLimit, ctx)
  3267  	} else {
  3268  		result, err = d.do_Query(params, pagedQuery, pagedQueryPageCountLimit)
  3269  	}
  3270  
  3271  	if err != nil {
  3272  		return nil, d.handleError(err, "DynamoDB QueryItems Failed: (QueryItems)")
  3273  	}
  3274  
  3275  	if result == nil {
  3276  		return nil, d.handleError(err, "DynamoDB QueryItems Failed: (QueryItems)")
  3277  	}
  3278  
  3279  	// unmarshal result items to target object map
  3280  	if err = dynamodbattribute.UnmarshalListOfMaps(result.Items, resultItemsPtr); err != nil {
  3281  		ddbErr = d.handleError(err, "Dynamo QueryItems Failed: (Unmarshal Result Items)")
  3282  	} else {
  3283  		ddbErr = nil
  3284  	}
  3285  
  3286  	// query items successful
  3287  	return result.LastEvaluatedKey, ddbErr
  3288  }
  3289  
  3290  // QueryItemsWithRetry handles dynamodb retries in case action temporarily fails
  3291  //
  3292  // warning
  3293  //
  3294  //	projectedAttributes = if specified, must include PartitionKey (Hash key) typically "PK" as the first attribute in projected attributes
  3295  func (d *DynamoDB) QueryItemsWithRetry(maxRetries uint,
  3296  	resultItemsPtr interface{},
  3297  	timeOutDuration *time.Duration,
  3298  	consistentRead *bool,
  3299  	indexName *string,
  3300  	pageLimit *int64,
  3301  	pagedQuery bool,
  3302  	pagedQueryPageCountLimit *int64,
  3303  	exclusiveStartKey map[string]*dynamodb.AttributeValue,
  3304  	keyConditionExpression string,
  3305  	expressionAttributeNames map[string]*string,
  3306  	expressionAttributeValues map[string]*dynamodb.AttributeValue,
  3307  	filterConditionExpression *expression.ConditionBuilder,
  3308  	projectedAttributes ...string) (prevEvalKey map[string]*dynamodb.AttributeValue, ddbErr *DynamoDBError) {
  3309  	if maxRetries > 10 {
  3310  		maxRetries = 10
  3311  	}
  3312  
  3313  	timeout := 5 * time.Second
  3314  
  3315  	if timeOutDuration != nil {
  3316  		timeout = *timeOutDuration
  3317  	}
  3318  
  3319  	if timeout < 5*time.Second {
  3320  		timeout = 5 * time.Second
  3321  	} else if timeout > 15*time.Second {
  3322  		timeout = 15 * time.Second
  3323  	}
  3324  
  3325  	if prevEvalKey, ddbErr = d.QueryItems(resultItemsPtr, util.DurationPtr(timeout), consistentRead, indexName, pageLimit,
  3326  		pagedQuery, pagedQueryPageCountLimit, exclusiveStartKey, keyConditionExpression,
  3327  		expressionAttributeNames, expressionAttributeValues, filterConditionExpression, projectedAttributes...); ddbErr != nil {
  3328  		// has error
  3329  		if maxRetries > 0 {
  3330  			if ddbErr.AllowRetry {
  3331  				if ddbErr.RetryNeedsBackOff {
  3332  					time.Sleep(500 * time.Millisecond)
  3333  				} else {
  3334  					time.Sleep(100 * time.Millisecond)
  3335  				}
  3336  
  3337  				log.Println("QueryItemsWithRetry Failed: " + ddbErr.ErrorMessage)
  3338  				return d.QueryItemsWithRetry(maxRetries-1,
  3339  					resultItemsPtr, util.DurationPtr(timeout), consistentRead, indexName, pageLimit,
  3340  					pagedQuery, pagedQueryPageCountLimit, exclusiveStartKey, keyConditionExpression,
  3341  					expressionAttributeNames, expressionAttributeValues, filterConditionExpression, projectedAttributes...)
  3342  			} else {
  3343  				if ddbErr.SuppressError {
  3344  					log.Println("QueryItemsWithRetry DynamoDB Error Suppressed, Returning Error Nil (MaxRetries = " + util.UintToStr(maxRetries) + ")")
  3345  					return nil, nil
  3346  				} else {
  3347  					return nil, &DynamoDBError{
  3348  						ErrorMessage:      "QueryItemsWithRetry Failed: " + ddbErr.ErrorMessage,
  3349  						SuppressError:     false,
  3350  						AllowRetry:        false,
  3351  						RetryNeedsBackOff: false,
  3352  					}
  3353  				}
  3354  			}
  3355  		} else {
  3356  			if ddbErr.SuppressError {
  3357  				log.Println("QueryItemsWithRetry DynamoDB Error Suppressed, Returning Error Nil (MaxRetries = 0)")
  3358  				return nil, nil
  3359  			} else {
  3360  				return nil, &DynamoDBError{
  3361  					ErrorMessage:      "QueryItemsWithRetry Failed: (MaxRetries = 0) " + ddbErr.ErrorMessage,
  3362  					SuppressError:     false,
  3363  					AllowRetry:        false,
  3364  					RetryNeedsBackOff: false,
  3365  				}
  3366  			}
  3367  		}
  3368  	} else {
  3369  		// no error
  3370  		return prevEvalKey, nil
  3371  	}
  3372  }
  3373  
  3374  // QueryPagedItemsWithRetry will query dynamodb items in given table using primary key (PK, SK for example),
  3375  // or one of Global/Local Secondary Keys (indexName must be defined if using GSI)
  3376  //
  3377  // To query against non-key attributes, use Scan (bad for performance however)
  3378  // QueryItems requires using Key attributes, and limited to TWO key attributes in condition maximum;
  3379  //
  3380  // important
  3381  //
  3382  //	if dynamodb table is defined as PK and SK together, then to search without GSI/LSI, MUST use PK and SK together or error will trigger
  3383  //
  3384  // parameters:
  3385  //
  3386  //	pagedSlicePtr = required, identifies the actual slice pointer for use during paged query
  3387  //					(this parameter is not the output of result, actual result is returned via return variable returnItemsList)
  3388  //	resultSlicePtr = required, pointer to working items list struct to contain queried result;
  3389  //					 i.e. []Item{} where Item is struct; if projected attributes less than struct fields, unmatched is defaulted;
  3390  //					 (this parameter is not the output of result, actual result is returned via return variable returnItemsList)
  3391  //	timeOutDuration = optional, timeout duration sent via context to scan method; nil if not using timeout duration
  3392  //	consistentRead = (always set to false for paged query internally)
  3393  //	indexName = optional, global secondary index or local secondary index name to help in query operation
  3394  //	pageLimit = (always set to 100 internally)
  3395  //	pagedQuery = (always set to true internally)
  3396  //	pagedQueryPageCountLimit = (always set to 25 internally)
  3397  //	exclusiveStartKey = (set internally by the paged query loop if any exists)
  3398  //	keyConditionExpression = required, ATTRIBUTES ARE CASE SENSITIVE; either the primary key (PK SK for example) or global secondary index (SK Data for example) or another secondary index (secondary index must be named)
  3399  //		Usage Syntax:
  3400  //			1) Max 2 Attribute Fields
  3401  //			2) First Field must be Partition Key (Must Evaluate to True or False)
  3402  //				a) = ONLY
  3403  //			3) Second Field is Sort Key (May Evaluate to True or False or Range)
  3404  //				a) =, <, <=, >, >=, BETWEEN, begins_with()
  3405  //			4) Combine Two Fields with AND
  3406  //			5) When Attribute Name is Reserved Keyword, Use ExpressionAttributeNames to Define #xyz to Alias
  3407  //				a) Use the #xyz in the KeyConditionExpression such as #yr = :year (:year is Defined ExpressionAttributeValue)
  3408  //			6) Example
  3409  //				a) partitionKeyName = :partitionKeyVal
  3410  //				b) partitionKeyName = :partitionKeyVal AND sortKeyName = :sortKeyVal
  3411  //				c) #yr = :year
  3412  //			7) If Using GSI / Local Index
  3413  //				a) When Using, Must Specify the IndexName
  3414  //				b) First Field is the GSI's Partition Key, such as SK (Evals to True/False), While Second Field is the GSI's SortKey (Range)
  3415  //	expressionAttributeNames = (always nil internally, not used in paged query)
  3416  //	expressionAttributeValues = required, ATTRIBUTES ARE CASE SENSITIVE; sets the value token and value actual to be used within the keyConditionExpression; this sets both compare token and compare value
  3417  //		Usage Syntax:
  3418  //			1) map[string]*dynamodb.AttributeValue: where string is the :xyz, and *dynamodb.AttributeValue is { S: aws.String("abc"), },
  3419  //				a) map[string]*dynamodb.AttributeValue { ":xyz" : { S: aws.String("abc"), }, ":xyy" : { N: aws.String("123"), }, }
  3420  //			2) Add to Map
  3421  //				a) m := make(map[string]*dynamodb.AttributeValue)
  3422  //				b) m[":xyz"] = &dynamodb.AttributeValue{ S: aws.String("xyz") }
  3423  //			3) Slice of Strings -> CONVERT To Slice of *dynamodb.AttributeValue = []string -> []*dynamodb.AttributeValue
  3424  //				a) av, err := dynamodbattribute.MarshalList(xyzSlice)
  3425  //				b) ExpressionAttributeValue, Use 'L' To Represent the List for av defined in 3.a above
  3426  //	filterConditionExpression = optional; ATTRIBUTES ARE CASE SENSITIVE; once query on key conditions returned, this filter condition further restricts return data before output to caller;
  3427  //		Usage Syntax:
  3428  //			1) &expression.Name(xyz).Equals(expression.Value(abc))
  3429  //			2) &expression.Name(xyz).Equals(expression.Value(abc)).And(...)
  3430  //	projectedAttributes = (always nil internally for paged query)
  3431  //
  3432  // Return Values:
  3433  //
  3434  //	returnItemsList = interface{} of return slice, use assert to cast to target type
  3435  //	err = error info if error is encountered
  3436  //
  3437  // notes:
  3438  //
  3439  //	item struct tags
  3440  //		use `json:"" dynamodbav:""`
  3441  //			json = sets the name used in json
  3442  //			dynamodbav = sets the name used in dynamodb
  3443  //		reference child element
  3444  //			if struct has field with complex type (another struct), to reference it in code, use the parent struct field dot child field notation
  3445  //				Info in parent struct with struct tag as info; to reach child element: info.xyz
  3446  func (d *DynamoDB) QueryPagedItemsWithRetry(maxRetries uint,
  3447  	pagedSlicePtr interface{},
  3448  	resultSlicePtr interface{},
  3449  	timeOutDuration *time.Duration,
  3450  	indexName string,
  3451  	keyConditionExpression string,
  3452  	expressionAttributeValues map[string]*dynamodb.AttributeValue,
  3453  	filterConditionExpression *expression.ConditionBuilder) (returnItemsList interface{}, err error) {
  3454  
  3455  	if pagedSlicePtr == nil {
  3456  		return nil, fmt.Errorf("PagedSlicePtr Identifies Working Slice Pointer During Query and is Required")
  3457  	} else {
  3458  		if valPaged := reflect.ValueOf(pagedSlicePtr); valPaged.Kind() != reflect.Ptr {
  3459  			return nil, fmt.Errorf("PagedSlicePtr Expected To Be Slice Pointer (Not Ptr)")
  3460  		} else if valPaged.Elem().Kind() != reflect.Slice {
  3461  			return nil, fmt.Errorf("PagedSlicePtr Expected To Be Slice Pointer (Not Slice)")
  3462  		}
  3463  	}
  3464  
  3465  	if resultSlicePtr == nil {
  3466  		return nil, fmt.Errorf("ResultSlicePtr Contains Query Result in Slice Pointer and is Required")
  3467  	} else {
  3468  		if valResult := reflect.ValueOf(resultSlicePtr); valResult.Kind() != reflect.Ptr {
  3469  			return nil, fmt.Errorf("ResultSlicePtr Expected To Be Slice Pointer (Not Ptr)")
  3470  		} else if valResult.Elem().Kind() != reflect.Slice {
  3471  			return nil, fmt.Errorf("ResultSlicePtr Expected To Be Slice Pointer (Not Slice)")
  3472  		}
  3473  	}
  3474  
  3475  	var prevEvalKey map[string]*dynamodb.AttributeValue
  3476  	prevEvalKey = nil
  3477  
  3478  	var e *DynamoDBError
  3479  
  3480  	pageLimit := int64(250)              // changed from 100 to 250, since typical record is 4k or less and 250 is about 1mb or less
  3481  	pagedQueryPageCountLimit := int64(1) // changed to 1 from 25
  3482  
  3483  	var indexNamePtr *string
  3484  
  3485  	if util.LenTrim(indexName) > 0 {
  3486  		indexNamePtr = aws.String(indexName)
  3487  	} else {
  3488  		indexNamePtr = nil
  3489  	}
  3490  
  3491  	var pagedSliceTemp reflect.Value
  3492  	// Initialize pagedSliceTemp to avoid encountering the 'call of unknown method on zero Value' issue with subsequent reflect.AppendSlice calls.
  3493  	if reflect.TypeOf(resultSlicePtr).Kind() == reflect.Ptr {
  3494  		pagedSliceTemp = reflect.ValueOf(resultSlicePtr).Elem()
  3495  	} else {
  3496  		pagedSliceTemp = reflect.ValueOf(resultSlicePtr)
  3497  	}
  3498  
  3499  	for {
  3500  		// We create a new `pagedSlicePtr` variable for each `for` loop iteration instead of reusing the same one
  3501  		// because it is a pointer. When using `reflect.AppendSlice`, only a pointer struct is copied into the slice.
  3502  		// Each iteration of the `dynamodbattribute.UnmarshalListOfMaps` method modifies the content pointed to by this pointer,
  3503  		// resulting in the slice containing data from only the last iteration.
  3504  		originalSliceValue := reflect.ValueOf(pagedSlicePtr).Elem()
  3505  		newPagedSlice := reflect.MakeSlice(originalSliceValue.Type(), 0, 0)
  3506  		newPagedSlicePtr := reflect.New(newPagedSlice.Type()).Interface()
  3507  
  3508  		// each time queried, we process up to 25 pages with each page up to 100 items,
  3509  		// if there are more data, the prevEvalKey will contain value,
  3510  		// so the for loop will continue query again until prevEvalKey is nil,
  3511  		// this method will retrieve all filtered data from data store, but may take longer time if there are more data
  3512  		if prevEvalKey, e = d.QueryItemsWithRetry(maxRetries, newPagedSlicePtr, timeOutDuration, nil, indexNamePtr,
  3513  			aws.Int64(pageLimit), true, aws.Int64(pagedQueryPageCountLimit), prevEvalKey,
  3514  			keyConditionExpression, nil, expressionAttributeValues,
  3515  			filterConditionExpression); e != nil {
  3516  			// error
  3517  			return nil, fmt.Errorf("QueryPagedItemsWithRetry Failed: %s", e)
  3518  		} else {
  3519  			// success
  3520  			//var valTarget reflect.Value
  3521  
  3522  			//if reflect.TypeOf(resultSlicePtr).Kind() == reflect.Ptr {
  3523  			//	valTarget = reflect.ValueOf(resultSlicePtr).Elem()
  3524  			//} else {
  3525  			//	valTarget = reflect.ValueOf(resultSlicePtr)
  3526  			//}
  3527  
  3528  			//val := reflect.AppendSlice(valTarget, reflect.ValueOf(pagedSlicePtr).Elem())
  3529  			//resultSlicePtr = val.Interface()
  3530  			pagedSliceTemp = reflect.AppendSlice(pagedSliceTemp, reflect.ValueOf(newPagedSlicePtr).Elem())
  3531  
  3532  			if prevEvalKey == nil {
  3533  				break
  3534  			}
  3535  
  3536  			if len(prevEvalKey) == 0 {
  3537  				break
  3538  			}
  3539  		}
  3540  	}
  3541  
  3542  	resultSlicePtr = pagedSliceTemp.Interface()
  3543  
  3544  	return resultSlicePtr, nil
  3545  }
  3546  
  3547  // QueryPerPageItemsWithRetry will query dynamodb items in given table using primary key (PK, SK for example),
  3548  // or one of Global/Local Secondary Keys (indexName must be defined if using GSI);
  3549  //
  3550  // *** This Query is used for pagination where each query returns a specified set of items, along with the prevEvalKey,
  3551  // in the subsequent paged queries using this method, passing prevEvalKey to the exclusiveStartKey parameter will return
  3552  // next page of items from the exclusiveStartKey position ***
  3553  //
  3554  // To query against non-key attributes, use Scan (bad for performance however)
  3555  // QueryItems requires using Key attributes, and limited to TWO key attributes in condition maximum;
  3556  //
  3557  // important
  3558  //
  3559  //	if dynamodb table is defined as PK and SK together, then to search without GSI/LSI, MUST use PK and SK together or error will trigger
  3560  //
  3561  // parameters:
  3562  //
  3563  //	maxRetries = number of retries to attempt
  3564  //	itemsPerPage = query per page items count, if < 0 = 10; if > 500 = 500; defaults to 10 if 0
  3565  //	exclusiveStartKey = if query is continuation from prior pagination, then the prior query's prevEvalKey is passed into this field
  3566  //	pagedSlicePtr = required, identifies the actual slice pointer for use during paged query
  3567  //					i.e. []Item{} where Item is struct; if projected attributes less than struct fields, unmatched is defaulted;
  3568  //					(this parameter is not the output of result, actual result is returned via return variable returnItemsList)
  3569  //	timeOutDuration = optional, timeout duration sent via context to scan method; nil if not using timeout duration
  3570  //	indexName = optional, global secondary index or local secondary index name to help in query operation
  3571  //	keyConditionExpression = required, ATTRIBUTES ARE CASE SENSITIVE; either the primary key (PK SK for example) or global secondary index (SK Data for example) or another secondary index (secondary index must be named)
  3572  //		Usage Syntax:
  3573  //			1) Max 2 Attribute Fields
  3574  //			2) First Field must be Partition Key (Must Evaluate to True or False)
  3575  //				a) = ONLY
  3576  //			3) Second Field is Sort Key (May Evaluate to True or False or Range)
  3577  //				a) =, <, <=, >, >=, BETWEEN, begins_with()
  3578  //			4) Combine Two Fields with AND
  3579  //			5) When Attribute Name is Reserved Keyword, Use ExpressionAttributeNames to Define #xyz to Alias
  3580  //				a) Use the #xyz in the KeyConditionExpression such as #yr = :year (:year is Defined ExpressionAttributeValue)
  3581  //			6) Example
  3582  //				a) partitionKeyName = :partitionKeyVal
  3583  //				b) partitionKeyName = :partitionKeyVal AND sortKeyName = :sortKeyVal
  3584  //				c) #yr = :year
  3585  //			7) If Using GSI / Local Index
  3586  //				a) When Using, Must Specify the IndexName
  3587  //				b) First Field is the GSI's Partition Key, such as SK (Evals to True/False), While Second Field is the GSI's SortKey (Range)
  3588  //	expressionAttributeValues = required, ATTRIBUTES ARE CASE SENSITIVE; sets the value token and value actual to be used within the keyConditionExpression; this sets both compare token and compare value
  3589  //		Usage Syntax:
  3590  //			1) map[string]*dynamodb.AttributeValue: where string is the :xyz, and *dynamodb.AttributeValue is { S: aws.String("abc"), },
  3591  //				a) map[string]*dynamodb.AttributeValue { ":xyz" : { S: aws.String("abc"), }, ":xyy" : { N: aws.String("123"), }, }
  3592  //			2) Add to Map
  3593  //				a) m := make(map[string]*dynamodb.AttributeValue)
  3594  //				b) m[":xyz"] = &dynamodb.AttributeValue{ S: aws.String("xyz") }
  3595  //			3) Slice of Strings -> CONVERT To Slice of *dynamodb.AttributeValue = []string -> []*dynamodb.AttributeValue
  3596  //				a) av, err := dynamodbattribute.MarshalList(xyzSlice)
  3597  //				b) ExpressionAttributeValue, Use 'L' To Represent the List for av defined in 3.a above
  3598  //	filterConditionExpression = optional; ATTRIBUTES ARE CASE SENSITIVE; once query on key conditions returned, this filter condition further restricts return data before output to caller;
  3599  //		Usage Syntax:
  3600  //			1) &expression.Name(xyz).Equals(expression.Value(abc))
  3601  //			2) &expression.Name(xyz).Equals(expression.Value(abc)).And(...)
  3602  //
  3603  // Return Values:
  3604  //
  3605  //	returnItemsList = interface{} of return slice, use assert to cast to target type
  3606  //	prevEvalKey = map[string]*dynamodb.Attribute, if there are more pages, this value is then used in the subsequent query's exclusiveStartKey parameter
  3607  //	err = error info if error is encountered
  3608  //
  3609  // notes:
  3610  //
  3611  //	item struct tags
  3612  //		use `json:"" dynamodbav:""`
  3613  //			json = sets the name used in json
  3614  //			dynamodbav = sets the name used in dynamodb
  3615  //		reference child element
  3616  //			if struct has field with complex type (another struct), to reference it in code, use the parent struct field dot child field notation
  3617  //				Info in parent struct with struct tag as info; to reach child element: info.xyz
  3618  func (d *DynamoDB) QueryPerPageItemsWithRetry(
  3619  	maxRetries uint,
  3620  	itemsPerPage int64,
  3621  	exclusiveStartKey map[string]*dynamodb.AttributeValue,
  3622  	pagedSlicePtr interface{},
  3623  	timeOutDuration *time.Duration,
  3624  	indexName string,
  3625  	keyConditionExpression string,
  3626  	expressionAttributeValues map[string]*dynamodb.AttributeValue,
  3627  	filterConditionExpression *expression.ConditionBuilder) (returnItemsList interface{}, prevEvalKey map[string]*dynamodb.AttributeValue, err error) {
  3628  
  3629  	if pagedSlicePtr == nil {
  3630  		return nil, nil, fmt.Errorf("PagedSlicePtr Identifies Working Slice Pointer During Query and is Required")
  3631  	} else {
  3632  		if valPaged := reflect.ValueOf(pagedSlicePtr); valPaged.Kind() != reflect.Ptr {
  3633  			return nil, nil, fmt.Errorf("PagedSlicePtr Expected To Be Slice Pointer (Not Ptr)")
  3634  		} else if valPaged.Elem().Kind() != reflect.Slice {
  3635  			return nil, nil, fmt.Errorf("PagedSlicePtr Expected To Be Slice Pointer (Not Slice)")
  3636  		}
  3637  	}
  3638  
  3639  	var e *DynamoDBError
  3640  
  3641  	if itemsPerPage <= 0 {
  3642  		itemsPerPage = 10
  3643  	} else if itemsPerPage > 500 {
  3644  		itemsPerPage = 500
  3645  	}
  3646  
  3647  	var indexNamePtr *string
  3648  
  3649  	if util.LenTrim(indexName) > 0 {
  3650  		indexNamePtr = aws.String(indexName)
  3651  	} else {
  3652  		indexNamePtr = nil
  3653  	}
  3654  
  3655  	if prevEvalKey, e = d.QueryItemsWithRetry(maxRetries, pagedSlicePtr, timeOutDuration, nil, indexNamePtr,
  3656  		aws.Int64(itemsPerPage), true, aws.Int64(1), exclusiveStartKey,
  3657  		keyConditionExpression, nil, expressionAttributeValues,
  3658  		filterConditionExpression); e != nil {
  3659  		// error
  3660  		return nil, nil, fmt.Errorf("QueryPerPageItemsWithRetry Failed: %s", e)
  3661  	} else {
  3662  		// success
  3663  		if len(prevEvalKey) == 0 {
  3664  			prevEvalKey = nil
  3665  		}
  3666  
  3667  		return reflect.ValueOf(pagedSlicePtr).Elem().Interface(), prevEvalKey, nil
  3668  	}
  3669  }
  3670  
  3671  // ScanItems will scan dynamodb items in given table, project results, using filter expression
  3672  // >>> DO NOT USE SCAN IF POSSIBLE - SCAN IS NOT EFFICIENT ON RCU <<<
  3673  //
  3674  // warning
  3675  //
  3676  //	projectedAttributes = if specified, must include PartitionKey (Hash key) typically "PK" as the first attribute in projected attributes
  3677  //
  3678  // parameters:
  3679  //
  3680  //	resultItemsPtr = required, pointer to items list struct to contain queried result; i.e. []Item{} where Item is struct; if projected attributes less than struct fields, unmatched is defaulted
  3681  //	timeOutDuration = optional, timeout duration sent via context to scan method; nil if not using timeout duration
  3682  //	consistentRead = optional, scan uses consistent read or eventual consistent read, default is eventual consistent read
  3683  //	indexName = optional, global secondary index or local secondary index name to help in scan operation
  3684  //	pageLimit = optional, scan page limit if set, this limits number of items examined per page during scan operation, allowing scan to work better for RCU
  3685  //	pagedQuery = optional, indicates if query is page based or not; if true, query will be performed via pages, this helps overcome 1 MB limit of each query result
  3686  //	pagedQueryPageCountLimit = optional, indicates how many pages to query during paged query action
  3687  //	exclusiveStartKey = optional, if using pagedQuery and starting the query from prior results
  3688  //
  3689  //	filterConditionExpression = required; ATTRIBUTES ARE CASE SENSITIVE; sets the scan filter condition;
  3690  //		Usage Syntax:
  3691  //			1) expFilter := expression.Name(xyz).Equals(expression.Value(abc))
  3692  //			2) expFilter := expression.Name(xyz).Equals(expression.Value(abc)).And(...)
  3693  //			3) Assign expFilter into filterConditionExpression
  3694  //
  3695  //	projectedAttributes = optional; ATTRIBUTES ARE CASE SENSITIVE; variadic list of attribute names that this query will project into result items;
  3696  //					      attribute names must match struct field name or struct tag's json / dynamodbav tag values
  3697  //
  3698  // Return Values:
  3699  //
  3700  //	prevEvalKey = if paged query, the last evaluate key returned, to be used in subsequent query via exclusiveStartKey; otherwise always nil is returned
  3701  //
  3702  // notes:
  3703  //
  3704  //	item struct tags
  3705  //		use `json:"" dynamodbav:""`
  3706  //			json = sets the name used in json
  3707  //			dynamodbav = sets the name used in dynamodb
  3708  //		reference child element
  3709  //			if struct has field with complex type (another struct), to reference it in code, use the parent struct field dot child field notation
  3710  //				Info in parent struct with struct tag as info; to reach child element: info.xyz
  3711  func (d *DynamoDB) ScanItems(resultItemsPtr interface{},
  3712  	timeOutDuration *time.Duration,
  3713  	consistentRead *bool,
  3714  	indexName *string,
  3715  	pageLimit *int64,
  3716  	pagedQuery bool,
  3717  	pagedQueryPageCountLimit *int64,
  3718  	exclusiveStartKey map[string]*dynamodb.AttributeValue,
  3719  	filterConditionExpression expression.ConditionBuilder, projectedAttributes ...string) (prevEvalKey map[string]*dynamodb.AttributeValue, ddbErr *DynamoDBError) {
  3720  	if xray.XRayServiceOn() {
  3721  		return d.scanItemsWithTrace(resultItemsPtr, timeOutDuration, consistentRead, indexName, pageLimit, pagedQuery, pagedQueryPageCountLimit, exclusiveStartKey, filterConditionExpression, projectedAttributes...)
  3722  	} else {
  3723  		return d.scanItemsNormal(resultItemsPtr, timeOutDuration, consistentRead, indexName, pageLimit, pagedQuery, pagedQueryPageCountLimit, exclusiveStartKey, filterConditionExpression, projectedAttributes...)
  3724  	}
  3725  }
  3726  
  3727  func (d *DynamoDB) scanItemsWithTrace(resultItemsPtr interface{},
  3728  	timeOutDuration *time.Duration,
  3729  	consistentRead *bool,
  3730  	indexName *string,
  3731  	pageLimit *int64,
  3732  	pagedQuery bool,
  3733  	pagedQueryPageCountLimit *int64,
  3734  	exclusiveStartKey map[string]*dynamodb.AttributeValue,
  3735  	filterConditionExpression expression.ConditionBuilder, projectedAttributes ...string) (prevEvalKey map[string]*dynamodb.AttributeValue, ddbErr *DynamoDBError) {
  3736  	trace := xray.NewSegment("DynamoDB-ScanItems", d._parentSegment)
  3737  	defer trace.Close()
  3738  	defer func() {
  3739  		if ddbErr != nil {
  3740  			_ = trace.Seg.AddError(fmt.Errorf(ddbErr.ErrorMessage))
  3741  		}
  3742  	}()
  3743  
  3744  	if d.cn == nil {
  3745  		ddbErr = d.handleError(errors.New("DynamoDB Connection is Required"))
  3746  		return nil, ddbErr
  3747  	}
  3748  
  3749  	if util.LenTrim(d.TableName) <= 0 {
  3750  		ddbErr = d.handleError(errors.New("DynamoDB Table Name is Required"))
  3751  		return nil, ddbErr
  3752  	}
  3753  
  3754  	// result items pointer must be set
  3755  	if resultItemsPtr == nil {
  3756  		ddbErr = d.handleError(errors.New("DynamoDB ScanItems Failed: " + "ResultItems is Nil"))
  3757  		return nil, ddbErr
  3758  	}
  3759  
  3760  	// execute dynamodb service
  3761  	var result *dynamodb.ScanOutput
  3762  
  3763  	trace.Capture("ScanItems", func() error {
  3764  		// create projected attributes
  3765  		var proj expression.ProjectionBuilder
  3766  		projSet := false
  3767  
  3768  		if len(projectedAttributes) > 0 {
  3769  			firstProjectedAttribute := expression.Name(projectedAttributes[0])
  3770  			moreProjectedAttributes := []expression.NameBuilder{}
  3771  
  3772  			if len(projectedAttributes) > 1 {
  3773  				firstAttribute := true
  3774  
  3775  				for _, v := range projectedAttributes {
  3776  					if !firstAttribute {
  3777  						moreProjectedAttributes = append(moreProjectedAttributes, expression.Name(v))
  3778  					} else {
  3779  						firstAttribute = false
  3780  					}
  3781  				}
  3782  			}
  3783  
  3784  			if len(moreProjectedAttributes) > 0 {
  3785  				proj = expression.NamesList(firstProjectedAttribute, moreProjectedAttributes...)
  3786  			} else {
  3787  				proj = expression.NamesList(firstProjectedAttribute)
  3788  			}
  3789  
  3790  			projSet = true
  3791  		}
  3792  
  3793  		// build expression
  3794  		var expr expression.Expression
  3795  		var err error
  3796  
  3797  		if projSet {
  3798  			expr, err = expression.NewBuilder().WithFilter(filterConditionExpression).WithProjection(proj).Build()
  3799  		} else {
  3800  			expr, err = expression.NewBuilder().WithFilter(filterConditionExpression).Build()
  3801  		}
  3802  
  3803  		if err != nil {
  3804  			ddbErr = d.handleError(err, "DynamoDB ScanItems Failed: (Expression NewBuilder)")
  3805  			return fmt.Errorf(ddbErr.ErrorMessage)
  3806  		}
  3807  
  3808  		// build query input params
  3809  		params := &dynamodb.ScanInput{
  3810  			TableName:                 aws.String(d.TableName),
  3811  			FilterExpression:          expr.Filter(),
  3812  			ExpressionAttributeNames:  expr.Names(),
  3813  			ExpressionAttributeValues: expr.Values(),
  3814  		}
  3815  
  3816  		if projSet {
  3817  			params.ProjectionExpression = expr.Projection()
  3818  			params.ExpressionAttributeNames = expr.Names()
  3819  		}
  3820  
  3821  		if consistentRead != nil {
  3822  			if *consistentRead {
  3823  				if len(*indexName) > 0 {
  3824  					// gsi not valid for consistent read, turn off consistent read
  3825  					*consistentRead = false
  3826  				}
  3827  			}
  3828  
  3829  			params.ConsistentRead = consistentRead
  3830  		}
  3831  
  3832  		if indexName != nil && util.LenTrim(*indexName) > 0 {
  3833  			params.IndexName = indexName
  3834  		}
  3835  
  3836  		if pageLimit != nil {
  3837  			params.Limit = pageLimit
  3838  		}
  3839  
  3840  		if exclusiveStartKey != nil {
  3841  			params.ExclusiveStartKey = exclusiveStartKey
  3842  		}
  3843  
  3844  		// record params payload
  3845  		d.LastExecuteParamsPayload = "ScanItems = " + params.String()
  3846  
  3847  		subTrace := trace.NewSubSegment("ScanItems_Do")
  3848  		defer subTrace.Close()
  3849  
  3850  		// create timeout context
  3851  		if timeOutDuration != nil {
  3852  			ctx, cancel := context.WithTimeout(subTrace.Ctx, *timeOutDuration)
  3853  			defer cancel()
  3854  			result, err = d.do_Scan(params, pagedQuery, pagedQueryPageCountLimit, ctx)
  3855  		} else {
  3856  			result, err = d.do_Scan(params, pagedQuery, pagedQueryPageCountLimit, subTrace.Ctx)
  3857  		}
  3858  
  3859  		if err != nil {
  3860  			ddbErr = d.handleError(err, "DynamoDB ScanItems Failed: (ScanItems)")
  3861  			return fmt.Errorf(ddbErr.ErrorMessage)
  3862  		}
  3863  
  3864  		if result == nil {
  3865  			return nil
  3866  		}
  3867  
  3868  		// unmarshal result items to target object map
  3869  		if err = dynamodbattribute.UnmarshalListOfMaps(result.Items, resultItemsPtr); err != nil {
  3870  			ddbErr = d.handleError(err, "DynamoDB ScanItems Failed: (Unmarshal Result Items)")
  3871  			return fmt.Errorf(ddbErr.ErrorMessage)
  3872  		} else {
  3873  			return nil
  3874  		}
  3875  	}, &xray.XTraceData{
  3876  		Meta: map[string]interface{}{
  3877  			"TableName":                 d.TableName,
  3878  			"IndexName":                 aws.StringValue(indexName),
  3879  			"ExclusiveStartKey":         exclusiveStartKey,
  3880  			"FilterConditionExpression": filterConditionExpression,
  3881  		},
  3882  	})
  3883  
  3884  	// scan items successful
  3885  	if result != nil {
  3886  		return result.LastEvaluatedKey, ddbErr
  3887  	} else {
  3888  		return nil, ddbErr
  3889  	}
  3890  }
  3891  
  3892  func (d *DynamoDB) scanItemsNormal(resultItemsPtr interface{},
  3893  	timeOutDuration *time.Duration,
  3894  	consistentRead *bool,
  3895  	indexName *string,
  3896  	pageLimit *int64,
  3897  	pagedQuery bool,
  3898  	pagedQueryPageCountLimit *int64,
  3899  	exclusiveStartKey map[string]*dynamodb.AttributeValue,
  3900  	filterConditionExpression expression.ConditionBuilder, projectedAttributes ...string) (prevEvalKey map[string]*dynamodb.AttributeValue, ddbErr *DynamoDBError) {
  3901  	if d.cn == nil {
  3902  		return nil, d.handleError(errors.New("DynamoDB Connection is Required"))
  3903  	}
  3904  
  3905  	if util.LenTrim(d.TableName) <= 0 {
  3906  		return nil, d.handleError(errors.New("DynamoDB Table Name is Required"))
  3907  	}
  3908  
  3909  	// result items pointer must be set
  3910  	if resultItemsPtr == nil {
  3911  		return nil, d.handleError(errors.New("DynamoDB ScanItems Failed: " + "ResultItems is Nil"))
  3912  	}
  3913  
  3914  	// execute dynamodb service
  3915  	var result *dynamodb.ScanOutput
  3916  
  3917  	// create projected attributes
  3918  	var proj expression.ProjectionBuilder
  3919  	projSet := false
  3920  
  3921  	if len(projectedAttributes) > 0 {
  3922  		firstProjectedAttribute := expression.Name(projectedAttributes[0])
  3923  		moreProjectedAttributes := []expression.NameBuilder{}
  3924  
  3925  		if len(projectedAttributes) > 1 {
  3926  			firstAttribute := true
  3927  
  3928  			for _, v := range projectedAttributes {
  3929  				if !firstAttribute {
  3930  					moreProjectedAttributes = append(moreProjectedAttributes, expression.Name(v))
  3931  				} else {
  3932  					firstAttribute = false
  3933  				}
  3934  			}
  3935  		}
  3936  
  3937  		if len(moreProjectedAttributes) > 0 {
  3938  			proj = expression.NamesList(firstProjectedAttribute, moreProjectedAttributes...)
  3939  		} else {
  3940  			proj = expression.NamesList(firstProjectedAttribute)
  3941  		}
  3942  
  3943  		projSet = true
  3944  	}
  3945  
  3946  	// build expression
  3947  	var expr expression.Expression
  3948  	var err error
  3949  
  3950  	if projSet {
  3951  		expr, err = expression.NewBuilder().WithFilter(filterConditionExpression).WithProjection(proj).Build()
  3952  	} else {
  3953  		expr, err = expression.NewBuilder().WithFilter(filterConditionExpression).Build()
  3954  	}
  3955  
  3956  	if err != nil {
  3957  		return nil, d.handleError(err, "DynamoDB ScanItems Failed: (Expression NewBuilder)")
  3958  	}
  3959  
  3960  	// build query input params
  3961  	params := &dynamodb.ScanInput{
  3962  		TableName:                 aws.String(d.TableName),
  3963  		FilterExpression:          expr.Filter(),
  3964  		ExpressionAttributeNames:  expr.Names(),
  3965  		ExpressionAttributeValues: expr.Values(),
  3966  	}
  3967  
  3968  	if projSet {
  3969  		params.ProjectionExpression = expr.Projection()
  3970  		params.ExpressionAttributeNames = expr.Names()
  3971  	}
  3972  
  3973  	if consistentRead != nil {
  3974  		if *consistentRead {
  3975  			if len(*indexName) > 0 {
  3976  				// gsi not valid for consistent read, turn off consistent read
  3977  				*consistentRead = false
  3978  			}
  3979  		}
  3980  
  3981  		params.ConsistentRead = consistentRead
  3982  	}
  3983  
  3984  	if indexName != nil && util.LenTrim(*indexName) > 0 {
  3985  		params.IndexName = indexName
  3986  	}
  3987  
  3988  	if pageLimit != nil {
  3989  		params.Limit = pageLimit
  3990  	}
  3991  
  3992  	if exclusiveStartKey != nil {
  3993  		params.ExclusiveStartKey = exclusiveStartKey
  3994  	}
  3995  
  3996  	// record params payload
  3997  	d.LastExecuteParamsPayload = "ScanItems = " + params.String()
  3998  
  3999  	// create timeout context
  4000  	if timeOutDuration != nil {
  4001  		ctx, cancel := context.WithTimeout(context.Background(), *timeOutDuration)
  4002  		defer cancel()
  4003  		result, err = d.do_Scan(params, pagedQuery, pagedQueryPageCountLimit, ctx)
  4004  	} else {
  4005  		result, err = d.do_Scan(params, pagedQuery, pagedQueryPageCountLimit)
  4006  	}
  4007  
  4008  	if err != nil {
  4009  		return nil, d.handleError(err, "DynamoDB ScanItems Failed: (ScanItems)")
  4010  	}
  4011  
  4012  	if result == nil {
  4013  		return nil, d.handleError(err, "DynamoDB ScanItems Failed: (ScanItems)")
  4014  	}
  4015  
  4016  	// unmarshal result items to target object map
  4017  	if err = dynamodbattribute.UnmarshalListOfMaps(result.Items, resultItemsPtr); err != nil {
  4018  		ddbErr = d.handleError(err, "DynamoDB ScanItems Failed: (Unmarshal Result Items)")
  4019  	} else {
  4020  		ddbErr = nil
  4021  	}
  4022  
  4023  	// scan items successful
  4024  	return result.LastEvaluatedKey, ddbErr
  4025  }
  4026  
  4027  // ScanItemsWithRetry handles dynamodb retries in case action temporarily fails
  4028  //
  4029  // warning
  4030  //
  4031  //	projectedAttributes = if specified, must include PartitionKey (Hash key) typically "PK" as the first attribute in projected attributes
  4032  func (d *DynamoDB) ScanItemsWithRetry(maxRetries uint,
  4033  	resultItemsPtr interface{},
  4034  	timeOutDuration *time.Duration,
  4035  	consistentRead *bool,
  4036  	indexName *string,
  4037  	pageLimit *int64,
  4038  	pagedQuery bool,
  4039  	pagedQueryPageCountLimit *int64,
  4040  	exclusiveStartKey map[string]*dynamodb.AttributeValue,
  4041  	filterConditionExpression expression.ConditionBuilder, projectedAttributes ...string) (prevEvalKey map[string]*dynamodb.AttributeValue, ddbErr *DynamoDBError) {
  4042  	if maxRetries > 10 {
  4043  		maxRetries = 10
  4044  	}
  4045  
  4046  	timeout := 10 * time.Second
  4047  
  4048  	if timeOutDuration != nil {
  4049  		timeout = *timeOutDuration
  4050  	}
  4051  
  4052  	if timeout < 10*time.Second {
  4053  		timeout = 10 * time.Second
  4054  	} else if timeout > 30*time.Second {
  4055  		timeout = 30 * time.Second
  4056  	}
  4057  
  4058  	if prevEvalKey, ddbErr = d.ScanItems(resultItemsPtr, util.DurationPtr(timeout), consistentRead, indexName, pageLimit,
  4059  		pagedQuery, pagedQueryPageCountLimit,
  4060  		exclusiveStartKey, filterConditionExpression, projectedAttributes...); ddbErr != nil {
  4061  		// has error
  4062  		if maxRetries > 0 {
  4063  			if ddbErr.AllowRetry {
  4064  				if ddbErr.RetryNeedsBackOff {
  4065  					time.Sleep(500 * time.Millisecond)
  4066  				} else {
  4067  					time.Sleep(100 * time.Millisecond)
  4068  				}
  4069  
  4070  				log.Println("ScanItemsWithRetry Failed: " + ddbErr.ErrorMessage)
  4071  				return d.ScanItemsWithRetry(maxRetries-1,
  4072  					resultItemsPtr, util.DurationPtr(timeout), consistentRead, indexName, pageLimit,
  4073  					pagedQuery, pagedQueryPageCountLimit,
  4074  					exclusiveStartKey, filterConditionExpression, projectedAttributes...)
  4075  			} else {
  4076  				if ddbErr.SuppressError {
  4077  					log.Println("ScanItemsWithRetry DynamoDB Error Suppressed, Returning Error Nil (MaxRetries = " + util.UintToStr(maxRetries) + ")")
  4078  					return nil, nil
  4079  				} else {
  4080  					return nil, &DynamoDBError{
  4081  						ErrorMessage:      "ScanItemsWithRetry Failed: " + ddbErr.ErrorMessage,
  4082  						SuppressError:     false,
  4083  						AllowRetry:        false,
  4084  						RetryNeedsBackOff: false,
  4085  					}
  4086  				}
  4087  			}
  4088  		} else {
  4089  			if ddbErr.SuppressError {
  4090  				log.Println("ScanItemsWithRetry DynamoDB Error Suppressed, Returning Error Nil (MaxRetries = 0)")
  4091  				return nil, nil
  4092  			} else {
  4093  				return nil, &DynamoDBError{
  4094  					ErrorMessage:      "ScanItemsWithRetry Failed: (MaxRetries = 0) " + ddbErr.ErrorMessage,
  4095  					SuppressError:     false,
  4096  					AllowRetry:        false,
  4097  					RetryNeedsBackOff: false,
  4098  				}
  4099  			}
  4100  		}
  4101  	} else {
  4102  		// no error
  4103  		return prevEvalKey, nil
  4104  	}
  4105  }
  4106  
  4107  // ScanPagedItemsWithRetry will scan dynamodb items in given table using paged mode with retry, project results, using filter expression
  4108  // >>> DO NOT USE SCAN IF POSSIBLE - SCAN IS NOT EFFICIENT ON RCU <<<
  4109  //
  4110  // parameters:
  4111  //
  4112  //	maxRetries = required, max number of auto retries per paged query
  4113  //	pagedSlicePtr = required, working variable to store paged query (actual return items list is via return variable)
  4114  //	resultSlicePtr = required, pointer to items list struct to contain queried result; i.e. []Item{} where Item is struct; if projected attributes less than struct fields, unmatched is defaulted
  4115  //	timeOutDuration = optional, timeout duration sent via context to scan method; nil if not using timeout duration
  4116  //	consistentRead = (always false)
  4117  //	indexName = optional, global secondary index or local secondary index name to help in scan operation
  4118  //	pageLimit = (always 100)
  4119  //	pagedQuery = (always true)
  4120  //	pagedQueryPageCountLimit = (always 25)
  4121  //	exclusiveStartKey = (always internally controlled during paged query)
  4122  //
  4123  //	filterConditionExpression = required; ATTRIBUTES ARE CASE SENSITIVE; sets the scan filter condition;
  4124  //		Usage Syntax:
  4125  //			1) expFilter := expression.Name(xyz).Equals(expression.Value(abc))
  4126  //			2) expFilter := expression.Name(xyz).Equals(expression.Value(abc)).And(...)
  4127  //			3) Assign expFilter into filterConditionExpression
  4128  //
  4129  //	projectedAttributes = (always project all attributes)
  4130  //
  4131  // Return Values:
  4132  //
  4133  //	returnItemsList = interface of slice returned, representing the items found during scan
  4134  //	err = error if encountered
  4135  //
  4136  // notes:
  4137  //
  4138  //	item struct tags
  4139  //		use `json:"" dynamodbav:""`
  4140  //			json = sets the name used in json
  4141  //			dynamodbav = sets the name used in dynamodb
  4142  //		reference child element
  4143  //			if struct has field with complex type (another struct), to reference it in code, use the parent struct field dot child field notation
  4144  //				Info in parent struct with struct tag as info; to reach child element: info.xyz
  4145  func (d *DynamoDB) ScanPagedItemsWithRetry(maxRetries uint,
  4146  	pagedSlicePtr interface{},
  4147  	resultSlicePtr interface{},
  4148  	timeOutDuration *time.Duration,
  4149  	indexName string,
  4150  	filterConditionExpression expression.ConditionBuilder) (returnItemsList interface{}, err error) {
  4151  
  4152  	if pagedSlicePtr == nil {
  4153  		return nil, fmt.Errorf("PagedSlicePtr Identifies Working Slice Pointer During Scan and is Required")
  4154  	} else {
  4155  		if valPaged := reflect.ValueOf(pagedSlicePtr); valPaged.Kind() != reflect.Ptr {
  4156  			return nil, fmt.Errorf("PagedSlicePtr Expected To Be Slice Pointer (Not Ptr)")
  4157  		} else if valPaged.Elem().Kind() != reflect.Slice {
  4158  			return nil, fmt.Errorf("PagedSlicePtr Expected To Be Slice Pointer (Not Slice)")
  4159  		}
  4160  	}
  4161  
  4162  	if resultSlicePtr == nil {
  4163  		return nil, fmt.Errorf("ResultSlicePtr Contains Scan Result in Slice Pointer and is Required")
  4164  	} else {
  4165  		if valResult := reflect.ValueOf(resultSlicePtr); valResult.Kind() != reflect.Ptr {
  4166  			return nil, fmt.Errorf("ResultSlicePtr Expected To Be Slice Pointer (Not Ptr)")
  4167  		} else if valResult.Elem().Kind() != reflect.Slice {
  4168  			return nil, fmt.Errorf("ResultSlicePtr Expected To Be Slice Pointer (Not Slice)")
  4169  		}
  4170  	}
  4171  
  4172  	var prevEvalKey map[string]*dynamodb.AttributeValue
  4173  	prevEvalKey = nil
  4174  
  4175  	var e *DynamoDBError
  4176  
  4177  	pageLimit := int64(100)
  4178  	pagedQueryPageCountLimit := int64(25)
  4179  
  4180  	var indexNamePtr *string
  4181  
  4182  	if util.LenTrim(indexName) > 0 {
  4183  		indexNamePtr = aws.String(indexName)
  4184  	} else {
  4185  		indexNamePtr = nil
  4186  	}
  4187  
  4188  	for {
  4189  		if prevEvalKey, e = d.ScanItemsWithRetry(maxRetries, pagedSlicePtr, timeOutDuration, nil, indexNamePtr,
  4190  			aws.Int64(pageLimit), true, aws.Int64(pagedQueryPageCountLimit), prevEvalKey, filterConditionExpression); e != nil {
  4191  			// error
  4192  			return nil, fmt.Errorf("ScanPagedItemsWithRetry Failed: %s", e)
  4193  		} else {
  4194  			// success
  4195  			var valTarget reflect.Value
  4196  
  4197  			if reflect.TypeOf(resultSlicePtr).Kind() == reflect.Ptr {
  4198  				valTarget = reflect.ValueOf(resultSlicePtr).Elem()
  4199  			} else {
  4200  				valTarget = reflect.ValueOf(resultSlicePtr)
  4201  			}
  4202  
  4203  			val := reflect.AppendSlice(valTarget, reflect.ValueOf(pagedSlicePtr).Elem())
  4204  			resultSlicePtr = val.Interface()
  4205  
  4206  			if prevEvalKey == nil {
  4207  				break
  4208  			}
  4209  
  4210  			if len(prevEvalKey) == 0 {
  4211  				break
  4212  			}
  4213  		}
  4214  	}
  4215  
  4216  	return resultSlicePtr, nil
  4217  }
  4218  
  4219  // BatchWriteItems will group up to 25 put and delete items in a single batch, and perform actions in parallel against dynamodb for better write efficiency,
  4220  // To update items, use UpdateItem instead for each item needing to be updated instead, BatchWriteItems does not support update items
  4221  //
  4222  // important
  4223  //
  4224  //	if dynamodb table is defined as PK and SK together, then to search, MUST use PK and SK together or error will trigger
  4225  //
  4226  // parameters:
  4227  //
  4228  //	putItems = slice of item struct objects to add to table (combine of putItems and deleteItems cannot exceed 25)
  4229  //		1) Each element of slice is an struct object to be added, struct object must have PK, SK or another named primary key for example, and other attributes as needed
  4230  //		2) putItems interface{} = Expects SLICE of STRUCT OBJECTS
  4231  //
  4232  //	deleteKeys = slice of search keys (as defined by DynamoDBTableKeys struct) to remove from table (combine of putItems and deleteKeys cannot exceed 25)
  4233  //		1) Each element of slice is an struct object of DynamoDBTableKeys
  4234  //
  4235  //	timeOutDuration = optional, timeout duration sent via context to scan method; nil if not using timeout duration
  4236  //
  4237  // return values:
  4238  //
  4239  //	successCount = total number of item actions succeeded
  4240  //	unprocessedItems = any item actions did not succeed is returned; nil means all processed
  4241  //	err = if method call failed, error is returned
  4242  //
  4243  // notes:
  4244  //
  4245  //	item struct tags
  4246  //		use `json:"" dynamodbav:""`
  4247  //			json = sets the name used in json
  4248  //			dynamodbav = sets the name used in dynamodb
  4249  //		reference child element
  4250  //			if struct has field with complex type (another struct), to reference it in code, use the parent struct field dot child field notation
  4251  //				Info in parent struct with struct tag as info; to reach child element: info.xyz
  4252  func (d *DynamoDB) BatchWriteItems(putItems interface{},
  4253  	deleteKeys []DynamoDBTableKeys,
  4254  	timeOutDuration *time.Duration) (successCount int, unprocessedItems *DynamoDBUnprocessedItemsAndKeys, err *DynamoDBError) {
  4255  	if xray.XRayServiceOn() {
  4256  		return d.batchWriteItemsWithTrace(putItems, deleteKeys, timeOutDuration)
  4257  	} else {
  4258  		return d.batchWriteItemsNormal(putItems, deleteKeys, timeOutDuration)
  4259  	}
  4260  }
  4261  
  4262  func (d *DynamoDB) batchWriteItemsWithTrace(putItems interface{},
  4263  	deleteKeys []DynamoDBTableKeys,
  4264  	timeOutDuration *time.Duration) (successCount int, unprocessedItems *DynamoDBUnprocessedItemsAndKeys, err *DynamoDBError) {
  4265  	trace := xray.NewSegment("DynamoDB-BatchWriteItems", d._parentSegment)
  4266  	defer trace.Close()
  4267  	defer func() {
  4268  		if err != nil {
  4269  			_ = trace.Seg.AddError(fmt.Errorf(err.ErrorMessage))
  4270  		}
  4271  	}()
  4272  
  4273  	if d.cn == nil {
  4274  		err = d.handleError(errors.New("DynamoDB Connection is Required"))
  4275  		return 0, nil, err
  4276  	}
  4277  
  4278  	if util.LenTrim(d.TableName) <= 0 {
  4279  		err = d.handleError(errors.New("DynamoDB Table Name is Required"))
  4280  		return 0, nil, err
  4281  	}
  4282  
  4283  	// validate input parameters
  4284  	if putItems == nil && deleteKeys == nil {
  4285  		err = d.handleError(errors.New("DynamoDB BatchWriteItems Failed: " + "PutItems and DeleteKeys Both Cannot Be Nil"))
  4286  		return 0, nil, err
  4287  	}
  4288  
  4289  	trace.Capture("BatchWriteItems", func() error {
  4290  		// marshal put and delete objects
  4291  		var putItemsAv []map[string]*dynamodb.AttributeValue
  4292  		var deleteKeysAv []map[string]*dynamodb.AttributeValue
  4293  
  4294  		if putItems != nil {
  4295  			// putItems is in interface
  4296  			// need to reflect into slice of interface{}
  4297  			putItemsIf := util.SliceObjectsToSliceInterface(putItems)
  4298  
  4299  			if putItemsIf != nil && len(putItemsIf) > 0 {
  4300  				for _, v := range putItemsIf {
  4301  					if m, e := dynamodbattribute.MarshalMap(v); e != nil {
  4302  						successCount = 0
  4303  						unprocessedItems = nil
  4304  						err = d.handleError(e, "DynamoDB BatchWriteItems Failed: (PutItems MarshalMap)")
  4305  						return fmt.Errorf(err.ErrorMessage)
  4306  					} else {
  4307  						if m != nil {
  4308  							putItemsAv = append(putItemsAv, m)
  4309  						} else {
  4310  							successCount = 0
  4311  							unprocessedItems = nil
  4312  							err = d.handleError(errors.New("DynamoDB BatchWriteItems Failed: (PutItems MarshalMap) " + "PutItem Marshal Result Object Nil"))
  4313  							return fmt.Errorf(err.ErrorMessage)
  4314  						}
  4315  					}
  4316  				}
  4317  			}
  4318  		}
  4319  
  4320  		if deleteKeys != nil {
  4321  			if len(deleteKeys) > 0 {
  4322  				for _, v := range deleteKeys {
  4323  					if m, e := dynamodbattribute.MarshalMap(v); e != nil {
  4324  						successCount = 0
  4325  						unprocessedItems = nil
  4326  						err = d.handleError(e, "DynamoDB BatchWriteItems Failed: (DeleteKeys MarshalMap)")
  4327  						return fmt.Errorf(err.ErrorMessage)
  4328  					} else {
  4329  						if m != nil {
  4330  							deleteKeysAv = append(deleteKeysAv, m)
  4331  						} else {
  4332  							successCount = 0
  4333  							unprocessedItems = nil
  4334  							err = d.handleError(errors.New("DynamoDB BatchWriteItems Failed: (DeleteKeys MarshalMap) " + "DeleteKey Marshal Result Object Nil"))
  4335  							return fmt.Errorf(err.ErrorMessage)
  4336  						}
  4337  					}
  4338  				}
  4339  			}
  4340  		}
  4341  
  4342  		putCount := 0
  4343  		deleteCount := 0
  4344  
  4345  		if putItemsAv != nil {
  4346  			putCount = len(putItemsAv)
  4347  		}
  4348  
  4349  		if deleteKeysAv != nil {
  4350  			deleteCount = len(deleteKeysAv)
  4351  		}
  4352  
  4353  		if (putCount+deleteCount) <= 0 || (putCount+deleteCount) > 25 {
  4354  			successCount = 0
  4355  			unprocessedItems = nil
  4356  			err = d.handleError(errors.New("DynamoDB BatchWriteItems Failed: " + "PutItems and DeleteKeys Count Must Be 1 to 25 Only"))
  4357  			return fmt.Errorf(err.ErrorMessage)
  4358  		}
  4359  
  4360  		// holder of delete and put item write requests
  4361  		var writeRequests []*dynamodb.WriteRequest
  4362  
  4363  		// define requestItems wrapper
  4364  		if deleteCount > 0 {
  4365  			for _, v := range deleteKeysAv {
  4366  				writeRequests = append(writeRequests, &dynamodb.WriteRequest{
  4367  					DeleteRequest: &dynamodb.DeleteRequest{
  4368  						Key: v,
  4369  					},
  4370  				})
  4371  			}
  4372  		}
  4373  
  4374  		if putCount > 0 {
  4375  			for _, v := range putItemsAv {
  4376  				writeRequests = append(writeRequests, &dynamodb.WriteRequest{
  4377  					PutRequest: &dynamodb.PutRequest{
  4378  						Item: v,
  4379  					},
  4380  				})
  4381  			}
  4382  		}
  4383  
  4384  		// compose batch write params
  4385  		params := &dynamodb.BatchWriteItemInput{
  4386  			RequestItems: map[string][]*dynamodb.WriteRequest{
  4387  				d.TableName: writeRequests,
  4388  			},
  4389  		}
  4390  
  4391  		// record params payload
  4392  		d.LastExecuteParamsPayload = "BatchWriteItems = " + params.String()
  4393  
  4394  		// execute batch write action
  4395  		var result *dynamodb.BatchWriteItemOutput
  4396  		var err1 error
  4397  
  4398  		subTrace := trace.NewSubSegment("BatchWriteItems_Do")
  4399  		defer subTrace.Close()
  4400  
  4401  		if timeOutDuration != nil {
  4402  			ctx, cancel := context.WithTimeout(subTrace.Ctx, *timeOutDuration)
  4403  			defer cancel()
  4404  			result, err1 = d.do_BatchWriteItem(params, ctx)
  4405  		} else {
  4406  			result, err1 = d.do_BatchWriteItem(params, subTrace.Ctx)
  4407  		}
  4408  
  4409  		if err1 != nil {
  4410  			successCount = 0
  4411  			unprocessedItems = nil
  4412  			err = d.handleError(err1, "DynamoDB BatchWriteItems Failed: (BatchWriteItem)")
  4413  			return fmt.Errorf(err.ErrorMessage)
  4414  		}
  4415  
  4416  		// evaluate results
  4417  		unprocessed := result.UnprocessedItems
  4418  
  4419  		if unprocessed != nil {
  4420  			list := unprocessed[d.TableName]
  4421  
  4422  			if list != nil && len(list) > 0 {
  4423  				outList := new(DynamoDBUnprocessedItemsAndKeys)
  4424  
  4425  				for _, v := range list {
  4426  					if v.PutRequest != nil && v.PutRequest.Item != nil {
  4427  						outList.PutItems = append(outList.PutItems, v.PutRequest.Item)
  4428  					}
  4429  
  4430  					if v.DeleteRequest != nil && v.DeleteRequest.Key != nil {
  4431  						var o DynamoDBTableKeys
  4432  
  4433  						if e := dynamodbattribute.UnmarshalMap(v.DeleteRequest.Key, &o); e == nil {
  4434  							outList.DeleteKeys = append(outList.DeleteKeys, &o)
  4435  						}
  4436  					}
  4437  				}
  4438  
  4439  				successCount = deleteCount + putCount - len(list)
  4440  				unprocessedItems = outList
  4441  				err = nil
  4442  				return nil
  4443  			}
  4444  		}
  4445  
  4446  		successCount = deleteCount + putCount
  4447  		unprocessedItems = nil
  4448  		err = nil
  4449  		return nil
  4450  	}, &xray.XTraceData{
  4451  		Meta: map[string]interface{}{
  4452  			"TableName":  d.TableName,
  4453  			"PutItems":   putItems,
  4454  			"DeleteKeys": deleteKeys,
  4455  		},
  4456  	})
  4457  
  4458  	// batch put and delete items successful
  4459  	return successCount, unprocessedItems, err
  4460  }
  4461  
  4462  func (d *DynamoDB) batchWriteItemsNormal(putItems interface{},
  4463  	deleteKeys []DynamoDBTableKeys,
  4464  	timeOutDuration *time.Duration) (successCount int, unprocessedItems *DynamoDBUnprocessedItemsAndKeys, err *DynamoDBError) {
  4465  	if d.cn == nil {
  4466  		return 0, nil, d.handleError(errors.New("DynamoDB Connection is Required"))
  4467  	}
  4468  
  4469  	if util.LenTrim(d.TableName) <= 0 {
  4470  		return 0, nil, d.handleError(errors.New("DynamoDB Table Name is Required"))
  4471  	}
  4472  
  4473  	// validate input parameters
  4474  	if putItems == nil && deleteKeys == nil {
  4475  		return 0, nil, d.handleError(errors.New("DynamoDB BatchWriteItems Failed: " + "PutItems and DeleteKeys Both Cannot Be Nil"))
  4476  	}
  4477  
  4478  	// marshal put and delete objects
  4479  	var putItemsAv []map[string]*dynamodb.AttributeValue
  4480  	var deleteKeysAv []map[string]*dynamodb.AttributeValue
  4481  
  4482  	if putItems != nil {
  4483  		// putItems is in interface
  4484  		// need to reflect into slice of interface{}
  4485  		putItemsIf := util.SliceObjectsToSliceInterface(putItems)
  4486  
  4487  		if putItemsIf != nil && len(putItemsIf) > 0 {
  4488  			for _, v := range putItemsIf {
  4489  				if m, e := dynamodbattribute.MarshalMap(v); e != nil {
  4490  					successCount = 0
  4491  					unprocessedItems = nil
  4492  					err = d.handleError(e, "DynamoDB BatchWriteItems Failed: (PutItems MarshalMap)")
  4493  					return successCount, unprocessedItems, err
  4494  				} else {
  4495  					if m != nil {
  4496  						putItemsAv = append(putItemsAv, m)
  4497  					} else {
  4498  						successCount = 0
  4499  						unprocessedItems = nil
  4500  						err = d.handleError(errors.New("DynamoDB BatchWriteItems Failed: (PutItems MarshalMap) " + "PutItem Marshal Result Object Nil"))
  4501  						return successCount, unprocessedItems, err
  4502  					}
  4503  				}
  4504  			}
  4505  		}
  4506  	}
  4507  
  4508  	if deleteKeys != nil {
  4509  		if len(deleteKeys) > 0 {
  4510  			for _, v := range deleteKeys {
  4511  				if m, e := dynamodbattribute.MarshalMap(v); e != nil {
  4512  					successCount = 0
  4513  					unprocessedItems = nil
  4514  					err = d.handleError(e, "DynamoDB BatchWriteItems Failed: (DeleteKeys MarshalMap)")
  4515  					return successCount, unprocessedItems, err
  4516  				} else {
  4517  					if m != nil {
  4518  						deleteKeysAv = append(deleteKeysAv, m)
  4519  					} else {
  4520  						successCount = 0
  4521  						unprocessedItems = nil
  4522  						err = d.handleError(errors.New("DynamoDB BatchWriteItems Failed: (DeleteKeys MarshalMap) " + "DeleteKey Marshal Result Object Nil"))
  4523  						return successCount, unprocessedItems, err
  4524  					}
  4525  				}
  4526  			}
  4527  		}
  4528  	}
  4529  
  4530  	putCount := 0
  4531  	deleteCount := 0
  4532  
  4533  	if putItemsAv != nil {
  4534  		putCount = len(putItemsAv)
  4535  	}
  4536  
  4537  	if deleteKeysAv != nil {
  4538  		deleteCount = len(deleteKeysAv)
  4539  	}
  4540  
  4541  	if (putCount+deleteCount) <= 0 || (putCount+deleteCount) > 25 {
  4542  		successCount = 0
  4543  		unprocessedItems = nil
  4544  		err = d.handleError(errors.New("DynamoDB BatchWriteItems Failed: " + "PutItems and DeleteKeys Count Must Be 1 to 25 Only"))
  4545  		return successCount, unprocessedItems, err
  4546  	}
  4547  
  4548  	// holder of delete and put item write requests
  4549  	var writeRequests []*dynamodb.WriteRequest
  4550  
  4551  	// define requestItems wrapper
  4552  	if deleteCount > 0 {
  4553  		for _, v := range deleteKeysAv {
  4554  			writeRequests = append(writeRequests, &dynamodb.WriteRequest{
  4555  				DeleteRequest: &dynamodb.DeleteRequest{
  4556  					Key: v,
  4557  				},
  4558  			})
  4559  		}
  4560  	}
  4561  
  4562  	if putCount > 0 {
  4563  		for _, v := range putItemsAv {
  4564  			writeRequests = append(writeRequests, &dynamodb.WriteRequest{
  4565  				PutRequest: &dynamodb.PutRequest{
  4566  					Item: v,
  4567  				},
  4568  			})
  4569  		}
  4570  	}
  4571  
  4572  	// compose batch write params
  4573  	params := &dynamodb.BatchWriteItemInput{
  4574  		RequestItems: map[string][]*dynamodb.WriteRequest{
  4575  			d.TableName: writeRequests,
  4576  		},
  4577  	}
  4578  
  4579  	// record params payload
  4580  	d.LastExecuteParamsPayload = "BatchWriteItems = " + params.String()
  4581  
  4582  	// execute batch write action
  4583  	var result *dynamodb.BatchWriteItemOutput
  4584  	var err1 error
  4585  
  4586  	if timeOutDuration != nil {
  4587  		ctx, cancel := context.WithTimeout(context.Background(), *timeOutDuration)
  4588  		defer cancel()
  4589  		result, err1 = d.do_BatchWriteItem(params, ctx)
  4590  	} else {
  4591  		result, err1 = d.do_BatchWriteItem(params)
  4592  	}
  4593  
  4594  	if err1 != nil {
  4595  		successCount = 0
  4596  		unprocessedItems = nil
  4597  		err = d.handleError(err1, "DynamoDB BatchWriteItems Failed: (BatchWriteItem)")
  4598  		return successCount, unprocessedItems, err
  4599  	}
  4600  
  4601  	// evaluate results
  4602  	unprocessed := result.UnprocessedItems
  4603  
  4604  	if unprocessed != nil {
  4605  		list := unprocessed[d.TableName]
  4606  
  4607  		if list != nil && len(list) > 0 {
  4608  			outList := new(DynamoDBUnprocessedItemsAndKeys)
  4609  
  4610  			for _, v := range list {
  4611  				if v.PutRequest != nil && v.PutRequest.Item != nil {
  4612  					outList.PutItems = append(outList.PutItems, v.PutRequest.Item)
  4613  				}
  4614  
  4615  				if v.DeleteRequest != nil && v.DeleteRequest.Key != nil {
  4616  					var o DynamoDBTableKeys
  4617  
  4618  					if e := dynamodbattribute.UnmarshalMap(v.DeleteRequest.Key, &o); e == nil {
  4619  						outList.DeleteKeys = append(outList.DeleteKeys, &o)
  4620  					}
  4621  				}
  4622  			}
  4623  
  4624  			successCount = deleteCount + putCount - len(list)
  4625  			unprocessedItems = outList
  4626  			err = nil
  4627  			return successCount, unprocessedItems, err
  4628  		}
  4629  	}
  4630  
  4631  	successCount = deleteCount + putCount
  4632  	unprocessedItems = nil
  4633  	err = nil
  4634  
  4635  	// batch put and delete items successful
  4636  	return successCount, unprocessedItems, err
  4637  }
  4638  
  4639  // BatchWriteItemsWithRetry handles dynamodb retries in case action temporarily fails
  4640  func (d *DynamoDB) BatchWriteItemsWithRetry(maxRetries uint,
  4641  	putItems interface{}, deleteKeys []DynamoDBTableKeys,
  4642  	timeOutDuration *time.Duration) (successCount int, unprocessedItems *DynamoDBUnprocessedItemsAndKeys, err *DynamoDBError) {
  4643  	if maxRetries > 10 {
  4644  		maxRetries = 10
  4645  	}
  4646  
  4647  	timeout := 10 * time.Second
  4648  
  4649  	if timeOutDuration != nil {
  4650  		timeout = *timeOutDuration
  4651  	}
  4652  
  4653  	if timeout < 10*time.Second {
  4654  		timeout = 10 * time.Second
  4655  	} else if timeout > 30*time.Second {
  4656  		timeout = 30 * time.Second
  4657  	}
  4658  
  4659  	if successCount, unprocessedItems, err = d.BatchWriteItems(putItems, deleteKeys, util.DurationPtr(timeout)); err != nil {
  4660  		// has error
  4661  		if maxRetries > 0 {
  4662  			if err.AllowRetry {
  4663  				if err.RetryNeedsBackOff {
  4664  					time.Sleep(500 * time.Millisecond)
  4665  				} else {
  4666  					time.Sleep(100 * time.Millisecond)
  4667  				}
  4668  
  4669  				log.Println("BatchWriteItemsWithRetry Failed: " + err.ErrorMessage)
  4670  				return d.BatchWriteItemsWithRetry(maxRetries-1, putItems, deleteKeys, util.DurationPtr(timeout))
  4671  			} else {
  4672  				if err.SuppressError {
  4673  					log.Println("BatchWriteItemsWithRetry DynamoDB Error Suppressed, Returning Error Nil (MaxRetries = " + util.UintToStr(maxRetries) + ")")
  4674  					return 0, nil, nil
  4675  				} else {
  4676  					return 0, nil, &DynamoDBError{
  4677  						ErrorMessage:      "BatchWriteItemsWithRetry Failed: " + err.ErrorMessage,
  4678  						SuppressError:     false,
  4679  						AllowRetry:        false,
  4680  						RetryNeedsBackOff: false,
  4681  					}
  4682  				}
  4683  			}
  4684  		} else {
  4685  			if err.SuppressError {
  4686  				log.Println("BatchWriteItemsWithRetry DynamoDB Error Suppressed, Returning Error Nil (MaxRetries = 0)")
  4687  				return 0, nil, nil
  4688  			} else {
  4689  				return 0, nil, &DynamoDBError{
  4690  					ErrorMessage:      "BatchWriteItemsWithRetry Failed: (MaxRetries = 0) " + err.ErrorMessage,
  4691  					SuppressError:     false,
  4692  					AllowRetry:        false,
  4693  					RetryNeedsBackOff: false,
  4694  				}
  4695  			}
  4696  		}
  4697  	} else {
  4698  		// no error
  4699  		return successCount, unprocessedItems, nil
  4700  	}
  4701  }
  4702  
  4703  // BatchGetItems accepts a slice of search keys (of DynamoDBSearchKeys struct object), optionally define attribute projections, and return found result items;
  4704  //
  4705  // important
  4706  //
  4707  //	if dynamodb table is defined as PK and SK together, then to search, MUST use PK and SK together or error will trigger
  4708  //
  4709  // warning
  4710  //
  4711  //	projectedAttributes = if specified, must include PartitionKey (Hash key) typically "PK" as the first attribute in projected attributes
  4712  //
  4713  // parameters:
  4714  //
  4715  //	resultItemsPtr = required, pointer to items list struct to contain queried result; i.e. []Item{} where Item is struct; if projected attributes less than struct fields, unmatched is defaulted
  4716  //	searchKeys = required, slice of DynamoDBTableKeys struct objects to perform search against
  4717  //	timeOutDuration = optional, timeout duration sent via context to scan method; nil if not using timeout duration
  4718  //	consistentRead = optional, indicates if the read operation requires consistent read status
  4719  //	projectedAttributes = optional; ATTRIBUTES ARE CASE SENSITIVE; variadic list of attribute names that this query will project into result items;
  4720  //					      attribute names must match struct field name or struct tag's json / dynamodbav tag values
  4721  //
  4722  // return values:
  4723  //
  4724  //	notFound = true if no items found; if error encountered, this field returns false with error field filled
  4725  //	err = if error is encountered, this field will be filled; otherwise nil
  4726  //
  4727  // notes:
  4728  //
  4729  //	item struct tags
  4730  //		use `json:"" dynamodbav:""`
  4731  //			json = sets the name used in json
  4732  //			dynamodbav = sets the name used in dynamodb
  4733  //		reference child element
  4734  //			if struct has field with complex type (another struct), to reference it in code, use the parent struct field dot child field notation
  4735  //				Info in parent struct with struct tag as info; to reach child element: info.xyz
  4736  func (d *DynamoDB) BatchGetItems(resultItemsPtr interface{},
  4737  	searchKeys []DynamoDBTableKeys,
  4738  	timeOutDuration *time.Duration,
  4739  	consistentRead *bool,
  4740  	projectedAttributes ...string) (notFound bool, err *DynamoDBError) {
  4741  	if xray.XRayServiceOn() {
  4742  		return d.batchGetItemsWithTrace(resultItemsPtr, searchKeys, timeOutDuration, consistentRead, projectedAttributes...)
  4743  	} else {
  4744  		return d.batchGetItemsNormal(resultItemsPtr, searchKeys, timeOutDuration, consistentRead, projectedAttributes...)
  4745  	}
  4746  }
  4747  
  4748  func (d *DynamoDB) batchGetItemsWithTrace(resultItemsPtr interface{},
  4749  	searchKeys []DynamoDBTableKeys,
  4750  	timeOutDuration *time.Duration,
  4751  	consistentRead *bool,
  4752  	projectedAttributes ...string) (notFound bool, err *DynamoDBError) {
  4753  	trace := xray.NewSegment("DynamoDB-BatchGetItems", d._parentSegment)
  4754  	defer trace.Close()
  4755  	defer func() {
  4756  		if err != nil {
  4757  			_ = trace.Seg.AddError(fmt.Errorf(err.ErrorMessage))
  4758  		}
  4759  	}()
  4760  
  4761  	if d.cn == nil {
  4762  		err = d.handleError(errors.New("DynamoDB Connection is Required"))
  4763  		return false, err
  4764  	}
  4765  
  4766  	if util.LenTrim(d.TableName) <= 0 {
  4767  		err = d.handleError(errors.New("DynamoDB Table Name is Required"))
  4768  		return false, err
  4769  	}
  4770  
  4771  	// validate input parameters
  4772  	if resultItemsPtr == nil {
  4773  		err = d.handleError(errors.New("DynamoDB BatchGetItems Failed: " + "ResultItems is Nil"))
  4774  		return false, err
  4775  	}
  4776  
  4777  	if searchKeys == nil {
  4778  		err = d.handleError(errors.New("DynamoDB BatchGetItems Failed: " + "SearchKeys Cannot Be Nil"))
  4779  		return false, err
  4780  	}
  4781  
  4782  	if len(searchKeys) <= 0 {
  4783  		err = d.handleError(errors.New("DynamoDB BatchGetItems Failed: " + "SearchKeys Required"))
  4784  		return false, err
  4785  	}
  4786  
  4787  	if len(searchKeys) > 100 {
  4788  		err = d.handleError(errors.New("DynamoDB BatchGetItems Failed: " + "SearchKeys Maximum is 100"))
  4789  		return false, err
  4790  	}
  4791  
  4792  	trace.Capture("BatchGetItems", func() error {
  4793  		// marshal search keys into slice of map of dynamodb attribute values
  4794  		var keysAv []map[string]*dynamodb.AttributeValue
  4795  
  4796  		for _, v := range searchKeys {
  4797  			if m, e := dynamodbattribute.MarshalMap(v); e != nil {
  4798  				notFound = false
  4799  				err = d.handleError(e, "DynamoDB BatchGetItems Failed: (SearchKey Marshal)")
  4800  				return fmt.Errorf(err.ErrorMessage)
  4801  			} else {
  4802  				if m != nil {
  4803  					keysAv = append(keysAv, m)
  4804  				} else {
  4805  					notFound = false
  4806  					err = d.handleError(errors.New("DynamoDB BatchGetItems Failed: (SearchKey Marshal) " + "Marshaled Result Nil"))
  4807  					return fmt.Errorf(err.ErrorMessage)
  4808  				}
  4809  			}
  4810  		}
  4811  
  4812  		// define projected fields
  4813  		// define projected attributes
  4814  		var proj expression.ProjectionBuilder
  4815  		projSet := false
  4816  
  4817  		if len(projectedAttributes) > 0 {
  4818  			// compose projected attributes if specified
  4819  			firstProjectedAttribute := expression.Name(projectedAttributes[0])
  4820  			moreProjectedAttributes := []expression.NameBuilder{}
  4821  
  4822  			if len(projectedAttributes) > 1 {
  4823  				firstAttribute := true
  4824  
  4825  				for _, v := range projectedAttributes {
  4826  					if !firstAttribute {
  4827  						moreProjectedAttributes = append(moreProjectedAttributes, expression.Name(v))
  4828  					} else {
  4829  						firstAttribute = false
  4830  					}
  4831  				}
  4832  			}
  4833  
  4834  			if len(moreProjectedAttributes) > 0 {
  4835  				proj = expression.NamesList(firstProjectedAttribute, moreProjectedAttributes...)
  4836  			} else {
  4837  				proj = expression.NamesList(firstProjectedAttribute)
  4838  			}
  4839  
  4840  			projSet = true
  4841  		}
  4842  
  4843  		var expr expression.Expression
  4844  		var err1 error
  4845  
  4846  		if projSet {
  4847  			if expr, err1 = expression.NewBuilder().WithProjection(proj).Build(); err1 != nil {
  4848  				notFound = false
  4849  				err = d.handleError(err1, "DynamoDB BatchGetItems Failed: (Projecting Attributes)")
  4850  				return fmt.Errorf(err.ErrorMessage)
  4851  			}
  4852  		}
  4853  
  4854  		// define params
  4855  		params := &dynamodb.BatchGetItemInput{
  4856  			RequestItems: map[string]*dynamodb.KeysAndAttributes{
  4857  				d.TableName: {
  4858  					Keys: keysAv,
  4859  				},
  4860  			},
  4861  		}
  4862  
  4863  		if projSet {
  4864  			params.RequestItems[d.TableName].ProjectionExpression = expr.Projection()
  4865  			params.RequestItems[d.TableName].ExpressionAttributeNames = expr.Names()
  4866  		}
  4867  
  4868  		if consistentRead != nil {
  4869  			if *consistentRead {
  4870  				params.RequestItems[d.TableName].ConsistentRead = consistentRead
  4871  			}
  4872  		}
  4873  
  4874  		// record params payload
  4875  		d.LastExecuteParamsPayload = "BatchGetItems = " + params.String()
  4876  
  4877  		// execute batch
  4878  		var result *dynamodb.BatchGetItemOutput
  4879  
  4880  		subTrace := trace.NewSubSegment("BatchGetItems_Do")
  4881  		defer subTrace.Close()
  4882  
  4883  		if timeOutDuration != nil {
  4884  			ctx, cancel := context.WithTimeout(subTrace.Ctx, *timeOutDuration)
  4885  			defer cancel()
  4886  			result, err1 = d.do_BatchGetItem(params, ctx)
  4887  		} else {
  4888  			result, err1 = d.do_BatchGetItem(params, subTrace.Ctx)
  4889  		}
  4890  
  4891  		// evaluate batch execute result
  4892  		if err1 != nil {
  4893  			notFound = false
  4894  			err = d.handleError(err1, "DynamoDB BatchGetItems Failed: (BatchGetItem)")
  4895  			return fmt.Errorf(err.ErrorMessage)
  4896  		}
  4897  
  4898  		if result.Responses == nil {
  4899  			// not found
  4900  			notFound = true
  4901  			err = nil
  4902  			return nil
  4903  		} else {
  4904  			// retrieve items found for the given table name
  4905  			x := result.Responses[d.TableName]
  4906  
  4907  			if x == nil {
  4908  				notFound = true
  4909  				err = nil
  4910  				return nil
  4911  			} else {
  4912  				// unmarshal results
  4913  				if err1 = dynamodbattribute.UnmarshalListOfMaps(x, resultItemsPtr); err1 != nil {
  4914  					notFound = false
  4915  					err = d.handleError(err1, "DynamoDB BatchGetItems Failed: (Unmarshal ResultItems)")
  4916  					return fmt.Errorf(err.ErrorMessage)
  4917  				} else {
  4918  					// unmarshal successful
  4919  					notFound = false
  4920  					err = nil
  4921  					return nil
  4922  				}
  4923  			}
  4924  		}
  4925  	}, &xray.XTraceData{
  4926  		Meta: map[string]interface{}{
  4927  			"TableName":  d.TableName,
  4928  			"SearchKeys": searchKeys,
  4929  		},
  4930  	})
  4931  
  4932  	return notFound, err
  4933  }
  4934  
  4935  func (d *DynamoDB) batchGetItemsNormal(resultItemsPtr interface{},
  4936  	searchKeys []DynamoDBTableKeys,
  4937  	timeOutDuration *time.Duration,
  4938  	consistentRead *bool,
  4939  	projectedAttributes ...string) (notFound bool, err *DynamoDBError) {
  4940  	if d.cn == nil {
  4941  		return false, d.handleError(errors.New("DynamoDB Connection is Required"))
  4942  	}
  4943  
  4944  	if util.LenTrim(d.TableName) <= 0 {
  4945  		return false, d.handleError(errors.New("DynamoDB Table Name is Required"))
  4946  	}
  4947  
  4948  	// validate input parameters
  4949  	if resultItemsPtr == nil {
  4950  		return false, d.handleError(errors.New("DynamoDB BatchGetItems Failed: " + "ResultItems is Nil"))
  4951  	}
  4952  
  4953  	if searchKeys == nil {
  4954  		return false, d.handleError(errors.New("DynamoDB BatchGetItems Failed: " + "SearchKeys Cannot Be Nil"))
  4955  	}
  4956  
  4957  	if len(searchKeys) <= 0 {
  4958  		return false, d.handleError(errors.New("DynamoDB BatchGetItems Failed: " + "SearchKeys Required"))
  4959  	}
  4960  
  4961  	if len(searchKeys) > 100 {
  4962  		return false, d.handleError(errors.New("DynamoDB BatchGetItems Failed: " + "SearchKeys Maximum is 100"))
  4963  	}
  4964  
  4965  	// marshal search keys into slice of map of dynamodb attribute values
  4966  	var keysAv []map[string]*dynamodb.AttributeValue
  4967  
  4968  	for _, v := range searchKeys {
  4969  		if m, e := dynamodbattribute.MarshalMap(v); e != nil {
  4970  			notFound = false
  4971  			err = d.handleError(e, "DynamoDB BatchGetItems Failed: (SearchKey Marshal)")
  4972  			return notFound, err
  4973  		} else {
  4974  			if m != nil {
  4975  				keysAv = append(keysAv, m)
  4976  			} else {
  4977  				notFound = false
  4978  				err = d.handleError(errors.New("DynamoDB BatchGetItems Failed: (SearchKey Marshal) " + "Marshaled Result Nil"))
  4979  				return notFound, err
  4980  			}
  4981  		}
  4982  	}
  4983  
  4984  	// define projected fields
  4985  	// define projected attributes
  4986  	var proj expression.ProjectionBuilder
  4987  	projSet := false
  4988  
  4989  	if len(projectedAttributes) > 0 {
  4990  		// compose projected attributes if specified
  4991  		firstProjectedAttribute := expression.Name(projectedAttributes[0])
  4992  		moreProjectedAttributes := []expression.NameBuilder{}
  4993  
  4994  		if len(projectedAttributes) > 1 {
  4995  			firstAttribute := true
  4996  
  4997  			for _, v := range projectedAttributes {
  4998  				if !firstAttribute {
  4999  					moreProjectedAttributes = append(moreProjectedAttributes, expression.Name(v))
  5000  				} else {
  5001  					firstAttribute = false
  5002  				}
  5003  			}
  5004  		}
  5005  
  5006  		if len(moreProjectedAttributes) > 0 {
  5007  			proj = expression.NamesList(firstProjectedAttribute, moreProjectedAttributes...)
  5008  		} else {
  5009  			proj = expression.NamesList(firstProjectedAttribute)
  5010  		}
  5011  
  5012  		projSet = true
  5013  	}
  5014  
  5015  	var expr expression.Expression
  5016  	var err1 error
  5017  
  5018  	if projSet {
  5019  		if expr, err1 = expression.NewBuilder().WithProjection(proj).Build(); err1 != nil {
  5020  			notFound = false
  5021  			err = d.handleError(err1, "DynamoDB BatchGetItems Failed: (Projecting Attributes)")
  5022  			return notFound, err
  5023  		}
  5024  	}
  5025  
  5026  	// define params
  5027  	params := &dynamodb.BatchGetItemInput{
  5028  		RequestItems: map[string]*dynamodb.KeysAndAttributes{
  5029  			d.TableName: {
  5030  				Keys: keysAv,
  5031  			},
  5032  		},
  5033  	}
  5034  
  5035  	if projSet {
  5036  		params.RequestItems[d.TableName].ProjectionExpression = expr.Projection()
  5037  		params.RequestItems[d.TableName].ExpressionAttributeNames = expr.Names()
  5038  	}
  5039  
  5040  	if consistentRead != nil {
  5041  		if *consistentRead {
  5042  			params.RequestItems[d.TableName].ConsistentRead = consistentRead
  5043  		}
  5044  	}
  5045  
  5046  	// record params payload
  5047  	d.LastExecuteParamsPayload = "BatchGetItems = " + params.String()
  5048  
  5049  	// execute batch
  5050  	var result *dynamodb.BatchGetItemOutput
  5051  
  5052  	if timeOutDuration != nil {
  5053  		ctx, cancel := context.WithTimeout(context.Background(), *timeOutDuration)
  5054  		defer cancel()
  5055  		result, err1 = d.do_BatchGetItem(params, ctx)
  5056  	} else {
  5057  		result, err1 = d.do_BatchGetItem(params)
  5058  	}
  5059  
  5060  	// evaluate batch execute result
  5061  	if err1 != nil {
  5062  		notFound = false
  5063  		err = d.handleError(err1, "DynamoDB BatchGetItems Failed: (BatchGetItem)")
  5064  		return notFound, err
  5065  	}
  5066  
  5067  	if result.Responses == nil {
  5068  		// not found
  5069  		return true, nil
  5070  	} else {
  5071  		// retrieve items found for the given table name
  5072  		x := result.Responses[d.TableName]
  5073  
  5074  		if x == nil {
  5075  			return true, nil
  5076  		} else {
  5077  			// unmarshal results
  5078  			if err1 = dynamodbattribute.UnmarshalListOfMaps(x, resultItemsPtr); err1 != nil {
  5079  				notFound = false
  5080  				err = d.handleError(err1, "DynamoDB BatchGetItems Failed: (Unmarshal ResultItems)")
  5081  				return notFound, err
  5082  			} else {
  5083  				// unmarshal successful
  5084  				return false, nil
  5085  			}
  5086  		}
  5087  	}
  5088  }
  5089  
  5090  // BatchGetItemsWithRetry handles dynamodb retries in case action temporarily fails
  5091  //
  5092  // warning
  5093  //
  5094  //	projectedAttributes = if specified, must include PartitionKey (Hash key) typically "PK" as the first attribute in projected attributes
  5095  func (d *DynamoDB) BatchGetItemsWithRetry(maxRetries uint,
  5096  	resultItemsPtr interface{},
  5097  	searchKeys []DynamoDBTableKeys,
  5098  	timeOutDuration *time.Duration,
  5099  	consistentRead *bool,
  5100  	projectedAttributes ...string) (notFound bool, err *DynamoDBError) {
  5101  	if maxRetries > 10 {
  5102  		maxRetries = 10
  5103  	}
  5104  
  5105  	timeout := 5 * time.Second
  5106  
  5107  	if timeOutDuration != nil {
  5108  		timeout = *timeOutDuration
  5109  	}
  5110  
  5111  	if timeout < 5*time.Second {
  5112  		timeout = 5 * time.Second
  5113  	} else if timeout > 15*time.Second {
  5114  		timeout = 15 * time.Second
  5115  	}
  5116  
  5117  	if notFound, err = d.BatchGetItems(resultItemsPtr, searchKeys, util.DurationPtr(timeout), consistentRead, projectedAttributes...); err != nil {
  5118  		// has error
  5119  		if maxRetries > 0 {
  5120  			if err.AllowRetry {
  5121  				if err.RetryNeedsBackOff {
  5122  					time.Sleep(500 * time.Millisecond)
  5123  				} else {
  5124  					time.Sleep(100 * time.Millisecond)
  5125  				}
  5126  
  5127  				log.Println("BatchGetItemsWithRetry Failed: " + err.ErrorMessage)
  5128  				return d.BatchGetItemsWithRetry(maxRetries-1, resultItemsPtr, searchKeys, util.DurationPtr(timeout), consistentRead, projectedAttributes...)
  5129  			} else {
  5130  				if err.SuppressError {
  5131  					log.Println("BatchGetItemsWithRetry DynamoDB Error Suppressed, Returning Error Nil (MaxRetries = " + util.UintToStr(maxRetries) + ")")
  5132  					return true, nil
  5133  				} else {
  5134  					return true, &DynamoDBError{
  5135  						ErrorMessage:      "BatchGetItemsWithRetry Failed: " + err.ErrorMessage,
  5136  						SuppressError:     false,
  5137  						AllowRetry:        false,
  5138  						RetryNeedsBackOff: false,
  5139  					}
  5140  				}
  5141  			}
  5142  		} else {
  5143  			if err.SuppressError {
  5144  				log.Println("BatchGetItemsWithRetry DynamoDB Error Suppressed, Returning Error Nil (MaxRetries = 0)")
  5145  				return true, nil
  5146  			} else {
  5147  				return true, &DynamoDBError{
  5148  					ErrorMessage:      "BatchGetItemsWithRetry Failed: (MaxRetries = 0) " + err.ErrorMessage,
  5149  					SuppressError:     false,
  5150  					AllowRetry:        false,
  5151  					RetryNeedsBackOff: false,
  5152  				}
  5153  			}
  5154  		}
  5155  	} else {
  5156  		// no error
  5157  		return notFound, nil
  5158  	}
  5159  }
  5160  
  5161  // BatchDeleteItemsWithRetry will attempt to delete one or more table records by on deleteKeys input,
  5162  // will auto retry delete if temporarily failed,
  5163  // if there are deleteFailKeys, its returned, if all succeeded, nil is returned
  5164  func (d *DynamoDB) BatchDeleteItemsWithRetry(maxRetries uint,
  5165  	timeOutDuration *time.Duration,
  5166  	deleteKeys ...*DynamoDBTableKeys) (deleteFailKeys []*DynamoDBTableKeys, err error) {
  5167  	if len(deleteKeys) == 0 {
  5168  		return []*DynamoDBTableKeys{}, fmt.Errorf("BatchDeleteItemsWithRetry Failed: %s", err)
  5169  	}
  5170  
  5171  	if maxRetries > 10 {
  5172  		maxRetries = 10
  5173  	}
  5174  
  5175  	timeout := 5 * time.Second
  5176  
  5177  	if timeOutDuration != nil {
  5178  		timeout = *timeOutDuration
  5179  	}
  5180  
  5181  	if timeout < 5*time.Second {
  5182  		timeout = 5 * time.Second
  5183  	} else if timeout > 15*time.Second {
  5184  		timeout = 15 * time.Second
  5185  	}
  5186  
  5187  	for _, key := range deleteKeys {
  5188  		if key != nil && util.LenTrim(key.PK) > 0 {
  5189  			retries := maxRetries
  5190  
  5191  			if e := d.DeleteItemWithRetry(retries, key.PK, key.SK, util.DurationPtr(timeout)); e != nil {
  5192  				key.ResultError = e
  5193  				deleteFailKeys = append(deleteFailKeys, key)
  5194  			}
  5195  		}
  5196  	}
  5197  
  5198  	if len(deleteFailKeys) == len(deleteKeys) {
  5199  		// all failed
  5200  		return deleteFailKeys, fmt.Errorf("BatchDeleteItemsWithRetry Failed: All Delete Actions Failed")
  5201  
  5202  	} else if len(deleteFailKeys) == 0 {
  5203  		// all success
  5204  		return []*DynamoDBTableKeys{}, nil
  5205  
  5206  	} else {
  5207  		// some failed
  5208  		return deleteFailKeys, fmt.Errorf("BatchDeleteItemsWithRetry Partial Failure: Some Delete Actions Failed")
  5209  	}
  5210  }
  5211  
  5212  // TransactionWriteItems performs a transaction write action for one or more DynamoDBTransactionWrites struct objects,
  5213  // Either all success or all fail,
  5214  // Total Items Count in a Single Transaction for All transItems combined (inner elements) cannot exceed 25
  5215  //
  5216  // important
  5217  //
  5218  //	if dynamodb table is defined as PK and SK together, then to search, MUST use PK and SK together or error will trigger
  5219  func (d *DynamoDB) TransactionWriteItems(timeOutDuration *time.Duration, tranItems ...*DynamoDBTransactionWrites) (success bool, err *DynamoDBError) {
  5220  	if xray.XRayServiceOn() {
  5221  		return d.transactionWriteItemsWithTrace(timeOutDuration, tranItems...)
  5222  	} else {
  5223  		return d.transactionWriteItemsNormal(timeOutDuration, tranItems...)
  5224  	}
  5225  }
  5226  
  5227  func (d *DynamoDB) transactionWriteItemsWithTrace(timeOutDuration *time.Duration, tranItems ...*DynamoDBTransactionWrites) (success bool, err *DynamoDBError) {
  5228  	trace := xray.NewSegment("DynamoDB-TransactionWriteItems", d._parentSegment)
  5229  	defer trace.Close()
  5230  	defer func() {
  5231  		if err != nil {
  5232  			_ = trace.Seg.AddError(fmt.Errorf(err.ErrorMessage))
  5233  		}
  5234  	}()
  5235  
  5236  	if d.cn == nil {
  5237  		err = d.handleError(errors.New("DynamoDB Connection is Required"))
  5238  		return false, err
  5239  	}
  5240  
  5241  	if util.LenTrim(d.TableName) <= 0 {
  5242  		err = d.handleError(errors.New("DynamoDB Table Name is Required"))
  5243  		return false, err
  5244  	}
  5245  
  5246  	if util.LenTrim(d.PKName) <= 0 {
  5247  		err = d.handleError(errors.New("DynamoDB TransactionWriteItems Failed: " + "PK Name is Required"))
  5248  		return false, err
  5249  	}
  5250  
  5251  	if len(tranItems) == 0 {
  5252  		err = d.handleError(errors.New("DynamoDB TransactionWriteItems Failed: " + "Minimum of 1 TranItems is Required"))
  5253  		return false, err
  5254  	}
  5255  
  5256  	trace.Capture("TransactionWriteItems", func() error {
  5257  		// create working data
  5258  		var items []*dynamodb.TransactWriteItem
  5259  
  5260  		// loop through all tranItems slice to pre-populate transaction write items slice
  5261  		skOK := false
  5262  
  5263  		for _, t := range tranItems {
  5264  			tableName := t.TableNameOverride
  5265  
  5266  			if util.LenTrim(tableName) <= 0 {
  5267  				tableName = d.TableName
  5268  			}
  5269  
  5270  			if t.DeleteItems != nil && len(t.DeleteItems) > 0 {
  5271  				for _, v := range t.DeleteItems {
  5272  					m := new(dynamodb.TransactWriteItem)
  5273  
  5274  					md := make(map[string]*dynamodb.AttributeValue)
  5275  					md[d.PKName] = &dynamodb.AttributeValue{S: aws.String(v.PK)}
  5276  
  5277  					if util.LenTrim(v.SK) > 0 {
  5278  						if !skOK {
  5279  							if util.LenTrim(d.SKName) <= 0 {
  5280  								success = false
  5281  								err = d.handleError(errors.New("DynamoDB TransactionWriteItems Failed: (Payload Validate) " + "SK Name is Required"))
  5282  								return fmt.Errorf(err.ErrorMessage)
  5283  							} else {
  5284  								skOK = true
  5285  							}
  5286  						}
  5287  
  5288  						md[d.SKName] = &dynamodb.AttributeValue{S: aws.String(v.SK)}
  5289  					}
  5290  
  5291  					m.Delete = &dynamodb.Delete{
  5292  						TableName: aws.String(tableName),
  5293  						Key:       md,
  5294  					}
  5295  
  5296  					items = append(items, m)
  5297  				}
  5298  			}
  5299  
  5300  			if t.PutItems != nil {
  5301  				if md, e := t.MarshalPutItems(); e != nil {
  5302  					success = false
  5303  					err = d.handleError(e, "DynamoDB TransactionWriteItems Failed: (Marshal PutItems)")
  5304  					return fmt.Errorf(err.ErrorMessage)
  5305  				} else {
  5306  					for _, v := range md {
  5307  						m := new(dynamodb.TransactWriteItem)
  5308  
  5309  						m.Put = &dynamodb.Put{
  5310  							TableName: aws.String(tableName),
  5311  							Item:      v,
  5312  						}
  5313  
  5314  						items = append(items, m)
  5315  					}
  5316  				}
  5317  			}
  5318  
  5319  			if t.UpdateItems != nil && len(t.UpdateItems) > 0 {
  5320  				for _, v := range t.UpdateItems {
  5321  					m := new(dynamodb.TransactWriteItem)
  5322  
  5323  					mk := make(map[string]*dynamodb.AttributeValue)
  5324  					mk[d.PKName] = &dynamodb.AttributeValue{S: aws.String(v.PK)}
  5325  
  5326  					if util.LenTrim(v.SK) > 0 {
  5327  						if !skOK {
  5328  							if util.LenTrim(d.SKName) <= 0 {
  5329  								success = false
  5330  								err = d.handleError(errors.New("DynamoDB TransactionWriteItems Failed: (Payload Validate) " + "SK Name is Required"))
  5331  								return fmt.Errorf(err.ErrorMessage)
  5332  							} else {
  5333  								skOK = true
  5334  							}
  5335  						}
  5336  
  5337  						mk[d.SKName] = &dynamodb.AttributeValue{S: aws.String(v.SK)}
  5338  					}
  5339  
  5340  					m.Update = &dynamodb.Update{
  5341  						TableName: aws.String(tableName),
  5342  						Key:       mk,
  5343  					}
  5344  
  5345  					if util.LenTrim(v.ConditionExpression) > 0 {
  5346  						m.Update.ConditionExpression = aws.String(v.ConditionExpression)
  5347  					}
  5348  
  5349  					if util.LenTrim(v.UpdateExpression) > 0 {
  5350  						m.Update.UpdateExpression = aws.String(v.UpdateExpression)
  5351  					}
  5352  
  5353  					if v.ExpressionAttributeNames != nil && len(v.ExpressionAttributeNames) > 0 {
  5354  						m.Update.ExpressionAttributeNames = v.ExpressionAttributeNames
  5355  					}
  5356  
  5357  					if v.ExpressionAttributeValues != nil && len(v.ExpressionAttributeValues) > 0 {
  5358  						m.Update.ExpressionAttributeValues = v.ExpressionAttributeValues
  5359  					}
  5360  
  5361  					items = append(items, m)
  5362  				}
  5363  			}
  5364  		}
  5365  
  5366  		// items must not exceed 25
  5367  		if len(items) > 25 {
  5368  			success = false
  5369  			err = d.handleError(errors.New("DynamoDB TransactionWriteItems Failed: (Payload Validate) " + "Transaction Items May Not Exceed 25"))
  5370  			return fmt.Errorf(err.ErrorMessage)
  5371  		}
  5372  
  5373  		if len(items) <= 0 {
  5374  			success = false
  5375  			err = d.handleError(errors.New("DynamoDB TransactionWriteItems Failed: (Payload Validate) " + "Transaction Items Minimum of 1 is Required"))
  5376  			return fmt.Errorf(err.ErrorMessage)
  5377  		}
  5378  
  5379  		// compose transaction write items input var
  5380  		params := &dynamodb.TransactWriteItemsInput{
  5381  			TransactItems: items,
  5382  		}
  5383  
  5384  		// record params payload
  5385  		d.LastExecuteParamsPayload = "TransactionWriteItems = " + params.String()
  5386  
  5387  		// execute transaction write operation
  5388  		var err1 error
  5389  
  5390  		subTrace := trace.NewSubSegment("TransactionWriteItems_Do")
  5391  		defer subTrace.Close()
  5392  
  5393  		if timeOutDuration != nil {
  5394  			ctx, cancel := context.WithTimeout(subTrace.Ctx, *timeOutDuration)
  5395  			defer cancel()
  5396  			_, err1 = d.do_TransactWriteItems(params, ctx)
  5397  		} else {
  5398  			_, err1 = d.do_TransactWriteItems(params, subTrace.Ctx)
  5399  		}
  5400  
  5401  		if err1 != nil {
  5402  			success = false
  5403  			err = d.handleError(err1, "DynamoDB TransactionWriteItems Failed: (Transaction Canceled)")
  5404  			return fmt.Errorf(err.ErrorMessage)
  5405  		} else {
  5406  			success = true
  5407  			err = nil
  5408  			return nil
  5409  		}
  5410  	}, &xray.XTraceData{
  5411  		Meta: map[string]interface{}{
  5412  			"TableName": d.TableName,
  5413  			"Items":     tranItems,
  5414  		},
  5415  	})
  5416  
  5417  	// success
  5418  	return success, err
  5419  }
  5420  
  5421  func (d *DynamoDB) transactionWriteItemsNormal(timeOutDuration *time.Duration, tranItems ...*DynamoDBTransactionWrites) (success bool, err *DynamoDBError) {
  5422  	if d.cn == nil {
  5423  		return false, d.handleError(errors.New("DynamoDB Connection is Required"))
  5424  	}
  5425  
  5426  	if util.LenTrim(d.TableName) <= 0 {
  5427  		return false, d.handleError(errors.New("DynamoDB Table Name is Required"))
  5428  	}
  5429  
  5430  	if util.LenTrim(d.PKName) <= 0 {
  5431  		return false, d.handleError(errors.New("DynamoDB TransactionWriteItems Failed: " + "PK Name is Required"))
  5432  	}
  5433  
  5434  	if len(tranItems) == 0 {
  5435  		return false, d.handleError(errors.New("DynamoDB TransactionWriteItems Failed: " + "Minimum of 1 TranItems is Required"))
  5436  	}
  5437  
  5438  	// create working data
  5439  	var items []*dynamodb.TransactWriteItem
  5440  
  5441  	// loop through all tranItems slice to pre-populate transaction write items slice
  5442  	skOK := false
  5443  
  5444  	for _, t := range tranItems {
  5445  		tableName := t.TableNameOverride
  5446  
  5447  		if util.LenTrim(tableName) <= 0 {
  5448  			tableName = d.TableName
  5449  		}
  5450  
  5451  		if t.DeleteItems != nil && len(t.DeleteItems) > 0 {
  5452  			for _, v := range t.DeleteItems {
  5453  				m := new(dynamodb.TransactWriteItem)
  5454  
  5455  				md := make(map[string]*dynamodb.AttributeValue)
  5456  				md[d.PKName] = &dynamodb.AttributeValue{S: aws.String(v.PK)}
  5457  
  5458  				if util.LenTrim(v.SK) > 0 {
  5459  					if !skOK {
  5460  						if util.LenTrim(d.SKName) <= 0 {
  5461  							success = false
  5462  							err = d.handleError(errors.New("DynamoDB TransactionWriteItems Failed: (Payload Validate) " + "SK Name is Required"))
  5463  							return success, err
  5464  						} else {
  5465  							skOK = true
  5466  						}
  5467  					}
  5468  
  5469  					md[d.SKName] = &dynamodb.AttributeValue{S: aws.String(v.SK)}
  5470  				}
  5471  
  5472  				m.Delete = &dynamodb.Delete{
  5473  					TableName: aws.String(tableName),
  5474  					Key:       md,
  5475  				}
  5476  
  5477  				items = append(items, m)
  5478  			}
  5479  		}
  5480  
  5481  		if t.PutItems != nil {
  5482  			if md, e := t.MarshalPutItems(); e != nil {
  5483  				success = false
  5484  				err = d.handleError(e, "DynamoDB TransactionWriteItems Failed: (Marshal PutItems)")
  5485  				return success, err
  5486  			} else {
  5487  				for _, v := range md {
  5488  					m := new(dynamodb.TransactWriteItem)
  5489  
  5490  					m.Put = &dynamodb.Put{
  5491  						TableName: aws.String(tableName),
  5492  						Item:      v,
  5493  					}
  5494  
  5495  					items = append(items, m)
  5496  				}
  5497  			}
  5498  		}
  5499  
  5500  		if t.UpdateItems != nil && len(t.UpdateItems) > 0 {
  5501  			for _, v := range t.UpdateItems {
  5502  				m := new(dynamodb.TransactWriteItem)
  5503  
  5504  				mk := make(map[string]*dynamodb.AttributeValue)
  5505  				mk[d.PKName] = &dynamodb.AttributeValue{S: aws.String(v.PK)}
  5506  
  5507  				if util.LenTrim(v.SK) > 0 {
  5508  					if !skOK {
  5509  						if util.LenTrim(d.SKName) <= 0 {
  5510  							success = false
  5511  							err = d.handleError(errors.New("DynamoDB TransactionWriteItems Failed: (Payload Validate) " + "SK Name is Required"))
  5512  							return success, err
  5513  						} else {
  5514  							skOK = true
  5515  						}
  5516  					}
  5517  
  5518  					mk[d.SKName] = &dynamodb.AttributeValue{S: aws.String(v.SK)}
  5519  				}
  5520  
  5521  				m.Update = &dynamodb.Update{
  5522  					TableName: aws.String(tableName),
  5523  					Key:       mk,
  5524  				}
  5525  
  5526  				if util.LenTrim(v.ConditionExpression) > 0 {
  5527  					m.Update.ConditionExpression = aws.String(v.ConditionExpression)
  5528  				}
  5529  
  5530  				if util.LenTrim(v.UpdateExpression) > 0 {
  5531  					m.Update.UpdateExpression = aws.String(v.UpdateExpression)
  5532  				}
  5533  
  5534  				if v.ExpressionAttributeNames != nil && len(v.ExpressionAttributeNames) > 0 {
  5535  					m.Update.ExpressionAttributeNames = v.ExpressionAttributeNames
  5536  				}
  5537  
  5538  				if v.ExpressionAttributeValues != nil && len(v.ExpressionAttributeValues) > 0 {
  5539  					m.Update.ExpressionAttributeValues = v.ExpressionAttributeValues
  5540  				}
  5541  
  5542  				items = append(items, m)
  5543  			}
  5544  		}
  5545  	}
  5546  
  5547  	// items must not exceed 25
  5548  	if len(items) > 25 {
  5549  		success = false
  5550  		err = d.handleError(errors.New("DynamoDB TransactionWriteItems Failed: (Payload Validate) " + "Transaction Items May Not Exceed 25"))
  5551  		return success, err
  5552  	}
  5553  
  5554  	if len(items) <= 0 {
  5555  		success = false
  5556  		err = d.handleError(errors.New("DynamoDB TransactionWriteItems Failed: (Payload Validate) " + "Transaction Items Minimum of 1 is Required"))
  5557  		return success, err
  5558  	}
  5559  
  5560  	// compose transaction write items input var
  5561  	params := &dynamodb.TransactWriteItemsInput{
  5562  		TransactItems: items,
  5563  	}
  5564  
  5565  	// record params payload
  5566  	d.LastExecuteParamsPayload = "TransactionWriteItems = " + params.String()
  5567  
  5568  	// execute transaction write operation
  5569  	var err1 error
  5570  
  5571  	if timeOutDuration != nil {
  5572  		ctx, cancel := context.WithTimeout(context.Background(), *timeOutDuration)
  5573  		defer cancel()
  5574  		_, err1 = d.do_TransactWriteItems(params, ctx)
  5575  	} else {
  5576  		_, err1 = d.do_TransactWriteItems(params)
  5577  	}
  5578  
  5579  	if err1 != nil {
  5580  		success = false
  5581  		err = d.handleError(err1, "DynamoDB TransactionWriteItems Failed: (Transaction Canceled)")
  5582  		return success, err
  5583  	} else {
  5584  		return true, nil
  5585  	}
  5586  }
  5587  
  5588  // TransactionWriteItemsWithRetry handles dynamodb retries in case action temporarily fails
  5589  func (d *DynamoDB) TransactionWriteItemsWithRetry(maxRetries uint,
  5590  	timeOutDuration *time.Duration,
  5591  	tranItems ...*DynamoDBTransactionWrites) (success bool, err *DynamoDBError) {
  5592  	if maxRetries > 10 {
  5593  		maxRetries = 10
  5594  	}
  5595  
  5596  	timeout := 10 * time.Second
  5597  
  5598  	if timeOutDuration != nil {
  5599  		timeout = *timeOutDuration
  5600  	}
  5601  
  5602  	if timeout < 10*time.Second {
  5603  		timeout = 10 * time.Second
  5604  	} else if timeout > 30*time.Second {
  5605  		timeout = 30 * time.Second
  5606  	}
  5607  
  5608  	if success, err = d.TransactionWriteItems(util.DurationPtr(timeout), tranItems...); err != nil {
  5609  		// has error
  5610  		if maxRetries > 0 {
  5611  			if err.AllowRetry {
  5612  				if err.RetryNeedsBackOff {
  5613  					time.Sleep(500 * time.Millisecond)
  5614  				} else {
  5615  					time.Sleep(100 * time.Millisecond)
  5616  				}
  5617  
  5618  				log.Println("TransactionWriteItemsWithRetry Failed: " + err.ErrorMessage)
  5619  				return d.TransactionWriteItemsWithRetry(maxRetries-1, util.DurationPtr(timeout), tranItems...)
  5620  			} else {
  5621  				if err.SuppressError {
  5622  					log.Println("TransactionWriteItemsWithRetry DynamoDB Error Suppressed, Returning Error Nil (MaxRetries = " + util.UintToStr(maxRetries) + ")")
  5623  					return false, nil
  5624  				} else {
  5625  					return false, &DynamoDBError{
  5626  						ErrorMessage:      "TransactionWriteItemsWithRetry Failed: " + err.ErrorMessage,
  5627  						SuppressError:     false,
  5628  						AllowRetry:        false,
  5629  						RetryNeedsBackOff: false,
  5630  					}
  5631  				}
  5632  			}
  5633  		} else {
  5634  			if err.SuppressError {
  5635  				log.Println("TransactionWriteItemsWithRetry DynamoDB Error Suppressed, Returning Error Nil (MaxRetries = 0)")
  5636  				return false, nil
  5637  			} else {
  5638  				return false, &DynamoDBError{
  5639  					ErrorMessage:      "TransactionWriteItemsWithRetry Failed: (MaxRetries = 0) " + err.ErrorMessage,
  5640  					SuppressError:     false,
  5641  					AllowRetry:        false,
  5642  					RetryNeedsBackOff: false,
  5643  				}
  5644  			}
  5645  		}
  5646  	} else {
  5647  		// no error
  5648  		return success, nil
  5649  	}
  5650  }
  5651  
  5652  // TransactionGetItems receives parameters via tranKeys variadic objects of type DynamoDBTransactionReads; each object has TableName override in case querying against other tables
  5653  // Each tranKeys struct object can contain one or more DynamoDBTableKeys struct, which contains PK, SK fields, and ResultItemPtr,
  5654  // The PK (required) and SK (optional) is used for search, while ResultItemPtr interface{} receives pointer to the output object, so that once query completes the appropriate item data will unmarshal into object
  5655  //
  5656  // important
  5657  //
  5658  //	if dynamodb table is defined as PK and SK together, then to search, MUST use PK and SK together or error will trigger
  5659  //
  5660  // setting result item ptr info
  5661  //  1. Each DynamoDBTableKeys struct object must set pointer of output struct object to ResultItemPtr
  5662  //  2. In the external calling code, must define slice of struct object pointers to receive such unmarshaled results
  5663  //     a) output := []*MID{
  5664  //     &MID{},
  5665  //     &MID{},
  5666  //     }
  5667  //     b) Usage
  5668  //     Passing each element of output to ResultItemPtr within DynamoDBTableKeys struct object
  5669  //
  5670  // notes:
  5671  //  1. transKeys' must contain at laest one object
  5672  //  2. within transKeys object, at least one object of DynamoDBTableKeys must exist for search
  5673  //  3. no more than total of 25 search keys allowed across all variadic objects
  5674  //  4. the ResultItemPtr in all DynamoDBTableKeys objects within all variadic objects MUST BE SET
  5675  func (d *DynamoDB) TransactionGetItems(timeOutDuration *time.Duration, tranKeys ...*DynamoDBTransactionReads) (successCount int, err *DynamoDBError) {
  5676  	if xray.XRayServiceOn() {
  5677  		return d.transactionGetItemsWithTrace(timeOutDuration, tranKeys...)
  5678  	} else {
  5679  		return d.transactionGetItemsNormal(timeOutDuration, tranKeys...)
  5680  	}
  5681  }
  5682  
  5683  func (d *DynamoDB) transactionGetItemsWithTrace(timeOutDuration *time.Duration, tranKeys ...*DynamoDBTransactionReads) (successCount int, err *DynamoDBError) {
  5684  	trace := xray.NewSegment("DynamoDB-TransactionGetItems", d._parentSegment)
  5685  	defer trace.Close()
  5686  	defer func() {
  5687  		if err != nil {
  5688  			_ = trace.Seg.AddError(fmt.Errorf(err.ErrorMessage))
  5689  		}
  5690  	}()
  5691  
  5692  	if d.cn == nil {
  5693  		err = d.handleError(errors.New("DynamoDB Connection is Required"))
  5694  		return 0, err
  5695  	}
  5696  
  5697  	if util.LenTrim(d.TableName) <= 0 {
  5698  		err = d.handleError(errors.New("DynamoDB Table Name is Required"))
  5699  		return 0, err
  5700  	}
  5701  
  5702  	if util.LenTrim(d.PKName) <= 0 {
  5703  		err = d.handleError(errors.New("DynamoDB TransactionGetItems Failed: " + "PK Name is Required"))
  5704  		return 0, err
  5705  	}
  5706  
  5707  	if len(tranKeys) == 0 {
  5708  		err = d.handleError(errors.New("DynamoDB TransactionGetItems Failed: " + "Minimum of 1 TranKeys is Required"))
  5709  		return 0, err
  5710  	}
  5711  
  5712  	trace.Capture("TransactionGetItems", func() error {
  5713  		// create working data
  5714  		var keys []*dynamodb.TransactGetItem
  5715  		var output []*DynamoDBTableKeys
  5716  
  5717  		// loop through all tranKeys slice to pre-populate transaction get items key slice
  5718  		skOK := false
  5719  
  5720  		for _, k := range tranKeys {
  5721  			tableName := k.TableNameOverride
  5722  
  5723  			if util.LenTrim(tableName) <= 0 {
  5724  				tableName = d.TableName
  5725  			}
  5726  
  5727  			if k.Keys != nil && len(k.Keys) > 0 {
  5728  				for _, v := range k.Keys {
  5729  					if v.ResultItemPtr == nil {
  5730  						successCount = 0
  5731  						err = d.handleError(errors.New("DynamoDB TransactionGetItems Failed: " + "All SearchKeys Must Define Unmarshal Target Object"))
  5732  						return fmt.Errorf(err.ErrorMessage)
  5733  					} else {
  5734  						// add to output
  5735  						output = append(output, v)
  5736  					}
  5737  
  5738  					m := new(dynamodb.TransactGetItem)
  5739  
  5740  					md := make(map[string]*dynamodb.AttributeValue)
  5741  					md[d.PKName] = &dynamodb.AttributeValue{S: aws.String(v.PK)}
  5742  
  5743  					if util.LenTrim(v.SK) > 0 {
  5744  						if !skOK {
  5745  							if util.LenTrim(d.SKName) <= 0 {
  5746  								successCount = 0
  5747  								err = d.handleError(errors.New("DynamoDB TransactionGetItems Failed: " + "SK Name is Required"))
  5748  								return fmt.Errorf(err.ErrorMessage)
  5749  							} else {
  5750  								skOK = true
  5751  							}
  5752  						}
  5753  
  5754  						md[d.SKName] = &dynamodb.AttributeValue{S: aws.String(v.SK)}
  5755  					}
  5756  
  5757  					m.Get = &dynamodb.Get{
  5758  						TableName: aws.String(tableName),
  5759  						Key:       md,
  5760  					}
  5761  
  5762  					keys = append(keys, m)
  5763  				}
  5764  			}
  5765  		}
  5766  
  5767  		// keys must not exceed 25
  5768  		if len(keys) > 25 {
  5769  			successCount = 0
  5770  			err = d.handleError(errors.New("DynamoDB TransactionGetItems Failed: (Payload Validate) " + "Search Keys May Not Exceed 25"))
  5771  			return fmt.Errorf(err.ErrorMessage)
  5772  		}
  5773  
  5774  		if len(keys) <= 0 {
  5775  			successCount = 0
  5776  			err = d.handleError(errors.New("DynamoDB TransactionGetItems Failed: (Payload Validate) " + "Search Keys Minimum of 1 is Required"))
  5777  			return fmt.Errorf(err.ErrorMessage)
  5778  		}
  5779  
  5780  		// compose transaction get items input var
  5781  		params := &dynamodb.TransactGetItemsInput{
  5782  			TransactItems: keys,
  5783  		}
  5784  
  5785  		// record params payload
  5786  		d.LastExecuteParamsPayload = "TransactionGetItems = " + params.String()
  5787  
  5788  		// execute transaction get operation
  5789  		var result *dynamodb.TransactGetItemsOutput
  5790  		var err1 error
  5791  
  5792  		subTrace := trace.NewSubSegment("TransactionGetItems_Do")
  5793  		defer subTrace.Close()
  5794  
  5795  		if timeOutDuration != nil {
  5796  			ctx, cancel := context.WithTimeout(subTrace.Ctx, *timeOutDuration)
  5797  			defer cancel()
  5798  			result, err1 = d.do_TransactGetItems(params, ctx)
  5799  		} else {
  5800  			result, err1 = d.do_TransactGetItems(params, subTrace.Ctx)
  5801  		}
  5802  
  5803  		if err1 != nil {
  5804  			successCount = 0
  5805  			err = d.handleError(err1, "DynamoDB TransactionGetItems Failed: (Transaction Reads)")
  5806  			return fmt.Errorf(err.ErrorMessage)
  5807  		}
  5808  
  5809  		// evaluate response
  5810  		successCount = 0
  5811  
  5812  		if result.Responses != nil && len(result.Responses) > 0 {
  5813  			hasSK := util.LenTrim(d.SKName) > 0
  5814  
  5815  			for i, v := range result.Responses {
  5816  				itemAv := v.Item
  5817  
  5818  				if itemAv != nil {
  5819  					pk := util.Trim(aws.StringValue(itemAv[d.PKName].S))
  5820  					sk := ""
  5821  
  5822  					if hasSK {
  5823  						sk = util.Trim(aws.StringValue(itemAv[d.SKName].S))
  5824  					}
  5825  
  5826  					if len(pk) > 0 {
  5827  						if i < len(output) {
  5828  							if o := output[i]; o != nil && !o.resultProcessed {
  5829  								found := false
  5830  
  5831  								if len(sk) > 0 {
  5832  									// must match pk and sk
  5833  									if o.PK == pk && o.SK == sk && o.ResultItemPtr != nil {
  5834  										found = true
  5835  									}
  5836  								} else {
  5837  									// must match pk only
  5838  									if o.PK == pk && o.ResultItemPtr != nil {
  5839  										found = true
  5840  									}
  5841  								}
  5842  
  5843  								if found {
  5844  									o.resultProcessed = true
  5845  
  5846  									// unmarshal to object
  5847  									if e := dynamodbattribute.UnmarshalMap(itemAv, o.ResultItemPtr); e != nil {
  5848  										successCount = 0
  5849  										err = d.handleError(e, "DynamoDB TransactionGetItems Failed: (Unmarshal Result)")
  5850  										return fmt.Errorf(err.ErrorMessage)
  5851  									} else {
  5852  										// unmarshal successful
  5853  										successCount++
  5854  									}
  5855  								}
  5856  							}
  5857  						}
  5858  					}
  5859  				}
  5860  			}
  5861  		}
  5862  
  5863  		err = nil
  5864  		return nil
  5865  	}, &xray.XTraceData{
  5866  		Meta: map[string]interface{}{
  5867  			"TableName": d.TableName,
  5868  			"Keys":      tranKeys,
  5869  		},
  5870  	})
  5871  
  5872  	// nothing found or something found, both returns nil for error
  5873  	return successCount, err
  5874  }
  5875  
  5876  func (d *DynamoDB) transactionGetItemsNormal(timeOutDuration *time.Duration, tranKeys ...*DynamoDBTransactionReads) (successCount int, err *DynamoDBError) {
  5877  	if d.cn == nil {
  5878  		return 0, d.handleError(errors.New("DynamoDB Connection is Required"))
  5879  	}
  5880  
  5881  	if util.LenTrim(d.TableName) <= 0 {
  5882  		return 0, d.handleError(errors.New("DynamoDB Table Name is Required"))
  5883  	}
  5884  
  5885  	if util.LenTrim(d.PKName) <= 0 {
  5886  		return 0, d.handleError(errors.New("DynamoDB TransactionGetItems Failed: " + "PK Name is Required"))
  5887  	}
  5888  
  5889  	if len(tranKeys) == 0 {
  5890  		return 0, d.handleError(errors.New("DynamoDB TransactionGetItems Failed: " + "Minimum of 1 TranKeys is Required"))
  5891  	}
  5892  
  5893  	// create working data
  5894  	var keys []*dynamodb.TransactGetItem
  5895  	var output []*DynamoDBTableKeys
  5896  
  5897  	// loop through all tranKeys slice to pre-populate transaction get items key slice
  5898  	skOK := false
  5899  
  5900  	for _, k := range tranKeys {
  5901  		tableName := k.TableNameOverride
  5902  
  5903  		if util.LenTrim(tableName) <= 0 {
  5904  			tableName = d.TableName
  5905  		}
  5906  
  5907  		if k.Keys != nil && len(k.Keys) > 0 {
  5908  			for _, v := range k.Keys {
  5909  				if v.ResultItemPtr == nil {
  5910  					successCount = 0
  5911  					err = d.handleError(errors.New("DynamoDB TransactionGetItems Failed: " + "All SearchKeys Must Define Unmarshal Target Object"))
  5912  					return successCount, err
  5913  				} else {
  5914  					// add to output
  5915  					output = append(output, v)
  5916  				}
  5917  
  5918  				m := new(dynamodb.TransactGetItem)
  5919  
  5920  				md := make(map[string]*dynamodb.AttributeValue)
  5921  				md[d.PKName] = &dynamodb.AttributeValue{S: aws.String(v.PK)}
  5922  
  5923  				if util.LenTrim(v.SK) > 0 {
  5924  					if !skOK {
  5925  						if util.LenTrim(d.SKName) <= 0 {
  5926  							successCount = 0
  5927  							err = d.handleError(errors.New("DynamoDB TransactionGetItems Failed: " + "SK Name is Required"))
  5928  							return successCount, err
  5929  						} else {
  5930  							skOK = true
  5931  						}
  5932  					}
  5933  
  5934  					md[d.SKName] = &dynamodb.AttributeValue{S: aws.String(v.SK)}
  5935  				}
  5936  
  5937  				m.Get = &dynamodb.Get{
  5938  					TableName: aws.String(tableName),
  5939  					Key:       md,
  5940  				}
  5941  
  5942  				keys = append(keys, m)
  5943  			}
  5944  		}
  5945  	}
  5946  
  5947  	// keys must not exceed 25
  5948  	if len(keys) > 25 {
  5949  		successCount = 0
  5950  		err = d.handleError(errors.New("DynamoDB TransactionGetItems Failed: (Payload Validate) " + "Search Keys May Not Exceed 25"))
  5951  		return successCount, err
  5952  	}
  5953  
  5954  	if len(keys) <= 0 {
  5955  		successCount = 0
  5956  		err = d.handleError(errors.New("DynamoDB TransactionGetItems Failed: (Payload Validate) " + "Search Keys Minimum of 1 is Required"))
  5957  		return successCount, err
  5958  	}
  5959  
  5960  	// compose transaction get items input var
  5961  	params := &dynamodb.TransactGetItemsInput{
  5962  		TransactItems: keys,
  5963  	}
  5964  
  5965  	// record params payload
  5966  	d.LastExecuteParamsPayload = "TransactionGetItems = " + params.String()
  5967  
  5968  	// execute transaction get operation
  5969  	var result *dynamodb.TransactGetItemsOutput
  5970  	var err1 error
  5971  
  5972  	if timeOutDuration != nil {
  5973  		ctx, cancel := context.WithTimeout(context.Background(), *timeOutDuration)
  5974  		defer cancel()
  5975  		result, err1 = d.do_TransactGetItems(params, ctx)
  5976  	} else {
  5977  		result, err1 = d.do_TransactGetItems(params)
  5978  	}
  5979  
  5980  	if err1 != nil {
  5981  		successCount = 0
  5982  		err = d.handleError(err1, "DynamoDB TransactionGetItems Failed: (Transaction Reads)")
  5983  		return successCount, err
  5984  	}
  5985  
  5986  	// evaluate response
  5987  	successCount = 0
  5988  
  5989  	if result.Responses != nil && len(result.Responses) > 0 {
  5990  		hasSK := util.LenTrim(d.SKName) > 0
  5991  
  5992  		for i, v := range result.Responses {
  5993  			itemAv := v.Item
  5994  
  5995  			if itemAv != nil {
  5996  				pk := util.Trim(aws.StringValue(itemAv[d.PKName].S))
  5997  				sk := ""
  5998  
  5999  				if hasSK {
  6000  					sk = util.Trim(aws.StringValue(itemAv[d.SKName].S))
  6001  				}
  6002  
  6003  				if len(pk) > 0 {
  6004  					if i < len(output) {
  6005  						if o := output[i]; o != nil && !o.resultProcessed {
  6006  							found := false
  6007  
  6008  							if len(sk) > 0 {
  6009  								// must match pk and sk
  6010  								if o.PK == pk && o.SK == sk && o.ResultItemPtr != nil {
  6011  									found = true
  6012  								}
  6013  							} else {
  6014  								// must match pk only
  6015  								if o.PK == pk && o.ResultItemPtr != nil {
  6016  									found = true
  6017  								}
  6018  							}
  6019  
  6020  							if found {
  6021  								o.resultProcessed = true
  6022  
  6023  								// unmarshal to object
  6024  								if e := dynamodbattribute.UnmarshalMap(itemAv, o.ResultItemPtr); e != nil {
  6025  									successCount = 0
  6026  									err = d.handleError(e, "DynamoDB TransactionGetItems Failed: (Unmarshal Result)")
  6027  									return successCount, err
  6028  								} else {
  6029  									// unmarshal successful
  6030  									successCount++
  6031  								}
  6032  							}
  6033  						}
  6034  					}
  6035  				}
  6036  			}
  6037  		}
  6038  	}
  6039  
  6040  	// nothing found or something found, both returns nil for error
  6041  	return successCount, nil
  6042  }
  6043  
  6044  // TransactionGetItemsWithRetry handles dynamodb retries in case action temporarily fails
  6045  func (d *DynamoDB) TransactionGetItemsWithRetry(maxRetries uint,
  6046  	timeOutDuration *time.Duration,
  6047  	tranKeys ...*DynamoDBTransactionReads) (successCount int, err *DynamoDBError) {
  6048  	if maxRetries > 10 {
  6049  		maxRetries = 10
  6050  	}
  6051  
  6052  	timeout := 5 * time.Second
  6053  
  6054  	if timeOutDuration != nil {
  6055  		timeout = *timeOutDuration
  6056  	}
  6057  
  6058  	if timeout < 5*time.Second {
  6059  		timeout = 5 * time.Second
  6060  	} else if timeout > 15*time.Second {
  6061  		timeout = 15 * time.Second
  6062  	}
  6063  
  6064  	if successCount, err = d.TransactionGetItems(util.DurationPtr(timeout), tranKeys...); err != nil {
  6065  		// has error
  6066  		if maxRetries > 0 {
  6067  			if err.AllowRetry {
  6068  				if err.RetryNeedsBackOff {
  6069  					time.Sleep(500 * time.Millisecond)
  6070  				} else {
  6071  					time.Sleep(100 * time.Millisecond)
  6072  				}
  6073  
  6074  				log.Println("TransactionGetItemsWithRetry Failed: " + err.ErrorMessage)
  6075  				return d.TransactionGetItemsWithRetry(maxRetries-1, util.DurationPtr(timeout), tranKeys...)
  6076  			} else {
  6077  				if err.SuppressError {
  6078  					log.Println("TransactionGetItemsWithRetry DynamoDB Error Suppressed, Returning Error Nil (MaxRetries = " + util.UintToStr(maxRetries) + ")")
  6079  					return 0, nil
  6080  				} else {
  6081  					return 0, &DynamoDBError{
  6082  						ErrorMessage:      "TransactionGetItemsWithRetry Failed: " + err.ErrorMessage,
  6083  						SuppressError:     false,
  6084  						AllowRetry:        false,
  6085  						RetryNeedsBackOff: false,
  6086  					}
  6087  				}
  6088  			}
  6089  		} else {
  6090  			if err.SuppressError {
  6091  				log.Println("TransactionGetItemsWithRetry DynamoDB Error Suppressed, Returning Error Nil (MaxRetries = 0)")
  6092  				return 0, nil
  6093  			} else {
  6094  				return 0, &DynamoDBError{
  6095  					ErrorMessage:      "TransactionGetItemsWithRetry Failed: (MaxRetries = 0) " + err.ErrorMessage,
  6096  					SuppressError:     false,
  6097  					AllowRetry:        false,
  6098  					RetryNeedsBackOff: false,
  6099  				}
  6100  			}
  6101  		}
  6102  	} else {
  6103  		// no error
  6104  		return successCount, nil
  6105  	}
  6106  }
  6107  
  6108  // CreateTable creates a new dynamodb table to the default aws region (as configured by aws cli)
  6109  func (d *DynamoDB) CreateTable(input *dynamodb.CreateTableInput, ctx ...aws.Context) (*dynamodb.CreateTableOutput, error) {
  6110  	if d.cn == nil {
  6111  		return nil, fmt.Errorf("DynamoDB CreateTable Failed: " + "No DynamoDB Connection Available")
  6112  	}
  6113  
  6114  	if input == nil {
  6115  		return nil, fmt.Errorf("DynamoDB CreateTable Failed: " + "Input Object is Required")
  6116  	}
  6117  
  6118  	if len(ctx) <= 0 {
  6119  		return d.cn.CreateTable(input)
  6120  	} else {
  6121  		return d.cn.CreateTableWithContext(ctx[0], input)
  6122  	}
  6123  }
  6124  
  6125  // UpdateTable updates an existing dynamodb table with provided input parameter
  6126  func (d *DynamoDB) UpdateTable(input *dynamodb.UpdateTableInput, ctx ...aws.Context) (*dynamodb.UpdateTableOutput, error) {
  6127  	if d.cn == nil {
  6128  		return nil, fmt.Errorf("DynamoDB UpdateTable Failed: " + "No DynamoDB Connection Available")
  6129  	}
  6130  
  6131  	if input == nil {
  6132  		return nil, fmt.Errorf("DynamoDB UpdateTable Failed: " + "Input Object is Required")
  6133  	}
  6134  
  6135  	if len(ctx) <= 0 {
  6136  		return d.cn.UpdateTable(input)
  6137  	} else {
  6138  		return d.cn.UpdateTableWithContext(ctx[0], input)
  6139  	}
  6140  }
  6141  
  6142  // DeleteTable deletes an existing dynamodb table
  6143  func (d *DynamoDB) DeleteTable(input *dynamodb.DeleteTableInput, ctx ...aws.Context) (*dynamodb.DeleteTableOutput, error) {
  6144  	if d.cn == nil {
  6145  		return nil, fmt.Errorf("DynamoDB DeleteTable Failed: " + "No DynamoDB Connection Available")
  6146  	}
  6147  
  6148  	if input == nil {
  6149  		return nil, fmt.Errorf("DynamoDB DeleteTable Failed: " + "Input Object is Required")
  6150  	}
  6151  
  6152  	if len(ctx) <= 0 {
  6153  		return d.cn.DeleteTable(input)
  6154  	} else {
  6155  		return d.cn.DeleteTableWithContext(ctx[0], input)
  6156  	}
  6157  }
  6158  
  6159  // ListTables queries dynamodb tables list and returns found tables info
  6160  func (d *DynamoDB) ListTables(input *dynamodb.ListTablesInput, ctx ...aws.Context) (*dynamodb.ListTablesOutput, error) {
  6161  	if d.cn == nil {
  6162  		return nil, fmt.Errorf("DynamoDB ListTables Failed: " + "No DynamoDB Connection Available")
  6163  	}
  6164  
  6165  	if input == nil {
  6166  		return nil, fmt.Errorf("DynamoDB ListTable Failed: " + "Input Object is Required")
  6167  	}
  6168  
  6169  	if len(ctx) <= 0 {
  6170  		return d.cn.ListTables(input)
  6171  	} else {
  6172  		return d.cn.ListTablesWithContext(ctx[0], input)
  6173  	}
  6174  }
  6175  
  6176  // DescribeTable describes the dynamodb table info for target identified in input parameter
  6177  func (d *DynamoDB) DescribeTable(input *dynamodb.DescribeTableInput, ctx ...aws.Context) (*dynamodb.DescribeTableOutput, error) {
  6178  	if d.cn == nil {
  6179  		return nil, fmt.Errorf("DynamoDB DescribeTable Failed: " + "No DynamoDB Connection Available")
  6180  	}
  6181  
  6182  	if input == nil {
  6183  		return nil, fmt.Errorf("DynamoDB DescribeTable Failed: " + "Input Object is Required")
  6184  	}
  6185  
  6186  	if len(ctx) <= 0 {
  6187  		return d.cn.DescribeTable(input)
  6188  	} else {
  6189  		return d.cn.DescribeTableWithContext(ctx[0], input)
  6190  	}
  6191  }
  6192  
  6193  // CreateGlobalTable creates a dynamodb global table
  6194  func (d *DynamoDB) CreateGlobalTable(input *dynamodb.CreateGlobalTableInput, ctx ...aws.Context) (*dynamodb.CreateGlobalTableOutput, error) {
  6195  	if d.cn == nil {
  6196  		return nil, fmt.Errorf("DynamoDB CreateGlobalTable Failed: " + "No DynamoDB Connection Available")
  6197  	}
  6198  
  6199  	if input == nil {
  6200  		return nil, fmt.Errorf("DynamoDB CreateGlobalTable Failed: " + "Input Object is Required")
  6201  	}
  6202  
  6203  	if len(ctx) <= 0 {
  6204  		return d.cn.CreateGlobalTable(input)
  6205  	} else {
  6206  		return d.cn.CreateGlobalTableWithContext(ctx[0], input)
  6207  	}
  6208  }
  6209  
  6210  // UpdateGlobalTable updates a dynamodb global table
  6211  func (d *DynamoDB) UpdateGlobalTable(input *dynamodb.UpdateGlobalTableInput, ctx ...aws.Context) (*dynamodb.UpdateGlobalTableOutput, error) {
  6212  	if d.cn == nil {
  6213  		return nil, fmt.Errorf("DynamoDB UpdateGlobalTable Failed: " + "No DynamoDB Connection Available")
  6214  	}
  6215  
  6216  	if input == nil {
  6217  		return nil, fmt.Errorf("DynamoDB UpdateGlobalTable Failed: " + "Input Object is Required")
  6218  	}
  6219  
  6220  	if len(ctx) <= 0 {
  6221  		return d.cn.UpdateGlobalTable(input)
  6222  	} else {
  6223  		return d.cn.UpdateGlobalTableWithContext(ctx[0], input)
  6224  	}
  6225  }
  6226  
  6227  // ListGlobalTables lists dynamodb global tables
  6228  func (d *DynamoDB) ListGlobalTables(input *dynamodb.ListGlobalTablesInput, ctx ...aws.Context) (*dynamodb.ListGlobalTablesOutput, error) {
  6229  	if d.cn == nil {
  6230  		return nil, fmt.Errorf("DynamoDB ListGlobalTables Failed: " + "No DynamoDB Connection Available")
  6231  	}
  6232  
  6233  	if input == nil {
  6234  		return nil, fmt.Errorf("DynamoDB ListGlobalTables Failed: " + "Input Object is Required")
  6235  	}
  6236  
  6237  	if len(ctx) <= 0 {
  6238  		return d.cn.ListGlobalTables(input)
  6239  	} else {
  6240  		return d.cn.ListGlobalTablesWithContext(ctx[0], input)
  6241  	}
  6242  }
  6243  
  6244  // DescribeGlobalTable describes dynamodb global table
  6245  func (d *DynamoDB) DescribeGlobalTable(input *dynamodb.DescribeGlobalTableInput, ctx ...aws.Context) (*dynamodb.DescribeGlobalTableOutput, error) {
  6246  	if d.cn == nil {
  6247  		return nil, fmt.Errorf("DynamoDB DescribeGlobalTable Failed: " + "No DynamoDB Connection Available")
  6248  	}
  6249  
  6250  	if input == nil {
  6251  		return nil, fmt.Errorf("DynamoDB DescribeGlobalTable Failed: " + "Input Object is Required")
  6252  	}
  6253  
  6254  	if len(ctx) <= 0 {
  6255  		return d.cn.DescribeGlobalTable(input)
  6256  	} else {
  6257  		return d.cn.DescribeGlobalTableWithContext(ctx[0], input)
  6258  	}
  6259  }
  6260  
  6261  // CreateBackup creates dynamodb table backup
  6262  func (d *DynamoDB) CreateBackup(input *dynamodb.CreateBackupInput, ctx ...aws.Context) (*dynamodb.CreateBackupOutput, error) {
  6263  	if d.cn == nil {
  6264  		return nil, fmt.Errorf("DynamoDB CreateBackup Failed: " + "No DynamoDB Connection Available")
  6265  	}
  6266  
  6267  	if input == nil {
  6268  		return nil, fmt.Errorf("DynamoDB CreateBackup Failed: " + "Input Object is Required")
  6269  	}
  6270  
  6271  	if len(ctx) <= 0 {
  6272  		return d.cn.CreateBackup(input)
  6273  	} else {
  6274  		return d.cn.CreateBackupWithContext(ctx[0], input)
  6275  	}
  6276  }
  6277  
  6278  // DeleteBackup deletes an existing dynamodb table backup
  6279  func (d *DynamoDB) DeleteBackup(input *dynamodb.DeleteBackupInput, ctx ...aws.Context) (*dynamodb.DeleteBackupOutput, error) {
  6280  	if d.cn == nil {
  6281  		return nil, fmt.Errorf("DynamoDB DeleteBackup Failed: " + "No DynamoDB Connection Available")
  6282  	}
  6283  
  6284  	if input == nil {
  6285  		return nil, fmt.Errorf("DynamoDB DeleteBackup Failed: " + "Input Object is Required")
  6286  	}
  6287  
  6288  	if len(ctx) <= 0 {
  6289  		return d.cn.DeleteBackup(input)
  6290  	} else {
  6291  		return d.cn.DeleteBackupWithContext(ctx[0], input)
  6292  	}
  6293  }
  6294  
  6295  // ListBackups lists dynamodb table backup
  6296  func (d *DynamoDB) ListBackups(input *dynamodb.ListBackupsInput, ctx ...aws.Context) (*dynamodb.ListBackupsOutput, error) {
  6297  	if d.cn == nil {
  6298  		return nil, fmt.Errorf("DynamoDB ListBackups Failed: " + "No DynamoDB Connection Available")
  6299  	}
  6300  
  6301  	if input == nil {
  6302  		return nil, fmt.Errorf("DynamoDB ListBackups Failed: " + "Input Object is Required")
  6303  	}
  6304  
  6305  	if len(ctx) <= 0 {
  6306  		return d.cn.ListBackups(input)
  6307  	} else {
  6308  		return d.cn.ListBackupsWithContext(ctx[0], input)
  6309  	}
  6310  }
  6311  
  6312  // DescribeBackup describes dynamodb table backup
  6313  func (d *DynamoDB) DescribeBackup(input *dynamodb.DescribeBackupInput, ctx ...aws.Context) (*dynamodb.DescribeBackupOutput, error) {
  6314  	if d.cn == nil {
  6315  		return nil, fmt.Errorf("DynamoDB DescribeBackup Failed: " + "No DynamoDB Connection Available")
  6316  	}
  6317  
  6318  	if input == nil {
  6319  		return nil, fmt.Errorf("DynamoDB DescribeBackup Failed: " + "Input Object is Required")
  6320  	}
  6321  
  6322  	if len(ctx) <= 0 {
  6323  		return d.cn.DescribeBackup(input)
  6324  	} else {
  6325  		return d.cn.DescribeBackupWithContext(ctx[0], input)
  6326  	}
  6327  }
  6328  
  6329  // UpdatePointInTimeBackup updates dynamodb table point in time backup option
  6330  func (d *DynamoDB) UpdatePointInTimeBackup(input *dynamodb.UpdateContinuousBackupsInput, ctx ...aws.Context) (*dynamodb.UpdateContinuousBackupsOutput, error) {
  6331  	if d.cn == nil {
  6332  		return nil, fmt.Errorf("DynamoDB UpdatePointInTimeBackup Failed: " + "No DynamoDB Connection Available")
  6333  	}
  6334  
  6335  	if input == nil {
  6336  		return nil, fmt.Errorf("DynamoDB UpdatePointInTimeBackup Failed: " + "Input Object is Required")
  6337  	}
  6338  
  6339  	if len(ctx) <= 0 {
  6340  		return d.cn.UpdateContinuousBackups(input)
  6341  	} else {
  6342  		return d.cn.UpdateContinuousBackupsWithContext(ctx[0], input)
  6343  	}
  6344  }