github.com/angryronald/go-kit@v0.0.0-20240505173814-ff2bd9c79dbf/generic/repository/memcached/generic.repository.go (about)

     1  package memcached
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/google/uuid"
     9  	"github.com/redis/go-redis/v9"
    10  
    11  	"github.com/angryronald/go-kit/cast"
    12  	"github.com/angryronald/go-kit/generic/repository"
    13  )
    14  
    15  const defaultIDPropertyName string = "ID"
    16  
    17  type KeyIdentifier struct {
    18  	key                  string
    19  	isCollection         bool
    20  	propertyNameMapToKey string
    21  }
    22  
    23  func NewKeyIdentifier(
    24  	key string,
    25  	isCollection bool,
    26  	propertyNameMapToKey string,
    27  ) *KeyIdentifier {
    28  	return &KeyIdentifier{
    29  		key:                  key,
    30  		isCollection:         isCollection,
    31  		propertyNameMapToKey: propertyNameMapToKey,
    32  	}
    33  }
    34  
    35  type GenericRepository struct {
    36  	client             *redis.Client
    37  	key                string
    38  	expirationDuration time.Duration
    39  	keyIdentifiers     []*KeyIdentifier
    40  }
    41  
    42  func (r *GenericRepository) isKeyIdentifierRegistered(key string) bool {
    43  	for _, keyIdentifier := range r.keyIdentifiers {
    44  		if keyIdentifier.key == key {
    45  			return true
    46  		}
    47  	}
    48  	return false
    49  }
    50  
    51  func (r *GenericRepository) getKeyIdentifier(key string) *KeyIdentifier {
    52  	for _, keyIdentifier := range r.keyIdentifiers {
    53  		if keyIdentifier.key == key {
    54  			return keyIdentifier
    55  		}
    56  	}
    57  	return nil
    58  }
    59  
    60  func (r *GenericRepository) findByKeyIdentifier(ctx context.Context, keyIdentifierWanted string, key string, model interface{}) (interface{}, error) {
    61  	for _, keyIdentifier := range r.keyIdentifiers {
    62  		if keyIdentifier.key == keyIdentifierWanted {
    63  			var result interface{}
    64  			if keyIdentifier.isCollection {
    65  				result = CreateStructPointerSlice(model, 0)
    66  			} else {
    67  				result = CreateNewStructObject(model)
    68  			}
    69  
    70  			resultRaw, err := r.client.Get(ctx, fmt.Sprintf("%s%s%s", keyIdentifier.key, CONJUNCTION, key)).Result()
    71  			if err != nil {
    72  				return nil, err
    73  			}
    74  
    75  			cast.FromBytes([]byte(resultRaw), &result)
    76  
    77  			if !keyIdentifier.isCollection {
    78  				return cast.ObjectToStructPointerSlice(result), nil
    79  			}
    80  
    81  			return result, nil
    82  		}
    83  	}
    84  	return nil, repository.ErrNotFound
    85  }
    86  
    87  // isParametersValid return
    88  // 1. true if either all parameters are key identifiers or all parameters are filtered parameters
    89  // 2. true if all parameters are key identifiers
    90  func (r *GenericRepository) isParametersValidAndAllParametersAreKeyIdentifiers(params map[string]interface{}) (bool, bool) {
    91  	numberOfKeyIdentifierParams := 0
    92  
    93  	for key, _ := range params {
    94  		if r.isKeyIdentifierRegistered(key) {
    95  			numberOfKeyIdentifierParams++
    96  		}
    97  	}
    98  
    99  	return (numberOfKeyIdentifierParams == len(params)) || (numberOfKeyIdentifierParams == 0), (numberOfKeyIdentifierParams == len(params))
   100  }
   101  
   102  func (r *GenericRepository) insertInAllKeys(ctx context.Context, data interface{}) error {
   103  	for _, keyIdentifier := range r.keyIdentifiers {
   104  		key, err := repository.GetStructPropertyAsString(data, keyIdentifier.propertyNameMapToKey)
   105  		if err != nil {
   106  			return err
   107  		}
   108  
   109  		if keyIdentifier.isCollection {
   110  			dataInArray := CreateStructPointerSlice(data, 0)
   111  
   112  			resultRaw, err := r.client.Get(ctx, fmt.Sprintf("%s%s%s", keyIdentifier.key, CONJUNCTION, key)).Result()
   113  			if err != nil && err != redis.Nil {
   114  				return err
   115  			}
   116  
   117  			cast.FromBytes([]byte(resultRaw), &dataInArray)
   118  
   119  			resultInArray := cast.StructPointerArrayToInterfacePointerArray(dataInArray)
   120  			resultInArray = append(resultInArray, data)
   121  
   122  			dataBytes, err := cast.ToBytes(resultInArray)
   123  			if err != nil {
   124  				return err
   125  			}
   126  
   127  			if err = r.client.Set(
   128  				ctx,
   129  				fmt.Sprintf("%s%s%s", keyIdentifier.key, CONJUNCTION, key),
   130  				dataBytes,
   131  				r.expirationDuration).Err(); err != nil {
   132  				return err
   133  			}
   134  		} else {
   135  			dataBytes, err := cast.ToBytes(data)
   136  			if err != nil {
   137  				return err
   138  			}
   139  
   140  			if err = r.client.Set(
   141  				ctx,
   142  				fmt.Sprintf("%s%s%s", keyIdentifier.key, CONJUNCTION, key),
   143  				dataBytes,
   144  				r.expirationDuration).Err(); err != nil {
   145  				return err
   146  			}
   147  		}
   148  	}
   149  
   150  	return nil
   151  }
   152  
   153  func (r *GenericRepository) upsertInAllKeys(ctx context.Context, data interface{}) error {
   154  	for _, keyIdentifier := range r.keyIdentifiers {
   155  		key, err := repository.GetStructPropertyAsString(data, keyIdentifier.propertyNameMapToKey)
   156  		if err != nil {
   157  			return err
   158  		}
   159  
   160  		if keyIdentifier.isCollection {
   161  			dataInArray := CreateStructPointerSlice(data, 0)
   162  
   163  			resultRaw, err := r.client.Get(ctx, fmt.Sprintf("%s%s%s", keyIdentifier.key, CONJUNCTION, key)).Result()
   164  			if err != nil && err != redis.Nil {
   165  				return err
   166  			}
   167  
   168  			cast.FromBytes([]byte(resultRaw), &dataInArray)
   169  
   170  			// ignore error if not found
   171  			resultInArray, err := UpdateCollection(dataInArray, data)
   172  			if err == repository.ErrNotFound {
   173  				resultInArray = append(resultInArray, data)
   174  			}
   175  
   176  			dataBytes, err := cast.ToBytes(resultInArray)
   177  			if err != nil {
   178  				return err
   179  			}
   180  
   181  			if err = r.client.Set(
   182  				ctx,
   183  				fmt.Sprintf("%s%s%s", keyIdentifier.key, CONJUNCTION, key),
   184  				dataBytes,
   185  				r.expirationDuration).Err(); err != nil {
   186  				return err
   187  			}
   188  		} else {
   189  			dataBytes, err := cast.ToBytes(data)
   190  			if err != nil {
   191  				return err
   192  			}
   193  
   194  			if err = r.client.Set(
   195  				ctx,
   196  				fmt.Sprintf("%s%s%s", keyIdentifier.key, CONJUNCTION, key),
   197  				dataBytes,
   198  				r.expirationDuration).Err(); err != nil {
   199  				return err
   200  			}
   201  		}
   202  	}
   203  
   204  	return nil
   205  }
   206  
   207  func (r *GenericRepository) deleteInAllKeys(ctx context.Context, data interface{}) error {
   208  	for _, keyIdentifier := range r.keyIdentifiers {
   209  		key, err := repository.GetStructPropertyAsString(data, keyIdentifier.propertyNameMapToKey)
   210  		if err != nil {
   211  			return err
   212  		}
   213  
   214  		if keyIdentifier.isCollection {
   215  			dataInArray := CreateStructPointerSlice(data, 0)
   216  
   217  			resultRaw, err := r.client.Get(ctx, fmt.Sprintf("%s%s%s", keyIdentifier.key, CONJUNCTION, key)).Result()
   218  			if err != nil {
   219  				return err
   220  			}
   221  
   222  			cast.FromBytes([]byte(resultRaw), &dataInArray)
   223  
   224  			// ignore error if not found
   225  			resultInArray, err := DeleteCollection(dataInArray, data)
   226  			if err == repository.ErrNotFound {
   227  				resultInArray = append(resultInArray, data)
   228  			}
   229  
   230  			dataBytes, err := cast.ToBytes(resultInArray)
   231  			if err != nil {
   232  				return err
   233  			}
   234  
   235  			if err = r.client.Set(
   236  				ctx,
   237  				fmt.Sprintf("%s%s%s", keyIdentifier.key, CONJUNCTION, key),
   238  				dataBytes,
   239  				r.expirationDuration).Err(); err != nil {
   240  				return err
   241  			}
   242  		} else {
   243  			if err = r.client.Del(ctx, fmt.Sprintf("%s%s%s", keyIdentifier.key, CONJUNCTION, key)).Err(); err != nil {
   244  				return err
   245  			}
   246  		}
   247  	}
   248  
   249  	return nil
   250  }
   251  
   252  func (r *GenericRepository) bulkInsertInAllKeys(ctx context.Context, data map[string]map[string][]interface{}) error {
   253  	for keyIdentifier, dataInKeyMap := range data {
   254  		registeredKeyIdentifier := r.getKeyIdentifier(keyIdentifier)
   255  		if registeredKeyIdentifier.isCollection {
   256  			for key, dataInArray := range dataInKeyMap {
   257  				dataBytes, err := cast.ToBytes(dataInArray)
   258  				if err != nil {
   259  					return err
   260  				}
   261  
   262  				if err = r.client.Set(
   263  					ctx,
   264  					fmt.Sprintf("%s%s%s", keyIdentifier, CONJUNCTION, key),
   265  					dataBytes,
   266  					r.expirationDuration).Err(); err != nil {
   267  					return err
   268  				}
   269  			}
   270  		} else {
   271  			for key, dataInArray := range dataInKeyMap {
   272  				dataBytes, err := cast.ToBytes(dataInArray[0])
   273  				if err != nil {
   274  					return err
   275  				}
   276  
   277  				if err = r.client.Set(
   278  					ctx,
   279  					fmt.Sprintf("%s%s%s", keyIdentifier, CONJUNCTION, key),
   280  					dataBytes,
   281  					r.expirationDuration).Err(); err != nil {
   282  					return err
   283  				}
   284  			}
   285  		}
   286  	}
   287  	return nil
   288  }
   289  
   290  func (r *GenericRepository) bulkUpsertInAllKeys(ctx context.Context, data map[string]map[string][]interface{}) error {
   291  	for keyIdentifier, dataInKeyMap := range data {
   292  		registeredKeyIdentifier := r.getKeyIdentifier(keyIdentifier)
   293  		if registeredKeyIdentifier.isCollection {
   294  			for key, dataInArray := range dataInKeyMap {
   295  				dataBytes, err := cast.ToBytes(dataInArray)
   296  				if err != nil {
   297  					return err
   298  				}
   299  
   300  				if err = r.client.Del(ctx, fmt.Sprintf("%s%s%s", keyIdentifier, CONJUNCTION, key)).Err(); err != nil {
   301  					return err
   302  				}
   303  
   304  				if err = r.client.Set(
   305  					ctx,
   306  					fmt.Sprintf("%s%s%s", keyIdentifier, CONJUNCTION, key),
   307  					dataBytes,
   308  					r.expirationDuration).Err(); err != nil {
   309  					return err
   310  				}
   311  			}
   312  		} else {
   313  			for key, dataInArray := range dataInKeyMap {
   314  				dataBytes, err := cast.ToBytes(dataInArray[0])
   315  				if err != nil {
   316  					return err
   317  				}
   318  
   319  				if err = r.client.Del(ctx, fmt.Sprintf("%s%s%s", keyIdentifier, CONJUNCTION, key)).Err(); err != nil {
   320  					return err
   321  				}
   322  
   323  				if err = r.client.Set(
   324  					ctx,
   325  					fmt.Sprintf("%s%s%s", keyIdentifier, CONJUNCTION, key),
   326  					dataBytes,
   327  					r.expirationDuration).Err(); err != nil {
   328  					return err
   329  				}
   330  			}
   331  		}
   332  	}
   333  	return nil
   334  }
   335  
   336  func (r *GenericRepository) transformDataToKeyMap(data interface{}) map[string]map[string][]interface{} {
   337  	dataMapToKey := map[string]map[string][]interface{}{}
   338  	for _, keyIdentifier := range r.keyIdentifiers {
   339  		dataMapToKey[keyIdentifier.key] = map[string][]interface{}{}
   340  	}
   341  
   342  	dataInArray := cast.StructPointerArrayToInterfacePointerArray(data)
   343  	for _, dt := range dataInArray {
   344  		for _, keyIdentifier := range r.keyIdentifiers {
   345  			key, err := repository.GetStructPropertyAsString(dt, keyIdentifier.propertyNameMapToKey)
   346  			if err != nil {
   347  				return nil
   348  			}
   349  			if dataMapToKey[keyIdentifier.key][key] == nil {
   350  				dataMapToKey[keyIdentifier.key][key] = []interface{}{}
   351  			}
   352  			dataMapToKey[keyIdentifier.key][key] = append(dataMapToKey[keyIdentifier.key][key], dt)
   353  		}
   354  	}
   355  
   356  	return dataMapToKey
   357  }
   358  
   359  // FindAll to get all the data with filtered by parameters given (Not Recommended passing the params on memcached, the process might slowed down and take more memory since it is relying on reflect)
   360  func (r *GenericRepository) FindAll(ctx context.Context, params map[string]interface{}, conditionalOperations []repository.ConditionalOperation, relationalOperations []repository.RelationalOperation, page int, limit int, result interface{}) (interface{}, error) {
   361  	var isConditionFulfill bool
   362  	var err error
   363  
   364  	if !repository.IsValidOperations(conditionalOperations, relationalOperations, params) {
   365  		return nil, repository.ErrBadParameters
   366  	}
   367  
   368  	resultRaw, err := r.client.Get(ctx, r.key).Result()
   369  	if err != nil {
   370  		if err == redis.Nil {
   371  			return nil, repository.ErrNotFound
   372  		}
   373  		return nil, err
   374  	}
   375  
   376  	resultInArray := CreateStructPointerSlice(result, 0)
   377  	cast.FromBytes([]byte(resultRaw), &resultInArray)
   378  
   379  	filteredResult := cast.StructPointerArrayToInterfacePointerArray(resultInArray)
   380  
   381  	var finalResult []interface{}
   382  	if len(params) > 0 {
   383  		isParametersValid, allParametersAreKeyIdentifiers := r.isParametersValidAndAllParametersAreKeyIdentifiers(params)
   384  		if !isParametersValid {
   385  			return nil, repository.ErrOperationIsNotAllowed
   386  		}
   387  
   388  		if allParametersAreKeyIdentifiers {
   389  			for key, value := range params {
   390  				valueInString, err := cast.ToString(value)
   391  				if err != nil {
   392  					return nil, err
   393  				}
   394  
   395  				singleResultRaw, err := r.findByKeyIdentifier(ctx, key, valueInString, result)
   396  				if err != nil {
   397  					if err == redis.Nil {
   398  						return nil, repository.ErrNotFound
   399  					}
   400  					return nil, err
   401  				}
   402  
   403  				singleResult := cast.StructPointerArrayToInterfacePointerArray(singleResultRaw)
   404  				finalResult = append(finalResult, singleResult...)
   405  			}
   406  		} else {
   407  			for _, singleResult := range filteredResult {
   408  				if isConditionFulfill, err = extractObjectAndCheckConditions(singleResult, params, conditionalOperations, relationalOperations); err != nil {
   409  					return nil, err
   410  				}
   411  
   412  				if isConditionFulfill {
   413  					finalResult = append(finalResult, singleResult)
   414  				}
   415  			}
   416  		}
   417  	} else {
   418  		finalResult = filteredResult
   419  	}
   420  
   421  	if page == 0 && limit == 0 {
   422  		return finalResult, nil
   423  	}
   424  
   425  	if page == 0 {
   426  		page = 1
   427  	}
   428  
   429  	if limit == 0 {
   430  		limit = 10
   431  	}
   432  
   433  	finalResult = finalResult[(page-1)*limit:]
   434  	if len(finalResult) > 0 {
   435  		if limit > len(finalResult) {
   436  			limit = len(finalResult)
   437  		}
   438  		finalResult = finalResult[:limit]
   439  	}
   440  	return finalResult, nil
   441  }
   442  
   443  func (r *GenericRepository) FindOne(ctx context.Context, key string, value interface{}, result interface{}) (interface{}, error) {
   444  	singleResultRaw, err := r.FindAll(ctx, map[string]interface{}{key: value}, []repository.ConditionalOperation{repository.EQUAL_WITH}, nil, 1, 1, result)
   445  	if err != nil {
   446  		return nil, err
   447  	}
   448  	singleResult := cast.StructPointerArrayToInterfaceArray(singleResultRaw)
   449  	if len(singleResult) == 0 {
   450  		return nil, repository.ErrNotFound
   451  	}
   452  	return singleResult[0], nil
   453  }
   454  
   455  func (r *GenericRepository) FindByID(ctx context.Context, id uuid.UUID, result interface{}) (interface{}, error) {
   456  	resultRaw, err := r.client.Get(ctx, fmt.Sprintf("%s%s%s", r.key, CONJUNCTION, id)).Result()
   457  	if err != nil {
   458  		if err == redis.Nil {
   459  			return nil, repository.ErrNotFound
   460  		}
   461  		return nil, err
   462  	}
   463  
   464  	cast.FromBytes([]byte(resultRaw), &result)
   465  	return result, nil
   466  }
   467  
   468  func (r *GenericRepository) Insert(ctx context.Context, data interface{}) (interface{}, error) {
   469  	result := CreateStructPointerSlice(data, 0)
   470  	resultRaw, err := r.client.Get(ctx, r.key).Result()
   471  	if err != nil && err != redis.Nil {
   472  		return nil, err
   473  	}
   474  
   475  	cast.FromBytes([]byte(resultRaw), &result)
   476  
   477  	resultInArray := cast.StructPointerArrayToInterfacePointerArray(result)
   478  	resultInArray = append([]interface{}{data}, resultInArray...)
   479  
   480  	dataBytes, err := cast.ToBytes(resultInArray)
   481  	if err != nil {
   482  		return nil, err
   483  	}
   484  
   485  	if err = r.client.Set(
   486  		ctx,
   487  		r.key,
   488  		dataBytes,
   489  		r.expirationDuration).Err(); err != nil {
   490  		return nil, err
   491  	}
   492  
   493  	singleDataBytes, err := cast.ToBytes(data)
   494  	if err != nil {
   495  		return nil, err
   496  	}
   497  
   498  	id, err := repository.GetStructPropertyAsString(data, defaultIDPropertyName)
   499  	if err != nil {
   500  		return nil, err
   501  	}
   502  
   503  	if err = r.client.Set(
   504  		ctx,
   505  		fmt.Sprintf("%s%s%s", r.key, CONJUNCTION, id),
   506  		singleDataBytes,
   507  		r.expirationDuration).Err(); err != nil {
   508  		return nil, err
   509  	}
   510  
   511  	if err = r.insertInAllKeys(ctx, data); err != nil {
   512  		return nil, err
   513  	}
   514  
   515  	return data, nil
   516  }
   517  
   518  func (r *GenericRepository) Update(ctx context.Context, data interface{}) (interface{}, error) {
   519  	result := CreateStructPointerSlice(data, 0)
   520  	resultRaw, err := r.client.Get(ctx, r.key).Result()
   521  	if err != nil {
   522  		if err == redis.Nil {
   523  			return nil, repository.ErrNotFound
   524  		}
   525  		return nil, err
   526  	}
   527  
   528  	cast.FromBytes([]byte(resultRaw), &result)
   529  
   530  	resultInArray, err := UpdateCollection(result, data)
   531  	if err != nil {
   532  		return nil, err
   533  	}
   534  
   535  	dataBytes, err := cast.ToBytes(resultInArray)
   536  	if err != nil {
   537  		return nil, err
   538  	}
   539  
   540  	if err = r.client.Set(
   541  		ctx,
   542  		r.key,
   543  		dataBytes,
   544  		r.expirationDuration).Err(); err != nil {
   545  		return nil, err
   546  	}
   547  
   548  	singleDataBytes, err := cast.ToBytes(data)
   549  	if err != nil {
   550  		return nil, err
   551  	}
   552  
   553  	id, err := repository.GetStructPropertyAsString(data, defaultIDPropertyName)
   554  	if err != nil {
   555  		return nil, err
   556  	}
   557  
   558  	if err = r.client.Set(
   559  		ctx,
   560  		fmt.Sprintf("%s%s%s", r.key, CONJUNCTION, id),
   561  		singleDataBytes,
   562  		r.expirationDuration).Err(); err != nil {
   563  		return nil, err
   564  	}
   565  
   566  	if err = r.upsertInAllKeys(ctx, data); err != nil {
   567  		return nil, err
   568  	}
   569  
   570  	return data, nil
   571  }
   572  
   573  func (r *GenericRepository) Delete(ctx context.Context, data interface{}) (interface{}, error) {
   574  	result := CreateStructPointerSlice(data, 0)
   575  	resultRaw, err := r.client.Get(ctx, r.key).Result()
   576  	if err != nil {
   577  		if err == redis.Nil {
   578  			return nil, repository.ErrNotFound
   579  		}
   580  		return nil, err
   581  	}
   582  
   583  	cast.FromBytes([]byte(resultRaw), &result)
   584  
   585  	resultInArray, err := DeleteCollection(result, data)
   586  	if err != nil {
   587  		return nil, err
   588  	}
   589  
   590  	dataBytes, err := cast.ToBytes(resultInArray)
   591  	if err != nil {
   592  		return nil, err
   593  	}
   594  
   595  	if err = r.client.Set(
   596  		ctx,
   597  		r.key,
   598  		dataBytes,
   599  		r.expirationDuration).Err(); err != nil {
   600  		return nil, err
   601  	}
   602  
   603  	id, err := repository.GetStructPropertyAsString(data, defaultIDPropertyName)
   604  	if err != nil {
   605  		return nil, err
   606  	}
   607  
   608  	if err = r.client.Del(ctx, fmt.Sprintf("%s%s%s", r.key, CONJUNCTION, id)).Err(); err != nil {
   609  		return nil, err
   610  	}
   611  
   612  	if err = r.deleteInAllKeys(ctx, data); err != nil {
   613  		return nil, err
   614  	}
   615  
   616  	return data, nil
   617  }
   618  
   619  func (r *GenericRepository) Upsert(ctx context.Context, data interface{}) (interface{}, error) {
   620  	result := CreateStructPointerSlice(data, 0)
   621  	resultRaw, err := r.client.Get(ctx, r.key).Result()
   622  	if err != nil && err != redis.Nil {
   623  		return nil, err
   624  	}
   625  
   626  	cast.FromBytes([]byte(resultRaw), &result)
   627  
   628  	resultInArray, err := UpdateCollection(result, data)
   629  	if err == repository.ErrNotFound {
   630  		resultInArray = append(cast.StructPointerArrayToInterfacePointerArray(result), data)
   631  	}
   632  
   633  	dataBytes, err := cast.ToBytes(resultInArray)
   634  	if err != nil {
   635  		return nil, err
   636  	}
   637  
   638  	if err = r.client.Set(
   639  		ctx,
   640  		r.key,
   641  		dataBytes,
   642  		r.expirationDuration).Err(); err != nil {
   643  		return nil, err
   644  	}
   645  
   646  	singleDataBytes, err := cast.ToBytes(data)
   647  	if err != nil {
   648  		return nil, err
   649  	}
   650  
   651  	id, err := repository.GetStructPropertyAsString(data, defaultIDPropertyName)
   652  	if err != nil {
   653  		return nil, err
   654  	}
   655  
   656  	if err = r.client.Set(
   657  		ctx,
   658  		fmt.Sprintf("%s%s%s", r.key, CONJUNCTION, id),
   659  		singleDataBytes,
   660  		r.expirationDuration).Err(); err != nil {
   661  		return nil, err
   662  	}
   663  
   664  	if err = r.upsertInAllKeys(ctx, data); err != nil {
   665  		return nil, err
   666  	}
   667  
   668  	return data, nil
   669  }
   670  
   671  // BulkInsert for now just going to do bulkinsert for whole data
   672  /*
   673  consideration - trade offs:
   674  + much faster
   675  - needs to implement sharding services for reading and writing to cutoff the data size as soon as possible
   676  */
   677  func (r *GenericRepository) BulkInsert(ctx context.Context, data interface{}) (interface{}, error) {
   678  	dataBytes, err := cast.ToBytes(data)
   679  	if err != nil && err != redis.Nil {
   680  		return nil, err
   681  	}
   682  
   683  	if err = r.client.Set(
   684  		ctx,
   685  		r.key,
   686  		dataBytes,
   687  		r.expirationDuration).Err(); err != nil {
   688  		return nil, err
   689  	}
   690  
   691  	dataInArray := cast.StructPointerArrayToInterfacePointerArray(data)
   692  	for _, dt := range dataInArray {
   693  		singleDataBytes, err := cast.ToBytes(dt)
   694  		if err != nil {
   695  			return nil, err
   696  		}
   697  
   698  		id, err := repository.GetStructPropertyAsString(dt, defaultIDPropertyName)
   699  		if err != nil {
   700  			return nil, err
   701  		}
   702  
   703  		if err = r.client.Set(
   704  			ctx,
   705  			fmt.Sprintf("%s%s%s", r.key, CONJUNCTION, id),
   706  			singleDataBytes,
   707  			r.expirationDuration).Err(); err != nil {
   708  			return nil, err
   709  		}
   710  	}
   711  
   712  	if err = r.bulkInsertInAllKeys(ctx, r.transformDataToKeyMap(data)); err != nil {
   713  		return nil, err
   714  	}
   715  
   716  	return data, nil
   717  }
   718  
   719  // BulkUpsert for now just going to do bulkupsert for whole data by removing and reinsert new data
   720  /*
   721  consideration - trade offs:
   722  + much faster
   723  - needs to implement sharding services for reading and writing to cutoff the data size as soon as possible
   724  */
   725  func (r *GenericRepository) BulkUpsert(ctx context.Context, data interface{}) (interface{}, error) {
   726  	dataBytes, err := cast.ToBytes(data)
   727  	if err != nil && err != redis.Nil {
   728  		return nil, err
   729  	}
   730  
   731  	if err = r.client.Del(ctx, r.key).Err(); err != nil {
   732  		return nil, err
   733  	}
   734  
   735  	if err = r.client.Set(
   736  		ctx,
   737  		r.key,
   738  		dataBytes,
   739  		r.expirationDuration).Err(); err != nil {
   740  		return nil, err
   741  	}
   742  
   743  	dataInArray := cast.StructPointerArrayToInterfacePointerArray(data)
   744  	for _, dt := range dataInArray {
   745  		singleDataBytes, err := cast.ToBytes(dt)
   746  		if err != nil {
   747  			return nil, err
   748  		}
   749  
   750  		id, err := repository.GetStructPropertyAsString(dt, defaultIDPropertyName)
   751  		if err != nil {
   752  			return nil, err
   753  		}
   754  
   755  		if err = r.client.Set(
   756  			ctx,
   757  			fmt.Sprintf("%s%s%s", r.key, CONJUNCTION, id),
   758  			singleDataBytes,
   759  			r.expirationDuration).Err(); err != nil {
   760  			return nil, err
   761  		}
   762  	}
   763  
   764  	if err = r.bulkUpsertInAllKeys(ctx, r.transformDataToKeyMap(data)); err != nil {
   765  		return nil, err
   766  	}
   767  
   768  	return data, nil
   769  }
   770  
   771  func (r *GenericRepository) Query(ctx context.Context, query string, params []interface{}, result interface{}) (interface{}, error) {
   772  	// Not Implemented on memcached
   773  	return nil, repository.ErrNotImplement
   774  }
   775  
   776  func NewRepository(client *redis.Client, key string, expirationDuration time.Duration, keyIdentifiers []*KeyIdentifier) repository.GenericRepositoryInterface {
   777  	return &GenericRepository{
   778  		client:             client,
   779  		key:                key,
   780  		expirationDuration: expirationDuration,
   781  		keyIdentifiers:     keyIdentifiers,
   782  	}
   783  }
   784  
   785  func NewMutableRepository(client *redis.Client, key string, expirationDuration time.Duration, keyIdentifiers []*KeyIdentifier) repository.MutableGenericRepositoryInterface {
   786  	return &GenericRepository{
   787  		client:             client,
   788  		key:                key,
   789  		expirationDuration: expirationDuration,
   790  		keyIdentifiers:     keyIdentifiers,
   791  	}
   792  }
   793  
   794  func NewImmutableRepository(client *redis.Client, key string, expirationDuration time.Duration, keyIdentifiers []*KeyIdentifier) repository.ImmutableGenericRepositoryInterface {
   795  	return &GenericRepository{
   796  		client:             client,
   797  		key:                key,
   798  		expirationDuration: expirationDuration,
   799  		keyIdentifiers:     keyIdentifiers,
   800  	}
   801  }