github.com/weaviate/weaviate@v1.24.6/usecases/objects/auto_schema_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 objects
    13  
    14  import (
    15  	"context"
    16  	"encoding/json"
    17  	"fmt"
    18  	"reflect"
    19  	"testing"
    20  
    21  	"github.com/go-openapi/strfmt"
    22  	"github.com/sirupsen/logrus/hooks/test"
    23  	"github.com/stretchr/testify/assert"
    24  	"github.com/stretchr/testify/mock"
    25  	"github.com/stretchr/testify/require"
    26  	"github.com/weaviate/weaviate/entities/models"
    27  	"github.com/weaviate/weaviate/entities/schema"
    28  	"github.com/weaviate/weaviate/entities/schema/test_utils"
    29  	"github.com/weaviate/weaviate/entities/search"
    30  	"github.com/weaviate/weaviate/usecases/config"
    31  	"github.com/weaviate/weaviate/usecases/objects/validation"
    32  )
    33  
    34  func Test_autoSchemaManager_determineType(t *testing.T) {
    35  	type fields struct {
    36  		config config.AutoSchema
    37  	}
    38  	type args struct {
    39  		value interface{}
    40  	}
    41  
    42  	autoSchemaEnabledFields := fields{
    43  		config: config.AutoSchema{
    44  			Enabled: true,
    45  		},
    46  	}
    47  
    48  	tests := []struct {
    49  		name    string
    50  		fields  fields
    51  		args    args
    52  		want    []schema.DataType
    53  		errMsgs []string
    54  	}{
    55  		{
    56  			name: "fail determining type of nested array (1)",
    57  			fields: fields{
    58  				config: config.AutoSchema{
    59  					Enabled:       true,
    60  					DefaultString: schema.DataTypeText.String(),
    61  				},
    62  			},
    63  			args: args{
    64  				value: []interface{}{[]interface{}{"panic"}},
    65  			},
    66  			errMsgs: []string{"unrecognized data type"},
    67  		},
    68  		{
    69  			name: "fail determining type of nested array (2)",
    70  			fields: fields{
    71  				config: config.AutoSchema{
    72  					Enabled:       true,
    73  					DefaultString: schema.DataTypeText.String(),
    74  				},
    75  			},
    76  			args: args{
    77  				value: []interface{}{[]string{}},
    78  			},
    79  			errMsgs: []string{"unrecognized data type"},
    80  		},
    81  		{
    82  			name: "fail determining type of mixed elements in array",
    83  			fields: fields{
    84  				config: config.AutoSchema{
    85  					Enabled:       true,
    86  					DefaultString: schema.DataTypeText.String(),
    87  				},
    88  			},
    89  			args: args{
    90  				value: []interface{}{"something", false},
    91  			},
    92  			errMsgs: []string{"mismatched data type", "'text' expected, got 'boolean'"},
    93  		},
    94  		{
    95  			name: "fail determining type of mixed refs and objects (1)",
    96  			fields: fields{
    97  				config: config.AutoSchema{
    98  					Enabled: true,
    99  				},
   100  			},
   101  			args: args{
   102  				value: []interface{}{
   103  					map[string]interface{}{"beacon": "weaviate://localhost/df48b9f6-ba48-470c-bf6a-57657cb07390"},
   104  					map[string]interface{}{"propOfObject": "something"},
   105  				},
   106  			},
   107  			errMsgs: []string{"mismatched data type", "reference expected, got 'object'"},
   108  		},
   109  		{
   110  			name: "fail determining type of mixed refs and objects (2)",
   111  			fields: fields{
   112  				config: config.AutoSchema{
   113  					Enabled: true,
   114  				},
   115  			},
   116  			args: args{
   117  				value: []interface{}{
   118  					map[string]interface{}{"propOfObject": "something"},
   119  					map[string]interface{}{"beacon": "weaviate://localhost/df48b9f6-ba48-470c-bf6a-57657cb07390"},
   120  				},
   121  			},
   122  			errMsgs: []string{"mismatched data type", "'object' expected, got reference"},
   123  		},
   124  		{
   125  			name: "determine text",
   126  			fields: fields{
   127  				config: config.AutoSchema{
   128  					Enabled:       true,
   129  					DefaultString: schema.DataTypeText.String(),
   130  				},
   131  			},
   132  			args: args{
   133  				value: "string",
   134  			},
   135  			want: []schema.DataType{schema.DataTypeText},
   136  		},
   137  		{
   138  			name: "determine text (implicit)",
   139  			fields: fields{
   140  				config: config.AutoSchema{
   141  					Enabled: true,
   142  				},
   143  			},
   144  			args: args{
   145  				value: "string",
   146  			},
   147  			want: []schema.DataType{schema.DataTypeText},
   148  		},
   149  		{
   150  			name: "determine date",
   151  			fields: fields{
   152  				config: config.AutoSchema{
   153  					Enabled:     true,
   154  					DefaultDate: "date",
   155  				},
   156  			},
   157  			args: args{
   158  				value: "2002-10-02T15:00:00Z",
   159  			},
   160  			want: []schema.DataType{schema.DataTypeDate},
   161  		},
   162  		{
   163  			name: "determine uuid (1)",
   164  			fields: fields{
   165  				config: config.AutoSchema{
   166  					Enabled: true,
   167  				},
   168  			},
   169  			args: args{
   170  				value: "5b2cbe85-c38a-41f7-9e8c-7406ff6d15aa",
   171  			},
   172  			want: []schema.DataType{schema.DataTypeUUID},
   173  		},
   174  		{
   175  			name: "determine uuid (2)",
   176  			fields: fields{
   177  				config: config.AutoSchema{
   178  					Enabled: true,
   179  				},
   180  			},
   181  			args: args{
   182  				value: "5b2cbe85c38a41f79e8c7406ff6d15aa",
   183  			},
   184  			want: []schema.DataType{schema.DataTypeUUID},
   185  		},
   186  		{
   187  			name: "determine int",
   188  			fields: fields{
   189  				config: config.AutoSchema{
   190  					Enabled:       true,
   191  					DefaultNumber: "int",
   192  				},
   193  			},
   194  			args: args{
   195  				value: json.Number("1"),
   196  			},
   197  			want: []schema.DataType{schema.DataTypeInt},
   198  		},
   199  		{
   200  			name: "determine number",
   201  			fields: fields{
   202  				config: config.AutoSchema{
   203  					Enabled:       true,
   204  					DefaultNumber: "number",
   205  				},
   206  			},
   207  			args: args{
   208  				value: json.Number("1"),
   209  			},
   210  			want: []schema.DataType{schema.DataTypeNumber},
   211  		},
   212  		{
   213  			name: "determine boolean",
   214  			fields: fields{
   215  				config: config.AutoSchema{
   216  					Enabled:       true,
   217  					DefaultNumber: "number",
   218  				},
   219  			},
   220  			args: args{
   221  				value: true,
   222  			},
   223  			want: []schema.DataType{schema.DataTypeBoolean},
   224  		},
   225  		{
   226  			name: "determine geoCoordinates",
   227  			fields: fields{
   228  				config: config.AutoSchema{
   229  					Enabled: true,
   230  				},
   231  			},
   232  			args: args{
   233  				value: map[string]interface{}{
   234  					"latitude":  json.Number("1.1"),
   235  					"longitude": json.Number("1.1"),
   236  				},
   237  			},
   238  			want: []schema.DataType{schema.DataTypeGeoCoordinates},
   239  		},
   240  		{
   241  			name: "determine phoneNumber",
   242  			fields: fields{
   243  				config: config.AutoSchema{
   244  					Enabled: true,
   245  				},
   246  			},
   247  			args: args{
   248  				value: map[string]interface{}{
   249  					"input": "020 1234567",
   250  				},
   251  			},
   252  			want: []schema.DataType{schema.DataTypePhoneNumber},
   253  		},
   254  		{
   255  			name: "determine phoneNumber (2)",
   256  			fields: fields{
   257  				config: config.AutoSchema{
   258  					Enabled: true,
   259  				},
   260  			},
   261  			args: args{
   262  				value: map[string]interface{}{
   263  					"input":          "020 1234567",
   264  					"defaultCountry": "nl",
   265  				},
   266  			},
   267  			want: []schema.DataType{schema.DataTypePhoneNumber},
   268  		},
   269  		{
   270  			name: "determine cross reference",
   271  			fields: fields{
   272  				config: config.AutoSchema{
   273  					Enabled: true,
   274  				},
   275  			},
   276  			args: args{
   277  				value: []interface{}{
   278  					map[string]interface{}{"beacon": "weaviate://localhost/df48b9f6-ba48-470c-bf6a-57657cb07390"},
   279  				},
   280  			},
   281  			want: []schema.DataType{schema.DataType("Publication")},
   282  		},
   283  		{
   284  			name: "determine cross references",
   285  			fields: fields{
   286  				config: config.AutoSchema{
   287  					Enabled: true,
   288  				},
   289  			},
   290  			args: args{
   291  				value: []interface{}{
   292  					map[string]interface{}{"beacon": "weaviate://localhost/df48b9f6-ba48-470c-bf6a-57657cb07390"},
   293  					map[string]interface{}{"beacon": "weaviate://localhost/df48b9f6-ba48-470c-bf6a-57657cb07391"},
   294  				},
   295  			},
   296  			want: []schema.DataType{schema.DataType("Publication"), schema.DataType("Article")},
   297  		},
   298  		{
   299  			name: "determine text array",
   300  			fields: fields{
   301  				config: config.AutoSchema{
   302  					Enabled:       true,
   303  					DefaultString: schema.DataTypeText.String(),
   304  				},
   305  			},
   306  			args: args{
   307  				value: []interface{}{"a", "b"},
   308  			},
   309  			want: []schema.DataType{schema.DataTypeTextArray},
   310  		},
   311  		{
   312  			name: "determine text array (implicit)",
   313  			fields: fields{
   314  				config: config.AutoSchema{
   315  					Enabled: true,
   316  				},
   317  			},
   318  			args: args{
   319  				value: []interface{}{"a", "b"},
   320  			},
   321  			want: []schema.DataType{schema.DataTypeTextArray},
   322  		},
   323  		{
   324  			name: "determine int array",
   325  			fields: fields{
   326  				config: config.AutoSchema{
   327  					Enabled:       true,
   328  					DefaultNumber: "int",
   329  				},
   330  			},
   331  			args: args{
   332  				value: []interface{}{json.Number("11"), json.Number("12")},
   333  			},
   334  			want: []schema.DataType{schema.DataTypeIntArray},
   335  		},
   336  		{
   337  			name: "determine number array",
   338  			fields: fields{
   339  				config: config.AutoSchema{
   340  					Enabled:       true,
   341  					DefaultNumber: "number",
   342  				},
   343  			},
   344  			args: args{
   345  				value: []interface{}{json.Number("1.1"), json.Number("1.2")},
   346  			},
   347  			want: []schema.DataType{schema.DataTypeNumberArray},
   348  		},
   349  		{
   350  			name: "determine boolean array",
   351  			fields: fields{
   352  				config: config.AutoSchema{
   353  					Enabled: true,
   354  				},
   355  			},
   356  			args: args{
   357  				value: []interface{}{true, false},
   358  			},
   359  			want: []schema.DataType{schema.DataTypeBooleanArray},
   360  		},
   361  		{
   362  			name: "determine date array",
   363  			fields: fields{
   364  				config: config.AutoSchema{
   365  					Enabled:     true,
   366  					DefaultDate: "date",
   367  				},
   368  			},
   369  			args: args{
   370  				value: []interface{}{"2002-10-02T15:00:00Z", "2002-10-02T15:01:00Z"},
   371  			},
   372  			want: []schema.DataType{schema.DataTypeDateArray},
   373  		},
   374  		{
   375  			name: "determine uuid array (1)",
   376  			fields: fields{
   377  				config: config.AutoSchema{
   378  					Enabled: true,
   379  				},
   380  			},
   381  			args: args{
   382  				value: []interface{}{
   383  					"5b2cbe85-c38a-41f7-9e8c-7406ff6d15aa",
   384  					"57a8564d-089b-4cd9-be39-56681605e0da",
   385  				},
   386  			},
   387  			want: []schema.DataType{schema.DataTypeUUIDArray},
   388  		},
   389  		{
   390  			name: "determine uuid array (2)",
   391  			fields: fields{
   392  				config: config.AutoSchema{
   393  					Enabled: true,
   394  				},
   395  			},
   396  			args: args{
   397  				value: []interface{}{
   398  					"5b2cbe85c38a41f79e8c7406ff6d15aa",
   399  					"57a8564d089b4cd9be3956681605e0da",
   400  				},
   401  			},
   402  			want: []schema.DataType{schema.DataTypeUUIDArray},
   403  		},
   404  		{
   405  			name: "[deprecated string] determine string",
   406  			fields: fields{
   407  				config: config.AutoSchema{
   408  					Enabled:       true,
   409  					DefaultString: schema.DataTypeString.String(),
   410  				},
   411  			},
   412  			args: args{
   413  				value: "string",
   414  			},
   415  			want: []schema.DataType{schema.DataTypeString},
   416  		},
   417  		{
   418  			name: "[deprecated string] determine string array",
   419  			fields: fields{
   420  				config: config.AutoSchema{
   421  					Enabled:       true,
   422  					DefaultString: schema.DataTypeString.String(),
   423  				},
   424  			},
   425  			args: args{
   426  				value: []interface{}{"a", "b"},
   427  			},
   428  			want: []schema.DataType{schema.DataTypeStringArray},
   429  		},
   430  		{
   431  			name:   "determine object",
   432  			fields: autoSchemaEnabledFields,
   433  			args: args{
   434  				value: map[string]interface{}{
   435  					"some_number": 1.23,
   436  					"some_bool":   false,
   437  				},
   438  			},
   439  			want: []schema.DataType{schema.DataTypeObject},
   440  		},
   441  		{
   442  			name:   "determine object array",
   443  			fields: autoSchemaEnabledFields,
   444  			args: args{
   445  				value: []interface{}{
   446  					map[string]interface{}{
   447  						"some_number": 1.23,
   448  						"some_bool":   false,
   449  					},
   450  				},
   451  			},
   452  			want: []schema.DataType{schema.DataTypeObjectArray},
   453  		},
   454  		{
   455  			name:   "determine object, not geoCoordinates (too few props 1)",
   456  			fields: autoSchemaEnabledFields,
   457  			args: args{
   458  				value: map[string]interface{}{
   459  					"latitude": json.Number("1.1"),
   460  				},
   461  			},
   462  			want: []schema.DataType{schema.DataTypeObject},
   463  		},
   464  		{
   465  			name:   "determine object, not geoCoordinates (too few props 2)",
   466  			fields: autoSchemaEnabledFields,
   467  			args: args{
   468  				value: map[string]interface{}{
   469  					"longitude": json.Number("1.1"),
   470  				},
   471  			},
   472  			want: []schema.DataType{schema.DataTypeObject},
   473  		},
   474  		{
   475  			name:   "determine object, not geoCoordinates (too many props)",
   476  			fields: autoSchemaEnabledFields,
   477  			args: args{
   478  				value: map[string]interface{}{
   479  					"latitude":   json.Number("1.1"),
   480  					"longitude":  json.Number("1.1"),
   481  					"unrelevant": "some text",
   482  				},
   483  			},
   484  			want: []schema.DataType{schema.DataTypeObject},
   485  		},
   486  		{
   487  			name:   "determine object, not phoneNumber (too few props)",
   488  			fields: autoSchemaEnabledFields,
   489  			args: args{
   490  				value: map[string]interface{}{
   491  					"defaultCountry": "nl",
   492  				},
   493  			},
   494  			want: []schema.DataType{schema.DataTypeObject},
   495  		},
   496  		{
   497  			name:   "determine object, not phoneNumber (too many props)",
   498  			fields: autoSchemaEnabledFields,
   499  			args: args{
   500  				value: map[string]interface{}{
   501  					"input":                  "020 1234567",
   502  					"defaultCountry":         "nl",
   503  					"internationalFormatted": "+31 20 1234567",
   504  					"countryCode":            31,
   505  					"national":               201234567,
   506  					"nationalFormatted":      "020 1234567",
   507  					"valid":                  true,
   508  				},
   509  			},
   510  			want: []schema.DataType{schema.DataTypeObject},
   511  		},
   512  	}
   513  	for _, tt := range tests {
   514  		vectorRepo := &fakeVectorRepo{}
   515  		vectorRepo.On("ObjectByID", strfmt.UUID("df48b9f6-ba48-470c-bf6a-57657cb07390"), mock.Anything, mock.Anything, mock.Anything).
   516  			Return(&search.Result{ClassName: "Publication"}, nil).Once()
   517  		vectorRepo.On("ObjectByID", strfmt.UUID("df48b9f6-ba48-470c-bf6a-57657cb07391"), mock.Anything, mock.Anything, mock.Anything).
   518  			Return(&search.Result{ClassName: "Article"}, nil).Once()
   519  		m := &autoSchemaManager{
   520  			schemaManager: &fakeSchemaManager{},
   521  			vectorRepo:    vectorRepo,
   522  			config:        tt.fields.config,
   523  		}
   524  		t.Run(tt.name, func(t *testing.T) {
   525  			got, err := m.determineType(tt.args.value, false)
   526  			if len(tt.errMsgs) == 0 {
   527  				require.NoError(t, err)
   528  				if !reflect.DeepEqual(got, tt.want) {
   529  					t.Errorf("autoSchemaManager.determineType() = %v, want %v", got, tt.want)
   530  				}
   531  			} else {
   532  				for _, errMsg := range tt.errMsgs {
   533  					require.ErrorContains(t, err, errMsg)
   534  				}
   535  				assert.Nil(t, got)
   536  			}
   537  		})
   538  	}
   539  }
   540  
   541  func Test_autoSchemaManager_autoSchema_emptyRequest(t *testing.T) {
   542  	// given
   543  	vectorRepo := &fakeVectorRepo{}
   544  	vectorRepo.On("ObjectByID", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
   545  		Return(&search.Result{ClassName: "Publication"}, nil).Once()
   546  	schemaManager := &fakeSchemaManager{}
   547  	logger, _ := test.NewNullLogger()
   548  	autoSchemaManager := &autoSchemaManager{
   549  		schemaManager: schemaManager,
   550  		vectorRepo:    vectorRepo,
   551  		config: config.AutoSchema{
   552  			Enabled:       true,
   553  			DefaultString: schema.DataTypeText.String(),
   554  			DefaultNumber: "number",
   555  			DefaultDate:   "date",
   556  		},
   557  		logger: logger,
   558  	}
   559  
   560  	var obj *models.Object
   561  
   562  	err := autoSchemaManager.autoSchema(context.Background(), &models.Principal{}, obj, true)
   563  	assert.EqualError(t, fmt.Errorf(validation.ErrorMissingObject), err.Error())
   564  }
   565  
   566  func Test_autoSchemaManager_autoSchema_create(t *testing.T) {
   567  	// given
   568  	vectorRepo := &fakeVectorRepo{}
   569  	vectorRepo.On("ObjectByID", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
   570  		Return(&search.Result{ClassName: "Publication"}, nil).Once()
   571  	schemaManager := &fakeSchemaManager{}
   572  	logger, _ := test.NewNullLogger()
   573  	autoSchemaManager := &autoSchemaManager{
   574  		schemaManager: schemaManager,
   575  		vectorRepo:    vectorRepo,
   576  		config: config.AutoSchema{
   577  			Enabled:       true,
   578  			DefaultString: schema.DataTypeText.String(),
   579  			DefaultNumber: "number",
   580  			DefaultDate:   "date",
   581  		},
   582  		logger: logger,
   583  	}
   584  	obj := &models.Object{
   585  		Class: "Publication",
   586  		Properties: map[string]interface{}{
   587  			"name":            "Jodie Sparrow",
   588  			"age":             json.Number("30"),
   589  			"publicationDate": "2002-10-02T15:00:00Z",
   590  			"textArray":       []interface{}{"a", "b"},
   591  			"numberArray":     []interface{}{json.Number("30")},
   592  		},
   593  	}
   594  	// when
   595  	schemaBefore := schemaManager.GetSchemaResponse
   596  	err := autoSchemaManager.autoSchema(context.Background(), &models.Principal{}, obj, true)
   597  	schemaAfter := schemaManager.GetSchemaResponse
   598  
   599  	// then
   600  	require.Nil(t, schemaBefore.Objects)
   601  	require.Nil(t, err)
   602  	require.NotNil(t, schemaAfter.Objects)
   603  	assert.Equal(t, 1, len(schemaAfter.Objects.Classes))
   604  	assert.Equal(t, "Publication", (schemaAfter.Objects.Classes)[0].Class)
   605  	assert.Equal(t, 5, len((schemaAfter.Objects.Classes)[0].Properties))
   606  	require.NotNil(t, getProperty((schemaAfter.Objects.Classes)[0].Properties, "name"))
   607  	assert.Equal(t, "name", getProperty((schemaAfter.Objects.Classes)[0].Properties, "name").Name)
   608  	assert.Equal(t, "text", getProperty((schemaAfter.Objects.Classes)[0].Properties, "name").DataType[0])
   609  	require.NotNil(t, getProperty((schemaAfter.Objects.Classes)[0].Properties, "age"))
   610  	assert.Equal(t, "age", getProperty((schemaAfter.Objects.Classes)[0].Properties, "age").Name)
   611  	assert.Equal(t, "number", getProperty((schemaAfter.Objects.Classes)[0].Properties, "age").DataType[0])
   612  	require.NotNil(t, getProperty((schemaAfter.Objects.Classes)[0].Properties, "publicationDate"))
   613  	assert.Equal(t, "publicationDate", getProperty((schemaAfter.Objects.Classes)[0].Properties, "publicationDate").Name)
   614  	assert.Equal(t, "date", getProperty((schemaAfter.Objects.Classes)[0].Properties, "publicationDate").DataType[0])
   615  	require.NotNil(t, getProperty((schemaAfter.Objects.Classes)[0].Properties, "textArray"))
   616  	assert.Equal(t, "textArray", getProperty((schemaAfter.Objects.Classes)[0].Properties, "textArray").Name)
   617  	assert.Equal(t, "text[]", getProperty((schemaAfter.Objects.Classes)[0].Properties, "textArray").DataType[0])
   618  	require.NotNil(t, getProperty((schemaAfter.Objects.Classes)[0].Properties, "numberArray"))
   619  	assert.Equal(t, "numberArray", getProperty((schemaAfter.Objects.Classes)[0].Properties, "numberArray").Name)
   620  	assert.Equal(t, "number[]", getProperty((schemaAfter.Objects.Classes)[0].Properties, "numberArray").DataType[0])
   621  }
   622  
   623  func Test_autoSchemaManager_autoSchema_update(t *testing.T) {
   624  	// given
   625  	vectorRepo := &fakeVectorRepo{}
   626  	vectorRepo.On("ObjectByID", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
   627  		Return(&search.Result{ClassName: "Publication"}, nil).Once()
   628  	logger, _ := test.NewNullLogger()
   629  	schemaManager := &fakeSchemaManager{
   630  		GetSchemaResponse: schema.Schema{
   631  			Objects: &models.Schema{
   632  				Classes: []*models.Class{
   633  					{
   634  						Class: "Publication",
   635  						Properties: []*models.Property{
   636  							{
   637  								Name:     "age",
   638  								DataType: []string{"int"},
   639  							},
   640  						},
   641  					},
   642  				},
   643  			},
   644  		},
   645  	}
   646  	autoSchemaManager := &autoSchemaManager{
   647  		schemaManager: schemaManager,
   648  		vectorRepo:    vectorRepo,
   649  		config: config.AutoSchema{
   650  			Enabled:       true,
   651  			DefaultString: schema.DataTypeText.String(),
   652  			DefaultNumber: "int",
   653  			DefaultDate:   "date",
   654  		},
   655  		logger: logger,
   656  	}
   657  	obj := &models.Object{
   658  		Class: "Publication",
   659  		Properties: map[string]interface{}{
   660  			"name":            "Jodie Sparrow",
   661  			"age":             json.Number("30"),
   662  			"publicationDate": "2002-10-02T15:00:00Z",
   663  			"textArray":       []interface{}{"a", "b"},
   664  			"numberArray":     []interface{}{json.Number("30")},
   665  		},
   666  	}
   667  	// when
   668  	// then
   669  	schemaBefore := schemaManager.GetSchemaResponse
   670  	require.NotNil(t, schemaBefore.Objects)
   671  	assert.Equal(t, 1, len(schemaBefore.Objects.Classes))
   672  	assert.Equal(t, "Publication", (schemaBefore.Objects.Classes)[0].Class)
   673  	assert.Equal(t, 1, len((schemaBefore.Objects.Classes)[0].Properties))
   674  	assert.Equal(t, "age", (schemaBefore.Objects.Classes)[0].Properties[0].Name)
   675  	assert.Equal(t, "int", (schemaBefore.Objects.Classes)[0].Properties[0].DataType[0])
   676  
   677  	err := autoSchemaManager.autoSchema(context.Background(), &models.Principal{}, obj, true)
   678  	require.Nil(t, err)
   679  
   680  	schemaAfter := schemaManager.GetSchemaResponse
   681  	require.NotNil(t, schemaAfter.Objects)
   682  	assert.Equal(t, 1, len(schemaAfter.Objects.Classes))
   683  	assert.Equal(t, "Publication", (schemaAfter.Objects.Classes)[0].Class)
   684  	assert.Equal(t, 5, len((schemaAfter.Objects.Classes)[0].Properties))
   685  	require.NotNil(t, getProperty((schemaAfter.Objects.Classes)[0].Properties, "age"))
   686  	assert.Equal(t, "age", getProperty((schemaAfter.Objects.Classes)[0].Properties, "age").Name)
   687  	assert.Equal(t, "int", getProperty((schemaAfter.Objects.Classes)[0].Properties, "age").DataType[0])
   688  	require.NotNil(t, getProperty((schemaAfter.Objects.Classes)[0].Properties, "name"))
   689  	assert.Equal(t, "name", getProperty((schemaAfter.Objects.Classes)[0].Properties, "name").Name)
   690  	assert.Equal(t, "text", getProperty((schemaAfter.Objects.Classes)[0].Properties, "name").DataType[0])
   691  	require.NotNil(t, getProperty((schemaAfter.Objects.Classes)[0].Properties, "publicationDate"))
   692  	assert.Equal(t, "publicationDate", getProperty((schemaAfter.Objects.Classes)[0].Properties, "publicationDate").Name)
   693  	assert.Equal(t, "date", getProperty((schemaAfter.Objects.Classes)[0].Properties, "publicationDate").DataType[0])
   694  	require.NotNil(t, getProperty((schemaAfter.Objects.Classes)[0].Properties, "textArray"))
   695  	assert.Equal(t, "textArray", getProperty((schemaAfter.Objects.Classes)[0].Properties, "textArray").Name)
   696  	assert.Equal(t, "text[]", getProperty((schemaAfter.Objects.Classes)[0].Properties, "textArray").DataType[0])
   697  	require.NotNil(t, getProperty((schemaAfter.Objects.Classes)[0].Properties, "numberArray"))
   698  	assert.Equal(t, "numberArray", getProperty((schemaAfter.Objects.Classes)[0].Properties, "numberArray").Name)
   699  	assert.Equal(t, "int[]", getProperty((schemaAfter.Objects.Classes)[0].Properties, "numberArray").DataType[0])
   700  }
   701  
   702  func Test_autoSchemaManager_getProperties(t *testing.T) {
   703  	type testCase struct {
   704  		name               string
   705  		valProperties      map[string]interface{}
   706  		expectedProperties []*models.Property
   707  	}
   708  
   709  	testCases := []testCase{
   710  		{
   711  			name: "mixed 1",
   712  			valProperties: map[string]interface{}{
   713  				"name": "someName",
   714  				"objectProperty": map[string]interface{}{
   715  					"nested_int":  json.Number("123"),
   716  					"nested_text": "some text",
   717  					"nested_objects": []interface{}{
   718  						map[string]interface{}{
   719  							"nested_bool_lvl2": false,
   720  							"nested_numbers_lvl2": []interface{}{
   721  								json.Number("11.11"),
   722  							},
   723  						},
   724  					},
   725  				},
   726  			},
   727  			expectedProperties: []*models.Property{
   728  				{
   729  					Name:     "name",
   730  					DataType: schema.DataTypeText.PropString(),
   731  				},
   732  				{
   733  					Name:     "objectProperty",
   734  					DataType: schema.DataTypeObject.PropString(),
   735  					NestedProperties: []*models.NestedProperty{
   736  						{
   737  							Name:     "nested_int",
   738  							DataType: schema.DataTypeNumber.PropString(),
   739  						},
   740  						{
   741  							Name:     "nested_text",
   742  							DataType: schema.DataTypeText.PropString(),
   743  						},
   744  						{
   745  							Name:     "nested_objects",
   746  							DataType: schema.DataTypeObjectArray.PropString(),
   747  							NestedProperties: []*models.NestedProperty{
   748  								{
   749  									Name:     "nested_bool_lvl2",
   750  									DataType: schema.DataTypeBoolean.PropString(),
   751  								},
   752  								{
   753  									Name:     "nested_numbers_lvl2",
   754  									DataType: schema.DataTypeNumberArray.PropString(),
   755  								},
   756  							},
   757  						},
   758  					},
   759  				},
   760  			},
   761  		},
   762  		{
   763  			name: "mixed 2",
   764  			valProperties: map[string]interface{}{
   765  				"name": "someName",
   766  				"objectProperty": map[string]interface{}{
   767  					"nested_number": json.Number("123"),
   768  					"nested_text":   "some text",
   769  					"nested_objects": []interface{}{
   770  						map[string]interface{}{
   771  							"nested_date_lvl2": "2022-01-01T00:00:00+02:00",
   772  							"nested_numbers_lvl2": []interface{}{
   773  								json.Number("11.11"),
   774  							},
   775  						},
   776  					},
   777  				},
   778  			},
   779  			expectedProperties: []*models.Property{
   780  				{
   781  					Name:     "name",
   782  					DataType: schema.DataTypeText.PropString(),
   783  				},
   784  				{
   785  					Name:     "objectProperty",
   786  					DataType: schema.DataTypeObject.PropString(),
   787  					NestedProperties: []*models.NestedProperty{
   788  						{
   789  							Name:     "nested_number",
   790  							DataType: schema.DataTypeNumber.PropString(),
   791  						},
   792  						{
   793  							Name:     "nested_text",
   794  							DataType: schema.DataTypeText.PropString(),
   795  						},
   796  						{
   797  							Name:     "nested_objects",
   798  							DataType: schema.DataTypeObjectArray.PropString(),
   799  							NestedProperties: []*models.NestedProperty{
   800  								{
   801  									Name:     "nested_date_lvl2",
   802  									DataType: schema.DataTypeDate.PropString(),
   803  								},
   804  								{
   805  									Name:     "nested_numbers_lvl2",
   806  									DataType: schema.DataTypeNumberArray.PropString(),
   807  								},
   808  							},
   809  						},
   810  					},
   811  				},
   812  			},
   813  		},
   814  		{
   815  			name: "ref",
   816  			valProperties: map[string]interface{}{
   817  				"name": "someName",
   818  				"objectProperty": map[string]interface{}{
   819  					"nested_ref_wannabe": []interface{}{
   820  						map[string]interface{}{
   821  							"beacon": "weaviate://localhost/Soup/8c156d37-81aa-4ce9-a811-621e2702b825",
   822  						},
   823  					},
   824  					"nested_objects": []interface{}{
   825  						map[string]interface{}{
   826  							"nested_ref_wannabe_lvl2": []interface{}{
   827  								map[string]interface{}{
   828  									"beacon": "weaviate://localhost/Soup/8c156d37-81aa-4ce9-a811-621e2702b825",
   829  								},
   830  							},
   831  						},
   832  					},
   833  				},
   834  				"ref": []interface{}{
   835  					map[string]interface{}{
   836  						"beacon": "weaviate://localhost/Soup/8c156d37-81aa-4ce9-a811-621e2702b825",
   837  					},
   838  				},
   839  			},
   840  			expectedProperties: []*models.Property{
   841  				{
   842  					Name:     "name",
   843  					DataType: schema.DataTypeText.PropString(),
   844  				},
   845  				{
   846  					Name:     "objectProperty",
   847  					DataType: schema.DataTypeObject.PropString(),
   848  					NestedProperties: []*models.NestedProperty{
   849  						{
   850  							Name:     "nested_ref_wannabe",
   851  							DataType: schema.DataTypeObjectArray.PropString(),
   852  							NestedProperties: []*models.NestedProperty{
   853  								{
   854  									Name:     "beacon",
   855  									DataType: schema.DataTypeText.PropString(),
   856  								},
   857  							},
   858  						},
   859  						{
   860  							Name:     "nested_objects",
   861  							DataType: schema.DataTypeObjectArray.PropString(),
   862  							NestedProperties: []*models.NestedProperty{
   863  								{
   864  									Name:     "nested_ref_wannabe_lvl2",
   865  									DataType: schema.DataTypeObjectArray.PropString(),
   866  									NestedProperties: []*models.NestedProperty{
   867  										{
   868  											Name:     "beacon",
   869  											DataType: schema.DataTypeText.PropString(),
   870  										},
   871  									},
   872  								},
   873  							},
   874  						},
   875  					},
   876  				},
   877  				{
   878  					Name:     "ref",
   879  					DataType: []string{"Soup"},
   880  				},
   881  			},
   882  		},
   883  		{
   884  			name: "phone",
   885  			valProperties: map[string]interface{}{
   886  				"name": "someName",
   887  				"objectProperty": map[string]interface{}{
   888  					"nested_phone_wannabe": map[string]interface{}{
   889  						"input":          "020 1234567",
   890  						"defaultCountry": "nl",
   891  					},
   892  					"nested_phone_wannabes": []interface{}{
   893  						map[string]interface{}{
   894  							"input":          "020 1234567",
   895  							"defaultCountry": "nl",
   896  						},
   897  					},
   898  					"nested_objects": []interface{}{
   899  						map[string]interface{}{
   900  							"nested_phone_wannabe_lvl2": map[string]interface{}{
   901  								"input":          "020 1234567",
   902  								"defaultCountry": "nl",
   903  							},
   904  							"nested_phone_wannabes_lvl2": []interface{}{
   905  								map[string]interface{}{
   906  									"input":          "020 1234567",
   907  									"defaultCountry": "nl",
   908  								},
   909  							},
   910  						},
   911  					},
   912  				},
   913  				"phone": map[string]interface{}{
   914  					"input":          "020 1234567",
   915  					"defaultCountry": "nl",
   916  				},
   917  				"phone_wannabes": []interface{}{
   918  					map[string]interface{}{
   919  						"input":          "020 1234567",
   920  						"defaultCountry": "nl",
   921  					},
   922  				},
   923  			},
   924  			expectedProperties: []*models.Property{
   925  				{
   926  					Name:     "name",
   927  					DataType: schema.DataTypeText.PropString(),
   928  				},
   929  				{
   930  					Name:     "objectProperty",
   931  					DataType: schema.DataTypeObject.PropString(),
   932  					NestedProperties: []*models.NestedProperty{
   933  						{
   934  							Name:     "nested_phone_wannabe",
   935  							DataType: schema.DataTypeObject.PropString(),
   936  							NestedProperties: []*models.NestedProperty{
   937  								{
   938  									Name:     "input",
   939  									DataType: schema.DataTypeText.PropString(),
   940  								},
   941  								{
   942  									Name:     "defaultCountry",
   943  									DataType: schema.DataTypeText.PropString(),
   944  								},
   945  							},
   946  						},
   947  						{
   948  							Name:     "nested_phone_wannabes",
   949  							DataType: schema.DataTypeObjectArray.PropString(),
   950  							NestedProperties: []*models.NestedProperty{
   951  								{
   952  									Name:     "input",
   953  									DataType: schema.DataTypeText.PropString(),
   954  								},
   955  								{
   956  									Name:     "defaultCountry",
   957  									DataType: schema.DataTypeText.PropString(),
   958  								},
   959  							},
   960  						},
   961  						{
   962  							Name:     "nested_objects",
   963  							DataType: schema.DataTypeObjectArray.PropString(),
   964  							NestedProperties: []*models.NestedProperty{
   965  								{
   966  									Name:     "nested_phone_wannabe_lvl2",
   967  									DataType: schema.DataTypeObject.PropString(),
   968  									NestedProperties: []*models.NestedProperty{
   969  										{
   970  											Name:     "input",
   971  											DataType: schema.DataTypeText.PropString(),
   972  										},
   973  										{
   974  											Name:     "defaultCountry",
   975  											DataType: schema.DataTypeText.PropString(),
   976  										},
   977  									},
   978  								},
   979  								{
   980  									Name:     "nested_phone_wannabes_lvl2",
   981  									DataType: schema.DataTypeObjectArray.PropString(),
   982  									NestedProperties: []*models.NestedProperty{
   983  										{
   984  											Name:     "input",
   985  											DataType: schema.DataTypeText.PropString(),
   986  										},
   987  										{
   988  											Name:     "defaultCountry",
   989  											DataType: schema.DataTypeText.PropString(),
   990  										},
   991  									},
   992  								},
   993  							},
   994  						},
   995  					},
   996  				},
   997  				{
   998  					Name:     "phone",
   999  					DataType: schema.DataTypePhoneNumber.PropString(),
  1000  				},
  1001  				{
  1002  					Name:     "phone_wannabes",
  1003  					DataType: schema.DataTypeObjectArray.PropString(),
  1004  					NestedProperties: []*models.NestedProperty{
  1005  						{
  1006  							Name:     "input",
  1007  							DataType: schema.DataTypeText.PropString(),
  1008  						},
  1009  						{
  1010  							Name:     "defaultCountry",
  1011  							DataType: schema.DataTypeText.PropString(),
  1012  						},
  1013  					},
  1014  				},
  1015  			},
  1016  		},
  1017  		{
  1018  			name: "geo",
  1019  			valProperties: map[string]interface{}{
  1020  				"name": "someName",
  1021  				"objectProperty": map[string]interface{}{
  1022  					"nested_geo_wannabe": map[string]interface{}{
  1023  						"latitude":  json.Number("1.1"),
  1024  						"longitude": json.Number("2.2"),
  1025  					},
  1026  					"nested_geo_wannabes": []interface{}{
  1027  						map[string]interface{}{
  1028  							"latitude":  json.Number("1.1"),
  1029  							"longitude": json.Number("2.2"),
  1030  						},
  1031  					},
  1032  					"nested_objects": []interface{}{
  1033  						map[string]interface{}{
  1034  							"nested_geo_wannabe_lvl2": map[string]interface{}{
  1035  								"latitude":  json.Number("1.1"),
  1036  								"longitude": json.Number("2.2"),
  1037  							},
  1038  							"nested_geo_wannabes_lvl2": []interface{}{
  1039  								map[string]interface{}{
  1040  									"latitude":  json.Number("1.1"),
  1041  									"longitude": json.Number("2.2"),
  1042  								},
  1043  							},
  1044  						},
  1045  					},
  1046  				},
  1047  				"geo": map[string]interface{}{
  1048  					"latitude":  json.Number("1.1"),
  1049  					"longitude": json.Number("2.2"),
  1050  				},
  1051  				"geo_wannabes": []interface{}{
  1052  					map[string]interface{}{
  1053  						"latitude":  json.Number("1.1"),
  1054  						"longitude": json.Number("2.2"),
  1055  					},
  1056  				},
  1057  			},
  1058  			expectedProperties: []*models.Property{
  1059  				{
  1060  					Name:     "name",
  1061  					DataType: schema.DataTypeText.PropString(),
  1062  				},
  1063  				{
  1064  					Name:     "objectProperty",
  1065  					DataType: schema.DataTypeObject.PropString(),
  1066  					NestedProperties: []*models.NestedProperty{
  1067  						{
  1068  							Name:     "nested_geo_wannabe",
  1069  							DataType: schema.DataTypeObject.PropString(),
  1070  							NestedProperties: []*models.NestedProperty{
  1071  								{
  1072  									Name:     "latitude",
  1073  									DataType: schema.DataTypeNumber.PropString(),
  1074  								},
  1075  								{
  1076  									Name:     "longitude",
  1077  									DataType: schema.DataTypeNumber.PropString(),
  1078  								},
  1079  							},
  1080  						},
  1081  						{
  1082  							Name:     "nested_geo_wannabes",
  1083  							DataType: schema.DataTypeObjectArray.PropString(),
  1084  							NestedProperties: []*models.NestedProperty{
  1085  								{
  1086  									Name:     "latitude",
  1087  									DataType: schema.DataTypeNumber.PropString(),
  1088  								},
  1089  								{
  1090  									Name:     "longitude",
  1091  									DataType: schema.DataTypeNumber.PropString(),
  1092  								},
  1093  							},
  1094  						},
  1095  						{
  1096  							Name:     "nested_objects",
  1097  							DataType: schema.DataTypeObjectArray.PropString(),
  1098  							NestedProperties: []*models.NestedProperty{
  1099  								{
  1100  									Name:     "nested_geo_wannabe_lvl2",
  1101  									DataType: schema.DataTypeObject.PropString(),
  1102  									NestedProperties: []*models.NestedProperty{
  1103  										{
  1104  											Name:     "latitude",
  1105  											DataType: schema.DataTypeNumber.PropString(),
  1106  										},
  1107  										{
  1108  											Name:     "longitude",
  1109  											DataType: schema.DataTypeNumber.PropString(),
  1110  										},
  1111  									},
  1112  								},
  1113  								{
  1114  									Name:     "nested_geo_wannabes_lvl2",
  1115  									DataType: schema.DataTypeObjectArray.PropString(),
  1116  									NestedProperties: []*models.NestedProperty{
  1117  										{
  1118  											Name:     "latitude",
  1119  											DataType: schema.DataTypeNumber.PropString(),
  1120  										},
  1121  										{
  1122  											Name:     "longitude",
  1123  											DataType: schema.DataTypeNumber.PropString(),
  1124  										},
  1125  									},
  1126  								},
  1127  							},
  1128  						},
  1129  					},
  1130  				},
  1131  				{
  1132  					Name:     "geo",
  1133  					DataType: schema.DataTypeGeoCoordinates.PropString(),
  1134  				},
  1135  				{
  1136  					Name:     "geo_wannabes",
  1137  					DataType: schema.DataTypeObjectArray.PropString(),
  1138  					NestedProperties: []*models.NestedProperty{
  1139  						{
  1140  							Name:     "latitude",
  1141  							DataType: schema.DataTypeNumber.PropString(),
  1142  						},
  1143  						{
  1144  							Name:     "longitude",
  1145  							DataType: schema.DataTypeNumber.PropString(),
  1146  						},
  1147  					},
  1148  				},
  1149  			},
  1150  		},
  1151  	}
  1152  
  1153  	manager := &autoSchemaManager{
  1154  		schemaManager: &fakeSchemaManager{},
  1155  		vectorRepo:    &fakeVectorRepo{},
  1156  		config: config.AutoSchema{
  1157  			Enabled:       true,
  1158  			DefaultNumber: schema.DataTypeNumber.String(),
  1159  			DefaultString: schema.DataTypeText.String(),
  1160  			DefaultDate:   schema.DataTypeDate.String(),
  1161  		},
  1162  	}
  1163  
  1164  	for i, tc := range testCases {
  1165  		t.Run(fmt.Sprintf("testCase_%d", i), func(t *testing.T) {
  1166  			properties, _ := manager.getProperties(&models.Object{
  1167  				Class:      "ClassWithObjectProps",
  1168  				Properties: tc.valProperties,
  1169  			})
  1170  
  1171  			assertPropsMatch(t, tc.expectedProperties, properties)
  1172  		})
  1173  	}
  1174  }
  1175  
  1176  func Test_autoSchemaManager_perform_withNested(t *testing.T) {
  1177  	logger, _ := test.NewNullLogger()
  1178  	className := "ClassWithObjectProps"
  1179  
  1180  	class := &models.Class{
  1181  		Class: className,
  1182  		Properties: []*models.Property{
  1183  			{
  1184  				Name:     "name",
  1185  				DataType: schema.DataTypeText.PropString(),
  1186  			},
  1187  			{
  1188  				Name:     "objectProperty",
  1189  				DataType: schema.DataTypeObject.PropString(),
  1190  				NestedProperties: []*models.NestedProperty{
  1191  					{
  1192  						Name:     "nested_int",
  1193  						DataType: schema.DataTypeNumber.PropString(),
  1194  					},
  1195  					{
  1196  						Name:     "nested_text",
  1197  						DataType: schema.DataTypeText.PropString(),
  1198  					},
  1199  					{
  1200  						Name:     "nested_objects",
  1201  						DataType: schema.DataTypeObjectArray.PropString(),
  1202  						NestedProperties: []*models.NestedProperty{
  1203  							{
  1204  								Name:     "nested_bool_lvl2",
  1205  								DataType: schema.DataTypeBoolean.PropString(),
  1206  							},
  1207  							{
  1208  								Name:     "nested_numbers_lvl2",
  1209  								DataType: schema.DataTypeNumberArray.PropString(),
  1210  							},
  1211  						},
  1212  					},
  1213  				},
  1214  			},
  1215  		},
  1216  	}
  1217  	object := &models.Object{
  1218  		Class: className,
  1219  		Properties: map[string]interface{}{
  1220  			"name": "someName",
  1221  			"objectProperty": map[string]interface{}{
  1222  				"nested_number": json.Number("123"),
  1223  				"nested_text":   "some text",
  1224  				"nested_objects": []interface{}{
  1225  					map[string]interface{}{
  1226  						"nested_date_lvl2": "2022-01-01T00:00:00+02:00",
  1227  						"nested_numbers_lvl2": []interface{}{
  1228  							json.Number("11.11"),
  1229  						},
  1230  						"nested_phone_wannabe_lvl2": map[string]interface{}{
  1231  							"input":          "020 1234567",
  1232  							"defaultCountry": "nl",
  1233  						},
  1234  						"nested_phone_wannabes_lvl2": []interface{}{
  1235  							map[string]interface{}{
  1236  								"input":          "020 1234567",
  1237  								"defaultCountry": "nl",
  1238  							},
  1239  						},
  1240  					},
  1241  				},
  1242  				"nested_phone_wannabe": map[string]interface{}{
  1243  					"input":          "020 1234567",
  1244  					"defaultCountry": "nl",
  1245  				},
  1246  				"nested_phone_wannabes": []interface{}{
  1247  					map[string]interface{}{
  1248  						"input":          "020 1234567",
  1249  						"defaultCountry": "nl",
  1250  					},
  1251  				},
  1252  			},
  1253  			"phone": map[string]interface{}{
  1254  				"input":          "020 1234567",
  1255  				"defaultCountry": "nl",
  1256  			},
  1257  			"phone_wannabes": []interface{}{
  1258  				map[string]interface{}{
  1259  					"input":          "020 1234567",
  1260  					"defaultCountry": "nl",
  1261  				},
  1262  			},
  1263  			"objectPropertyGeo": map[string]interface{}{
  1264  				"nested_objects": []interface{}{
  1265  					map[string]interface{}{
  1266  						"nested_geo_wannabe_lvl2": map[string]interface{}{
  1267  							"latitude":  json.Number("1.1"),
  1268  							"longitude": json.Number("2.2"),
  1269  						},
  1270  						"nested_geo_wannabes_lvl2": []interface{}{
  1271  							map[string]interface{}{
  1272  								"latitude":  json.Number("1.1"),
  1273  								"longitude": json.Number("2.2"),
  1274  							},
  1275  						},
  1276  					},
  1277  				},
  1278  				"nested_geo_wannabe": map[string]interface{}{
  1279  					"latitude":  json.Number("1.1"),
  1280  					"longitude": json.Number("2.2"),
  1281  				},
  1282  				"nested_geo_wannabes": []interface{}{
  1283  					map[string]interface{}{
  1284  						"latitude":  json.Number("1.1"),
  1285  						"longitude": json.Number("2.2"),
  1286  					},
  1287  				},
  1288  			},
  1289  			"geo": map[string]interface{}{
  1290  				"latitude":  json.Number("1.1"),
  1291  				"longitude": json.Number("2.2"),
  1292  			},
  1293  			"geo_wannabes": []interface{}{
  1294  				map[string]interface{}{
  1295  					"latitude":  json.Number("1.1"),
  1296  					"longitude": json.Number("2.2"),
  1297  				},
  1298  			},
  1299  		},
  1300  	}
  1301  	expectedClass := &models.Class{
  1302  		Class: className,
  1303  		Properties: []*models.Property{
  1304  			{
  1305  				Name:     "name",
  1306  				DataType: schema.DataTypeText.PropString(),
  1307  			},
  1308  			{
  1309  				Name:     "objectProperty",
  1310  				DataType: schema.DataTypeObject.PropString(),
  1311  				NestedProperties: []*models.NestedProperty{
  1312  					{
  1313  						Name:     "nested_int",
  1314  						DataType: schema.DataTypeNumber.PropString(),
  1315  					},
  1316  					{
  1317  						Name:     "nested_number",
  1318  						DataType: schema.DataTypeNumber.PropString(),
  1319  					},
  1320  					{
  1321  						Name:     "nested_text",
  1322  						DataType: schema.DataTypeText.PropString(),
  1323  					},
  1324  					{
  1325  						Name:     "nested_phone_wannabe",
  1326  						DataType: schema.DataTypeObject.PropString(),
  1327  						NestedProperties: []*models.NestedProperty{
  1328  							{
  1329  								Name:     "input",
  1330  								DataType: schema.DataTypeText.PropString(),
  1331  							},
  1332  							{
  1333  								Name:     "defaultCountry",
  1334  								DataType: schema.DataTypeText.PropString(),
  1335  							},
  1336  						},
  1337  					},
  1338  					{
  1339  						Name:     "nested_phone_wannabes",
  1340  						DataType: schema.DataTypeObjectArray.PropString(),
  1341  						NestedProperties: []*models.NestedProperty{
  1342  							{
  1343  								Name:     "input",
  1344  								DataType: schema.DataTypeText.PropString(),
  1345  							},
  1346  							{
  1347  								Name:     "defaultCountry",
  1348  								DataType: schema.DataTypeText.PropString(),
  1349  							},
  1350  						},
  1351  					},
  1352  					{
  1353  						Name:     "nested_objects",
  1354  						DataType: schema.DataTypeObjectArray.PropString(),
  1355  						NestedProperties: []*models.NestedProperty{
  1356  							{
  1357  								Name:     "nested_bool_lvl2",
  1358  								DataType: schema.DataTypeBoolean.PropString(),
  1359  							},
  1360  							{
  1361  								Name:     "nested_date_lvl2",
  1362  								DataType: schema.DataTypeDate.PropString(),
  1363  							},
  1364  							{
  1365  								Name:     "nested_numbers_lvl2",
  1366  								DataType: schema.DataTypeNumberArray.PropString(),
  1367  							},
  1368  							{
  1369  								Name:     "nested_phone_wannabe_lvl2",
  1370  								DataType: schema.DataTypeObject.PropString(),
  1371  								NestedProperties: []*models.NestedProperty{
  1372  									{
  1373  										Name:     "input",
  1374  										DataType: schema.DataTypeText.PropString(),
  1375  									},
  1376  									{
  1377  										Name:     "defaultCountry",
  1378  										DataType: schema.DataTypeText.PropString(),
  1379  									},
  1380  								},
  1381  							},
  1382  							{
  1383  								Name:     "nested_phone_wannabes_lvl2",
  1384  								DataType: schema.DataTypeObjectArray.PropString(),
  1385  								NestedProperties: []*models.NestedProperty{
  1386  									{
  1387  										Name:     "input",
  1388  										DataType: schema.DataTypeText.PropString(),
  1389  									},
  1390  									{
  1391  										Name:     "defaultCountry",
  1392  										DataType: schema.DataTypeText.PropString(),
  1393  									},
  1394  								},
  1395  							},
  1396  						},
  1397  					},
  1398  				},
  1399  			},
  1400  			{
  1401  				Name:     "phone",
  1402  				DataType: schema.DataTypePhoneNumber.PropString(),
  1403  			},
  1404  			{
  1405  				Name:     "phone_wannabes",
  1406  				DataType: schema.DataTypeObjectArray.PropString(),
  1407  				NestedProperties: []*models.NestedProperty{
  1408  					{
  1409  						Name:     "input",
  1410  						DataType: schema.DataTypeText.PropString(),
  1411  					},
  1412  					{
  1413  						Name:     "defaultCountry",
  1414  						DataType: schema.DataTypeText.PropString(),
  1415  					},
  1416  				},
  1417  			},
  1418  			{
  1419  				Name:     "objectPropertyGeo",
  1420  				DataType: schema.DataTypeObject.PropString(),
  1421  				NestedProperties: []*models.NestedProperty{
  1422  					{
  1423  						Name:     "nested_geo_wannabe",
  1424  						DataType: schema.DataTypeObject.PropString(),
  1425  						NestedProperties: []*models.NestedProperty{
  1426  							{
  1427  								Name:     "latitude",
  1428  								DataType: schema.DataTypeNumber.PropString(),
  1429  							},
  1430  							{
  1431  								Name:     "longitude",
  1432  								DataType: schema.DataTypeNumber.PropString(),
  1433  							},
  1434  						},
  1435  					},
  1436  					{
  1437  						Name:     "nested_geo_wannabes",
  1438  						DataType: schema.DataTypeObjectArray.PropString(),
  1439  						NestedProperties: []*models.NestedProperty{
  1440  							{
  1441  								Name:     "latitude",
  1442  								DataType: schema.DataTypeNumber.PropString(),
  1443  							},
  1444  							{
  1445  								Name:     "longitude",
  1446  								DataType: schema.DataTypeNumber.PropString(),
  1447  							},
  1448  						},
  1449  					},
  1450  					{
  1451  						Name:     "nested_objects",
  1452  						DataType: schema.DataTypeObjectArray.PropString(),
  1453  						NestedProperties: []*models.NestedProperty{
  1454  							{
  1455  								Name:     "nested_geo_wannabe_lvl2",
  1456  								DataType: schema.DataTypeObject.PropString(),
  1457  								NestedProperties: []*models.NestedProperty{
  1458  									{
  1459  										Name:     "latitude",
  1460  										DataType: schema.DataTypeNumber.PropString(),
  1461  									},
  1462  									{
  1463  										Name:     "longitude",
  1464  										DataType: schema.DataTypeNumber.PropString(),
  1465  									},
  1466  								},
  1467  							},
  1468  							{
  1469  								Name:     "nested_geo_wannabes_lvl2",
  1470  								DataType: schema.DataTypeObjectArray.PropString(),
  1471  								NestedProperties: []*models.NestedProperty{
  1472  									{
  1473  										Name:     "latitude",
  1474  										DataType: schema.DataTypeNumber.PropString(),
  1475  									},
  1476  									{
  1477  										Name:     "longitude",
  1478  										DataType: schema.DataTypeNumber.PropString(),
  1479  									},
  1480  								},
  1481  							},
  1482  						},
  1483  					},
  1484  				},
  1485  			},
  1486  			{
  1487  				Name:     "geo",
  1488  				DataType: schema.DataTypeGeoCoordinates.PropString(),
  1489  			},
  1490  			{
  1491  				Name:     "geo_wannabes",
  1492  				DataType: schema.DataTypeObjectArray.PropString(),
  1493  				NestedProperties: []*models.NestedProperty{
  1494  					{
  1495  						Name:     "latitude",
  1496  						DataType: schema.DataTypeNumber.PropString(),
  1497  					},
  1498  					{
  1499  						Name:     "longitude",
  1500  						DataType: schema.DataTypeNumber.PropString(),
  1501  					},
  1502  				},
  1503  			},
  1504  		},
  1505  	}
  1506  
  1507  	schemaManager := &fakeSchemaManager{
  1508  		GetSchemaResponse: schema.Schema{
  1509  			Objects: &models.Schema{
  1510  				Classes: []*models.Class{class},
  1511  			},
  1512  		},
  1513  	}
  1514  	manager := &autoSchemaManager{
  1515  		schemaManager: schemaManager,
  1516  		vectorRepo:    &fakeVectorRepo{},
  1517  		config: config.AutoSchema{
  1518  			Enabled:       true,
  1519  			DefaultNumber: schema.DataTypeNumber.String(),
  1520  			DefaultString: schema.DataTypeText.String(),
  1521  			DefaultDate:   schema.DataTypeDate.String(),
  1522  		},
  1523  		logger: logger,
  1524  	}
  1525  
  1526  	err := manager.autoSchema(context.Background(), &models.Principal{}, object, true)
  1527  	require.NoError(t, err)
  1528  
  1529  	schemaAfter := schemaManager.GetSchemaResponse
  1530  	require.NotNil(t, schemaAfter.Objects)
  1531  	require.Len(t, schemaAfter.Objects.Classes, 1)
  1532  	require.Equal(t, className, schemaAfter.Objects.Classes[0].Class)
  1533  
  1534  	assertPropsMatch(t, expectedClass.Properties, schemaAfter.Objects.Classes[0].Properties)
  1535  }
  1536  
  1537  func getProperty(properties []*models.Property, name string) *models.Property {
  1538  	for _, prop := range properties {
  1539  		if prop.Name == name {
  1540  			return prop
  1541  		}
  1542  	}
  1543  	return nil
  1544  }
  1545  
  1546  func assertPropsMatch(t *testing.T, propsA, propsB []*models.Property) {
  1547  	require.Len(t, propsB, len(propsA), "props: different length")
  1548  
  1549  	pMap := map[string]int{}
  1550  	for index, p := range propsA {
  1551  		pMap[p.Name] = index
  1552  	}
  1553  
  1554  	for _, pB := range propsB {
  1555  		require.Contains(t, pMap, pB.Name)
  1556  		pA := propsA[pMap[pB.Name]]
  1557  
  1558  		assert.Equal(t, pA.DataType, pB.DataType)
  1559  		test_utils.AssertNestedPropsMatch(t, pA.NestedProperties, pB.NestedProperties)
  1560  	}
  1561  }