github.com/weaviate/weaviate@v1.24.6/usecases/schema/validation_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 schema
    13  
    14  import (
    15  	"context"
    16  	"fmt"
    17  	"testing"
    18  
    19  	"github.com/stretchr/testify/assert"
    20  	"github.com/stretchr/testify/require"
    21  	"github.com/weaviate/weaviate/adapters/repos/db/helpers"
    22  	"github.com/weaviate/weaviate/entities/models"
    23  	"github.com/weaviate/weaviate/entities/schema"
    24  )
    25  
    26  func Test_Validation_ClassNames(t *testing.T) {
    27  	type testCase struct {
    28  		input    string
    29  		valid    bool
    30  		storedAs string
    31  		name     string
    32  	}
    33  
    34  	// all inputs represent class names (!)
    35  	tests := []testCase{
    36  		// valid names
    37  		{
    38  			name:     "Single uppercase word",
    39  			input:    "Car",
    40  			valid:    true,
    41  			storedAs: "Car",
    42  		},
    43  		{
    44  			name:     "Single lowercase word, stored as uppercase",
    45  			input:    "car",
    46  			valid:    true,
    47  			storedAs: "Car",
    48  		},
    49  		{
    50  			name:  "empty class",
    51  			input: "",
    52  			valid: false,
    53  		},
    54  	}
    55  
    56  	t.Run("adding a class", func(t *testing.T) {
    57  		t.Run("different class names without keywords or properties", func(t *testing.T) {
    58  			for _, test := range tests {
    59  				t.Run(test.name+" as thing class", func(t *testing.T) {
    60  					class := &models.Class{
    61  						Vectorizer: "text2vec-contextionary",
    62  						Class:      test.input,
    63  					}
    64  
    65  					m := newSchemaManager()
    66  					err := m.AddClass(context.Background(), nil, class)
    67  					t.Log(err)
    68  					assert.Equal(t, test.valid, err == nil)
    69  
    70  					// only proceed if input was supposed to be valid
    71  					if test.valid == false {
    72  						return
    73  					}
    74  
    75  					classNames := testGetClassNames(m)
    76  					assert.Contains(t, classNames, test.storedAs, "class should be stored correctly")
    77  				})
    78  			}
    79  		})
    80  
    81  		t.Run("different class names with valid keywords", func(t *testing.T) {
    82  			for _, test := range tests {
    83  				t.Run(test.name+" as thing class", func(t *testing.T) {
    84  					class := &models.Class{
    85  						Vectorizer: "text2vec-contextionary",
    86  						Class:      test.input,
    87  					}
    88  
    89  					m := newSchemaManager()
    90  					err := m.AddClass(context.Background(), nil, class)
    91  					t.Log(err)
    92  					assert.Equal(t, test.valid, err == nil)
    93  
    94  					// only proceed if input was supposed to be valid
    95  					if test.valid == false {
    96  						return
    97  					}
    98  
    99  					classNames := testGetClassNames(m)
   100  					assert.Contains(t, classNames, test.storedAs, "class should be stored correctly")
   101  				})
   102  			}
   103  		})
   104  	})
   105  }
   106  
   107  func Test_Validation_PropertyNames(t *testing.T) {
   108  	type testCase struct {
   109  		input    string
   110  		valid    bool
   111  		storedAs string
   112  		name     string
   113  	}
   114  
   115  	// for all test cases keep in mind that the word "carrot" is not present in
   116  	// the fake c11y, but every other word is
   117  	//
   118  	// all inputs represent property names (!)
   119  	tests := []testCase{
   120  		// valid names
   121  		{
   122  			name:     "Single uppercase word, stored as lowercase",
   123  			input:    "Brand",
   124  			valid:    true,
   125  			storedAs: "brand",
   126  		},
   127  		{
   128  			name:     "Single lowercase word",
   129  			input:    "brand",
   130  			valid:    true,
   131  			storedAs: "brand",
   132  		},
   133  		{
   134  			name:     "Property with underscores",
   135  			input:    "property_name",
   136  			valid:    true,
   137  			storedAs: "property_name",
   138  		},
   139  		{
   140  			name:     "Property with underscores and numbers",
   141  			input:    "property_name_2",
   142  			valid:    true,
   143  			storedAs: "property_name_2",
   144  		},
   145  		{
   146  			name:     "Property starting with underscores",
   147  			input:    "_property_name",
   148  			valid:    true,
   149  			storedAs: "_property_name",
   150  		},
   151  		{
   152  			name:  "empty prop name",
   153  			input: "",
   154  			valid: false,
   155  		},
   156  		{
   157  			name:  "reserved prop name: id",
   158  			input: "id",
   159  			valid: false,
   160  		},
   161  		{
   162  			name:  "reserved prop name: _id",
   163  			input: "_id",
   164  			valid: false,
   165  		},
   166  		{
   167  			name:  "reserved prop name: _additional",
   168  			input: "_additional",
   169  			valid: false,
   170  		},
   171  	}
   172  
   173  	t.Run("when adding a new class", func(t *testing.T) {
   174  		t.Run("different property names without keywords for the prop", func(t *testing.T) {
   175  			for _, test := range tests {
   176  				t.Run(test.name+" as thing class", func(t *testing.T) {
   177  					class := &models.Class{
   178  						Vectorizer: "text2vec-contextionary",
   179  						Class:      "ValidName",
   180  						Properties: []*models.Property{{
   181  							DataType: schema.DataTypeText.PropString(),
   182  							Name:     test.input,
   183  						}},
   184  					}
   185  
   186  					m := newSchemaManager()
   187  					err := m.AddClass(context.Background(), nil, class)
   188  					t.Log(err)
   189  					assert.Equal(t, test.valid, err == nil)
   190  
   191  					// only proceed if input was supposed to be valid
   192  					if test.valid == false {
   193  						return
   194  					}
   195  
   196  					schema, _ := m.GetSchema(nil)
   197  					propName := schema.Objects.Classes[0].Properties[0].Name
   198  					assert.Equal(t, propName, test.storedAs, "class should be stored correctly")
   199  				})
   200  			}
   201  		})
   202  
   203  		t.Run("different property names  with valid keywords for the prop", func(t *testing.T) {
   204  			for _, test := range tests {
   205  				t.Run(test.name+" as thing class", func(t *testing.T) {
   206  					class := &models.Class{
   207  						Vectorizer: "text2vec-contextionary",
   208  						Class:      "ValidName",
   209  						Properties: []*models.Property{{
   210  							DataType: schema.DataTypeText.PropString(),
   211  							Name:     test.input,
   212  						}},
   213  					}
   214  
   215  					m := newSchemaManager()
   216  					err := m.AddClass(context.Background(), nil, class)
   217  					t.Log(err)
   218  					assert.Equal(t, test.valid, err == nil)
   219  
   220  					// only proceed if input was supposed to be valid
   221  					if test.valid == false {
   222  						return
   223  					}
   224  
   225  					schema, _ := m.GetSchema(nil)
   226  					propName := schema.Objects.Classes[0].Properties[0].Name
   227  					assert.Equal(t, propName, test.storedAs, "class should be stored correctly")
   228  				})
   229  			}
   230  		})
   231  	})
   232  
   233  	t.Run("when updating an existing class with a new property", func(t *testing.T) {
   234  		t.Run("different property names without keywords for the prop", func(t *testing.T) {
   235  			for _, test := range tests {
   236  				t.Run(test.name+" as thing class", func(t *testing.T) {
   237  					class := &models.Class{
   238  						Vectorizer: "text2vec-contextionary",
   239  						Class:      "ValidName",
   240  						Properties: []*models.Property{
   241  							{
   242  								Name:     "dummyPropSoWeDontRunIntoAllNoindexedError",
   243  								DataType: schema.DataTypeText.PropString(),
   244  							},
   245  						},
   246  					}
   247  
   248  					m := newSchemaManager()
   249  					err := m.AddClass(context.Background(), nil, class)
   250  					require.Nil(t, err)
   251  
   252  					property := &models.Property{
   253  						DataType: schema.DataTypeText.PropString(),
   254  						Name:     test.input,
   255  						ModuleConfig: map[string]interface{}{
   256  							"text2vec-contextionary": map[string]interface{}{},
   257  						},
   258  					}
   259  					err = m.AddClassProperty(context.Background(), nil, "ValidName", property)
   260  					t.Log(err)
   261  					require.Equal(t, test.valid, err == nil)
   262  
   263  					// only proceed if input was supposed to be valid
   264  					if test.valid == false {
   265  						return
   266  					}
   267  
   268  					schema, _ := m.GetSchema(nil)
   269  					propName := schema.Objects.Classes[0].Properties[1].Name
   270  					assert.Equal(t, propName, test.storedAs, "class should be stored correctly")
   271  				})
   272  			}
   273  		})
   274  
   275  		t.Run("different property names  with valid keywords for the prop", func(t *testing.T) {
   276  			for _, test := range tests {
   277  				t.Run(test.name+" as thing class", func(t *testing.T) {
   278  					class := &models.Class{
   279  						Vectorizer: "text2vec-contextionary",
   280  						Class:      "ValidName",
   281  						Properties: []*models.Property{{
   282  							DataType: schema.DataTypeText.PropString(),
   283  							Name:     test.input,
   284  						}},
   285  					}
   286  
   287  					m := newSchemaManager()
   288  					err := m.AddClass(context.Background(), nil, class)
   289  					t.Log(err)
   290  					assert.Equal(t, test.valid, err == nil)
   291  
   292  					// only proceed if input was supposed to be valid
   293  					if test.valid == false {
   294  						return
   295  					}
   296  
   297  					schema, _ := m.GetSchema(nil)
   298  					propName := schema.Objects.Classes[0].Properties[0].Name
   299  					assert.Equal(t, propName, test.storedAs, "class should be stored correctly")
   300  				})
   301  			}
   302  		})
   303  	})
   304  }
   305  
   306  func Test_Validation_PropertyTokenization(t *testing.T) {
   307  	type testCase struct {
   308  		name             string
   309  		tokenization     string
   310  		propertyDataType schema.PropertyDataType
   311  		expectedErrMsg   string
   312  	}
   313  
   314  	runTestCases := func(t *testing.T, testCases []testCase) {
   315  		m := newSchemaManager()
   316  		for _, tc := range testCases {
   317  			t.Run(tc.name, func(t *testing.T) {
   318  				err := m.validatePropertyTokenization(tc.tokenization, tc.propertyDataType)
   319  				if tc.expectedErrMsg == "" {
   320  					assert.Nil(t, err)
   321  				} else {
   322  					assert.NotNil(t, err)
   323  					assert.EqualError(t, err, tc.expectedErrMsg)
   324  				}
   325  			})
   326  		}
   327  	}
   328  
   329  	t.Run("validates text/textArray and all tokenizations", func(t *testing.T) {
   330  		testCases := []testCase{}
   331  		for _, dataType := range []schema.DataType{
   332  			schema.DataTypeText, schema.DataTypeTextArray,
   333  		} {
   334  			for _, tokenization := range helpers.Tokenizations {
   335  				testCases = append(testCases, testCase{
   336  					name:             fmt.Sprintf("%s + '%s'", dataType, tokenization),
   337  					propertyDataType: newFakePrimitivePDT(dataType),
   338  					tokenization:     tokenization,
   339  					expectedErrMsg:   "",
   340  				})
   341  			}
   342  
   343  			for _, tokenization := range []string{"non_existing", ""} {
   344  				testCases = append(testCases, testCase{
   345  					name:             fmt.Sprintf("%s + '%s'", dataType, tokenization),
   346  					propertyDataType: newFakePrimitivePDT(dataType),
   347  					tokenization:     tokenization,
   348  					expectedErrMsg:   fmt.Sprintf("Tokenization '%s' is not allowed for data type '%s'", tokenization, dataType),
   349  				})
   350  			}
   351  		}
   352  
   353  		runTestCases(t, testCases)
   354  	})
   355  
   356  	t.Run("validates non text/textArray and all tokenizations", func(t *testing.T) {
   357  		testCases := []testCase{}
   358  		for _, dataType := range schema.PrimitiveDataTypes {
   359  			switch dataType {
   360  			case schema.DataTypeText, schema.DataTypeTextArray:
   361  				continue
   362  			default:
   363  				testCases = append(testCases, testCase{
   364  					name:             fmt.Sprintf("%s + ''", dataType),
   365  					propertyDataType: newFakePrimitivePDT(dataType),
   366  					tokenization:     "",
   367  					expectedErrMsg:   "",
   368  				})
   369  
   370  				for _, tokenization := range append(helpers.Tokenizations, "non_existing") {
   371  					testCases = append(testCases, testCase{
   372  						name:             fmt.Sprintf("%s + '%s'", dataType, tokenization),
   373  						propertyDataType: newFakePrimitivePDT(dataType),
   374  						tokenization:     tokenization,
   375  						expectedErrMsg:   fmt.Sprintf("Tokenization is not allowed for data type '%s'", dataType),
   376  					})
   377  				}
   378  			}
   379  		}
   380  
   381  		runTestCases(t, testCases)
   382  	})
   383  
   384  	t.Run("validates nested datatype and all tokenizations", func(t *testing.T) {
   385  		testCases := []testCase{}
   386  		for _, dataType := range schema.NestedDataTypes {
   387  			testCases = append(testCases, testCase{
   388  				name:             fmt.Sprintf("%s + ''", dataType),
   389  				propertyDataType: newFakeNestedPDT(dataType),
   390  				tokenization:     "",
   391  				expectedErrMsg:   "",
   392  			})
   393  
   394  			for _, tokenization := range append(helpers.Tokenizations, "non_existent") {
   395  				testCases = append(testCases, testCase{
   396  					name:             fmt.Sprintf("%s + '%s'", dataType, tokenization),
   397  					propertyDataType: newFakeNestedPDT(dataType),
   398  					tokenization:     tokenization,
   399  					expectedErrMsg:   "Tokenization is not allowed for object/object[] data types",
   400  				})
   401  			}
   402  		}
   403  
   404  		runTestCases(t, testCases)
   405  	})
   406  
   407  	t.Run("validates ref datatype (empty) and all tokenizations", func(t *testing.T) {
   408  		testCases := []testCase{}
   409  
   410  		testCases = append(testCases, testCase{
   411  			name:             "ref + ''",
   412  			propertyDataType: newFakePrimitivePDT(""),
   413  			tokenization:     "",
   414  			expectedErrMsg:   "",
   415  		})
   416  
   417  		for _, tokenization := range append(helpers.Tokenizations, "non_existing") {
   418  			testCases = append(testCases, testCase{
   419  				name:             fmt.Sprintf("ref + '%s'", tokenization),
   420  				propertyDataType: newFakePrimitivePDT(""),
   421  				tokenization:     tokenization,
   422  				expectedErrMsg:   "Tokenization is not allowed for reference data type",
   423  			})
   424  		}
   425  
   426  		runTestCases(t, testCases)
   427  	})
   428  
   429  	t.Run("[deprecated string] validates string/stringArray and all tokenizations", func(t *testing.T) {
   430  		testCases := []testCase{}
   431  		for _, dataType := range []schema.DataType{
   432  			schema.DataTypeString, schema.DataTypeStringArray,
   433  		} {
   434  			for _, tokenization := range append(helpers.Tokenizations, "non_existing") {
   435  				switch tokenization {
   436  				case models.PropertyTokenizationWord, models.PropertyTokenizationField:
   437  					testCases = append(testCases, testCase{
   438  						name:             fmt.Sprintf("%s + %s", dataType, tokenization),
   439  						propertyDataType: newFakePrimitivePDT(dataType),
   440  						tokenization:     tokenization,
   441  						expectedErrMsg:   "",
   442  					})
   443  				default:
   444  					testCases = append(testCases, testCase{
   445  						name:             fmt.Sprintf("%s + %s", dataType, tokenization),
   446  						propertyDataType: newFakePrimitivePDT(dataType),
   447  						tokenization:     tokenization,
   448  						expectedErrMsg:   fmt.Sprintf("Tokenization '%s' is not allowed for data type '%s'", tokenization, dataType),
   449  					})
   450  				}
   451  			}
   452  		}
   453  
   454  		runTestCases(t, testCases)
   455  	})
   456  }
   457  
   458  func Test_Validation_PropertyIndexing(t *testing.T) {
   459  	t.Run("validates indexInverted / indexFilterable / indexSearchable combinations", func(t *testing.T) {
   460  		vFalse := false
   461  		vTrue := true
   462  		allBoolPtrs := []*bool{nil, &vFalse, &vTrue}
   463  
   464  		type testCase struct {
   465  			propName        string
   466  			dataType        schema.DataType
   467  			indexInverted   *bool
   468  			indexFilterable *bool
   469  			indexSearchable *bool
   470  
   471  			expectedErrMsg string
   472  		}
   473  
   474  		boolPtrToStr := func(ptr *bool) string {
   475  			if ptr == nil {
   476  				return "nil"
   477  			}
   478  			return fmt.Sprintf("%v", *ptr)
   479  		}
   480  		propName := func(dt schema.DataType, inverted, filterable, searchable *bool) string {
   481  			return fmt.Sprintf("%s_inverted_%s_filterable_%s_searchable_%s",
   482  				dt.String(), boolPtrToStr(inverted), boolPtrToStr(filterable), boolPtrToStr(searchable))
   483  		}
   484  
   485  		testCases := []testCase{}
   486  
   487  		for _, dataType := range []schema.DataType{schema.DataTypeText, schema.DataTypeInt, schema.DataTypeObject} {
   488  			for _, inverted := range allBoolPtrs {
   489  				for _, filterable := range allBoolPtrs {
   490  					for _, searchable := range allBoolPtrs {
   491  						if inverted != nil {
   492  							if filterable != nil || searchable != nil {
   493  								testCases = append(testCases, testCase{
   494  									propName:        propName(dataType, inverted, filterable, searchable),
   495  									dataType:        dataType,
   496  									indexInverted:   inverted,
   497  									indexFilterable: filterable,
   498  									indexSearchable: searchable,
   499  									expectedErrMsg:  "`indexInverted` is deprecated and can not be set together with `indexFilterable` or `indexSearchable`",
   500  								})
   501  								continue
   502  							}
   503  						}
   504  
   505  						if searchable != nil && *searchable {
   506  							if dataType != schema.DataTypeText {
   507  								testCases = append(testCases, testCase{
   508  									propName:        propName(dataType, inverted, filterable, searchable),
   509  									dataType:        dataType,
   510  									indexInverted:   inverted,
   511  									indexFilterable: filterable,
   512  									indexSearchable: searchable,
   513  									expectedErrMsg:  "`indexSearchable` is not allowed for other than text/text[] data types",
   514  								})
   515  								continue
   516  							}
   517  						}
   518  
   519  						testCases = append(testCases, testCase{
   520  							propName:        propName(dataType, inverted, filterable, searchable),
   521  							dataType:        dataType,
   522  							indexInverted:   inverted,
   523  							indexFilterable: filterable,
   524  							indexSearchable: searchable,
   525  							expectedErrMsg:  "",
   526  						})
   527  					}
   528  				}
   529  			}
   530  		}
   531  
   532  		mgr := newSchemaManager()
   533  		for _, tc := range testCases {
   534  			t.Run(tc.propName, func(t *testing.T) {
   535  				err := mgr.validatePropertyIndexing(&models.Property{
   536  					Name:            tc.propName,
   537  					DataType:        tc.dataType.PropString(),
   538  					IndexInverted:   tc.indexInverted,
   539  					IndexFilterable: tc.indexFilterable,
   540  					IndexSearchable: tc.indexSearchable,
   541  				})
   542  
   543  				if tc.expectedErrMsg != "" {
   544  					require.NotNil(t, err)
   545  					assert.EqualError(t, err, tc.expectedErrMsg)
   546  				} else {
   547  					require.Nil(t, err)
   548  				}
   549  			})
   550  		}
   551  	})
   552  }
   553  
   554  func Test_Validation_NestedProperties(t *testing.T) {
   555  	vFalse := false
   556  	vTrue := true
   557  
   558  	t.Run("does not validate wrong names", func(t *testing.T) {
   559  		for _, name := range []string{"prop@1", "prop-2", "prop$3", "4prop"} {
   560  			t.Run(name, func(t *testing.T) {
   561  				nestedProperties := []*models.NestedProperty{
   562  					{
   563  						Name:            name,
   564  						DataType:        schema.DataTypeInt.PropString(),
   565  						IndexFilterable: &vFalse,
   566  						IndexSearchable: &vFalse,
   567  						Tokenization:    "",
   568  					},
   569  				}
   570  
   571  				for _, ndt := range schema.NestedDataTypes {
   572  					t.Run(ndt.String(), func(t *testing.T) {
   573  						propPrimitives := &models.Property{
   574  							Name:             "objectProp",
   575  							DataType:         ndt.PropString(),
   576  							IndexFilterable:  &vFalse,
   577  							IndexSearchable:  &vFalse,
   578  							Tokenization:     "",
   579  							NestedProperties: nestedProperties,
   580  						}
   581  						propLvl2Primitives := &models.Property{
   582  							Name:            "objectPropLvl2",
   583  							DataType:        ndt.PropString(),
   584  							IndexFilterable: &vFalse,
   585  							IndexSearchable: &vFalse,
   586  							Tokenization:    "",
   587  							NestedProperties: []*models.NestedProperty{
   588  								{
   589  									Name:             "nested_object",
   590  									DataType:         ndt.PropString(),
   591  									IndexFilterable:  &vFalse,
   592  									IndexSearchable:  &vFalse,
   593  									Tokenization:     "",
   594  									NestedProperties: nestedProperties,
   595  								},
   596  							},
   597  						}
   598  
   599  						for _, prop := range []*models.Property{propPrimitives, propLvl2Primitives} {
   600  							t.Run(prop.Name, func(t *testing.T) {
   601  								err := validateNestedProperties(prop.NestedProperties, prop.Name)
   602  								assert.ErrorContains(t, err, prop.Name)
   603  								assert.ErrorContains(t, err, "is not a valid nested property name")
   604  							})
   605  						}
   606  					})
   607  				}
   608  			})
   609  		}
   610  	})
   611  
   612  	t.Run("validates primitive data types", func(t *testing.T) {
   613  		nestedProperties := []*models.NestedProperty{}
   614  		for _, pdt := range schema.PrimitiveDataTypes {
   615  			tokenization := ""
   616  			switch pdt {
   617  			case schema.DataTypeGeoCoordinates, schema.DataTypePhoneNumber:
   618  				// skip - not supported as nested
   619  				continue
   620  			case schema.DataTypeText, schema.DataTypeTextArray:
   621  				tokenization = models.PropertyTokenizationWord
   622  			default:
   623  				// do nothing
   624  			}
   625  
   626  			nestedProperties = append(nestedProperties, &models.NestedProperty{
   627  				Name:            "nested_" + pdt.AsName(),
   628  				DataType:        pdt.PropString(),
   629  				IndexFilterable: &vFalse,
   630  				IndexSearchable: &vFalse,
   631  				Tokenization:    tokenization,
   632  			})
   633  		}
   634  
   635  		for _, ndt := range schema.NestedDataTypes {
   636  			t.Run(ndt.String(), func(t *testing.T) {
   637  				propPrimitives := &models.Property{
   638  					Name:             "objectProp",
   639  					DataType:         ndt.PropString(),
   640  					IndexFilterable:  &vFalse,
   641  					IndexSearchable:  &vFalse,
   642  					Tokenization:     "",
   643  					NestedProperties: nestedProperties,
   644  				}
   645  				propLvl2Primitives := &models.Property{
   646  					Name:            "objectPropLvl2",
   647  					DataType:        ndt.PropString(),
   648  					IndexFilterable: &vFalse,
   649  					IndexSearchable: &vFalse,
   650  					Tokenization:    "",
   651  					NestedProperties: []*models.NestedProperty{
   652  						{
   653  							Name:             "nested_object",
   654  							DataType:         ndt.PropString(),
   655  							IndexFilterable:  &vFalse,
   656  							IndexSearchable:  &vFalse,
   657  							Tokenization:     "",
   658  							NestedProperties: nestedProperties,
   659  						},
   660  					},
   661  				}
   662  
   663  				for _, prop := range []*models.Property{propPrimitives, propLvl2Primitives} {
   664  					t.Run(prop.Name, func(t *testing.T) {
   665  						err := validateNestedProperties(prop.NestedProperties, prop.Name)
   666  						assert.NoError(t, err)
   667  					})
   668  				}
   669  			})
   670  		}
   671  	})
   672  
   673  	t.Run("does not validate deprecated primitive types", func(t *testing.T) {
   674  		for _, pdt := range schema.DeprecatedPrimitiveDataTypes {
   675  			t.Run(pdt.String(), func(t *testing.T) {
   676  				nestedProperties := []*models.NestedProperty{
   677  					{
   678  						Name:            "nested_" + pdt.AsName(),
   679  						DataType:        pdt.PropString(),
   680  						IndexFilterable: &vFalse,
   681  						IndexSearchable: &vFalse,
   682  						Tokenization:    "",
   683  					},
   684  				}
   685  
   686  				for _, ndt := range schema.NestedDataTypes {
   687  					t.Run(ndt.String(), func(t *testing.T) {
   688  						propPrimitives := &models.Property{
   689  							Name:             "objectProp",
   690  							DataType:         ndt.PropString(),
   691  							IndexFilterable:  &vFalse,
   692  							IndexSearchable:  &vFalse,
   693  							Tokenization:     "",
   694  							NestedProperties: nestedProperties,
   695  						}
   696  						propLvl2Primitives := &models.Property{
   697  							Name:            "objectPropLvl2",
   698  							DataType:        ndt.PropString(),
   699  							IndexFilterable: &vFalse,
   700  							IndexSearchable: &vFalse,
   701  							Tokenization:    "",
   702  							NestedProperties: []*models.NestedProperty{
   703  								{
   704  									Name:             "nested_object",
   705  									DataType:         ndt.PropString(),
   706  									IndexFilterable:  &vFalse,
   707  									IndexSearchable:  &vFalse,
   708  									Tokenization:     "",
   709  									NestedProperties: nestedProperties,
   710  								},
   711  							},
   712  						}
   713  
   714  						for _, prop := range []*models.Property{propPrimitives, propLvl2Primitives} {
   715  							t.Run(prop.Name, func(t *testing.T) {
   716  								err := validateNestedProperties(prop.NestedProperties, prop.Name)
   717  								assert.ErrorContains(t, err, prop.Name)
   718  								assert.ErrorContains(t, err, fmt.Sprintf("data type '%s' is deprecated and not allowed as nested property", pdt.String()))
   719  							})
   720  						}
   721  					})
   722  				}
   723  			})
   724  		}
   725  	})
   726  
   727  	t.Run("does not validate unsupported primitive types", func(t *testing.T) {
   728  		for _, pdt := range []schema.DataType{schema.DataTypeGeoCoordinates, schema.DataTypePhoneNumber} {
   729  			t.Run(pdt.String(), func(t *testing.T) {
   730  				nestedProperties := []*models.NestedProperty{
   731  					{
   732  						Name:            "nested_" + pdt.AsName(),
   733  						DataType:        pdt.PropString(),
   734  						IndexFilterable: &vFalse,
   735  						IndexSearchable: &vFalse,
   736  						Tokenization:    "",
   737  					},
   738  				}
   739  
   740  				for _, ndt := range schema.NestedDataTypes {
   741  					t.Run(ndt.String(), func(t *testing.T) {
   742  						propPrimitives := &models.Property{
   743  							Name:             "objectProp",
   744  							DataType:         ndt.PropString(),
   745  							IndexFilterable:  &vFalse,
   746  							IndexSearchable:  &vFalse,
   747  							Tokenization:     "",
   748  							NestedProperties: nestedProperties,
   749  						}
   750  						propLvl2Primitives := &models.Property{
   751  							Name:            "objectPropLvl2",
   752  							DataType:        ndt.PropString(),
   753  							IndexFilterable: &vFalse,
   754  							IndexSearchable: &vFalse,
   755  							Tokenization:    "",
   756  							NestedProperties: []*models.NestedProperty{
   757  								{
   758  									Name:             "nested_object",
   759  									DataType:         ndt.PropString(),
   760  									IndexFilterable:  &vFalse,
   761  									IndexSearchable:  &vFalse,
   762  									Tokenization:     "",
   763  									NestedProperties: nestedProperties,
   764  								},
   765  							},
   766  						}
   767  
   768  						for _, prop := range []*models.Property{propPrimitives, propLvl2Primitives} {
   769  							t.Run(prop.Name, func(t *testing.T) {
   770  								err := validateNestedProperties(prop.NestedProperties, prop.Name)
   771  								assert.ErrorContains(t, err, prop.Name)
   772  								assert.ErrorContains(t, err, fmt.Sprintf("data type '%s' not allowed as nested property", pdt.String()))
   773  							})
   774  						}
   775  					})
   776  				}
   777  			})
   778  		}
   779  	})
   780  
   781  	t.Run("does not validate ref types", func(t *testing.T) {
   782  		nestedProperties := []*models.NestedProperty{
   783  			{
   784  				Name:            "nested_ref",
   785  				DataType:        []string{"SomeClass"},
   786  				IndexFilterable: &vFalse,
   787  				IndexSearchable: &vFalse,
   788  				Tokenization:    "",
   789  			},
   790  		}
   791  
   792  		for _, ndt := range schema.NestedDataTypes {
   793  			t.Run(ndt.String(), func(t *testing.T) {
   794  				propPrimitives := &models.Property{
   795  					Name:             "objectProp",
   796  					DataType:         ndt.PropString(),
   797  					IndexFilterable:  &vFalse,
   798  					IndexSearchable:  &vFalse,
   799  					Tokenization:     "",
   800  					NestedProperties: nestedProperties,
   801  				}
   802  				propLvl2Primitives := &models.Property{
   803  					Name:            "objectPropLvl2",
   804  					DataType:        ndt.PropString(),
   805  					IndexFilterable: &vFalse,
   806  					IndexSearchable: &vFalse,
   807  					Tokenization:    "",
   808  					NestedProperties: []*models.NestedProperty{
   809  						{
   810  							Name:             "nested_object",
   811  							DataType:         ndt.PropString(),
   812  							IndexFilterable:  &vFalse,
   813  							IndexSearchable:  &vFalse,
   814  							Tokenization:     "",
   815  							NestedProperties: nestedProperties,
   816  						},
   817  					},
   818  				}
   819  
   820  				for _, prop := range []*models.Property{propPrimitives, propLvl2Primitives} {
   821  					t.Run(prop.Name, func(t *testing.T) {
   822  						err := validateNestedProperties(prop.NestedProperties, prop.Name)
   823  						assert.ErrorContains(t, err, prop.Name)
   824  						assert.ErrorContains(t, err, "reference data type not allowed")
   825  					})
   826  				}
   827  			})
   828  		}
   829  	})
   830  
   831  	t.Run("does not validate empty nested properties", func(t *testing.T) {
   832  		for _, ndt := range schema.NestedDataTypes {
   833  			t.Run(ndt.String(), func(t *testing.T) {
   834  				propPrimitives := &models.Property{
   835  					Name:            "objectProp",
   836  					DataType:        ndt.PropString(),
   837  					IndexFilterable: &vFalse,
   838  					IndexSearchable: &vFalse,
   839  					Tokenization:    "",
   840  				}
   841  				propLvl2Primitives := &models.Property{
   842  					Name:            "objectPropLvl2",
   843  					DataType:        ndt.PropString(),
   844  					IndexFilterable: &vFalse,
   845  					IndexSearchable: &vFalse,
   846  					Tokenization:    "",
   847  					NestedProperties: []*models.NestedProperty{
   848  						{
   849  							Name:            "nested_object",
   850  							DataType:        ndt.PropString(),
   851  							IndexFilterable: &vFalse,
   852  							IndexSearchable: &vFalse,
   853  							Tokenization:    "",
   854  						},
   855  					},
   856  				}
   857  
   858  				for _, prop := range []*models.Property{propPrimitives, propLvl2Primitives} {
   859  					t.Run(prop.Name, func(t *testing.T) {
   860  						err := validateNestedProperties(prop.NestedProperties, prop.Name)
   861  						assert.ErrorContains(t, err, prop.Name)
   862  						assert.ErrorContains(t, err, "At least one nested property is required for data type object/object[]")
   863  					})
   864  				}
   865  			})
   866  		}
   867  	})
   868  
   869  	t.Run("does not validate tokenization on non text/text[] primitive data types", func(t *testing.T) {
   870  		for _, pdt := range schema.PrimitiveDataTypes {
   871  			switch pdt {
   872  			case schema.DataTypeText, schema.DataTypeTextArray:
   873  				continue
   874  			case schema.DataTypeGeoCoordinates, schema.DataTypePhoneNumber:
   875  				// skip - not supported as nested
   876  				continue
   877  			default:
   878  				// do nothing
   879  			}
   880  
   881  			t.Run(pdt.String(), func(t *testing.T) {
   882  				nestedProperties := []*models.NestedProperty{
   883  					{
   884  						Name:            "nested_" + pdt.AsName(),
   885  						DataType:        pdt.PropString(),
   886  						IndexFilterable: &vFalse,
   887  						IndexSearchable: &vFalse,
   888  						Tokenization:    models.PropertyTokenizationWord,
   889  					},
   890  				}
   891  
   892  				for _, ndt := range schema.NestedDataTypes {
   893  					t.Run(ndt.String(), func(t *testing.T) {
   894  						propPrimitives := &models.Property{
   895  							Name:             "objectProp",
   896  							DataType:         ndt.PropString(),
   897  							IndexFilterable:  &vFalse,
   898  							IndexSearchable:  &vFalse,
   899  							Tokenization:     "",
   900  							NestedProperties: nestedProperties,
   901  						}
   902  						propLvl2Primitives := &models.Property{
   903  							Name:            "objectPropLvl2",
   904  							DataType:        ndt.PropString(),
   905  							IndexFilterable: &vFalse,
   906  							IndexSearchable: &vFalse,
   907  							Tokenization:    "",
   908  							NestedProperties: []*models.NestedProperty{
   909  								{
   910  									Name:             "nested_object",
   911  									DataType:         ndt.PropString(),
   912  									IndexFilterable:  &vFalse,
   913  									IndexSearchable:  &vFalse,
   914  									Tokenization:     "",
   915  									NestedProperties: nestedProperties,
   916  								},
   917  							},
   918  						}
   919  
   920  						for _, prop := range []*models.Property{propPrimitives, propLvl2Primitives} {
   921  							t.Run(prop.Name, func(t *testing.T) {
   922  								err := validateNestedProperties(prop.NestedProperties, prop.Name)
   923  								assert.ErrorContains(t, err, prop.Name)
   924  								assert.ErrorContains(t, err, fmt.Sprintf("Tokenization is not allowed for data type '%s'", pdt.String()))
   925  							})
   926  						}
   927  					})
   928  				}
   929  			})
   930  		}
   931  	})
   932  
   933  	t.Run("does not validate tokenization on nested data types", func(t *testing.T) {
   934  		nestedProperties := []*models.NestedProperty{
   935  			{
   936  				Name:            "nested_int",
   937  				DataType:        schema.DataTypeInt.PropString(),
   938  				IndexFilterable: &vFalse,
   939  				IndexSearchable: &vFalse,
   940  				Tokenization:    "",
   941  			},
   942  		}
   943  
   944  		for _, ndt := range schema.NestedDataTypes {
   945  			t.Run(ndt.String(), func(t *testing.T) {
   946  				propLvl2Primitives := &models.Property{
   947  					Name:            "objectPropLvl2",
   948  					DataType:        ndt.PropString(),
   949  					IndexFilterable: &vFalse,
   950  					IndexSearchable: &vFalse,
   951  					Tokenization:    "",
   952  					NestedProperties: []*models.NestedProperty{
   953  						{
   954  							Name:             "nested_object",
   955  							DataType:         ndt.PropString(),
   956  							IndexFilterable:  &vFalse,
   957  							IndexSearchable:  &vFalse,
   958  							Tokenization:     models.PropertyTokenizationWord,
   959  							NestedProperties: nestedProperties,
   960  						},
   961  					},
   962  				}
   963  
   964  				for _, prop := range []*models.Property{propLvl2Primitives} {
   965  					t.Run(prop.Name, func(t *testing.T) {
   966  						err := validateNestedProperties(prop.NestedProperties, prop.Name)
   967  						assert.ErrorContains(t, err, prop.Name)
   968  						assert.ErrorContains(t, err, "Tokenization is not allowed for object/object[] data types")
   969  					})
   970  				}
   971  			})
   972  		}
   973  	})
   974  
   975  	t.Run("validates indexFilterable on primitive data types", func(t *testing.T) {
   976  		nestedProperties := []*models.NestedProperty{}
   977  		for _, pdt := range schema.PrimitiveDataTypes {
   978  			tokenization := ""
   979  			switch pdt {
   980  			case schema.DataTypeBlob:
   981  				// skip - not indexable
   982  				continue
   983  			case schema.DataTypeGeoCoordinates, schema.DataTypePhoneNumber:
   984  				// skip - not supported as nested
   985  				continue
   986  			case schema.DataTypeText, schema.DataTypeTextArray:
   987  				tokenization = models.PropertyTokenizationWord
   988  			default:
   989  				// do nothing
   990  			}
   991  
   992  			nestedProperties = append(nestedProperties, &models.NestedProperty{
   993  				Name:            "nested_" + pdt.AsName(),
   994  				DataType:        pdt.PropString(),
   995  				IndexFilterable: &vTrue,
   996  				IndexSearchable: &vFalse,
   997  				Tokenization:    tokenization,
   998  			})
   999  		}
  1000  
  1001  		for _, ndt := range schema.NestedDataTypes {
  1002  			t.Run(ndt.String(), func(t *testing.T) {
  1003  				propPrimitives := &models.Property{
  1004  					Name:             "objectProp",
  1005  					DataType:         ndt.PropString(),
  1006  					IndexFilterable:  &vFalse,
  1007  					IndexSearchable:  &vFalse,
  1008  					Tokenization:     "",
  1009  					NestedProperties: nestedProperties,
  1010  				}
  1011  				propLvl2Primitives := &models.Property{
  1012  					Name:            "objectPropLvl2",
  1013  					DataType:        ndt.PropString(),
  1014  					IndexFilterable: &vFalse,
  1015  					IndexSearchable: &vFalse,
  1016  					Tokenization:    "",
  1017  					NestedProperties: []*models.NestedProperty{
  1018  						{
  1019  							Name:             "nested_object",
  1020  							DataType:         ndt.PropString(),
  1021  							IndexFilterable:  &vFalse,
  1022  							IndexSearchable:  &vFalse,
  1023  							Tokenization:     "",
  1024  							NestedProperties: nestedProperties,
  1025  						},
  1026  					},
  1027  				}
  1028  
  1029  				for _, prop := range []*models.Property{propPrimitives, propLvl2Primitives} {
  1030  					t.Run(prop.Name, func(t *testing.T) {
  1031  						err := validateNestedProperties(prop.NestedProperties, prop.Name)
  1032  						assert.NoError(t, err)
  1033  					})
  1034  				}
  1035  			})
  1036  		}
  1037  	})
  1038  
  1039  	t.Run("does not validate indexFilterable on blob data type", func(t *testing.T) {
  1040  		nestedProperties := []*models.NestedProperty{
  1041  			{
  1042  				Name:            "nested_blob",
  1043  				DataType:        schema.DataTypeBlob.PropString(),
  1044  				IndexFilterable: &vTrue,
  1045  				IndexSearchable: &vFalse,
  1046  				Tokenization:    "",
  1047  			},
  1048  		}
  1049  
  1050  		for _, ndt := range schema.NestedDataTypes {
  1051  			t.Run(ndt.String(), func(t *testing.T) {
  1052  				propPrimitives := &models.Property{
  1053  					Name:             "objectProp",
  1054  					DataType:         ndt.PropString(),
  1055  					IndexFilterable:  &vFalse,
  1056  					IndexSearchable:  &vFalse,
  1057  					Tokenization:     "",
  1058  					NestedProperties: nestedProperties,
  1059  				}
  1060  				propLvl2Primitives := &models.Property{
  1061  					Name:            "objectPropLvl2",
  1062  					DataType:        ndt.PropString(),
  1063  					IndexFilterable: &vFalse,
  1064  					IndexSearchable: &vFalse,
  1065  					Tokenization:    "",
  1066  					NestedProperties: []*models.NestedProperty{
  1067  						{
  1068  							Name:             "nested_object",
  1069  							DataType:         ndt.PropString(),
  1070  							IndexFilterable:  &vFalse,
  1071  							IndexSearchable:  &vFalse,
  1072  							Tokenization:     "",
  1073  							NestedProperties: nestedProperties,
  1074  						},
  1075  					},
  1076  				}
  1077  
  1078  				for _, prop := range []*models.Property{propPrimitives, propLvl2Primitives} {
  1079  					t.Run(prop.Name, func(t *testing.T) {
  1080  						err := validateNestedProperties(prop.NestedProperties, prop.Name)
  1081  						assert.ErrorContains(t, err, prop.Name)
  1082  						assert.ErrorContains(t, err, "indexFilterable is not allowed for blob data type")
  1083  					})
  1084  				}
  1085  			})
  1086  		}
  1087  	})
  1088  
  1089  	t.Run("validates indexFilterable on nested data types", func(t *testing.T) {
  1090  		nestedProperties := []*models.NestedProperty{
  1091  			{
  1092  				Name:            "nested_int",
  1093  				DataType:        schema.DataTypeInt.PropString(),
  1094  				IndexFilterable: &vFalse,
  1095  				IndexSearchable: &vFalse,
  1096  				Tokenization:    "",
  1097  			},
  1098  		}
  1099  
  1100  		for _, ndt := range schema.NestedDataTypes {
  1101  			t.Run(ndt.String(), func(t *testing.T) {
  1102  				propLvl2Primitives := &models.Property{
  1103  					Name:            "objectPropLvl2",
  1104  					DataType:        ndt.PropString(),
  1105  					IndexFilterable: &vTrue,
  1106  					IndexSearchable: &vFalse,
  1107  					Tokenization:    "",
  1108  					NestedProperties: []*models.NestedProperty{
  1109  						{
  1110  							Name:             "nested_object",
  1111  							DataType:         ndt.PropString(),
  1112  							IndexFilterable:  &vFalse,
  1113  							IndexSearchable:  &vFalse,
  1114  							Tokenization:     "",
  1115  							NestedProperties: nestedProperties,
  1116  						},
  1117  					},
  1118  				}
  1119  
  1120  				for _, prop := range []*models.Property{propLvl2Primitives} {
  1121  					t.Run(prop.Name, func(t *testing.T) {
  1122  						err := validateNestedProperties(prop.NestedProperties, prop.Name)
  1123  						assert.NoError(t, err)
  1124  					})
  1125  				}
  1126  			})
  1127  		}
  1128  	})
  1129  
  1130  	t.Run("validates indexSearchable on text/text[] data types", func(t *testing.T) {
  1131  		nestedProperties := []*models.NestedProperty{}
  1132  		for _, pdt := range []schema.DataType{schema.DataTypeText, schema.DataTypeTextArray} {
  1133  			nestedProperties = append(nestedProperties, &models.NestedProperty{
  1134  				Name:            "nested_" + pdt.AsName(),
  1135  				DataType:        pdt.PropString(),
  1136  				IndexFilterable: &vFalse,
  1137  				IndexSearchable: &vTrue,
  1138  				Tokenization:    models.PropertyTokenizationWord,
  1139  			})
  1140  		}
  1141  
  1142  		for _, ndt := range schema.NestedDataTypes {
  1143  			t.Run(ndt.String(), func(t *testing.T) {
  1144  				propPrimitives := &models.Property{
  1145  					Name:             "objectProp",
  1146  					DataType:         ndt.PropString(),
  1147  					IndexFilterable:  &vFalse,
  1148  					IndexSearchable:  &vFalse,
  1149  					Tokenization:     "",
  1150  					NestedProperties: nestedProperties,
  1151  				}
  1152  				propLvl2Primitives := &models.Property{
  1153  					Name:            "objectPropLvl2",
  1154  					DataType:        ndt.PropString(),
  1155  					IndexFilterable: &vFalse,
  1156  					IndexSearchable: &vFalse,
  1157  					Tokenization:    "",
  1158  					NestedProperties: []*models.NestedProperty{
  1159  						{
  1160  							Name:             "nested_object",
  1161  							DataType:         ndt.PropString(),
  1162  							IndexFilterable:  &vFalse,
  1163  							IndexSearchable:  &vFalse,
  1164  							Tokenization:     "",
  1165  							NestedProperties: nestedProperties,
  1166  						},
  1167  					},
  1168  				}
  1169  
  1170  				for _, prop := range []*models.Property{propPrimitives, propLvl2Primitives} {
  1171  					t.Run(prop.Name, func(t *testing.T) {
  1172  						err := validateNestedProperties(prop.NestedProperties, prop.Name)
  1173  						assert.NoError(t, err)
  1174  					})
  1175  				}
  1176  			})
  1177  		}
  1178  	})
  1179  
  1180  	t.Run("does not validate indexSearchable on primitive data types", func(t *testing.T) {
  1181  		nestedProperties := []*models.NestedProperty{}
  1182  		for _, pdt := range schema.PrimitiveDataTypes {
  1183  			switch pdt {
  1184  			case schema.DataTypeText, schema.DataTypeTextArray:
  1185  				continue
  1186  			case schema.DataTypeGeoCoordinates, schema.DataTypePhoneNumber:
  1187  				// skip - not supported as nested
  1188  				continue
  1189  			default:
  1190  				// do nothing
  1191  			}
  1192  
  1193  			t.Run(pdt.String(), func(t *testing.T) {
  1194  				nestedProperties = append(nestedProperties, &models.NestedProperty{
  1195  					Name:            "nested_" + pdt.AsName(),
  1196  					DataType:        pdt.PropString(),
  1197  					IndexFilterable: &vFalse,
  1198  					IndexSearchable: &vTrue,
  1199  					Tokenization:    "",
  1200  				})
  1201  
  1202  				for _, ndt := range schema.NestedDataTypes {
  1203  					t.Run(ndt.String(), func(t *testing.T) {
  1204  						propPrimitives := &models.Property{
  1205  							Name:             "objectProp",
  1206  							DataType:         ndt.PropString(),
  1207  							IndexFilterable:  &vFalse,
  1208  							IndexSearchable:  &vFalse,
  1209  							Tokenization:     "",
  1210  							NestedProperties: nestedProperties,
  1211  						}
  1212  						propLvl2Primitives := &models.Property{
  1213  							Name:            "objectPropLvl2",
  1214  							DataType:        ndt.PropString(),
  1215  							IndexFilterable: &vFalse,
  1216  							IndexSearchable: &vFalse,
  1217  							Tokenization:    "",
  1218  							NestedProperties: []*models.NestedProperty{
  1219  								{
  1220  									Name:             "nested_object",
  1221  									DataType:         ndt.PropString(),
  1222  									IndexFilterable:  &vFalse,
  1223  									IndexSearchable:  &vFalse,
  1224  									Tokenization:     "",
  1225  									NestedProperties: nestedProperties,
  1226  								},
  1227  							},
  1228  						}
  1229  
  1230  						for _, prop := range []*models.Property{propPrimitives, propLvl2Primitives} {
  1231  							t.Run(prop.Name, func(t *testing.T) {
  1232  								err := validateNestedProperties(prop.NestedProperties, prop.Name)
  1233  								assert.ErrorContains(t, err, prop.Name)
  1234  								assert.ErrorContains(t, err, "`indexSearchable` is not allowed for other than text/text[] data types")
  1235  							})
  1236  						}
  1237  					})
  1238  				}
  1239  			})
  1240  		}
  1241  	})
  1242  
  1243  	t.Run("does not validate indexSearchable on nested data types", func(t *testing.T) {
  1244  		nestedProperties := []*models.NestedProperty{
  1245  			{
  1246  				Name:            "nested_int",
  1247  				DataType:        schema.DataTypeInt.PropString(),
  1248  				IndexFilterable: &vFalse,
  1249  				IndexSearchable: &vFalse,
  1250  				Tokenization:    "",
  1251  			},
  1252  		}
  1253  
  1254  		for _, ndt := range schema.NestedDataTypes {
  1255  			t.Run(ndt.String(), func(t *testing.T) {
  1256  				propLvl2Primitives := &models.Property{
  1257  					Name:            "objectPropLvl2",
  1258  					DataType:        ndt.PropString(),
  1259  					IndexFilterable: &vFalse,
  1260  					IndexSearchable: &vFalse,
  1261  					Tokenization:    "",
  1262  					NestedProperties: []*models.NestedProperty{
  1263  						{
  1264  							Name:             "nested_object",
  1265  							DataType:         ndt.PropString(),
  1266  							IndexFilterable:  &vFalse,
  1267  							IndexSearchable:  &vTrue,
  1268  							Tokenization:     "",
  1269  							NestedProperties: nestedProperties,
  1270  						},
  1271  					},
  1272  				}
  1273  
  1274  				for _, prop := range []*models.Property{propLvl2Primitives} {
  1275  					t.Run(prop.Name, func(t *testing.T) {
  1276  						err := validateNestedProperties(prop.NestedProperties, prop.Name)
  1277  						assert.ErrorContains(t, err, prop.Name)
  1278  						assert.ErrorContains(t, err, "`indexSearchable` is not allowed for other than text/text[] data types")
  1279  					})
  1280  				}
  1281  			})
  1282  		}
  1283  	})
  1284  }
  1285  
  1286  type fakePropertyDataType struct {
  1287  	primitiveDataType schema.DataType
  1288  	nestedDataType    schema.DataType
  1289  }
  1290  
  1291  func newFakePrimitivePDT(primitiveDataType schema.DataType) schema.PropertyDataType {
  1292  	return &fakePropertyDataType{primitiveDataType: primitiveDataType}
  1293  }
  1294  
  1295  func newFakeNestedPDT(nestedDataType schema.DataType) schema.PropertyDataType {
  1296  	return &fakePropertyDataType{nestedDataType: nestedDataType}
  1297  }
  1298  
  1299  func (pdt *fakePropertyDataType) Kind() schema.PropertyKind {
  1300  	if pdt.IsPrimitive() {
  1301  		return schema.PropertyKindPrimitive
  1302  	}
  1303  	if pdt.IsNested() {
  1304  		return schema.PropertyKindNested
  1305  	}
  1306  	return schema.PropertyKindRef
  1307  }
  1308  
  1309  func (pdt *fakePropertyDataType) IsPrimitive() bool {
  1310  	return pdt.primitiveDataType != ""
  1311  }
  1312  
  1313  func (pdt *fakePropertyDataType) AsPrimitive() schema.DataType {
  1314  	return pdt.primitiveDataType
  1315  }
  1316  
  1317  func (pdt *fakePropertyDataType) IsReference() bool {
  1318  	return !pdt.IsPrimitive() && !pdt.IsNested()
  1319  }
  1320  
  1321  func (pdt *fakePropertyDataType) Classes() []schema.ClassName {
  1322  	if !pdt.IsReference() {
  1323  		return nil
  1324  	}
  1325  	return []schema.ClassName{}
  1326  }
  1327  
  1328  func (pdt *fakePropertyDataType) ContainsClass(name schema.ClassName) bool {
  1329  	return false
  1330  }
  1331  
  1332  func (pdt *fakePropertyDataType) IsNested() bool {
  1333  	return pdt.nestedDataType != ""
  1334  }
  1335  
  1336  func (pdt *fakePropertyDataType) AsNested() schema.DataType {
  1337  	return pdt.nestedDataType
  1338  }