Skip to content

Commit 60c03e9

Browse files
authored
Merge pull request #477 from weaviate/v6-nested-object
v6: Nested object properties
2 parents c79631e + e4e12f0 commit 60c03e9

14 files changed

Lines changed: 398 additions & 131 deletions

File tree

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,10 +678,22 @@ for (var object : result.objects()) {
678678
}
679679
```
680680

681+
When _ingetsting_ data, Java records can be used to represent nested object properties:
682+
683+
```java
684+
record MusicVideo(@Property("link") String url, long runtimeSeconds) {}
685+
686+
songs.data.insert(Map.of(
687+
"title", "Billie Jean",
688+
"musicVideo", new MusicVideo("https://youtube.com/billijean", 294L),
689+
));
690+
```
691+
681692
We want to stress that this ORM's focus is on improving type-safety around object properties and simplifying serialization/deserialization. It is intentionally kept minimal and as such has the following limitations:
682693
683694
- **Does not support BLOB properties.** On the wire, blob properties are represented as base64-encoded strings, and both logically map to the Java's `String`. Presently there isn't a good way for the client to deduce which property type should be created, so it always maps `Sting -> TEXT`.
684695
- **Limited configuration options.** Vector indices, replication, multi-tenancy, and such need to be configured via a tucked builder in `.create(..., here -> here)`.
696+
- **Cannot include nested objects.** Java records can be used as nested properties in a `Map`, but cannot include nested properties themselves.
685697
- **Does not support cross-references.** Properties and Cross-References are conceptually and "physically" separated in Weaviate' client libraries, so doing something like in the snippet below is not supported.
686698

687699
```java

src/it/java/io/weaviate/integration/CollectionsITest.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import io.weaviate.client6.v1.api.WeaviateApiException;
1111
import io.weaviate.client6.v1.api.WeaviateClient;
1212
import io.weaviate.client6.v1.api.collections.CollectionConfig;
13+
import io.weaviate.client6.v1.api.collections.DataType;
1314
import io.weaviate.client6.v1.api.collections.InvertedIndex;
1415
import io.weaviate.client6.v1.api.collections.Property;
1516
import io.weaviate.client6.v1.api.collections.ReferenceProperty;
@@ -191,4 +192,39 @@ public void testShards() throws IOException {
191192
public void testInvalidCollectionName() throws IOException {
192193
client.collections.create("^collection@weaviate.io$");
193194
}
195+
196+
@Test
197+
public void testNestedProperties() throws IOException, Exception {
198+
var nsBuildings = ns("Buildings");
199+
200+
client.collections.create(
201+
nsBuildings, c -> c.properties(
202+
Property.object("address", p -> p.nestedProperties(
203+
Property.text("street"),
204+
Property.integer("building_nr"),
205+
Property.bool("isOneWay"))),
206+
Property.objectArray("apartments", p -> p.nestedProperties(
207+
Property.integer("door_nr"),
208+
Property.number("area")))));
209+
210+
var config = client.collections.getConfig(nsBuildings);
211+
212+
var properties = Assertions.assertThat(config).get()
213+
.extracting(CollectionConfig::properties, InstanceOfAssertFactories.list(Property.class))
214+
.hasSize(2).actual();
215+
216+
Assertions.assertThat(properties.get(0))
217+
.returns("address", Property::propertyName)
218+
.returns(DataType.OBJECT, p -> p.dataTypes().get(0))
219+
.extracting(Property::nestedProperties, InstanceOfAssertFactories.list(Property.class))
220+
.extracting(Property::dataTypes).extracting(types -> types.get(0))
221+
.containsExactly(DataType.TEXT, DataType.INT, DataType.BOOL);
222+
223+
Assertions.assertThat(properties.get(1))
224+
.returns("apartments", Property::propertyName)
225+
.returns(DataType.OBJECT_ARRAY, p -> p.dataTypes().get(0))
226+
.extracting(Property::nestedProperties, InstanceOfAssertFactories.list(Property.class))
227+
.extracting(Property::dataTypes).extracting(types -> types.get(0))
228+
.containsExactly(DataType.INT, DataType.NUMBER);
229+
}
194230
}

src/it/java/io/weaviate/integration/DataITest.java

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,13 @@ public void testDataTypes() throws IOException {
431431
Property.boolArray("prop_bool_array"),
432432
Property.dateArray("prop_date_array"),
433433
Property.uuidArray("prop_uuid_array"),
434-
Property.textArray("prop_text_array")));
434+
Property.textArray("prop_text_array"),
435+
Property.object("prop_object",
436+
p -> p.nestedProperties(
437+
Property.text("marco"))),
438+
Property.objectArray("prop_object_array",
439+
p -> p.nestedProperties(
440+
Property.text("marco")))));
435441

436442
var types = client.collections.use(nsDataTypes);
437443

