github.com/weaviate/weaviate@v1.24.6/adapters/repos/db/shard_skip_vector_reindex_integration_test.go (about)

     1  //                           _       _
     2  // __      _____  __ ___   ___  __ _| |_ ___
     3  // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
     4  //  \ V  V /  __/ (_| |\ V /| | (_| | ||  __/
     5  //   \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
     6  //
     7  //  Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
     8  //
     9  //  CONTACT: hello@weaviate.io
    10  //
    11  
    12  //go:build integrationTest
    13  // +build integrationTest
    14  
    15  package db
    16  
    17  import (
    18  	"context"
    19  	"os"
    20  	"testing"
    21  	"time"
    22  
    23  	"github.com/go-openapi/strfmt"
    24  	"github.com/google/uuid"
    25  	"github.com/stretchr/testify/require"
    26  	"github.com/weaviate/weaviate/entities/additional"
    27  	"github.com/weaviate/weaviate/entities/filters"
    28  	"github.com/weaviate/weaviate/entities/models"
    29  	"github.com/weaviate/weaviate/entities/schema"
    30  	"github.com/weaviate/weaviate/entities/storobj"
    31  	"github.com/weaviate/weaviate/entities/vectorindex/common"
    32  	"github.com/weaviate/weaviate/entities/vectorindex/hnsw"
    33  	"github.com/weaviate/weaviate/usecases/objects"
    34  )
    35  
    36  func TestShard_SkipVectorReindex(t *testing.T) {
    37  	ctx := context.Background()
    38  
    39  	uuid_ := strfmt.UUID(uuid.NewString())
    40  	origCreateTimeUnix := int64(1704161045)
    41  	origUpdateTimeUnix := int64(1704161046)
    42  	updCreateTimeUnix := int64(1704161047)
    43  	updUpdateTimeUnix := int64(1704161048)
    44  	vector := []float32{1, 2, 3}
    45  	altVector := []float32{10, 0, -20}
    46  
    47  	class := &models.Class{
    48  		Class: "TestClass",
    49  		InvertedIndexConfig: &models.InvertedIndexConfig{
    50  			IndexTimestamps:     true,
    51  			IndexNullState:      true,
    52  			IndexPropertyLength: true,
    53  		},
    54  		Properties: []*models.Property{
    55  			{
    56  				Name:         "texts",
    57  				DataType:     schema.DataTypeTextArray.PropString(),
    58  				Tokenization: models.PropertyTokenizationWord,
    59  			},
    60  			{
    61  				Name:     "numbers",
    62  				DataType: schema.DataTypeNumberArray.PropString(),
    63  			},
    64  			{
    65  				Name:     "ints",
    66  				DataType: schema.DataTypeIntArray.PropString(),
    67  			},
    68  			{
    69  				Name:     "booleans",
    70  				DataType: schema.DataTypeBooleanArray.PropString(),
    71  			},
    72  			{
    73  				Name:     "dates",
    74  				DataType: schema.DataTypeDateArray.PropString(),
    75  			},
    76  			{
    77  				Name:     "uuids",
    78  				DataType: schema.DataTypeUUIDArray.PropString(),
    79  			},
    80  			{
    81  				Name:         "text",
    82  				DataType:     schema.DataTypeText.PropString(),
    83  				Tokenization: models.PropertyTokenizationWord,
    84  			},
    85  			{
    86  				Name:     "number",
    87  				DataType: schema.DataTypeNumber.PropString(),
    88  			},
    89  			{
    90  				Name:     "int",
    91  				DataType: schema.DataTypeInt.PropString(),
    92  			},
    93  			{
    94  				Name:     "boolean",
    95  				DataType: schema.DataTypeBoolean.PropString(),
    96  			},
    97  			{
    98  				Name:     "date",
    99  				DataType: schema.DataTypeDate.PropString(),
   100  			},
   101  			{
   102  				Name:     "uuid",
   103  				DataType: schema.DataTypeUUID.PropString(),
   104  			},
   105  			{
   106  				Name:     "geo",
   107  				DataType: schema.DataTypeGeoCoordinates.PropString(),
   108  			},
   109  		},
   110  	}
   111  
   112  	createOrigObj := func() *storobj.Object {
   113  		return &storobj.Object{
   114  			MarshallerVersion: 1,
   115  			Object: models.Object{
   116  				ID:    uuid_,
   117  				Class: class.Class,
   118  				Properties: map[string]interface{}{
   119  					"texts": []string{
   120  						"aaa",
   121  						"bbb",
   122  						"ccc",
   123  					},
   124  					"numbers": []interface{}{},
   125  					"ints": []float64{
   126  						101, 101, 101, 101, 101, 101,
   127  						102,
   128  						103,
   129  						104,
   130  					},
   131  					"booleans": []bool{
   132  						true, true, true,
   133  						false,
   134  					},
   135  					"dates": []time.Time{
   136  						mustParseTime("2001-06-01T12:00:00.000000Z"),
   137  						mustParseTime("2002-06-02T12:00:00.000000Z"),
   138  					},
   139  					// no uuids
   140  					"text": "ddd",
   141  					// no number
   142  					"int":     float64(201),
   143  					"boolean": false,
   144  					"date":    mustParseTime("2003-06-01T12:00:00.000000Z"),
   145  					// no uuid
   146  					"geo": &models.GeoCoordinates{
   147  						Latitude:  ptFloat32(1.1),
   148  						Longitude: ptFloat32(2.2),
   149  					},
   150  				},
   151  				CreationTimeUnix:   origCreateTimeUnix,
   152  				LastUpdateTimeUnix: origUpdateTimeUnix,
   153  			},
   154  			Vector: vector,
   155  		}
   156  	}
   157  	createUpdObj := func() *storobj.Object {
   158  		return &storobj.Object{
   159  			MarshallerVersion: 1,
   160  			Object: models.Object{
   161  				ID:    uuid_,
   162  				Class: class.Class,
   163  				Properties: map[string]interface{}{
   164  					"texts": []interface{}{},
   165  					// no numbers
   166  					"ints": []float64{
   167  						101, 101, 101, 101,
   168  						103,
   169  						104,
   170  						105,
   171  					},
   172  					"booleans": []bool{
   173  						true, true, true,
   174  						false,
   175  					},
   176  					// no dates
   177  					"uuids": []uuid.UUID{
   178  						uuid.MustParse("d726c960-aede-411c-85d3-2c77e9290a6e"),
   179  					},
   180  					"text": "",
   181  					// no number
   182  					"int":     float64(202),
   183  					"boolean": true,
   184  					// no date
   185  					"uuid": uuid.MustParse("7fabaf01-9e10-458a-acea-cc627376c506"),
   186  					"geo": &models.GeoCoordinates{
   187  						Latitude:  ptFloat32(1.1),
   188  						Longitude: ptFloat32(2.2),
   189  					},
   190  				},
   191  				CreationTimeUnix:   updCreateTimeUnix,
   192  				LastUpdateTimeUnix: updUpdateTimeUnix,
   193  			},
   194  			Vector: vector,
   195  		}
   196  	}
   197  	createMergeDoc := func() objects.MergeDocument {
   198  		return objects.MergeDocument{
   199  			ID:    uuid_,
   200  			Class: class.Class,
   201  			PrimitiveSchema: map[string]interface{}{
   202  				"texts": []interface{}{},
   203  				"ints": []interface{}{
   204  					float64(101), float64(101), float64(101), float64(101),
   205  					float64(103),
   206  					float64(104),
   207  					float64(105),
   208  				},
   209  				"uuids": []interface{}{
   210  					uuid.MustParse("d726c960-aede-411c-85d3-2c77e9290a6e"),
   211  				},
   212  				"text":    "",
   213  				"int":     float64(202),
   214  				"boolean": true,
   215  				"uuid":    uuid.MustParse("7fabaf01-9e10-458a-acea-cc627376c506"),
   216  			},
   217  			UpdateTime:         updUpdateTimeUnix,
   218  			PropertiesToDelete: []string{"numbers", "dates", "date"},
   219  			Vector:             vector,
   220  		}
   221  	}
   222  
   223  	filterId := filterEqual[string](string(uuid_), schema.DataTypeText, class.Class, "_id")
   224  
   225  	filterTextsEqAAA := filterEqual[string]("aaa", schema.DataTypeText, class.Class, "texts")
   226  	filterTextsLen3 := filterEqual[int](3, schema.DataTypeInt, class.Class, "len(texts)")
   227  	filterTextsLen0 := filterEqual[int](0, schema.DataTypeInt, class.Class, "len(texts)")
   228  	filterTextsNotNil := filterNil(false, class.Class, "texts")
   229  	filterTextsNil := filterNil(true, class.Class, "texts")
   230  
   231  	filterNumbersEq123 := filterEqual[float64](1.23, schema.DataTypeNumber, class.Class, "numbers")
   232  	filterNumbersLen1 := filterEqual[int](1, schema.DataTypeInt, class.Class, "len(numbers)")
   233  	filterNumbersLen0 := filterEqual[int](0, schema.DataTypeInt, class.Class, "len(numbers)")
   234  	filterNumbersNotNil := filterNil(false, class.Class, "numbers")
   235  	filterNumbersNil := filterNil(true, class.Class, "numbers")
   236  
   237  	filterIntsEq102 := filterEqual[int](102, schema.DataTypeInt, class.Class, "ints")
   238  	filterIntsEq105 := filterEqual[int](105, schema.DataTypeInt, class.Class, "ints")
   239  	filterIntsLen9 := filterEqual[int](9, schema.DataTypeInt, class.Class, "len(ints)")
   240  	filterIntsLen7 := filterEqual[int](7, schema.DataTypeInt, class.Class, "len(ints)")
   241  	filterIntsNotNil := filterNil(false, class.Class, "ints")
   242  	filterIntsNil := filterNil(true, class.Class, "ints")
   243  
   244  	filterBoolsEqTrue := filterEqual[bool](true, schema.DataTypeBoolean, class.Class, "booleans")
   245  	filterBoolsEqFalse := filterEqual[bool](false, schema.DataTypeBoolean, class.Class, "booleans")
   246  	filterBoolsLen4 := filterEqual[int](4, schema.DataTypeInt, class.Class, "len(booleans)")
   247  	filterBoolsLen0 := filterEqual[int](0, schema.DataTypeInt, class.Class, "len(booleans)")
   248  	filterBoolsNotNil := filterNil(false, class.Class, "booleans")
   249  	filterBoolsNil := filterNil(true, class.Class, "booleans")
   250  
   251  	filterDatesEq2001 := filterEqual[string]("2001-06-01T12:00:00.000000Z", schema.DataTypeDate, class.Class, "dates")
   252  	filterDatesLen2 := filterEqual[int](2, schema.DataTypeInt, class.Class, "len(dates)")
   253  	filterDatesLen0 := filterEqual[int](0, schema.DataTypeInt, class.Class, "len(dates)")
   254  	filterDatesNotNil := filterNil(false, class.Class, "dates")
   255  	filterDatesNil := filterNil(true, class.Class, "dates")
   256  
   257  	filterUuidsEqD726 := filterEqual[string]("d726c960-aede-411c-85d3-2c77e9290a6e", schema.DataTypeText, class.Class, "uuids")
   258  	filterUuidsLen1 := filterEqual[int](1, schema.DataTypeInt, class.Class, "len(uuids)")
   259  	filterUuidsLen0 := filterEqual[int](0, schema.DataTypeInt, class.Class, "len(uuids)")
   260  	filterUuidsNotNil := filterNil(false, class.Class, "uuids")
   261  	filterUuidsNil := filterNil(true, class.Class, "uuids")
   262  
   263  	filterTextEqDDD := filterEqual[string]("ddd", schema.DataTypeText, class.Class, "text")
   264  	filterTextLen3 := filterEqual[int](3, schema.DataTypeInt, class.Class, "len(text)")
   265  	filterTextLen0 := filterEqual[int](0, schema.DataTypeInt, class.Class, "len(text)")
   266  	filterTextNotNil := filterNil(false, class.Class, "text")
   267  	filterTextNil := filterNil(true, class.Class, "text")
   268  
   269  	filterNumberEq123 := filterEqual[float64](1.23, schema.DataTypeNumber, class.Class, "number")
   270  	filterNumberNotNil := filterNil(false, class.Class, "number")
   271  	filterNumberNil := filterNil(true, class.Class, "number")
   272  
   273  	filterIntEq201 := filterEqual[int](201, schema.DataTypeInt, class.Class, "int")
   274  	filterIntEq202 := filterEqual[int](202, schema.DataTypeInt, class.Class, "int")
   275  	filterIntNotNil := filterNil(false, class.Class, "int")
   276  	filterIntNil := filterNil(true, class.Class, "int")
   277  
   278  	filterBoolEqFalse := filterEqual[bool](false, schema.DataTypeBoolean, class.Class, "boolean")
   279  	filterBoolEqTrue := filterEqual[bool](true, schema.DataTypeBoolean, class.Class, "boolean")
   280  	filterBoolNotNil := filterNil(false, class.Class, "boolean")
   281  	filterBoolNil := filterNil(true, class.Class, "boolean")
   282  
   283  	filterDateEq2003 := filterEqual[string]("2003-06-01T12:00:00.000000Z", schema.DataTypeDate, class.Class, "date")
   284  	filterDateNotNil := filterNil(false, class.Class, "date")
   285  	filterDateNil := filterNil(true, class.Class, "date")
   286  
   287  	filterUuidEq7FAB := filterEqual[string]("7fabaf01-9e10-458a-acea-cc627376c506", schema.DataTypeText, class.Class, "uuid")
   288  	filterUuidNotNil := filterNil(false, class.Class, "uuid")
   289  	filterUuidNil := filterNil(true, class.Class, "uuid")
   290  
   291  	search := func(t *testing.T, shard ShardLike, filter *filters.LocalFilter) []*storobj.Object {
   292  		searchLimit := 10
   293  		found, _, err := shard.ObjectSearch(ctx, searchLimit, filter,
   294  			nil, nil, nil, additional.Properties{})
   295  		require.NoError(t, err)
   296  		return found
   297  	}
   298  
   299  	verifySearchAfterAdd := func(shard ShardLike) func(t *testing.T) {
   300  		return func(t *testing.T) {
   301  			t.Run("to be found", func(t *testing.T) {
   302  				for name, filter := range map[string]*filters.LocalFilter{
   303  					"id": filterId,
   304  
   305  					"textsEqAAA":  filterTextsEqAAA,
   306  					"textsLen3":   filterTextsLen3,
   307  					"textsNotNil": filterTextsNotNil,
   308  
   309  					"numbersLen0": filterNumbersLen0,
   310  					"numbersNil":  filterNumbersNil,
   311  
   312  					"intsEq102":  filterIntsEq102,
   313  					"intsLen9":   filterIntsLen9,
   314  					"intsNotNil": filterIntsNotNil,
   315  
   316  					"boolsEqTrue":  filterBoolsEqTrue,
   317  					"boolsEqFalse": filterBoolsEqFalse,
   318  					"boolsLen4":    filterBoolsLen4,
   319  					"boolsNotNil":  filterBoolsNotNil,
   320  
   321  					"datesEq2001": filterDatesEq2001,
   322  					"datesLen2":   filterDatesLen2,
   323  					"datesNotNil": filterDatesNotNil,
   324  
   325  					"uuidsLen0": filterUuidsLen0,
   326  					"uuidsNil":  filterUuidsNil,
   327  
   328  					"textEqDDD":  filterTextEqDDD,
   329  					"textLen3":   filterTextLen3,
   330  					"textNotNil": filterTextNotNil,
   331  
   332  					"numberNil": filterNumberNil,
   333  
   334  					"intEq201":  filterIntEq201,
   335  					"intNotNil": filterIntNotNil,
   336  
   337  					"boolEqFalse": filterBoolEqFalse,
   338  					"boolNotNil":  filterBoolNotNil,
   339  
   340  					"dateEq2003": filterDateEq2003,
   341  					"dateNotNil": filterDateNotNil,
   342  
   343  					"uuidNil": filterUuidNil,
   344  				} {
   345  					t.Run(name, func(t *testing.T) {
   346  						found := search(t, shard, filter)
   347  						require.Len(t, found, 1)
   348  						require.Equal(t, uuid_, found[0].Object.ID)
   349  					})
   350  				}
   351  			})
   352  
   353  			t.Run("not to be found", func(t *testing.T) {
   354  				for name, filter := range map[string]*filters.LocalFilter{
   355  					"textsLen0": filterTextsLen0,
   356  					"textsNil":  filterTextsNil,
   357  
   358  					"numbersEq123":  filterNumbersEq123,
   359  					"numbersLen1":   filterNumbersLen1,
   360  					"numbersNotNil": filterNumbersNotNil,
   361  
   362  					"intsEq105": filterIntsEq105,
   363  					"intsLen7":  filterIntsLen7,
   364  					"intsNil":   filterIntsNil,
   365  
   366  					"boolsLen0": filterBoolsLen0,
   367  					"boolsNil":  filterBoolsNil,
   368  
   369  					"datesLen0": filterDatesLen0,
   370  					"datesNil":  filterDatesNil,
   371  
   372  					"uuidsEqD726": filterUuidsEqD726,
   373  					"uuidsLen1":   filterUuidsLen1,
   374  					"uuidsNotNil": filterUuidsNotNil,
   375  
   376  					"textLen0": filterTextLen0,
   377  					"textNil":  filterTextNil,
   378  
   379  					"numberEq123":  filterNumberEq123,
   380  					"numberNotNil": filterNumberNotNil,
   381  
   382  					"intEq202": filterIntEq202,
   383  					"intNil":   filterIntNil,
   384  
   385  					"boolEqTrue": filterBoolEqTrue,
   386  					"boolNil":    filterBoolNil,
   387  
   388  					"dateNil": filterDateNil,
   389  
   390  					"uuidEq7FAB": filterUuidEq7FAB,
   391  					"uuidNotNil": filterUuidNotNil,
   392  				} {
   393  					t.Run(name, func(t *testing.T) {
   394  						found := search(t, shard, filter)
   395  						require.Len(t, found, 0)
   396  					})
   397  				}
   398  			})
   399  		}
   400  	}
   401  	verifySearchAfterUpdate := func(shard ShardLike) func(t *testing.T) {
   402  		return func(t *testing.T) {
   403  			t.Run("to be found", func(t *testing.T) {
   404  				for name, filter := range map[string]*filters.LocalFilter{
   405  					"id": filterId,
   406  
   407  					"textsLen0": filterTextsLen0,
   408  					"textsNil":  filterTextsNil,
   409  
   410  					"numbersLen0": filterNumbersLen0,
   411  					"numbersNil":  filterNumbersNil,
   412  
   413  					"intsEq105":  filterIntsEq105,
   414  					"intsLen7":   filterIntsLen7,
   415  					"intsNotNil": filterIntsNotNil,
   416  
   417  					"boolsEqTrue":  filterBoolsEqTrue,
   418  					"boolsEqFalse": filterBoolsEqFalse,
   419  					"boolsLen4":    filterBoolsLen4,
   420  					"boolsNotNil":  filterBoolsNotNil,
   421  
   422  					"datesLen0": filterDatesLen0,
   423  					"datesNil":  filterDatesNil,
   424  
   425  					"uuidsEqD726": filterUuidsEqD726,
   426  					"uuidsLen1":   filterUuidsLen1,
   427  					"uuidsNotNil": filterUuidsNotNil,
   428  
   429  					"textLen0": filterTextLen0,
   430  					"textNil":  filterTextNil,
   431  
   432  					"numberNil": filterNumberNil,
   433  
   434  					"intEq202":  filterIntEq202,
   435  					"intNotNil": filterIntNotNil,
   436  
   437  					"boolEqTrue": filterBoolEqTrue,
   438  					"boolNotNil": filterBoolNotNil,
   439  
   440  					"dateNil": filterDateNil,
   441  
   442  					"uuidEq7FAB": filterUuidEq7FAB,
   443  					"uuidNotNil": filterUuidNotNil,
   444  				} {
   445  					t.Run(name, func(t *testing.T) {
   446  						found := search(t, shard, filter)
   447  						require.Len(t, found, 1)
   448  						require.Equal(t, uuid_, found[0].Object.ID)
   449  					})
   450  				}
   451  			})
   452  
   453  			t.Run("not to be found", func(t *testing.T) {
   454  				for name, filter := range map[string]*filters.LocalFilter{
   455  					"textsEqAAA":  filterTextsEqAAA,
   456  					"textsLen3":   filterTextsLen3,
   457  					"textsNotNil": filterTextsNotNil,
   458  
   459  					"numbersEq123":  filterNumbersEq123,
   460  					"numbersLen1":   filterNumbersLen1,
   461  					"numbersNotNil": filterNumbersNotNil,
   462  
   463  					"intsEq102": filterIntsEq102,
   464  					"intsLen9":  filterIntsLen9,
   465  					"intsNil":   filterIntsNil,
   466  
   467  					"boolsLen0": filterBoolsLen0,
   468  					"boolsNil":  filterBoolsNil,
   469  
   470  					"datesEq2001": filterDatesEq2001,
   471  					"datesLen2":   filterDatesLen2,
   472  					"datesNotNil": filterDatesNotNil,
   473  
   474  					"uuidsLen0": filterUuidsLen0,
   475  					"uuidsNil":  filterUuidsNil,
   476  
   477  					"textEqDDD":  filterTextEqDDD,
   478  					"textLen3":   filterTextLen3,
   479  					"textNotNil": filterTextNotNil,
   480  
   481  					"numberEq123":  filterNumberEq123,
   482  					"numberNotNil": filterNumberNotNil,
   483  
   484  					"intEq201": filterIntEq201,
   485  					"intNil":   filterIntNil,
   486  
   487  					"boolEqFalse": filterBoolEqFalse,
   488  					"boolNil":     filterBoolNil,
   489  
   490  					"dateEq2003": filterDateEq2003,
   491  					"dateNotNil": filterDateNotNil,
   492  
   493  					"uuidNil": filterUuidNil,
   494  				} {
   495  					t.Run(name, func(t *testing.T) {
   496  						found := search(t, shard, filter)
   497  						require.Len(t, found, 0)
   498  					})
   499  				}
   500  			})
   501  		}
   502  	}
   503  	verifyVectorSearch := func(shard ShardLike, vectorToBeFound, vectorNotToBeFound []float32) func(t *testing.T) {
   504  		vectorSearchLimit := -1 // negative to limit results by distance
   505  		vectorSearchDist := float32(1)
   506  		targetVector := ""
   507  
   508  		return func(t *testing.T) {
   509  			t.Run("to be found", func(t *testing.T) {
   510  				found, _, err := shard.ObjectVectorSearch(ctx, vectorToBeFound, targetVector,
   511  					vectorSearchDist, vectorSearchLimit, nil, nil, nil, additional.Properties{})
   512  				require.NoError(t, err)
   513  				require.Len(t, found, 1)
   514  				require.Equal(t, uuid_, found[0].Object.ID)
   515  			})
   516  
   517  			t.Run("not to be found", func(t *testing.T) {
   518  				found, _, err := shard.ObjectVectorSearch(ctx, vectorNotToBeFound, targetVector,
   519  					vectorSearchDist, vectorSearchLimit, nil, nil, nil, additional.Properties{})
   520  				require.NoError(t, err)
   521  				require.Len(t, found, 0)
   522  			})
   523  		}
   524  	}
   525  
   526  	createShard := func(t *testing.T) ShardLike {
   527  		vectorIndexConfig := hnsw.UserConfig{Distance: common.DefaultDistanceMetric}
   528  		shard, _ := testShardWithSettings(t, ctx, class, vectorIndexConfig, true, true)
   529  		return shard
   530  	}
   531  
   532  	t.Run("single object", func(t *testing.T) {
   533  		t.Run("sanity check - search after add", func(t *testing.T) {
   534  			shard := createShard(t)
   535  
   536  			t.Run("add object", func(t *testing.T) {
   537  				err := shard.PutObject(ctx, createOrigObj())
   538  				require.NoError(t, err)
   539  			})
   540  
   541  			t.Run("verify initial docID and timestamps", func(t *testing.T) {
   542  				expectedNextDocID := uint64(1)
   543  				require.Equal(t, expectedNextDocID, shard.Counter().Get())
   544  
   545  				found := search(t, shard, filterId)
   546  				require.Len(t, found, 1)
   547  				require.Equal(t, origCreateTimeUnix, found[0].CreationTimeUnix())
   548  				require.Equal(t, origUpdateTimeUnix, found[0].LastUpdateTimeUnix())
   549  			})
   550  
   551  			t.Run("verify search after add", verifySearchAfterAdd(shard))
   552  			t.Run("verify vector search after add", verifyVectorSearch(shard, vector, altVector))
   553  		})
   554  
   555  		t.Run("replace with different object, same vector", func(t *testing.T) {
   556  			shard := createShard(t)
   557  
   558  			t.Run("add object", func(t *testing.T) {
   559  				err := shard.PutObject(ctx, createOrigObj())
   560  				require.NoError(t, err)
   561  			})
   562  
   563  			t.Run("put object", func(t *testing.T) {
   564  				updObj := createUpdObj()
   565  
   566  				err := shard.PutObject(ctx, updObj)
   567  				require.NoError(t, err)
   568  			})
   569  
   570  			t.Run("verify same docID, changed create & update timestamps", func(t *testing.T) {
   571  				expectedNextDocID := uint64(1)
   572  				require.Equal(t, expectedNextDocID, shard.Counter().Get())
   573  
   574  				found := search(t, shard, filterId)
   575  				require.Len(t, found, 1)
   576  				require.Equal(t, updCreateTimeUnix, found[0].CreationTimeUnix())
   577  				require.Equal(t, updUpdateTimeUnix, found[0].LastUpdateTimeUnix())
   578  			})
   579  
   580  			t.Run("verify search after put", verifySearchAfterUpdate(shard))
   581  			t.Run("verify vector search after put", verifyVectorSearch(shard, vector, altVector))
   582  		})
   583  
   584  		t.Run("replace with different object, different vector", func(t *testing.T) {
   585  			shard := createShard(t)
   586  
   587  			t.Run("add object", func(t *testing.T) {
   588  				err := shard.PutObject(ctx, createOrigObj())
   589  				require.NoError(t, err)
   590  			})
   591  
   592  			t.Run("put object", func(t *testing.T) {
   593  				// overwrite vector in updated object
   594  				altUpdObj := createUpdObj()
   595  				altUpdObj.Vector = altVector
   596  
   597  				err := shard.PutObject(ctx, altUpdObj)
   598  				require.NoError(t, err)
   599  			})
   600  
   601  			t.Run("verify changed docID, changed create & update timestamps", func(t *testing.T) {
   602  				expectedNextDocID := uint64(2)
   603  				require.Equal(t, expectedNextDocID, shard.Counter().Get())
   604  
   605  				found := search(t, shard, filterId)
   606  				require.Len(t, found, 1)
   607  				require.Equal(t, updCreateTimeUnix, found[0].CreationTimeUnix())
   608  				require.Equal(t, updUpdateTimeUnix, found[0].LastUpdateTimeUnix())
   609  			})
   610  
   611  			t.Run("verify search after put", verifySearchAfterUpdate(shard))
   612  			t.Run("verify vector search after put", verifyVectorSearch(shard, altVector, vector))
   613  		})
   614  
   615  		t.Run("replace with different object, different geo", func(t *testing.T) {
   616  			shard := createShard(t)
   617  
   618  			t.Run("add object", func(t *testing.T) {
   619  				err := shard.PutObject(ctx, createOrigObj())
   620  				require.NoError(t, err)
   621  			})
   622  
   623  			t.Run("put object", func(t *testing.T) {
   624  				// overwrite geo in updated object
   625  				altUpdObj := createUpdObj()
   626  				altUpdObj.Object.Properties.(map[string]interface{})["geo"] = &models.GeoCoordinates{
   627  					Latitude:  ptFloat32(3.3),
   628  					Longitude: ptFloat32(4.4),
   629  				}
   630  
   631  				err := shard.PutObject(ctx, altUpdObj)
   632  				require.NoError(t, err)
   633  			})
   634  
   635  			t.Run("verify changed docID, changed create & update timestamps", func(t *testing.T) {
   636  				expectedNextDocID := uint64(2)
   637  				require.Equal(t, expectedNextDocID, shard.Counter().Get())
   638  
   639  				found := search(t, shard, filterId)
   640  				require.Len(t, found, 1)
   641  				require.Equal(t, updCreateTimeUnix, found[0].CreationTimeUnix())
   642  				require.Equal(t, updUpdateTimeUnix, found[0].LastUpdateTimeUnix())
   643  			})
   644  
   645  			t.Run("verify search after put", verifySearchAfterUpdate(shard))
   646  			t.Run("verify vector search after put", verifyVectorSearch(shard, vector, altVector))
   647  		})
   648  
   649  		t.Run("merge with different object, same vector", func(t *testing.T) {
   650  			shard := createShard(t)
   651  
   652  			t.Run("add object", func(t *testing.T) {
   653  				err := shard.PutObject(ctx, createOrigObj())
   654  				require.NoError(t, err)
   655  			})
   656  
   657  			t.Run("merge object", func(t *testing.T) {
   658  				mergeDoc := createMergeDoc()
   659  
   660  				err := shard.MergeObject(ctx, mergeDoc)
   661  				require.NoError(t, err)
   662  			})
   663  
   664  			t.Run("verify same docID, changed update timestamp", func(t *testing.T) {
   665  				expectedNextDocID := uint64(1)
   666  				require.Equal(t, expectedNextDocID, shard.Counter().Get())
   667  
   668  				found := search(t, shard, filterId)
   669  				require.Len(t, found, 1)
   670  				require.Equal(t, origCreateTimeUnix, found[0].CreationTimeUnix())
   671  				require.Equal(t, updUpdateTimeUnix, found[0].LastUpdateTimeUnix())
   672  			})
   673  
   674  			t.Run("verify search after merge", verifySearchAfterUpdate(shard))
   675  			t.Run("verify vector search after merge", verifyVectorSearch(shard, vector, altVector))
   676  		})
   677  
   678  		t.Run("merge with different object, different vector", func(t *testing.T) {
   679  			shard := createShard(t)
   680  
   681  			t.Run("add object", func(t *testing.T) {
   682  				err := shard.PutObject(ctx, createOrigObj())
   683  				require.NoError(t, err)
   684  			})
   685  
   686  			t.Run("merge object", func(t *testing.T) {
   687  				// overwrite vector in merge doc
   688  				altMergeDoc := createMergeDoc()
   689  				altMergeDoc.Vector = altVector
   690  
   691  				err := shard.MergeObject(ctx, altMergeDoc)
   692  				require.NoError(t, err)
   693  			})
   694  
   695  			t.Run("verify changed docID, changed update timestamp", func(t *testing.T) {
   696  				expectedNextDocID := uint64(2)
   697  				require.Equal(t, expectedNextDocID, shard.Counter().Get())
   698  
   699  				found := search(t, shard, filterId)
   700  				require.Len(t, found, 1)
   701  				require.Equal(t, origCreateTimeUnix, found[0].CreationTimeUnix())
   702  				require.Equal(t, updUpdateTimeUnix, found[0].LastUpdateTimeUnix())
   703  			})
   704  
   705  			t.Run("verify search after merge", verifySearchAfterUpdate(shard))
   706  			t.Run("verify vector search after merge", verifyVectorSearch(shard, altVector, vector))
   707  		})
   708  
   709  		t.Run("merge with different object, different geo", func(t *testing.T) {
   710  			shard := createShard(t)
   711  
   712  			t.Run("add object", func(t *testing.T) {
   713  				err := shard.PutObject(ctx, createOrigObj())
   714  				require.NoError(t, err)
   715  			})
   716  
   717  			t.Run("merge object", func(t *testing.T) {
   718  				// overwrite geo in merge doc
   719  				mergeDoc := createMergeDoc()
   720  				mergeDoc.PrimitiveSchema["geo"] = &models.GeoCoordinates{
   721  					Latitude:  ptFloat32(3.3),
   722  					Longitude: ptFloat32(4.4),
   723  				}
   724  
   725  				err := shard.MergeObject(ctx, mergeDoc)
   726  				require.NoError(t, err)
   727  			})
   728  
   729  			t.Run("verify changed docID, changed update timestamp", func(t *testing.T) {
   730  				expectedNextDocID := uint64(2)
   731  				require.Equal(t, expectedNextDocID, shard.Counter().Get())
   732  
   733  				found := search(t, shard, filterId)
   734  				require.Len(t, found, 1)
   735  				require.Equal(t, origCreateTimeUnix, found[0].CreationTimeUnix())
   736  				require.Equal(t, updUpdateTimeUnix, found[0].LastUpdateTimeUnix())
   737  			})
   738  
   739  			t.Run("verify search after merge", verifySearchAfterUpdate(shard))
   740  			t.Run("verify vector search after merge", verifyVectorSearch(shard, vector, altVector))
   741  		})
   742  
   743  		t.Run("replace with same object, same vector", func(t *testing.T) {
   744  			shard := createShard(t)
   745  
   746  			t.Run("add object", func(t *testing.T) {
   747  				err := shard.PutObject(ctx, createOrigObj())
   748  				require.NoError(t, err)
   749  			})
   750  
   751  			t.Run("put object", func(t *testing.T) {
   752  				// overwrite timestamps in original object
   753  				updObj := createOrigObj()
   754  				updObj.Object.CreationTimeUnix = updCreateTimeUnix
   755  				updObj.Object.LastUpdateTimeUnix = updUpdateTimeUnix
   756  
   757  				err := shard.PutObject(ctx, updObj)
   758  				require.NoError(t, err)
   759  			})
   760  
   761  			t.Run("verify same docID, same timestamps", func(t *testing.T) {
   762  				expectedNextDocID := uint64(1)
   763  				require.Equal(t, expectedNextDocID, shard.Counter().Get())
   764  
   765  				found := search(t, shard, filterId)
   766  				require.Len(t, found, 1)
   767  				require.Equal(t, origCreateTimeUnix, found[0].CreationTimeUnix())
   768  				require.Equal(t, origUpdateTimeUnix, found[0].LastUpdateTimeUnix())
   769  			})
   770  
   771  			t.Run("verify search after put same as add", verifySearchAfterAdd(shard))
   772  			t.Run("verify vector search after put", verifyVectorSearch(shard, vector, altVector))
   773  		})
   774  
   775  		t.Run("replace with same object, different vector", func(t *testing.T) {
   776  			shard := createShard(t)
   777  
   778  			t.Run("add object", func(t *testing.T) {
   779  				err := shard.PutObject(ctx, createOrigObj())
   780  				require.NoError(t, err)
   781  			})
   782  
   783  			t.Run("put object", func(t *testing.T) {
   784  				// overwrite timestamps and vector in original object
   785  				altUpdObj := createOrigObj()
   786  				altUpdObj.Object.CreationTimeUnix = updCreateTimeUnix
   787  				altUpdObj.Object.LastUpdateTimeUnix = updUpdateTimeUnix
   788  				altUpdObj.Vector = altVector
   789  
   790  				err := shard.PutObject(ctx, altUpdObj)
   791  				require.NoError(t, err)
   792  			})
   793  
   794  			t.Run("verify changed docID, changed create & update timestamps", func(t *testing.T) {
   795  				expectedNextDocID := uint64(2)
   796  				require.Equal(t, expectedNextDocID, shard.Counter().Get())
   797  
   798  				found := search(t, shard, filterId)
   799  				require.Len(t, found, 1)
   800  				require.Equal(t, updCreateTimeUnix, found[0].CreationTimeUnix())
   801  				require.Equal(t, updUpdateTimeUnix, found[0].LastUpdateTimeUnix())
   802  			})
   803  
   804  			t.Run("verify search after put same as add", verifySearchAfterAdd(shard))
   805  			t.Run("verify vector search after put", verifyVectorSearch(shard, altVector, vector))
   806  		})
   807  
   808  		t.Run("replace with same object, different geo", func(t *testing.T) {
   809  			shard := createShard(t)
   810  
   811  			t.Run("add object", func(t *testing.T) {
   812  				err := shard.PutObject(ctx, createOrigObj())
   813  				require.NoError(t, err)
   814  			})
   815  
   816  			t.Run("put object", func(t *testing.T) {
   817  				// overwrite timestamps and geo in original object
   818  				updObj := createOrigObj()
   819  				updObj.Object.CreationTimeUnix = updCreateTimeUnix
   820  				updObj.Object.LastUpdateTimeUnix = updUpdateTimeUnix
   821  				updObj.Object.Properties.(map[string]interface{})["geo"] = &models.GeoCoordinates{
   822  					Latitude:  ptFloat32(3.3),
   823  					Longitude: ptFloat32(4.4),
   824  				}
   825  
   826  				err := shard.PutObject(ctx, updObj)
   827  				require.NoError(t, err)
   828  			})
   829  
   830  			t.Run("verify changed docID, changed create & update timestamps", func(t *testing.T) {
   831  				expectedNextDocID := uint64(2)
   832  				require.Equal(t, expectedNextDocID, shard.Counter().Get())
   833  
   834  				found := search(t, shard, filterId)
   835  				require.Len(t, found, 1)
   836  				require.Equal(t, updCreateTimeUnix, found[0].CreationTimeUnix())
   837  				require.Equal(t, updUpdateTimeUnix, found[0].LastUpdateTimeUnix())
   838  			})
   839  
   840  			t.Run("verify search after put same as add", verifySearchAfterAdd(shard))
   841  			t.Run("verify vector search after put", verifyVectorSearch(shard, vector, altVector))
   842  		})
   843  
   844  		t.Run("merge with same object, same vector", func(t *testing.T) {
   845  			shard := createShard(t)
   846  
   847  			t.Run("add object", func(t *testing.T) {
   848  				err := shard.PutObject(ctx, createOrigObj())
   849  				require.NoError(t, err)
   850  			})
   851  
   852  			t.Run("merge object", func(t *testing.T) {
   853  				// same values as in original object
   854  				mergeDoc := objects.MergeDocument{
   855  					ID:    uuid_,
   856  					Class: class.Class,
   857  					PrimitiveSchema: map[string]interface{}{
   858  						"int":  float64(201),
   859  						"text": "ddd",
   860  					},
   861  					UpdateTime: updUpdateTimeUnix,
   862  					Vector:     vector,
   863  				}
   864  
   865  				err := shard.MergeObject(ctx, mergeDoc)
   866  				require.NoError(t, err)
   867  			})
   868  
   869  			t.Run("verify same docID, same timestamps", func(t *testing.T) {
   870  				expectedNextDocID := uint64(1)
   871  				require.Equal(t, expectedNextDocID, shard.Counter().Get())
   872  
   873  				found := search(t, shard, filterId)
   874  				require.Len(t, found, 1)
   875  				require.Equal(t, origCreateTimeUnix, found[0].CreationTimeUnix())
   876  				require.Equal(t, origUpdateTimeUnix, found[0].LastUpdateTimeUnix())
   877  			})
   878  
   879  			t.Run("verify search after merge same as add", verifySearchAfterAdd(shard))
   880  			t.Run("verify vector search after merge", verifyVectorSearch(shard, vector, altVector))
   881  		})
   882  
   883  		t.Run("merge with same object, different vector", func(t *testing.T) {
   884  			shard := createShard(t)
   885  
   886  			t.Run("add object", func(t *testing.T) {
   887  				err := shard.PutObject(ctx, createOrigObj())
   888  				require.NoError(t, err)
   889  			})
   890  
   891  			t.Run("merge object", func(t *testing.T) {
   892  				// same props as in original object, overwrite timestamp and vector
   893  				altMergeDoc := objects.MergeDocument{
   894  					ID:    uuid_,
   895  					Class: class.Class,
   896  					PrimitiveSchema: map[string]interface{}{
   897  						"int":  float64(201),
   898  						"text": "ddd",
   899  					},
   900  					UpdateTime: updUpdateTimeUnix,
   901  					Vector:     altVector,
   902  				}
   903  
   904  				err := shard.MergeObject(ctx, altMergeDoc)
   905  				require.NoError(t, err)
   906  			})
   907  
   908  			t.Run("verify changed docID, changed update timestamp", func(t *testing.T) {
   909  				expectedNextDocID := uint64(2)
   910  				require.Equal(t, expectedNextDocID, shard.Counter().Get())
   911  
   912  				found := search(t, shard, filterId)
   913  				require.Len(t, found, 1)
   914  				require.Equal(t, origCreateTimeUnix, found[0].CreationTimeUnix())
   915  				require.Equal(t, updUpdateTimeUnix, found[0].LastUpdateTimeUnix())
   916  			})
   917  
   918  			t.Run("verify search after merge same as add", verifySearchAfterAdd(shard))
   919  			t.Run("verify vector search after merge", verifyVectorSearch(shard, altVector, vector))
   920  		})
   921  
   922  		t.Run("merge with same object, different geo", func(t *testing.T) {
   923  			shard := createShard(t)
   924  
   925  			t.Run("add object", func(t *testing.T) {
   926  				err := shard.PutObject(ctx, createOrigObj())
   927  				require.NoError(t, err)
   928  			})
   929  
   930  			t.Run("merge object", func(t *testing.T) {
   931  				// overwrite geo and timestamp
   932  				mergeDoc := objects.MergeDocument{
   933  					ID:    uuid_,
   934  					Class: class.Class,
   935  					PrimitiveSchema: map[string]interface{}{
   936  						"geo": &models.GeoCoordinates{
   937  							Latitude:  ptFloat32(3.3),
   938  							Longitude: ptFloat32(4.4),
   939  						},
   940  					},
   941  					UpdateTime: updUpdateTimeUnix,
   942  				}
   943  
   944  				err := shard.MergeObject(ctx, mergeDoc)
   945  				require.NoError(t, err)
   946  			})
   947  
   948  			t.Run("verify changed docID, changed update timestamp", func(t *testing.T) {
   949  				expectedNextDocID := uint64(2)
   950  				require.Equal(t, expectedNextDocID, shard.Counter().Get())
   951  
   952  				found := search(t, shard, filterId)
   953  				require.Len(t, found, 1)
   954  				require.Equal(t, origCreateTimeUnix, found[0].CreationTimeUnix())
   955  				require.Equal(t, updUpdateTimeUnix, found[0].LastUpdateTimeUnix())
   956  			})
   957  
   958  			t.Run("verify search after merge same as add", verifySearchAfterAdd(shard))
   959  			t.Run("verify vector search after merge", verifyVectorSearch(shard, vector, altVector))
   960  		})
   961  	})
   962  
   963  	t.Run("batch", func(t *testing.T) {
   964  		runBatch := func(t *testing.T) {
   965  			t.Run("sanity check - search after add", func(t *testing.T) {
   966  				shard := createShard(t)
   967  
   968  				t.Run("add batch", func(t *testing.T) {
   969  					errs := shard.PutObjectBatch(ctx, []*storobj.Object{createOrigObj()})
   970  					for i := range errs {
   971  						require.NoError(t, errs[i])
   972  					}
   973  				})
   974  
   975  				t.Run("verify initial docID and timestamps", func(t *testing.T) {
   976  					expectedNextDocID := uint64(1)
   977  					require.Equal(t, expectedNextDocID, shard.Counter().Get())
   978  
   979  					found := search(t, shard, filterId)
   980  					require.Len(t, found, 1)
   981  					require.Equal(t, origCreateTimeUnix, found[0].CreationTimeUnix())
   982  					require.Equal(t, origUpdateTimeUnix, found[0].LastUpdateTimeUnix())
   983  				})
   984  
   985  				t.Run("verify search after batch", verifySearchAfterAdd(shard))
   986  				t.Run("verify vector search after batch", verifyVectorSearch(shard, vector, altVector))
   987  			})
   988  
   989  			t.Run("replace with different object, same vector", func(t *testing.T) {
   990  				shard := createShard(t)
   991  
   992  				t.Run("add batch", func(t *testing.T) {
   993  					errs := shard.PutObjectBatch(ctx, []*storobj.Object{createOrigObj()})
   994  					for i := range errs {
   995  						require.NoError(t, errs[i])
   996  					}
   997  				})
   998  
   999  				t.Run("add 2nd batch", func(t *testing.T) {
  1000  					updObj := createUpdObj()
  1001  
  1002  					errs := shard.PutObjectBatch(ctx, []*storobj.Object{updObj})
  1003  					for i := range errs {
  1004  						require.NoError(t, errs[i])
  1005  					}
  1006  				})
  1007  
  1008  				t.Run("verify same docID, changed create & update timestamps", func(t *testing.T) {
  1009  					expectedNextDocID := uint64(1)
  1010  					require.Equal(t, expectedNextDocID, shard.Counter().Get())
  1011  
  1012  					found := search(t, shard, filterId)
  1013  					require.Len(t, found, 1)
  1014  					require.Equal(t, updCreateTimeUnix, found[0].CreationTimeUnix())
  1015  					require.Equal(t, updUpdateTimeUnix, found[0].LastUpdateTimeUnix())
  1016  				})
  1017  
  1018  				t.Run("verify search after 2nd batch", verifySearchAfterUpdate(shard))
  1019  				t.Run("verify vector search after 2nd batch", verifyVectorSearch(shard, vector, altVector))
  1020  			})
  1021  
  1022  			t.Run("replace with different object, different vector", func(t *testing.T) {
  1023  				shard := createShard(t)
  1024  
  1025  				t.Run("add batch", func(t *testing.T) {
  1026  					errs := shard.PutObjectBatch(ctx, []*storobj.Object{createOrigObj()})
  1027  					for i := range errs {
  1028  						require.NoError(t, errs[i])
  1029  					}
  1030  				})
  1031  
  1032  				t.Run("add 2nd batch", func(t *testing.T) {
  1033  					// overwrite vector in updated object
  1034  					altUpdObj := createUpdObj()
  1035  					altUpdObj.Vector = altVector
  1036  
  1037  					errs := shard.PutObjectBatch(ctx, []*storobj.Object{altUpdObj})
  1038  					for i := range errs {
  1039  						require.NoError(t, errs[i])
  1040  					}
  1041  				})
  1042  
  1043  				t.Run("verify changed docID, changed create & update timestamps", func(t *testing.T) {
  1044  					expectedNextDocID := uint64(2)
  1045  					require.Equal(t, expectedNextDocID, shard.Counter().Get())
  1046  
  1047  					found := search(t, shard, filterId)
  1048  					require.Len(t, found, 1)
  1049  					require.Equal(t, updCreateTimeUnix, found[0].CreationTimeUnix())
  1050  					require.Equal(t, updUpdateTimeUnix, found[0].LastUpdateTimeUnix())
  1051  				})
  1052  
  1053  				t.Run("verify search after 2nd batch", verifySearchAfterUpdate(shard))
  1054  				t.Run("verify vector search after 2nd batch", verifyVectorSearch(shard, altVector, vector))
  1055  			})
  1056  
  1057  			t.Run("replace with different object, different geo", func(t *testing.T) {
  1058  				shard := createShard(t)
  1059  
  1060  				t.Run("add batch", func(t *testing.T) {
  1061  					errs := shard.PutObjectBatch(ctx, []*storobj.Object{createOrigObj()})
  1062  					for i := range errs {
  1063  						require.NoError(t, errs[i])
  1064  					}
  1065  				})
  1066  
  1067  				t.Run("add 2nd batch", func(t *testing.T) {
  1068  					// overwrite geo in updated object
  1069  					altUpdObj := createUpdObj()
  1070  					altUpdObj.Object.Properties.(map[string]interface{})["geo"] = &models.GeoCoordinates{
  1071  						Latitude:  ptFloat32(3.3),
  1072  						Longitude: ptFloat32(4.4),
  1073  					}
  1074  
  1075  					errs := shard.PutObjectBatch(ctx, []*storobj.Object{altUpdObj})
  1076  					for i := range errs {
  1077  						require.NoError(t, errs[i])
  1078  					}
  1079  				})
  1080  
  1081  				t.Run("verify changed docID, changed create & update timestamps", func(t *testing.T) {
  1082  					expectedNextDocID := uint64(2)
  1083  					require.Equal(t, expectedNextDocID, shard.Counter().Get())
  1084  
  1085  					found := search(t, shard, filterId)
  1086  					require.Len(t, found, 1)
  1087  					require.Equal(t, updCreateTimeUnix, found[0].CreationTimeUnix())
  1088  					require.Equal(t, updUpdateTimeUnix, found[0].LastUpdateTimeUnix())
  1089  				})
  1090  
  1091  				t.Run("verify search after 2nd batch", verifySearchAfterUpdate(shard))
  1092  				t.Run("verify vector search after 2nd batch", verifyVectorSearch(shard, vector, altVector))
  1093  			})
  1094  
  1095  			t.Run("replace with same object, same vector", func(t *testing.T) {
  1096  				shard := createShard(t)
  1097  
  1098  				t.Run("add batch", func(t *testing.T) {
  1099  					errs := shard.PutObjectBatch(ctx, []*storobj.Object{createOrigObj()})
  1100  					for i := range errs {
  1101  						require.NoError(t, errs[i])
  1102  					}
  1103  				})
  1104  
  1105  				t.Run("add 2nd batch", func(t *testing.T) {
  1106  					// overwrite timestamps in original object
  1107  					updObj := createOrigObj()
  1108  					updObj.Object.CreationTimeUnix = updCreateTimeUnix
  1109  					updObj.Object.LastUpdateTimeUnix = updUpdateTimeUnix
  1110  
  1111  					errs := shard.PutObjectBatch(ctx, []*storobj.Object{updObj})
  1112  					for i := range errs {
  1113  						require.NoError(t, errs[i])
  1114  					}
  1115  				})
  1116  
  1117  				t.Run("verify same docID, same timestamps", func(t *testing.T) {
  1118  					expectedNextDocID := uint64(1)
  1119  					require.Equal(t, expectedNextDocID, shard.Counter().Get())
  1120  
  1121  					found := search(t, shard, filterId)
  1122  					require.Len(t, found, 1)
  1123  					require.Equal(t, origCreateTimeUnix, found[0].CreationTimeUnix())
  1124  					require.Equal(t, origUpdateTimeUnix, found[0].LastUpdateTimeUnix())
  1125  				})
  1126  
  1127  				t.Run("verify search after 2nd batch same as 1st", verifySearchAfterAdd(shard))
  1128  				t.Run("verify vector search after 2nd batch", verifyVectorSearch(shard, vector, altVector))
  1129  			})
  1130  
  1131  			t.Run("replace with same object, different vector", func(t *testing.T) {
  1132  				shard := createShard(t)
  1133  
  1134  				t.Run("add batch", func(t *testing.T) {
  1135  					errs := shard.PutObjectBatch(ctx, []*storobj.Object{createOrigObj()})
  1136  					for i := range errs {
  1137  						require.NoError(t, errs[i])
  1138  					}
  1139  				})
  1140  
  1141  				t.Run("add 2nd batch", func(t *testing.T) {
  1142  					// overwrite timestamps and vector in original object
  1143  					altUpdObj := createOrigObj()
  1144  					altUpdObj.Object.CreationTimeUnix = updCreateTimeUnix
  1145  					altUpdObj.Object.LastUpdateTimeUnix = updUpdateTimeUnix
  1146  					altUpdObj.Vector = altVector
  1147  
  1148  					errs := shard.PutObjectBatch(ctx, []*storobj.Object{altUpdObj})
  1149  					for i := range errs {
  1150  						require.NoError(t, errs[i])
  1151  					}
  1152  				})
  1153  
  1154  				t.Run("verify changed docID, changed create & update timestamps", func(t *testing.T) {
  1155  					expectedNextDocID := uint64(2)
  1156  					require.Equal(t, expectedNextDocID, shard.Counter().Get())
  1157  
  1158  					found := search(t, shard, filterId)
  1159  					require.Len(t, found, 1)
  1160  					require.Equal(t, updCreateTimeUnix, found[0].CreationTimeUnix())
  1161  					require.Equal(t, updUpdateTimeUnix, found[0].LastUpdateTimeUnix())
  1162  				})
  1163  
  1164  				t.Run("verify search after 2nd batch same as 1st", verifySearchAfterAdd(shard))
  1165  				t.Run("verify vector search after 2nd batch", verifyVectorSearch(shard, altVector, vector))
  1166  			})
  1167  
  1168  			t.Run("replace with same object, different geo", func(t *testing.T) {
  1169  				shard := createShard(t)
  1170  
  1171  				t.Run("add batch", func(t *testing.T) {
  1172  					errs := shard.PutObjectBatch(ctx, []*storobj.Object{createOrigObj()})
  1173  					for i := range errs {
  1174  						require.NoError(t, errs[i])
  1175  					}
  1176  				})
  1177  
  1178  				t.Run("add 2nd batch", func(t *testing.T) {
  1179  					// overwrite geo and timestamp
  1180  					updObj := createOrigObj()
  1181  					updObj.Object.CreationTimeUnix = updCreateTimeUnix
  1182  					updObj.Object.LastUpdateTimeUnix = updUpdateTimeUnix
  1183  					updObj.Object.Properties.(map[string]interface{})["geo"] = &models.GeoCoordinates{
  1184  						Latitude:  ptFloat32(3.3),
  1185  						Longitude: ptFloat32(4.4),
  1186  					}
  1187  
  1188  					errs := shard.PutObjectBatch(ctx, []*storobj.Object{updObj})
  1189  					for i := range errs {
  1190  						require.NoError(t, errs[i])
  1191  					}
  1192  				})
  1193  
  1194  				t.Run("verify changed docID, changed create & update timestamps", func(t *testing.T) {
  1195  					expectedNextDocID := uint64(2)
  1196  					require.Equal(t, expectedNextDocID, shard.Counter().Get())
  1197  
  1198  					found := search(t, shard, filterId)
  1199  					require.Len(t, found, 1)
  1200  					require.Equal(t, updCreateTimeUnix, found[0].CreationTimeUnix())
  1201  					require.Equal(t, updUpdateTimeUnix, found[0].LastUpdateTimeUnix())
  1202  				})
  1203  
  1204  				t.Run("verify search after 2nd batch same as 1st", verifySearchAfterAdd(shard))
  1205  				t.Run("verify vector search after 2nd batch", verifyVectorSearch(shard, vector, altVector))
  1206  			})
  1207  		}
  1208  
  1209  		t.Run("sync", func(t *testing.T) {
  1210  			currentIndexing := os.Getenv("ASYNC_INDEXING")
  1211  			t.Setenv("ASYNC_INDEXING", "")
  1212  			defer t.Setenv("ASYNC_INDEXING", currentIndexing)
  1213  
  1214  			runBatch(t)
  1215  		})
  1216  
  1217  		t.Run("async", func(t *testing.T) {
  1218  			currentIndexing := os.Getenv("ASYNC_INDEXING")
  1219  			t.Setenv("ASYNC_INDEXING", "true")
  1220  			defer t.Setenv("ASYNC_INDEXING", currentIndexing)
  1221  
  1222  			runBatch(t)
  1223  		})
  1224  	})
  1225  }
  1226  
  1227  func filterEqual[T any](value T, dataType schema.DataType, className, propName string) *filters.LocalFilter {
  1228  	return &filters.LocalFilter{
  1229  		Root: &filters.Clause{
  1230  			Operator: filters.OperatorEqual,
  1231  			Value: &filters.Value{
  1232  				Value: value,
  1233  				Type:  dataType,
  1234  			},
  1235  			On: &filters.Path{
  1236  				Class:    schema.ClassName(className),
  1237  				Property: schema.PropertyName(propName),
  1238  			},
  1239  		},
  1240  	}
  1241  }
  1242  
  1243  func filterNil(value bool, className, propName string) *filters.LocalFilter {
  1244  	return &filters.LocalFilter{
  1245  		Root: &filters.Clause{
  1246  			Operator: filters.OperatorIsNull,
  1247  			Value: &filters.Value{
  1248  				Value: value,
  1249  				Type:  schema.DataTypeBoolean,
  1250  			},
  1251  			On: &filters.Path{
  1252  				Class:    schema.ClassName(className),
  1253  				Property: schema.PropertyName(propName),
  1254  			},
  1255  		},
  1256  	}
  1257  }