-
Notifications
You must be signed in to change notification settings - Fork 195
Support nullable vectors in Java SDK #1730
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: marcelo-cjl The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
WalkthroughAdds end-to-end nullable vector support: client-side packing now records and transmits per-element validity masks; packing/unpacking logic was extended to preserve and re-insert nulls when returning vector fields; schema conversion and runtime validation enforce that vector fields added to existing collections must be nullable; a Java example and integration tests exercise nullable vectors across multiple vector types, including dynamic addition of nullable fields and search/query behavior with mixed null/non-null data. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant MilvusClient as Milvus Client
participant ParamUtils
participant FieldWrapper as FieldDataWrapper
participant CollService as CollectionService
rect rgba(200,220,255,0.15)
Note over Client,MilvusClient: Insert with nullable vectors
Client->>MilvusClient: insert(payload with nullable vectors)
MilvusClient->>ParamUtils: genVectorField(fieldValues)
ParamUtils->>ParamUtils: build validity mask\nfilter null entries
ParamUtils-->>MilvusClient: packed vectors + validity mask
MilvusClient->>MilvusClient: send to Milvus
end
rect rgba(200,255,220,0.12)
Note over Client,FieldWrapper: Query/Search and unpack
Client->>MilvusClient: query()/search()
MilvusClient->>FieldWrapper: getVectorData(dt, vector, validData)
FieldWrapper->>FieldWrapper: assemble packed vectors
FieldWrapper->>FieldWrapper: map invalid positions -> null
FieldWrapper-->>MilvusClient: results with nulls restored
MilvusClient-->>Client: response
end
rect rgba(255,230,210,0.12)
Note over Client,CollService: Add field validation
Client->>CollService: addCollectionField(newField)
CollService->>CollService: convertToGrpcFieldSchema(newField, true)
CollService->>CollService: SchemaUtils validates nullable constraint
alt non-nullable vector
CollService-->>Client: INVALID_PARAMS error
else nullable vector
CollService-->>Client: field added
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (4)
sdk-core/src/main/java/io/milvus/v2/service/collection/CollectionService.java (1)
657-664: Consider reusingParamUtils.isVectorDataType()to avoid duplication.This helper duplicates logic from
ParamUtils.isVectorDataType(). While there's a type mismatch (io.milvus.v2.common.DataTypevsio.milvus.grpc.DataType), consider either:
- Converting the type before calling
ParamUtils.isVectorDataType()- Adding a similar method to a shared utility class
This would reduce maintenance burden if new vector types are added.
sdk-core/src/test/java/io/milvus/v2/service/vector/NullableVectorTest.java (3)
79-85: Consider externalizing the Milvus connection URI.The hardcoded
localhost:19530may not work in CI environments or when Milvus runs in a different location. Consider using an environment variable with a fallback:String uri = System.getenv().getOrDefault("MILVUS_URI", "http://localhost:19530");
316-334: Random null distribution may cause test flakiness.Using
RANDOM.nextInt(100) < nullPercentwithout a seed could theoretically produce edge cases (e.g., all nulls or no nulls) that break assertions on lines 383-384. Consider:
- Using a fixed seed:
new Random(42)for reproducibility- Or ensuring at least one null and one non-null entry explicitly
🔎 Suggested fix
- private static final Random RANDOM = new Random(); + private static final Random RANDOM = new Random(42); // Fixed seed for reproducibility
358-359: Avoid fixedThread.sleep()for data availability.Using
Thread.sleep(1000)is fragile and may cause flakiness under load. Consider polling with a timeout or using Milvus's flush/sync mechanisms if available to ensure data is queryable.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
examples/src/main/java/io/milvus/v2/NullableVectorExample.javasdk-core/src/main/java/io/milvus/param/ParamUtils.javasdk-core/src/main/java/io/milvus/response/FieldDataWrapper.javasdk-core/src/main/java/io/milvus/v2/service/collection/CollectionService.javasdk-core/src/test/java/io/milvus/v2/service/vector/NullableVectorTest.java
🧰 Additional context used
🧬 Code graph analysis (2)
sdk-core/src/test/java/io/milvus/v2/service/vector/NullableVectorTest.java (11)
tests/milvustestv2/src/main/java/com/zilliz/milvustestv2/utils/Float16Utils.java (1)
Float16Utils(9-245)sdk-core/src/main/java/io/milvus/v2/client/ConnectConfig.java (1)
ConnectConfig(31-455)sdk-core/src/main/java/io/milvus/v2/common/IndexParam.java (1)
IndexParam(24-245)sdk-core/src/main/java/io/milvus/v2/service/index/request/CreateIndexReq.java (1)
CreateIndexReq(26-144)sdk-core/src/main/java/io/milvus/v2/service/collection/request/DropCollectionReq.java (1)
DropCollectionReq(22-120)sdk-core/src/main/java/io/milvus/v2/service/collection/request/CreateCollectionReq.java (2)
CreateCollectionReq(36-1112)CollectionSchema(404-529)sdk-core/src/main/java/io/milvus/v2/service/collection/request/AddCollectionFieldReq.java (1)
AddCollectionFieldReq(22-83)sdk-core/src/main/java/io/milvus/v2/service/collection/request/LoadCollectionReq.java (1)
LoadCollectionReq(25-227)sdk-core/src/main/java/io/milvus/v2/service/vector/request/InsertReq.java (1)
InsertReq(26-141)sdk-core/src/main/java/io/milvus/v2/service/vector/request/QueryReq.java (1)
QueryReq(26-277)sdk-core/src/main/java/io/milvus/v2/service/vector/request/SearchReq.java (1)
SearchReq(32-482)
examples/src/main/java/io/milvus/v2/NullableVectorExample.java (8)
sdk-core/src/main/java/io/milvus/v2/common/IndexParam.java (1)
IndexParam(24-245)sdk-core/src/main/java/io/milvus/v2/service/collection/request/AddCollectionFieldReq.java (1)
AddCollectionFieldReq(22-83)sdk-core/src/main/java/io/milvus/v2/service/collection/request/DropCollectionReq.java (1)
DropCollectionReq(22-120)sdk-core/src/main/java/io/milvus/v2/service/collection/request/ReleaseCollectionReq.java (1)
ReleaseCollectionReq(22-118)sdk-core/src/main/java/io/milvus/v2/service/index/request/CreateIndexReq.java (1)
CreateIndexReq(26-144)sdk-core/src/main/java/io/milvus/v2/service/vector/request/InsertReq.java (1)
InsertReq(26-141)sdk-core/src/main/java/io/milvus/v2/service/vector/response/InsertResp.java (1)
InsertResp(25-81)sdk-core/src/main/java/io/milvus/v2/service/vector/response/SearchResp.java (1)
SearchResp(27-185)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Deploy milvus server,build and test
- GitHub Check: Build and test
- GitHub Check: Summary
🔇 Additional comments (7)
sdk-core/src/main/java/io/milvus/v2/service/collection/CollectionService.java (1)
265-271: LGTM - Proper validation for nullable vector fields.The validation correctly enforces that vector fields added to existing collections must be nullable. This aligns with Milvus server constraints where existing rows need null values for new vector fields.
sdk-core/src/main/java/io/milvus/param/ParamUtils.java (2)
1207-1216: LGTM - Correct nullable vector handling.The implementation properly:
- Records validity mask per element
- Filters out null values before vector construction
- Maintains alignment between validity mask and data
This mirrors the scalar field handling pattern on lines 1220-1228.
1241-1255: No issues found. The code correctly handles empty nullable vector fields by returningdim=0, which the Milvus server accepts. The schema dimension is preserved separately at the collection schema level and is not affected by the vector field data's dim value. The existing test coverage (NullableVectorTest.java) validates this behavior across all vector types.sdk-core/src/main/java/io/milvus/response/FieldDataWrapper.java (3)
141-144: LGTM - Correct row count handling for nullable vectors.When a validity mask is present, using
validData.size()correctly returns the total row count including nulls, rather than computing from the (filtered) vector data.Also applies to: 159-162, 170-173
291-369: LGTM - Well-structured nullable vector data handling.The refactored implementation:
- Packs vector data into appropriate format (List<List>, List, etc.)
- Correctly maps invalid positions to null using the validity mask
- Uses
dataIdxcounter to track position in packed (non-null) dataThe logic correctly handles the case where
packDatacontains only valid entries whilevalidDataindicates which positions should be null.
442-443: Verify null validData is intentional for struct vector elements.Passing
nullforvalidDatawhen processingArrayOfVectorelements assumes inner vectors within structs don't support nullable entries. Confirm this aligns with Milvus server behavior for nested vector fields.examples/src/main/java/io/milvus/v2/NullableVectorExample.java (1)
46-318: Good example demonstrating nullable vector usage.The example clearly shows:
- Creating collections with nullable vector fields
- Inserting data with null and non-null vectors
- Adding nullable vector fields to existing collections
- Querying and searching behavior with null vectors
The code is well-documented and serves as a practical reference for users.
afbe9f3 to
2c92a7a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (3)
sdk-core/src/test/java/io/milvus/v2/service/vector/NullableVectorTest.java (2)
319-328: Consider using a fixed seed for test reproducibility.Using
RANDOM.nextInt(100) < nullPercentmakes test outcomes non-deterministic. If a test fails, it may be difficult to reproduce the exact data distribution.🔎 Suggested improvement for deterministic tests
- private static final Random RANDOM = new Random(); + private static final Random RANDOM = new Random(42); // Fixed seed for reproducibilityAlternatively, use a predictable pattern:
boolean isNull = (i % 2 == 0); // Every other row is null
361-362: Consider replacing sleep with a polling mechanism for more reliable tests.
Thread.sleep(1000)can be flaky—data might not be available in time on slower systems, or it wastes time on faster ones. A retry loop with a timeout would be more robust, though acceptable for integration tests.sdk-core/src/main/java/io/milvus/response/FieldDataWrapper.java (1)
354-366: Add defensive bounds check to prevent potential IndexOutOfBoundsException.If the server sends inconsistent data where
validDatahas moretruevalues than elements inpackData, line 360 will throw anIndexOutOfBoundsExceptionwithout a helpful message. A defensive check would aid debugging.🔎 Suggested defensive check
if (validData != null && !validData.isEmpty()) { List<Object> result = new ArrayList<>(); int dataIdx = 0; for (Boolean valid : validData) { if (valid) { + if (dataIdx >= packData.size()) { + throw new IllegalResponseException( + String.format("ValidData/packData size mismatch: expected more data at index %d, packData size is %d", + dataIdx, packData.size())); + } result.add(packData.get(dataIdx++)); } else { result.add(null); } } return result; }
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
examples/src/main/java/io/milvus/v2/NullableVectorExample.javasdk-core/src/main/java/io/milvus/param/ParamUtils.javasdk-core/src/main/java/io/milvus/response/FieldDataWrapper.javasdk-core/src/main/java/io/milvus/v2/service/collection/CollectionService.javasdk-core/src/test/java/io/milvus/v2/service/vector/NullableVectorTest.java
🚧 Files skipped from review as they are similar to previous changes (3)
- examples/src/main/java/io/milvus/v2/NullableVectorExample.java
- sdk-core/src/main/java/io/milvus/v2/service/collection/CollectionService.java
- sdk-core/src/main/java/io/milvus/param/ParamUtils.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Deploy milvus server,build and test
- GitHub Check: Build and test
- GitHub Check: Summary
🔇 Additional comments (9)
sdk-core/src/test/java/io/milvus/v2/service/vector/NullableVectorTest.java (4)
43-64: LGTM - Well-structured test setup with comprehensive vector type coverage.The test class is well-organized with clear configuration for all six vector types. The use of Testcontainers ensures isolated integration testing.
110-165: LGTM - Vector generation covers all types correctly.The implementation handles each vector type appropriately. The
rewind()calls after populating ByteBuffers ensure they're ready for reading.
90-108: LGTM - Cleanup logic handles failures gracefully.Swallowing exceptions during test teardown is appropriate to ensure cleanup attempts don't mask actual test failures.
206-259: LGTM - Good negative test for validation logic.The test correctly verifies that adding a non-nullable vector field to an existing collection fails with an appropriate error message.
sdk-core/src/main/java/io/milvus/response/FieldDataWrapper.java (5)
140-144: LGTM - Correct precedence for validData in row count calculation.When a validity mask exists, its size correctly represents the total row count, including null values.
294-311: LGTM - Good guard clause for zero-dimension edge case.The
if (dim > 0)check prevents potential issues with division and unnecessary processing for edge cases.
278-289: LGTM - Correctly handles immutability of gRPC lists.The copy is necessary since gRPC-generated lists are immutable. The size equality check ensures data consistency before modification.
439-446: Verify: Nullable vectors not supported in ArrayOfStruct fields.Passing
nullforvalidDatameans vectors nested within struct arrays won't have null handling applied. Confirm this is intentional behavior.
291-292: LGTM - Method signature properly extended for nullable support.The new
validDataparameter is correctly passed from all call sites, enabling proper null handling throughout the vector data retrieval path.
2c92a7a to
a2c0994
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
sdk-core/src/main/java/io/milvus/response/FieldDataWrapper.java (1)
354-368: Nullable vector reconstruction logic is correct.The reconstruction correctly reverses the null-filtering done in
ParamUtils.genFieldData:
- Iterate through
validDatabitmap- For
true, take next element frompackData(line 360)- For
false, insertnull(line 362)The logic assumes
packData.size()equals the count oftruevalues invalidData, which should hold if the write path (ParamUtils) is consistent.Optional: Consider adding a defensive bounds check before
packData.get(dataIdx++)to catch inconsistencies between write and read paths, though this is likely unnecessary given the controlled flow.Optional: Add defensive bounds check
if (validData != null && !validData.isEmpty()) { List<Object> result = new ArrayList<>(); int dataIdx = 0; for (Boolean valid : validData) { if (valid) { + if (dataIdx >= packData.size()) { + throw new IllegalResponseException("Inconsistent validData and packData sizes"); + } result.add(packData.get(dataIdx++)); } else { result.add(null); } } return result; }
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
examples/src/main/java/io/milvus/v2/NullableVectorExample.javasdk-core/src/main/java/io/milvus/param/ParamUtils.javasdk-core/src/main/java/io/milvus/response/FieldDataWrapper.javasdk-core/src/main/java/io/milvus/v2/service/collection/CollectionService.javasdk-core/src/test/java/io/milvus/v2/service/vector/NullableVectorTest.java
🚧 Files skipped from review as they are similar to previous changes (1)
- sdk-core/src/main/java/io/milvus/v2/service/collection/CollectionService.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build and test
- GitHub Check: Summary
🔇 Additional comments (8)
sdk-core/src/main/java/io/milvus/param/ParamUtils.java (2)
1207-1216: LGTM! Clean nullable vector handling.The validity bitmap construction and null-filtering logic is correct. For each vector in the input list, you record whether it's non-null in
validData, then pass only the non-null vectors togenVectorField. This approach cleanly separates the validity mask from the packed vector data and mirrors the existing pattern for nullable scalar fields (lines 1220-1229).
1241-1255: Early return for empty vectors is correct.When all vectors are null (after filtering in
genFieldData), the objects list becomes empty. Returning aVectorFieldwithdim=0and empty data is appropriate since there's no data to infer the dimension from.However, verify that the server correctly handles
dim=0FieldData with allvalidData=falseentries. Note thatDataUtils.javaexplicitly rejectsdim=0vectors in struct field contexts, suggesting potential inconsistency in how the server handles this edge case across different code paths.sdk-core/src/test/java/io/milvus/v2/service/vector/NullableVectorTest.java (2)
56-80: Excellent test coverage across all vector types.The
VectorTypeConfigsetup covers all six vector types with appropriate index configurations. This parameterized approach makes the tests maintainable and ensures consistent coverage.
206-259: Good negative test for non-nullable vector field validation.This test correctly verifies that attempting to add a non-nullable vector field to an existing collection fails with an appropriate error message. This aligns with the PR objective of "client-side validation requiring nullable=true when adding vector fields."
examples/src/main/java/io/milvus/v2/NullableVectorExample.java (2)
53-176: Clear demonstration of nullable vector insertion and querying.Demo 1 effectively illustrates the key features:
- Creating a collection with
isNullable=truefor the vector field- Inserting mixed null/non-null vectors using
JsonNull.INSTANCE- Querying to verify null preservation
- Searching to confirm only non-null vectors are returned
The example is well-structured and educational.
178-298: Excellent demonstration of adding nullable vector fields to existing collections.Demo 2 showcases a practical schema evolution scenario:
- Adding a new nullable vector field to an existing collection
- Properly releasing the collection before schema modification
- Creating an index for the new field
- Verifying that existing rows have
nullfor the new fieldThis addresses the PR objective of "Add client-side validation requiring nullable=true when adding vector fields" and provides clear guidance for users.
sdk-core/src/main/java/io/milvus/response/FieldDataWrapper.java (2)
141-173: Correct nullable vector row count handling.The
getRowCountlogic properly distinguishes between nullable and non-nullable vectors:
- When
validDatais present, returnsvalidData.size()(total count including nulls)- When absent, falls back to dimension-based calculation from packed data
This is consistent across FloatVector (lines 141-144), Binary/Float16/BFloat16/Int8Vector (lines 159-162), and SparseFloatVector (lines 170-173).
291-353: Good addition of dimension guards in vector packing.The
dim > 0guards (lines 298, 319) prevent division-by-zero errors when all vectors are null (resulting in empty data). This safely handles the edge case introduced by nullable vector support.The packing logic correctly processes only non-null vectors (filtered earlier by
ParamUtils.genFieldData).
Signed-off-by: marcelo-cjl <[email protected]>
a2c0994 to
ae53b54
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
sdk-core/src/main/java/io/milvus/response/FieldDataWrapper.java (1)
354-366: Nullable vector handling is correctly implemented.The algorithm properly reconstructs the original vector list by:
- Iterating through the validData mask
- Taking the next vector from packData when valid=true
- Inserting null when valid=false
This maintains the original row order while preserving null positions.
Optional: Add defensive validation
Consider adding a validation check to catch potential bugs in the packing logic:
// Handle nullable vectors - insert null values at positions where validData is false if (validData != null && !validData.isEmpty()) { + long expectedValid = validData.stream().filter(b -> b).count(); + if (packData.size() != expectedValid) { + throw new IllegalResponseException(String.format( + "Mismatch between validData (%d valid) and packed data size (%d)", + expectedValid, packData.size())); + } List<Object> result = new ArrayList<>(); int dataIdx = 0;This catches invariant violations early with a clear error message rather than throwing IndexOutOfBoundsException.
sdk-core/src/main/java/io/milvus/param/ParamUtils.java (1)
1241-1255: Early return for empty vector lists handles the all-nulls case.When all vectors in a nullable field are null, the filtered list becomes empty and this early return is triggered. Setting dim=0 is acceptable because the actual dimension is defined in the schema, not in the data.
Optional: Clarify with a comment
Consider adding a comment to explain this edge case:
+// Handle empty vector list (occurs when inserting 0 rows or when all vectors are null in a nullable field) +// Set dim=0; the actual dimension is defined in the collection schema if (objects.isEmpty()) { if (dataType == DataType.FloatVector) {
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
examples/src/main/java/io/milvus/v2/NullableVectorExample.javasdk-core/src/main/java/io/milvus/param/ParamUtils.javasdk-core/src/main/java/io/milvus/response/FieldDataWrapper.javasdk-core/src/main/java/io/milvus/v2/service/collection/CollectionService.javasdk-core/src/main/java/io/milvus/v2/utils/SchemaUtils.javasdk-core/src/test/java/io/milvus/v2/service/vector/NullableVectorTest.java
🚧 Files skipped from review as they are similar to previous changes (1)
- sdk-core/src/main/java/io/milvus/v2/service/collection/CollectionService.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build and test
- GitHub Check: Summary
🔇 Additional comments (8)
sdk-core/src/main/java/io/milvus/v2/utils/SchemaUtils.java (1)
52-65: LGTM! Well-designed validation for nullable vector fields.The approach is sound:
- Existing collections require nullable=true for added vector fields (existing rows lack values for the new field)
- New collections allow non-nullable vector fields (all rows must provide values)
- Clear error message guides users to the correct usage
The backward-compatible overload preserves existing behavior while enabling the new validation path.
sdk-core/src/main/java/io/milvus/response/FieldDataWrapper.java (1)
141-173: Row count calculation correctly handles nullable vectors.For all vector types, the method properly prioritizes validData.size() over calculating from the data array. This is essential because:
- validData.size() reflects the true row count (including null vectors)
- Data array size reflects only non-null vectors
The early return prevents divide-by-zero errors when all vectors are null.
sdk-core/src/main/java/io/milvus/param/ParamUtils.java (1)
1207-1218: Nullable vector packing is correctly implemented.The logic properly:
- Records which elements are null in the validData bitmap
- Filters out nulls before passing to genVectorField
- Preserves the validity information for server-side reconstruction
This approach ensures the data array contains only non-null vectors while the validData bitmap preserves the original row positions.
sdk-core/src/test/java/io/milvus/v2/service/vector/NullableVectorTest.java (3)
57-80: Excellent test coverage across all vector types.The parameterized approach with VectorTypeConfig ensures consistent testing for:
- FloatVector, BinaryVector, Float16Vector, BFloat16Vector, SparseFloatVector, Int8Vector
- Each with appropriate metric types and index types
This validates that nullable support works uniformly across all vector types.
261-429: Comprehensive test validates the complete nullable vector workflow.The test thoroughly exercises:
- Schema creation with nullable vector field ✓
- Data insertion with ~50% null vectors ✓
- Query validation confirms correct null/valid counts ✓
- Search behavior verifies only non-null vectors are returned ✓
The assertion at line 421 is particularly important—it ensures that nullable vectors don't break the search/retrieval path, as null vectors should not participate in similarity search.
208-259: Validation constraint is properly tested.The test confirms that adding a non-nullable vector field to an existing collection fails as expected. This prevents users from creating an inconsistent state where existing rows would lack values for a required field.
examples/src/main/java/io/milvus/v2/NullableVectorExample.java (2)
53-176: Clear demonstration of nullable vector insert and retrieval.The example effectively shows:
- Creating a collection with nullable=true on the vector field
- Inserting mixed null and non-null vectors using JsonNull.INSTANCE
- Querying returns both null and non-null vectors
- Searching returns only non-null vectors
The explanatory print statements make this a useful learning resource.
178-298: Demonstrates the key use case of adding fields to existing collections.This example illustrates an important workflow:
- Create collection with one vector field
- Insert data
- Add a second nullable vector field (must be nullable!)
- Verify existing rows have null for the new field
This is a common schema evolution pattern, and the example clearly shows why the nullable constraint exists.
related: #1729