Repository: weaviate/weaviate
Pull request #6055 (2024-10-18 14:04:04)

Fix nested props with nil value
### What's being changed:

title

Closes: https://github.com/weaviate/weaviate/issues/3885

Full example to reproduce:

```
import time

import weaviate
import weaviate.classes as wvc
from weaviate.collections.classes.data import DataObject

client = weaviate.connect_to_local()

client.collections.delete(name="xyz")
collection = client.collections.create(
    name="xyz",
    properties=[
        wvc.config.Property(
            name="data",
            data_type=wvc.config.DataType.OBJECT,
            nested_properties=wvc.config.Property(
                    name="text",
                    data_type=wvc.config.DataType.TEXT,
                )),
        wvc.config.Property(
            name="text",
            data_type=wvc.config.DataType.TEXT)
    ],
)

# collection.data.insert({"text": None})
collection.data.insert_many([
    DataObject(properties={"data": {"text": None}}),
])

response = collection.query.fetch_objects(include_vector=True)
print(len(response.objects))
for i, obj in enumerate(response.objects):
    print(obj.vector)
```


### Review checklist

- [ ] Documentation has been updated, if necessary. Link to changed documentation:
- [ ] Chaos pipeline run or not necessary. Link to pipeline:
- [ ] All new code is covered by tests where it is reasonable.
- [ ] Performance tests have been run or not necessary.

<!-- Uncomment the following section if this PR requires changes in related projects (e.g., documentation, client libraries).

GitHub actions will automatically create an issue in the corresponding repository for each checked box below. (See `.github/workflows/create-cross-functional-issues.yml`)

### Cross-functional impact

- [ ] This change requires public documentation (weaviate-io) to be updated. Check the box to automatically create a corresponding issue.
- Does it require a change in the client libraries? If yes, please check the boxes for the affected client libraries.
    - [ ] Python (weaviate-python-client)
    - [ ] JavaScript/TypeScript (typescript-client)
    - [ ] Go (weaviate-go-client)
    - [ ] Java (java-client)

-->



Patch:
diff --git a/usecases/objects/validation/properties_validation.go b/usecases/objects/validation/properties_validation.go
index cefdbbc887..85706a20cf 100644
--- a/usecases/objects/validation/properties_validation.go
+++ b/usecases/objects/validation/properties_validation.go
@@ -197,6 +197,10 @@ func objectVal(ctx context.Context, v *Validator, val interface{}, propertyPrefi
 			return nil, fmt.Errorf("property '%s': %w", propertyName, err)
 		}
 
+		if nestedValue == nil {
+			continue
+		}
+
 		var data interface{}
 		if schema.IsNested(*nestedDataType) {
 			data, err = v.extractAndValidateNestedProperty(ctx, propertyName, nestedValue,
diff --git a/usecases/schema/class.go b/usecases/schema/class.go
index c6ec28c417..8022f51e50 100644
--- a/usecases/schema/class.go
+++ b/usecases/schema/class.go
@@ -527,7 +527,7 @@ func (h *Handler) validateProperty(
 		}
 
 		if existingPropertyNames[strings.ToLower(property.Name)] {
-			return fmt.Errorf("class %q: conflict for property %q: already in use or provided multiple times", class.Class, property.Name)
+			return fmt.Errorf("class %q: conflict for property %q: already in use or provided multiple times", property.Name, class.Class)
 		}
 
 		// Validate data type of property.


Test patch:
diff --git a/test/acceptance/objects/objects_test.go b/test/acceptance/objects/objects_test.go
index c38b4d03fb..acaeb48dab 100644
--- a/test/acceptance/objects/objects_test.go
+++ b/test/acceptance/objects/objects_test.go
@@ -272,11 +272,11 @@ func creatingObjects(t *testing.T) {
 				return &models.Object{
 					Class: "TestObject",
 					Properties: map[string]interface{}{
-						"testReference": map[string]interface{}{
+						"testReference": []interface{}{map[string]interface{}{
 							"beacon": fakeObjectId,
 							"x":      nil,
 							"type":   "Object",
-						},
+						}},
 					},
 				}
 			},
diff --git a/test/acceptance/objects/setup_test.go b/test/acceptance/objects/setup_test.go
index ad1ef8c7df..6f97a8f46a 100644
--- a/test/acceptance/objects/setup_test.go
+++ b/test/acceptance/objects/setup_test.go
@@ -17,6 +17,8 @@ import (
 	"fmt"
 	"testing"
 
+	"github.com/weaviate/weaviate/test/docker"
+
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 	"github.com/weaviate/weaviate/client/objects"
@@ -24,7 +26,6 @@ import (
 	clschema "github.com/weaviate/weaviate/client/schema"
 	"github.com/weaviate/weaviate/entities/models"
 	"github.com/weaviate/weaviate/entities/schema"
-	"github.com/weaviate/weaviate/test/docker"
 	"github.com/weaviate/weaviate/test/helper"
 )
 
@@ -106,6 +107,10 @@ func testObjects(t *testing.T) {
 				Name:     "testWholeNumber",
 				DataType: []string{"int"},
 			},
+			{
+				Name:     "testReference",
+				DataType: []string{"TestObject"},
+			},
 			{
 				Name:     "testNumber",
 				DataType: []string{"number"},
diff --git a/usecases/objects/validation/properties_validation_test.go b/usecases/objects/validation/properties_validation_test.go
index 280cdc0fd3..2b4c90373f 100644
--- a/usecases/objects/validation/properties_validation_test.go
+++ b/usecases/objects/validation/properties_validation_test.go
@@ -157,7 +157,14 @@ func getDataType(dataType schema.DataType) *schema.DataType {
 func TestProperties(t *testing.T) {
 	const myBeacon = "weaviate://localhost/things/8e555f0d-8590-48c2-a9a6-70772ed14c0a"
 	myJournalClass := &models.Class{
-		Properties: []*models.Property{{Name: "inJournal", DataType: []string{"Journal"}}},
+		Properties: []*models.Property{
+			{Name: "inJournal", DataType: []string{"Journal"}},
+			{
+				Name:             "nested",
+				DataType:         schema.DataTypeObject.PropString(),
+				NestedProperties: []*models.NestedProperty{{Name: "nested2", DataType: schema.DataTypeObject.PropString()}},
+			},
+		},
 	}
 	specs := map[string]struct {
 		class     *models.Class
@@ -192,6 +199,14 @@ func TestProperties(t *testing.T) {
 			},
 			expBeacon: "weaviate://localhost/Journal/8e555f0d-8590-48c2-a9a6-70772ed14c0a",
 		},
+		"nested with nil": {
+			class: myJournalClass,
+			obj: &models.Object{
+				Properties: map[string]interface{}{"nested": map[string]interface{}{
+					"nested2": nil,
+				}},
+			},
+		},
 	}
 	for name, spec := range specs {
 		t.Run(name, func(t *testing.T) {
@@ -205,8 +220,11 @@ func TestProperties(t *testing.T) {
 			}
 			require.NoError(t, gotErr)
 			props := spec.obj.Properties
-			gotBeacon := extractBeacon(t, props)
-			assert.Equal(t, spec.expBeacon, gotBeacon)
+			if spec.expBeacon != "" {
+				gotBeacon := extractBeacon(t, props)
+				assert.Equal(t, spec.expBeacon, gotBeacon)
+
+			}
 		})
 	}
 }