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

     1  //                           _       _
     2  // __      _____  __ ___   ___  __ _| |_ ___
     3  // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
     4  //  \ V  V /  __/ (_| |\ V /| | (_| | ||  __/
     5  //   \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
     6  //
     7  //  Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
     8  //
     9  //  CONTACT: hello@weaviate.io
    10  //
    11  
    12  //go:build integrationTest
    13  
    14  package db
    15  
    16  import (
    17  	"context"
    18  	"testing"
    19  
    20  	"github.com/weaviate/weaviate/entities/dto"
    21  	"github.com/weaviate/weaviate/entities/filters"
    22  	enthnsw "github.com/weaviate/weaviate/entities/vectorindex/hnsw"
    23  
    24  	"github.com/go-openapi/strfmt"
    25  	"github.com/stretchr/testify/assert"
    26  
    27  	"github.com/google/uuid"
    28  	"github.com/sirupsen/logrus/hooks/test"
    29  	"github.com/stretchr/testify/require"
    30  	"github.com/weaviate/weaviate/entities/additional"
    31  	"github.com/weaviate/weaviate/entities/models"
    32  	"github.com/weaviate/weaviate/entities/schema"
    33  	"github.com/weaviate/weaviate/usecases/objects"
    34  )
    35  
    36  // Cannot filter for null state without enabling in the InvertedIndexConfig
    37  func TestFilterNullStateError(t *testing.T) {
    38  	class := createClassWithEverything(false, false)
    39  	migrator, repo, schemaGetter := createRepo(t)
    40  	defer repo.Shutdown(context.Background())
    41  	err := migrator.AddClass(context.Background(), class, schemaGetter.shardState)
    42  	require.Nil(t, err)
    43  	// update schema getter so it's in sync with class
    44  	schemaGetter.schema = schema.Schema{
    45  		Objects: &models.Schema{
    46  			Classes: []*models.Class{class},
    47  		},
    48  	}
    49  
    50  	nilFilter := &filters.LocalFilter{
    51  		Root: &filters.Clause{
    52  			Operator: filters.OperatorIsNull,
    53  			On: &filters.Path{
    54  				Class:    schema.ClassName(carClass.Class),
    55  				Property: schema.PropertyName(class.Properties[0].Name),
    56  			},
    57  			Value: &filters.Value{
    58  				Value: true,
    59  				Type:  schema.DataTypeBoolean,
    60  			},
    61  		},
    62  	}
    63  
    64  	params := dto.GetParams{
    65  		SearchVector: []float32{0.1, 0.1, 0.1, 1.1, 0.1},
    66  		ClassName:    class.Class,
    67  		Pagination:   &filters.Pagination{Limit: 5},
    68  		Filters:      nilFilter,
    69  	}
    70  	_, err = repo.Search(context.Background(), params)
    71  	require.NotNil(t, err)
    72  }
    73  
    74  func TestNullArrayClass(t *testing.T) {
    75  	arrayClass := createClassWithEverything(true, false)
    76  
    77  	names := []string{"elements", "batches"}
    78  	for _, name := range names {
    79  		t.Run("add nil object via "+name, func(t *testing.T) {
    80  			migrator, repo, schemaGetter := createRepo(t)
    81  			defer repo.Shutdown(context.Background())
    82  			err := migrator.AddClass(context.Background(), arrayClass, schemaGetter.shardState)
    83  			require.Nil(t, err)
    84  			// update schema getter so it's in sync with class
    85  			schemaGetter.schema = schema.Schema{
    86  				Objects: &models.Schema{
    87  					Classes: []*models.Class{arrayClass},
    88  				},
    89  			}
    90  
    91  			ObjectUuid1 := uuid.New()
    92  			arrayObjNil := &models.Object{
    93  				ID:    strfmt.UUID(ObjectUuid1.String()),
    94  				Class: "EverythingClass",
    95  				Properties: map[string]interface{}{
    96  					"strings":        nil,
    97  					"ints":           nil,
    98  					"datesAsStrings": nil,
    99  					"numbers":        nil,
   100  					"booleans":       nil,
   101  					"texts":          nil,
   102  					"number":         nil,
   103  					"boolean":        nil,
   104  					"int":            nil,
   105  					"string":         nil,
   106  					"text":           nil,
   107  					"phoneNumber":    nil,
   108  					"phoneNumbers":   nil,
   109  				},
   110  			}
   111  
   112  			ObjectUuid2 := uuid.New()
   113  			arrayObjEmpty := &models.Object{
   114  				ID:         strfmt.UUID(ObjectUuid2.String()),
   115  				Class:      "EverythingClass",
   116  				Properties: map[string]interface{}{},
   117  			}
   118  
   119  			if name == names[0] {
   120  				assert.Nil(t, repo.PutObject(context.Background(), arrayObjNil, []float32{1}, nil, nil))
   121  				assert.Nil(t, repo.PutObject(context.Background(), arrayObjEmpty, []float32{1}, nil, nil))
   122  
   123  			} else {
   124  				batch := make([]objects.BatchObject, 2)
   125  				batch[0] = objects.BatchObject{Object: arrayObjNil, UUID: arrayObjNil.ID}
   126  				batch[1] = objects.BatchObject{Object: arrayObjEmpty, UUID: arrayObjEmpty.ID}
   127  				_, err := repo.BatchPutObjects(context.Background(), batch, nil)
   128  				assert.Nil(t, err)
   129  			}
   130  
   131  			item1, err := repo.ObjectByID(context.Background(), arrayObjNil.ID, nil, additional.Properties{}, "")
   132  			assert.Nil(t, err)
   133  			item2, err := repo.ObjectByID(context.Background(), arrayObjEmpty.ID, nil, additional.Properties{}, "")
   134  			assert.Nil(t, err)
   135  
   136  			item1Schema := item1.Schema.(map[string]interface{})
   137  			item2Schema := item2.Schema.(map[string]interface{})
   138  			delete(item1Schema, "id")
   139  			delete(item2Schema, "id")
   140  			assert.Equal(t, item1Schema, item2Schema)
   141  		})
   142  	}
   143  }
   144  
   145  func createRepo(t *testing.T) (*Migrator, *DB, *fakeSchemaGetter) {
   146  	schemaGetter := &fakeSchemaGetter{
   147  		schema:     schema.Schema{Objects: &models.Schema{Classes: nil}},
   148  		shardState: singleShardState(),
   149  	}
   150  	logger, _ := test.NewNullLogger()
   151  	dirName := t.TempDir()
   152  	repo, err := New(logger, Config{
   153  		MemtablesFlushDirtyAfter:  60,
   154  		RootPath:                  dirName,
   155  		QueryMaximumResults:       10,
   156  		MaxImportGoroutinesFactor: 1,
   157  	}, &fakeRemoteClient{}, &fakeNodeResolver{}, &fakeRemoteNodeClient{}, &fakeReplicationClient{}, nil)
   158  	require.Nil(t, err)
   159  	repo.SetSchemaGetter(schemaGetter)
   160  	require.Nil(t, repo.WaitForStartup(testCtx()))
   161  	return NewMigrator(repo, logger), repo, schemaGetter
   162  }
   163  
   164  func createClassWithEverything(IndexNullState bool, IndexPropertyLength bool) *models.Class {
   165  	return &models.Class{
   166  		VectorIndexConfig: enthnsw.NewDefaultUserConfig(),
   167  		InvertedIndexConfig: &models.InvertedIndexConfig{
   168  			CleanupIntervalSeconds: 60,
   169  			Stopwords: &models.StopwordConfig{
   170  				Preset: "none",
   171  			},
   172  			IndexNullState:      IndexNullState,
   173  			IndexPropertyLength: IndexPropertyLength,
   174  		},
   175  		Class: "EverythingClass",
   176  		Properties: []*models.Property{
   177  			{
   178  				Name:         "strings",
   179  				DataType:     schema.DataTypeTextArray.PropString(),
   180  				Tokenization: models.PropertyTokenizationWhitespace,
   181  			},
   182  			{
   183  				Name:         "texts",
   184  				DataType:     []string{"text[]"},
   185  				Tokenization: models.PropertyTokenizationWord,
   186  			},
   187  			{
   188  				Name:     "numbers",
   189  				DataType: []string{"number[]"},
   190  			},
   191  			{
   192  				Name:     "ints",
   193  				DataType: []string{"int[]"},
   194  			},
   195  			{
   196  				Name:     "booleans",
   197  				DataType: []string{"boolean[]"},
   198  			},
   199  			{
   200  				Name:     "datesAsStrings",
   201  				DataType: []string{"date[]"},
   202  			},
   203  			{
   204  				Name:     "number",
   205  				DataType: []string{"number"},
   206  			},
   207  			{
   208  				Name:     "bool",
   209  				DataType: []string{"boolean"},
   210  			},
   211  			{
   212  				Name:     "int",
   213  				DataType: []string{"int"},
   214  			},
   215  			{
   216  				Name:         "string",
   217  				DataType:     schema.DataTypeText.PropString(),
   218  				Tokenization: models.PropertyTokenizationWhitespace,
   219  			},
   220  			{
   221  				Name:     "text",
   222  				DataType: []string{"text"},
   223  			},
   224  			{
   225  				Name:     "phoneNumber",
   226  				DataType: []string{"phoneNumber"},
   227  			},
   228  			{
   229  				Name:     "phoneNumbers",
   230  				DataType: []string{"phoneNumber[]"},
   231  			},
   232  		},
   233  	}
   234  }