github.com/weaviate/weaviate@v1.24.6/adapters/repos/db/inverted/objects_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  package inverted
    13  
    14  import (
    15  	"fmt"
    16  	"reflect"
    17  	"testing"
    18  	"time"
    19  
    20  	"github.com/go-openapi/strfmt"
    21  	"github.com/google/uuid"
    22  	"github.com/stretchr/testify/assert"
    23  	"github.com/stretchr/testify/require"
    24  	"github.com/weaviate/weaviate/adapters/repos/db/helpers"
    25  	"github.com/weaviate/weaviate/entities/models"
    26  	"github.com/weaviate/weaviate/entities/schema"
    27  )
    28  
    29  func TestAnalyzeObject(t *testing.T) {
    30  	a := NewAnalyzer(nil)
    31  
    32  	t.Run("with multiple properties", func(t *testing.T) {
    33  		id1 := uuid.New()
    34  		id2 := uuid.New()
    35  		sch := map[string]interface{}{
    36  			"description": "I am great!",
    37  			"email":       "john@doe.com",
    38  			"about_me":    "I like reading sci-fi books",
    39  			"profession":  "Mechanical Engineer",
    40  			"id1":         id1,                 // correctly parsed
    41  			"id2":         id2.String(),        // untyped
    42  			"idArray1":    []uuid.UUID{id1},    // correctly parsed
    43  			"idArray2":    []any{id2.String()}, // untyped
    44  		}
    45  
    46  		uuid := "2609f1bc-7693-48f3-b531-6ddc52cd2501"
    47  		props := []*models.Property{
    48  			{
    49  				Name:         "description",
    50  				DataType:     schema.DataTypeText.PropString(),
    51  				Tokenization: models.PropertyTokenizationWord,
    52  			},
    53  			{
    54  				Name:         "email",
    55  				DataType:     schema.DataTypeText.PropString(),
    56  				Tokenization: models.PropertyTokenizationWhitespace,
    57  			},
    58  			{
    59  				Name:         "about_me",
    60  				DataType:     schema.DataTypeText.PropString(),
    61  				Tokenization: models.PropertyTokenizationLowercase,
    62  			},
    63  			{
    64  				Name:         "profession",
    65  				DataType:     schema.DataTypeText.PropString(),
    66  				Tokenization: models.PropertyTokenizationField,
    67  			},
    68  			{
    69  				Name:     "id1",
    70  				DataType: []string{"uuid"},
    71  			},
    72  			{
    73  				Name:     "id2",
    74  				DataType: []string{"uuid"},
    75  			},
    76  			{
    77  				Name:     "idArray1",
    78  				DataType: []string{"uuid[]"},
    79  			},
    80  			{
    81  				Name:     "idArray2",
    82  				DataType: []string{"uuid[]"},
    83  			},
    84  		}
    85  		res, err := a.Object(sch, props, strfmt.UUID(uuid))
    86  		require.Nil(t, err)
    87  
    88  		expectedDescription := []Countable{
    89  			{
    90  				Data:          []byte("i"),
    91  				TermFrequency: float32(1),
    92  			},
    93  			{
    94  				Data:          []byte("am"),
    95  				TermFrequency: float32(1),
    96  			},
    97  			{
    98  				Data:          []byte("great"),
    99  				TermFrequency: float32(1),
   100  			},
   101  		}
   102  
   103  		expectedEmail := []Countable{
   104  			{
   105  				Data:          []byte("john@doe.com"),
   106  				TermFrequency: float32(1),
   107  			},
   108  		}
   109  
   110  		expectedAboutMe := []Countable{
   111  			{
   112  				Data:          []byte("i"),
   113  				TermFrequency: float32(1),
   114  			},
   115  			{
   116  				Data:          []byte("like"),
   117  				TermFrequency: float32(1),
   118  			},
   119  			{
   120  				Data:          []byte("reading"),
   121  				TermFrequency: float32(1),
   122  			},
   123  			{
   124  				Data:          []byte("sci-fi"),
   125  				TermFrequency: float32(1),
   126  			},
   127  			{
   128  				Data:          []byte("books"),
   129  				TermFrequency: float32(1),
   130  			},
   131  		}
   132  
   133  		expectedProfession := []Countable{
   134  			{
   135  				Data:          []byte("Mechanical Engineer"),
   136  				TermFrequency: float32(1),
   137  			},
   138  		}
   139  
   140  		expectedUUID := []Countable{
   141  			{
   142  				Data:          []byte(uuid),
   143  				TermFrequency: 0,
   144  			},
   145  		}
   146  
   147  		expectedID1 := []Countable{
   148  			{
   149  				Data:          []byte(id1[:]),
   150  				TermFrequency: 0,
   151  			},
   152  		}
   153  
   154  		expectedID2 := []Countable{
   155  			{
   156  				Data:          []byte(id2[:]),
   157  				TermFrequency: 0,
   158  			},
   159  		}
   160  
   161  		expectedIDArray1 := []Countable{
   162  			{
   163  				Data:          []byte(id1[:]),
   164  				TermFrequency: 0,
   165  			},
   166  		}
   167  
   168  		expectedIDArray2 := []Countable{
   169  			{
   170  				Data:          []byte(id2[:]),
   171  				TermFrequency: 0,
   172  			},
   173  		}
   174  
   175  		require.Len(t, res, 9)
   176  		var actualDescription []Countable
   177  		var actualEmail []Countable
   178  		var actualAboutMe []Countable
   179  		var actualProfession []Countable
   180  		var actualUUID []Countable
   181  		var actualID1 []Countable
   182  		var actualID2 []Countable
   183  		var actualIDArray1 []Countable
   184  		var actualIDArray2 []Countable
   185  
   186  		for _, elem := range res {
   187  			if elem.Name == "email" {
   188  				actualEmail = elem.Items
   189  			}
   190  
   191  			if elem.Name == "description" {
   192  				actualDescription = elem.Items
   193  			}
   194  
   195  			if elem.Name == "about_me" {
   196  				actualAboutMe = elem.Items
   197  			}
   198  
   199  			if elem.Name == "profession" {
   200  				actualProfession = elem.Items
   201  			}
   202  
   203  			if elem.Name == "_id" {
   204  				actualUUID = elem.Items
   205  			}
   206  
   207  			if elem.Name == "id1" {
   208  				actualID1 = elem.Items
   209  			}
   210  
   211  			if elem.Name == "id2" {
   212  				actualID2 = elem.Items
   213  			}
   214  
   215  			if elem.Name == "idArray1" {
   216  				actualIDArray1 = elem.Items
   217  			}
   218  
   219  			if elem.Name == "idArray2" {
   220  				actualIDArray2 = elem.Items
   221  			}
   222  		}
   223  
   224  		assert.ElementsMatch(t, expectedEmail, actualEmail, res)
   225  		assert.ElementsMatch(t, expectedDescription, actualDescription, res)
   226  		assert.ElementsMatch(t, expectedAboutMe, actualAboutMe, res)
   227  		assert.ElementsMatch(t, expectedProfession, actualProfession, res)
   228  		assert.ElementsMatch(t, expectedUUID, actualUUID, res)
   229  		assert.ElementsMatch(t, expectedID1, actualID1, res)
   230  		assert.ElementsMatch(t, expectedID2, actualID2, res)
   231  		assert.ElementsMatch(t, expectedIDArray1, actualIDArray1, res)
   232  		assert.ElementsMatch(t, expectedIDArray2, actualIDArray2, res)
   233  	})
   234  
   235  	t.Run("with array properties", func(t *testing.T) {
   236  		sch := map[string]interface{}{
   237  			"descriptions": []interface{}{"I am great!", "I am also great!"},
   238  			"emails":       []interface{}{"john@doe.com", "john2@doe.com"},
   239  			"about_me":     []interface{}{"I like reading sci-fi books", "I like playing piano"},
   240  			"professions":  []interface{}{"Mechanical Engineer", "Marketing Analyst"},
   241  			"integers":     []interface{}{int64(1), int64(2), int64(3), int64(4)},
   242  			"numbers":      []interface{}{float64(1.1), float64(2.2), float64(3.0), float64(4)},
   243  		}
   244  
   245  		uuid := "2609f1bc-7693-48f3-b531-6ddc52cd2501"
   246  		props := []*models.Property{
   247  			{
   248  				Name:         "descriptions",
   249  				DataType:     schema.DataTypeTextArray.PropString(),
   250  				Tokenization: models.PropertyTokenizationWord,
   251  			},
   252  			{
   253  				Name:         "emails",
   254  				DataType:     schema.DataTypeTextArray.PropString(),
   255  				Tokenization: models.PropertyTokenizationWhitespace,
   256  			},
   257  			{
   258  				Name:         "about_me",
   259  				DataType:     schema.DataTypeTextArray.PropString(),
   260  				Tokenization: models.PropertyTokenizationLowercase,
   261  			},
   262  			{
   263  				Name:         "professions",
   264  				DataType:     schema.DataTypeTextArray.PropString(),
   265  				Tokenization: models.PropertyTokenizationField,
   266  			},
   267  			{
   268  				Name:     "integers",
   269  				DataType: []string{"int[]"},
   270  			},
   271  			{
   272  				Name:     "numbers",
   273  				DataType: []string{"number[]"},
   274  			},
   275  		}
   276  		res, err := a.Object(sch, props, strfmt.UUID(uuid))
   277  		require.Nil(t, err)
   278  
   279  		expectedDescriptions := []Countable{
   280  			{
   281  				Data:          []byte("i"),
   282  				TermFrequency: float32(2),
   283  			},
   284  			{
   285  				Data:          []byte("am"),
   286  				TermFrequency: float32(2),
   287  			},
   288  			{
   289  				Data:          []byte("great"),
   290  				TermFrequency: float32(2),
   291  			},
   292  			{
   293  				Data:          []byte("also"),
   294  				TermFrequency: float32(1),
   295  			},
   296  		}
   297  
   298  		expectedEmails := []Countable{
   299  			{
   300  				Data:          []byte("john@doe.com"),
   301  				TermFrequency: float32(1),
   302  			},
   303  			{
   304  				Data:          []byte("john2@doe.com"),
   305  				TermFrequency: float32(1),
   306  			},
   307  		}
   308  
   309  		expectedAboutMe := []Countable{
   310  			{
   311  				Data:          []byte("i"),
   312  				TermFrequency: float32(2),
   313  			},
   314  			{
   315  				Data:          []byte("like"),
   316  				TermFrequency: float32(2),
   317  			},
   318  			{
   319  				Data:          []byte("reading"),
   320  				TermFrequency: float32(1),
   321  			},
   322  			{
   323  				Data:          []byte("sci-fi"),
   324  				TermFrequency: float32(1),
   325  			},
   326  			{
   327  				Data:          []byte("books"),
   328  				TermFrequency: float32(1),
   329  			},
   330  			{
   331  				Data:          []byte("playing"),
   332  				TermFrequency: float32(1),
   333  			},
   334  			{
   335  				Data:          []byte("piano"),
   336  				TermFrequency: float32(1),
   337  			},
   338  		}
   339  
   340  		expectedProfessions := []Countable{
   341  			{
   342  				Data:          []byte("Mechanical Engineer"),
   343  				TermFrequency: float32(1),
   344  			},
   345  			{
   346  				Data:          []byte("Marketing Analyst"),
   347  				TermFrequency: float32(1),
   348  			},
   349  		}
   350  
   351  		expectedIntegers := []Countable{
   352  			{
   353  				Data: mustGetByteIntNumber(1),
   354  			},
   355  			{
   356  				Data: mustGetByteIntNumber(2),
   357  			},
   358  			{
   359  				Data: mustGetByteIntNumber(3),
   360  			},
   361  			{
   362  				Data: mustGetByteIntNumber(4),
   363  			},
   364  		}
   365  
   366  		expectedNumbers := []Countable{
   367  			{
   368  				Data: mustGetByteFloatNumber(1.1),
   369  			},
   370  			{
   371  				Data: mustGetByteFloatNumber(2.2),
   372  			},
   373  			{
   374  				Data: mustGetByteFloatNumber(3.0),
   375  			},
   376  			{
   377  				Data: mustGetByteFloatNumber(4),
   378  			},
   379  		}
   380  
   381  		expectedUUID := []Countable{
   382  			{
   383  				Data:          []byte(uuid),
   384  				TermFrequency: 0,
   385  			},
   386  		}
   387  
   388  		assert.Len(t, res, 7)
   389  		var actualDescriptions []Countable
   390  		var actualEmails []Countable
   391  		var actualAboutMe []Countable
   392  		var actualProfessions []Countable
   393  		var actualIntegers []Countable
   394  		var actualNumbers []Countable
   395  		var actualUUID []Countable
   396  
   397  		for _, elem := range res {
   398  			if elem.Name == "emails" {
   399  				actualEmails = elem.Items
   400  			}
   401  
   402  			if elem.Name == "descriptions" {
   403  				actualDescriptions = elem.Items
   404  			}
   405  
   406  			if elem.Name == "about_me" {
   407  				actualAboutMe = elem.Items
   408  			}
   409  
   410  			if elem.Name == "professions" {
   411  				actualProfessions = elem.Items
   412  			}
   413  
   414  			if elem.Name == "integers" {
   415  				actualIntegers = elem.Items
   416  			}
   417  
   418  			if elem.Name == "numbers" {
   419  				actualNumbers = elem.Items
   420  			}
   421  
   422  			if elem.Name == "_id" {
   423  				actualUUID = elem.Items
   424  			}
   425  		}
   426  
   427  		assert.ElementsMatch(t, expectedEmails, actualEmails, res)
   428  		assert.ElementsMatch(t, expectedDescriptions, actualDescriptions, res)
   429  		assert.ElementsMatch(t, expectedAboutMe, actualAboutMe, res)
   430  		assert.ElementsMatch(t, expectedProfessions, actualProfessions, res)
   431  		assert.ElementsMatch(t, expectedIntegers, actualIntegers, res)
   432  		assert.ElementsMatch(t, expectedNumbers, actualNumbers, res)
   433  		assert.ElementsMatch(t, expectedUUID, actualUUID, res)
   434  	})
   435  
   436  	t.Run("with refProps", func(t *testing.T) {
   437  		t.Run("with a single ref set in the object schema", func(t *testing.T) {
   438  			beacon := strfmt.URI(
   439  				"weaviate://localhost/c563d7fa-4a36-4eff-9f39-af1e1db276c4")
   440  			schema := map[string]interface{}{
   441  				"myRef": models.MultipleRef{
   442  					&models.SingleRef{
   443  						Beacon: beacon,
   444  					},
   445  				},
   446  			}
   447  
   448  			uuid := "2609f1bc-7693-48f3-b531-6ddc52cd2501"
   449  			props := []*models.Property{
   450  				{
   451  					Name:     "myRef",
   452  					DataType: []string{"RefClass"},
   453  				},
   454  			}
   455  			res, err := a.Object(schema, props, strfmt.UUID(uuid))
   456  			require.Nil(t, err)
   457  
   458  			expectedRefCount := []Countable{
   459  				{Data: []uint8{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}},
   460  			}
   461  
   462  			expectedRef := []Countable{
   463  				{Data: []byte(beacon)},
   464  			}
   465  
   466  			expectedUUID := []Countable{
   467  				{
   468  					Data:          []byte(uuid),
   469  					TermFrequency: 0,
   470  				},
   471  			}
   472  
   473  			require.Len(t, res, 3)
   474  			var actualRefCount []Countable
   475  			var actualUUID []Countable
   476  			var actualRef []Countable
   477  
   478  			for _, elem := range res {
   479  				switch elem.Name {
   480  				case helpers.MetaCountProp("myRef"):
   481  					actualRefCount = elem.Items
   482  				case "_id":
   483  					actualUUID = elem.Items
   484  				case "myRef":
   485  					actualRef = elem.Items
   486  				}
   487  			}
   488  
   489  			assert.ElementsMatch(t, expectedRefCount, actualRefCount, res)
   490  			assert.ElementsMatch(t, expectedUUID, actualUUID, res)
   491  			assert.ElementsMatch(t, expectedRef, actualRef, res)
   492  		})
   493  
   494  		t.Run("with multiple refs set in the object schema", func(t *testing.T) {
   495  			beacon1 := strfmt.URI(
   496  				"weaviate://localhost/c563d7fa-4a36-4eff-9f39-af1e1db276c4")
   497  			beacon2 := strfmt.URI(
   498  				"weaviate://localhost/49fe5d33-0b52-4189-8e8d-4268427c4317")
   499  
   500  			schema := map[string]interface{}{
   501  				"myRef": models.MultipleRef{
   502  					{Beacon: beacon1},
   503  					{Beacon: beacon2},
   504  				},
   505  			}
   506  
   507  			uuid := "2609f1bc-7693-48f3-b531-6ddc52cd2501"
   508  			props := []*models.Property{
   509  				{
   510  					Name:     "myRef",
   511  					DataType: []string{"RefClass"},
   512  				},
   513  			}
   514  			res, err := a.Object(schema, props, strfmt.UUID(uuid))
   515  			require.Nil(t, err)
   516  
   517  			expectedRefCount := []Countable{
   518  				{Data: []uint8{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02}},
   519  			}
   520  
   521  			expectedRef := []Countable{
   522  				{Data: []byte(beacon1)},
   523  				{Data: []byte(beacon2)},
   524  			}
   525  
   526  			expectedUUID := []Countable{
   527  				{
   528  					Data:          []byte(uuid),
   529  					TermFrequency: 0,
   530  				},
   531  			}
   532  
   533  			require.Len(t, res, 3)
   534  			var actualRefCount []Countable
   535  			var actualUUID []Countable
   536  			var actualRef []Countable
   537  
   538  			for _, elem := range res {
   539  				switch elem.Name {
   540  				case helpers.MetaCountProp("myRef"):
   541  					actualRefCount = elem.Items
   542  				case "_id":
   543  					actualUUID = elem.Items
   544  				case "myRef":
   545  					actualRef = elem.Items
   546  				}
   547  			}
   548  
   549  			assert.ElementsMatch(t, expectedRefCount, actualRefCount, res)
   550  			assert.ElementsMatch(t, expectedUUID, actualUUID, res)
   551  			assert.ElementsMatch(t, expectedRef, actualRef, res)
   552  		})
   553  
   554  		t.Run("with the ref omitted in the object schema", func(t *testing.T) {
   555  			schema := map[string]interface{}{}
   556  
   557  			uuid := "2609f1bc-7693-48f3-b531-6ddc52cd2501"
   558  			props := []*models.Property{
   559  				{
   560  					Name:     "myRef",
   561  					DataType: []string{"RefClass"},
   562  				},
   563  			}
   564  			res, err := a.Object(schema, props, strfmt.UUID(uuid))
   565  			require.Nil(t, err)
   566  
   567  			expectedRefCount := []Countable{
   568  				{Data: []uint8{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
   569  			}
   570  
   571  			expectedUUID := []Countable{
   572  				{
   573  					Data:          []byte(uuid),
   574  					TermFrequency: 0,
   575  				},
   576  			}
   577  
   578  			require.Len(t, res, 2)
   579  			var actualRefCount []Countable
   580  			var actualUUID []Countable
   581  
   582  			for _, elem := range res {
   583  				if elem.Name == helpers.MetaCountProp("myRef") {
   584  					actualRefCount = elem.Items
   585  				}
   586  				if elem.Name == "_id" {
   587  					actualUUID = elem.Items
   588  				}
   589  			}
   590  
   591  			assert.ElementsMatch(t, expectedRefCount, actualRefCount, res)
   592  			assert.ElementsMatch(t, expectedUUID, actualUUID, res)
   593  		})
   594  
   595  		// due to the fix introduced in https://github.com/weaviate/weaviate/pull/2320,
   596  		// MultipleRef's can appear as empty []interface{} when no actual refs are provided for
   597  		// an object's reference property.
   598  		//
   599  		// this test asserts that reference properties do not break when they are unmarshalled
   600  		// as empty interface{} slices.
   601  		t.Run("when rep prop is stored as empty interface{} slice", func(t *testing.T) {
   602  			uuid := "cf768bb0-03d8-4464-8f54-f787cf174c01"
   603  			name := "Transformers"
   604  			sch := map[string]interface{}{
   605  				"name":      name,
   606  				"reference": []interface{}{},
   607  			}
   608  
   609  			props := []*models.Property{
   610  				{
   611  					Name:         "name",
   612  					DataType:     schema.DataTypeText.PropString(),
   613  					Tokenization: models.PropertyTokenizationWhitespace,
   614  				},
   615  				{
   616  					Name:     "reference",
   617  					DataType: []string{"SomeClass"},
   618  				},
   619  			}
   620  			res, err := a.Object(sch, props, strfmt.UUID(uuid))
   621  			require.Nil(t, err)
   622  
   623  			expectedUUID := []Countable{
   624  				{
   625  					Data:          []byte(uuid),
   626  					TermFrequency: 0,
   627  				},
   628  			}
   629  
   630  			expectedName := []Countable{
   631  				{
   632  					Data:          []byte(name),
   633  					TermFrequency: 1,
   634  				},
   635  			}
   636  
   637  			require.Len(t, res, 2)
   638  			var actualUUID []Countable
   639  			var actualName []Countable
   640  
   641  			for _, elem := range res {
   642  				switch elem.Name {
   643  				case "_id":
   644  					actualUUID = elem.Items
   645  				case "name":
   646  					actualName = elem.Items
   647  				}
   648  			}
   649  
   650  			assert.ElementsMatch(t, expectedUUID, actualUUID, res)
   651  			assert.ElementsMatch(t, expectedName, actualName, res)
   652  		})
   653  	})
   654  
   655  	t.Run("when objects are indexed by timestamps", func(t *testing.T) {
   656  		sch := map[string]interface{}{
   657  			"description":         "pretty ok if you ask me",
   658  			"_creationTimeUnix":   1650551406404,
   659  			"_lastUpdateTimeUnix": 1650551406404,
   660  		}
   661  
   662  		uuid := strfmt.UUID("2609f1bc-7693-48f3-b531-6ddc52cd2501")
   663  		props := []*models.Property{
   664  			{
   665  				Name:         "description",
   666  				DataType:     schema.DataTypeText.PropString(),
   667  				Tokenization: models.PropertyTokenizationWord,
   668  			},
   669  		}
   670  
   671  		res, err := a.Object(sch, props, uuid)
   672  		require.Nil(t, err)
   673  		require.Len(t, res, 4)
   674  
   675  		expected := []Property{
   676  			{
   677  				Name: "description",
   678  				Items: []Countable{
   679  					{Data: []byte("pretty"), TermFrequency: 1},
   680  					{Data: []byte("ok"), TermFrequency: 1},
   681  					{Data: []byte("if"), TermFrequency: 1},
   682  					{Data: []byte("you"), TermFrequency: 1},
   683  					{Data: []byte("ask"), TermFrequency: 1},
   684  					{Data: []byte("me"), TermFrequency: 1},
   685  				},
   686  				HasFilterableIndex: true,
   687  				HasSearchableIndex: true,
   688  			},
   689  			{
   690  				Name:               "_id",
   691  				Items:              []Countable{{Data: []byte("2609f1bc-7693-48f3-b531-6ddc52cd2501")}},
   692  				HasFilterableIndex: true,
   693  				HasSearchableIndex: false,
   694  			},
   695  			{
   696  				Name:               "_creationTimeUnix",
   697  				Items:              []Countable{{Data: []byte("1650551406404")}},
   698  				HasFilterableIndex: true,
   699  				HasSearchableIndex: false,
   700  			},
   701  			{
   702  				Name:               "_lastUpdateTimeUnix",
   703  				Items:              []Countable{{Data: []byte("1650551406404")}},
   704  				HasFilterableIndex: true,
   705  				HasSearchableIndex: false,
   706  			},
   707  		}
   708  
   709  		for i := range res {
   710  			assert.Equal(t, expected[i].Name, res[i].Name)
   711  			assert.Equal(t, expected[i].HasFilterableIndex, res[i].HasFilterableIndex)
   712  			assert.Equal(t, expected[i].HasSearchableIndex, res[i].HasSearchableIndex)
   713  			assert.ElementsMatch(t, expected[i].Items, res[i].Items)
   714  		}
   715  	})
   716  }
   717  
   718  func TestConvertSliceToUntyped(t *testing.T) {
   719  	tests := []struct {
   720  		name        string
   721  		input       interface{}
   722  		expectedErr error
   723  	}{
   724  		{
   725  			name:  "interface{} slice",
   726  			input: []interface{}{map[string]interface{}{}},
   727  		},
   728  		{
   729  			name:  "string slice",
   730  			input: []string{"some", "slice"},
   731  		},
   732  		{
   733  			name:  "int slice",
   734  			input: []int{1, 2, 3, 4, 5},
   735  		},
   736  		{
   737  			name:  "time slice",
   738  			input: []time.Time{time.Now(), time.Now(), time.Now()},
   739  		},
   740  		{
   741  			name:  "bool slice",
   742  			input: []bool{false},
   743  		},
   744  		{
   745  			name:  "float64 slice",
   746  			input: []float64{1.2, 53555, 4.123, 2, 7.8877887, 0.0001},
   747  		},
   748  		{
   749  			name:  "empty slice",
   750  			input: []string{},
   751  		},
   752  		{
   753  			name:        "unsupported uint8 slice",
   754  			input:       []uint8{1, 2, 3, 4, 5},
   755  			expectedErr: fmt.Errorf("unsupported type []uint8"),
   756  		},
   757  		{
   758  			name:        "unsupported struct{}",
   759  			input:       struct{}{},
   760  			expectedErr: fmt.Errorf("unsupported type struct {}"),
   761  		},
   762  	}
   763  
   764  	for _, test := range tests {
   765  		t.Run(test.name, func(t *testing.T) {
   766  			output, err := typedSliceToUntyped(test.input)
   767  			if test.expectedErr != nil {
   768  				assert.EqualError(t, err, test.expectedErr.Error())
   769  			} else {
   770  				require.Nil(t, err)
   771  				assert.Len(t, output, reflect.ValueOf(test.input).Len())
   772  				assert.IsType(t, []interface{}{}, output)
   773  			}
   774  		})
   775  	}
   776  }
   777  
   778  func TestIndexInverted(t *testing.T) {
   779  	vFalse := false
   780  	vTrue := true
   781  
   782  	t.Run("has filterable index", func(t *testing.T) {
   783  		type testCase struct {
   784  			name            string
   785  			indexFilterable *bool
   786  			dataType        schema.DataType
   787  
   788  			expextedFilterable bool
   789  		}
   790  
   791  		testCases := []testCase{
   792  			{
   793  				name:            "int, filterable null",
   794  				indexFilterable: nil,
   795  				dataType:        schema.DataTypeInt,
   796  
   797  				expextedFilterable: true,
   798  			},
   799  			{
   800  				name:            "int, filterable false",
   801  				indexFilterable: &vFalse,
   802  				dataType:        schema.DataTypeInt,
   803  
   804  				expextedFilterable: false,
   805  			},
   806  			{
   807  				name:            "int, filterable true",
   808  				indexFilterable: &vTrue,
   809  				dataType:        schema.DataTypeInt,
   810  
   811  				expextedFilterable: true,
   812  			},
   813  			{
   814  				name:            "text, filterable null",
   815  				indexFilterable: nil,
   816  				dataType:        schema.DataTypeText,
   817  
   818  				expextedFilterable: true,
   819  			},
   820  			{
   821  				name:            "text, filterable false",
   822  				indexFilterable: &vFalse,
   823  				dataType:        schema.DataTypeText,
   824  
   825  				expextedFilterable: false,
   826  			},
   827  			{
   828  				name:            "text, filterable true",
   829  				indexFilterable: &vTrue,
   830  				dataType:        schema.DataTypeText,
   831  
   832  				expextedFilterable: true,
   833  			},
   834  		}
   835  
   836  		for _, tc := range testCases {
   837  			t.Run(tc.name, func(t *testing.T) {
   838  				hasFilterableIndex := HasFilterableIndex(&models.Property{
   839  					Name:            "prop",
   840  					DataType:        tc.dataType.PropString(),
   841  					IndexFilterable: tc.indexFilterable,
   842  				})
   843  
   844  				assert.Equal(t, tc.expextedFilterable, hasFilterableIndex)
   845  			})
   846  		}
   847  	})
   848  
   849  	t.Run("has searchable index", func(t *testing.T) {
   850  		type testCase struct {
   851  			name            string
   852  			indexSearchable *bool
   853  			dataType        schema.DataType
   854  
   855  			expextedSearchable bool
   856  		}
   857  
   858  		testCases := []testCase{
   859  			{
   860  				name:            "int, searchable null",
   861  				indexSearchable: nil,
   862  				dataType:        schema.DataTypeInt,
   863  
   864  				expextedSearchable: false,
   865  			},
   866  			{
   867  				name:            "int, searchable false",
   868  				indexSearchable: &vFalse,
   869  				dataType:        schema.DataTypeInt,
   870  
   871  				expextedSearchable: false,
   872  			},
   873  			{
   874  				name:            "int, searchable true",
   875  				indexSearchable: &vTrue,
   876  				dataType:        schema.DataTypeInt,
   877  
   878  				expextedSearchable: false,
   879  			},
   880  			{
   881  				name:            "text, searchable null",
   882  				indexSearchable: nil,
   883  				dataType:        schema.DataTypeText,
   884  
   885  				expextedSearchable: true,
   886  			},
   887  			{
   888  				name:            "text, searchable false",
   889  				indexSearchable: &vFalse,
   890  				dataType:        schema.DataTypeText,
   891  
   892  				expextedSearchable: false,
   893  			},
   894  			{
   895  				name:            "text, searchable true",
   896  				indexSearchable: &vTrue,
   897  				dataType:        schema.DataTypeText,
   898  
   899  				expextedSearchable: true,
   900  			},
   901  		}
   902  
   903  		for _, tc := range testCases {
   904  			t.Run(tc.name, func(t *testing.T) {
   905  				hasSearchableIndex := HasSearchableIndex(&models.Property{
   906  					Name:            "prop",
   907  					DataType:        tc.dataType.PropString(),
   908  					IndexSearchable: tc.indexSearchable,
   909  				})
   910  
   911  				assert.Equal(t, tc.expextedSearchable, hasSearchableIndex)
   912  			})
   913  		}
   914  	})
   915  }
   916  
   917  func mustGetByteIntNumber(in int) []byte {
   918  	out, err := LexicographicallySortableInt64(int64(in))
   919  	if err != nil {
   920  		panic(err)
   921  	}
   922  	return out
   923  }
   924  
   925  func mustGetByteFloatNumber(in float64) []byte {
   926  	out, err := LexicographicallySortableFloat64(in)
   927  	if err != nil {
   928  		panic(err)
   929  	}
   930  	return out
   931  }