@@ -450,13 +456,13 @@ public void testDataTypes() throws IOException {
450456
Map.entry("prop_bool_array", List.of(true, false)),
451457
Map.entry("prop_date_array", List.of(now, now)),
452458
Map.entry("prop_uuid_array", List.of(uuid, uuid)),
453-
Map.entry("prop_text_array", List.of("a", "b", "c")));
454-
var returnProperties = want.keySet().toArray(String[]::new);
459+
Map.entry("prop_text_array", List.of("a", "b", "c")),
460+
Map.entry("prop_object", Map.of("marco", "polo")),
461+
Map.entry("prop_object_array", List.of(Map.of("marco", "polo"))));
455462

456463
// Act
457464
var object = types.data.insert(want);
458-
var got = types.query.byId(object.uuid(),
459-
q -> q.returnProperties(returnProperties));
465+
var got = types.query.byId(object.uuid()); // return all properties
460466

461467
// Assert
462468
Assertions.assertThat(got).get()
@@ -465,4 +471,52 @@ public void testDataTypes() throws IOException {
465471
.containsAllEntriesOf(want);
466472

467473
}
474+
475+
record Address(
476+
String street,
477+
@io.weaviate.client6.v1.api.collections.annotations.Property("building_nr") int buildingNr,
478+
@io.weaviate.client6.v1.api.collections.annotations.Property("isOneWay") boolean oneWay) {
479+
}
480+
481+
@Test
482+
public void testNestedProperties_insertMany() throws IOException {
483+
// Arrange
484+
var nsBuildings = ns("Buildings");
485+
486+
client.collections.create(
487+
nsBuildings, c -> c.properties(
488+
Property.object("address", p -> p.nestedProperties(
489+
Property.text("street"),
490+
Property.integer("building_nr"),
491+
Property.bool("isOneWay"))),
492+
Property.objectArray("apartments", p -> p.nestedProperties(
493+
Property.integer("door_nr"),
494+
Property.number("area")))));
495+
496+
var buildings = client.collections.use(nsBuildings);
497+
498+
Map<String, Object> house_1 = Map.of(
499+
"address", Map.of(
500+
"street", "Burggasse",
501+
"building_nr", 51,
502+
"isOneWay", true),
503+
"apartments", List.of(
504+
Map.of("door_nr", 11, "area", 42.2),
505+
Map.of("door_nr", 12, "area", 26.7)));
506+
Map<String, Object> house_2 = Map.of(
507+
"address", new Address(
508+
"Port Mariland St.",
509+
111,
510+
false),
511+
"apartments", new Map[] {
512+
Map.of("door_nr", 21, "area", 42.2),
513+
Map.of("door_nr", 22, "area", 26.7),
514+
});
515+
516+
// Act
517+
var result = buildings.data.insertMany(house_1, house_2);
518+
519+
// Assert
520+
Assertions.assertThat(result.errors()).isEmpty();
521+
}
468522
}

src/it/java/io/weaviate/integration/ORMITest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,4 +350,8 @@ public void test_partialScan() throws IOException {
350350
.returns(true, Song::hasAward)
351351
.returns(null, Song::monthlyListeners);
352352
}
353+
354+
@Test
355+
public void test_nestedProperties() throws IOException {
356+
}
353357
}

src/main/java/io/weaviate/client6/v1/api/collections/DataType.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ public interface DataType {
1818
public static final String DATE_ARRAY = "date[]";
1919
public static final String UUID = "uuid";
2020
public static final String UUID_ARRAY = "uuid[]";
21+
public static final String OBJECT = "object";
22+
public static final String OBJECT_ARRAY = "object[]";
2123

2224
/**
2325
* Scalar/array types which Weaviate and WeaviateClient recognize.
@@ -31,6 +33,6 @@ public interface DataType {
3133
* using {@link Property}'s factory classes.
3234
*/
3335
public static final Set<String> KNOWN_TYPES = ImmutableSet.of(
34-
TEXT, INT, BLOB, BOOL, DATE, UUID, NUMBER,
35-
TEXT_ARRAY, INT_ARRAY, NUMBER_ARRAY, BOOL_ARRAY, DATE_ARRAY, UUID_ARRAY);
36+
TEXT, INT, BLOB, BOOL, DATE, UUID, NUMBER, OBJECT,
37+
TEXT_ARRAY, INT_ARRAY, NUMBER_ARRAY, BOOL_ARRAY, DATE_ARRAY, UUID_ARRAY, OBJECT_ARRAY);
3638
}

src/main/java/io/weaviate/client6/v1/api/collections/Property.java

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.weaviate.client6.v1.api.collections;
22

3+
import java.util.ArrayList;
4+
import java.util.Arrays;
35
import java.util.List;
46
import java.util.function.Function;
57

@@ -17,7 +19,8 @@ public record Property(
1719
@SerializedName("indexSearchable") Boolean indexSearchable,
1820
@SerializedName("tokenization") Tokenization tokenization,
1921
@SerializedName("skipVectorization") Boolean skipVectorization,
20-
@SerializedName("vectorizePropertyName") Boolean vectorizePropertyName) {
22+
@SerializedName("vectorizePropertyName") Boolean vectorizePropertyName,
23+
@SerializedName("nestedProperties") List<Property> nestedProperties) {
2124

2225
/**
2326
* Create a {@code text} property.
@@ -96,7 +99,7 @@ public static Property integerArray(String name, Function<Builder, ObjectBuilder
9699
}
97100

98101
/**
99-
* Create a {@code bool} property.
102+
* Create a {@code blob} property.
100103
*
101104
* @param name Property name.
102105
*/
@@ -267,6 +270,44 @@ public static Property numberArray(String name, Function<Builder, ObjectBuilder<
267270
return newProperty(name, DataType.NUMBER_ARRAY, fn);
268271
}
269272

273+
/**
274+
* Create a {@code object} property.
275+
*
276+
* @param name Property name.
277+
*/
278+
public static Property object(String name) {
279+
return object(name, ObjectBuilder.identity());
280+
}
281+
282+
/**
283+
* Create a {@code object} property with additional configuration.
284+
*
285+
* @param name Property name.
286+
* @param fn Lambda expression for optional parameters.
287+
*/
288+
public static Property object(String name, Function<Builder, ObjectBuilder<Property>> fn) {
289+
return newProperty(name, DataType.OBJECT, fn);
290+
}
291+
292+
/**
293+
* Create a {@code object[]} property.
294+
*
295+
* @param name Property name.
296+
*/
297+
public static Property objectArray(String name) {
298+
return objectArray(name, ObjectBuilder.identity());
299+
}
300+
301+
/**
302+
* Create a {@code objectArray[]} property with additional configuration.
303+
*
304+
* @param name Property name.
305+
* @param fn Lambda expression for optional parameters.
306+
*/
307+
public static Property objectArray(String name, Function<Builder, ObjectBuilder<Property>> fn) {
308+
return newProperty(name, DataType.OBJECT_ARRAY, fn);
309+
}
310+
270311
private static Property newProperty(String name, String dataType, Function<Builder, ObjectBuilder<Property>> fn) {
271312
return fn.apply(new Builder(name, dataType)).build();
272313
}
@@ -329,7 +370,8 @@ public Property(Builder builder) {
329370
builder.indexSearchable,
330371
builder.tokenization,
331372
builder.skipVectorization,
332-
builder.vectorizePropertyName);
373+
builder.vectorizePropertyName,
374+
builder.nestedProperties.isEmpty() ? null : builder.nestedProperties);
333375
}
334376

335377
// All methods accepting a `boolean` should have a boxed overload
@@ -346,9 +388,9 @@ public Property(Builder builder) {
346388
public static class Builder implements ObjectBuilder<Property> {
347389
// Required parameters.
348390
private final String propertyName;
391+
private final List<String> dataTypes = new ArrayList<>();
349392

350393
// Optional parameters.
351-
private List<String> dataTypes;
352394
private String description;
353395
private Boolean indexInverted;
354396
private Boolean indexFilterable;
@@ -357,6 +399,7 @@ public static class Builder implements ObjectBuilder<Property> {
357399
private Tokenization tokenization;
358400
private Boolean skipVectorization;
359401
private Boolean vectorizePropertyName;
402+
private List<Property> nestedProperties = new ArrayList<>();
360403

361404
/**
362405
* Create a scalar / array type property.
@@ -365,7 +408,7 @@ public static class Builder implements ObjectBuilder<Property> {
365408
*/
366409
public Builder(String propertyName, String dataType) {
367410
this.propertyName = propertyName;
368-
this.dataTypes = List.of(dataType);
411+
this.dataTypes.add(dataType);
369412
}
370413

371414
/**
@@ -375,7 +418,7 @@ public Builder(String propertyName, String dataType) {
375418
*/
376419
public Builder(String propertyName, List<String> dataTypes) {
377420
this.propertyName = propertyName;
378-
this.dataTypes = List.copyOf(dataTypes);
421+
this.dataTypes.addAll(dataTypes);
379422
}
380423

381424
/** Add property description. */
@@ -491,6 +534,32 @@ public Builder vectorizePropertyName(boolean vectorizePropertyName) {
491534
return this;
492535
}
493536

537+
/**
538+
* Defined nested properties. This configuration is only applicable to a
539+
* property of type {@code object} and {@code object[]}.
540+
*
541+
* <pre>{@code
542+
* Property.object("address",
543+
* p -> p.nestedProperties(
544+
* Property.text("street"),
545+
* Property.integer("building_nr")))
546+
* }</pre>
547+
*/
548+
public Builder nestedProperties(Property... properties) {
549+
return nestedProperties(Arrays.asList(properties));
550+
}
551+
552+
/**
553+
* Defined nested properties. This configuration is only applicable to a
554+
* property of type {@code object} and {@code object[]}.
555+
*
556+
* @see Builder#nestedProperties(Property...)
557+
*/
558+
public Builder nestedProperties(List<Property> properties) {
559+
this.nestedProperties.addAll(properties);
560+
return this;
561+
}
562+
494563
/** Convenience method to be used by {@link Property#edit}. */
495564
Builder vectorizePropertyName(Boolean vectorizePropertyName) {
496565
this.vectorizePropertyName = vectorizePropertyName;

0 commit comments

Comments
 (0)