Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -668,10 +668,22 @@ for (var object : result.objects()) {
}
```

When _ingetsting_ data, Java records can be used to represent nested object properties:

```java
record MusicVideo(@Property("link") String url, long runtimeSeconds) {}

songs.data.insert(Map.of(
"title", "Billie Jean",
"musicVideo", new MusicVideo("https://youtube.com/billijean", 294L),
));
```

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:

- **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`.
- **Limited configuration options.** Vector indices, replication, multi-tenancy, and such need to be configured via a tucked builder in `.create(..., here -> here)`.
- **Cannot include nested objects.** Java records can be used as nested properties in a `Map`, but cannot include nested properties themselves.
- **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.

```java
Expand Down
2 changes: 1 addition & 1 deletion src/it/java/io/weaviate/containers/Weaviate.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import io.weaviate.client6.v1.internal.ObjectBuilder;

public class Weaviate extends WeaviateContainer {
public static final String VERSION = "1.32.3";
public static final String VERSION = "1.33.0";
public static final String DOCKER_IMAGE = "semitechnologies/weaviate";

private volatile SharedClient clientInstance;
Expand Down
36 changes: 36 additions & 0 deletions src/it/java/io/weaviate/integration/CollectionsITest.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import io.weaviate.client6.v1.api.WeaviateApiException;
import io.weaviate.client6.v1.api.WeaviateClient;
import io.weaviate.client6.v1.api.collections.CollectionConfig;
import io.weaviate.client6.v1.api.collections.DataType;
import io.weaviate.client6.v1.api.collections.InvertedIndex;
import io.weaviate.client6.v1.api.collections.Property;
import io.weaviate.client6.v1.api.collections.ReferenceProperty;
Expand Down Expand Up @@ -191,4 +192,39 @@ public void testShards() throws IOException {
public void testInvalidCollectionName() throws IOException {
client.collections.create("^collection@weaviate.io$");
}

@Test
public void testNestedProperties() throws IOException, Exception {
var nsBuildings = ns("Buildings");

client.collections.create(
nsBuildings, c -> c.properties(
Property.object("address", p -> p.nestedProperties(
Property.text("street"),
Property.integer("building_nr"),
Property.bool("isOneWay"))),
Property.objectArray("apartments", p -> p.nestedProperties(
Property.integer("door_nr"),
Property.number("area")))));

var config = client.collections.getConfig(nsBuildings);

var properties = Assertions.assertThat(config).get()
.extracting(CollectionConfig::properties, InstanceOfAssertFactories.list(Property.class))
.hasSize(2).actual();

Assertions.assertThat(properties.get(0))
.returns("address", Property::propertyName)
.returns(DataType.OBJECT, p -> p.dataTypes().get(0))
.extracting(Property::nestedProperties, InstanceOfAssertFactories.list(Property.class))
.extracting(Property::dataTypes).extracting(types -> types.get(0))
.containsExactly(DataType.TEXT, DataType.INT, DataType.BOOL);

Assertions.assertThat(properties.get(1))
.returns("apartments", Property::propertyName)
.returns(DataType.OBJECT_ARRAY, p -> p.dataTypes().get(0))
.extracting(Property::nestedProperties, InstanceOfAssertFactories.list(Property.class))
.extracting(Property::dataTypes).extracting(types -> types.get(0))
.containsExactly(DataType.INT, DataType.NUMBER);
}
}
64 changes: 59 additions & 5 deletions src/it/java/io/weaviate/integration/DataITest.java
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,13 @@ public void testDataTypes() throws IOException {
Property.boolArray("prop_bool_array"),
Property.dateArray("prop_date_array"),
Property.uuidArray("prop_uuid_array"),
Property.textArray("prop_text_array")));
Property.textArray("prop_text_array"),
Property.object("prop_object",
p -> p.nestedProperties(
Property.text("marco"))),
Property.objectArray("prop_object_array",
p -> p.nestedProperties(
Property.text("marco")))));

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

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

// Act
var object = types.data.insert(want);
var got = types.query.byId(object.uuid(),
q -> q.returnProperties(returnProperties));
var got = types.query.byId(object.uuid()); // return all properties

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

}

record Address(
String street,
@io.weaviate.client6.v1.api.collections.annotations.Property("building_nr") int buildingNr,
@io.weaviate.client6.v1.api.collections.annotations.Property("isOneWay") boolean oneWay) {
}

@Test
public void testNestedProperties_insertMany() throws IOException {
// Arrange
var nsBuildings = ns("Buildings");

client.collections.create(
nsBuildings, c -> c.properties(
Property.object("address", p -> p.nestedProperties(
Property.text("street"),
Property.integer("building_nr"),
Property.bool("isOneWay"))),
Property.objectArray("apartments", p -> p.nestedProperties(
Property.integer("door_nr"),
Property.number("area")))));

var buildings = client.collections.use(nsBuildings);

Map<String, Object> house_1 = Map.of(
"address", Map.of(
"street", "Burggasse",
"building_nr", 51,
"isOneWay", true),
"apartments", List.of(
Map.of("door_nr", 11, "area", 42.2),
Map.of("door_nr", 12, "area", 26.7)));
Map<String, Object> house_2 = Map.of(
"address", new Address(
"Port Mariland St.",
111,
false),
"apartments", new Map[] {
Map.of("door_nr", 21, "area", 42.2),
Map.of("door_nr", 22, "area", 26.7),
});

// Act
var result = buildings.data.insertMany(house_1, house_2);

// Assert
Assertions.assertThat(result.errors()).isEmpty();
}
}
4 changes: 4 additions & 0 deletions src/it/java/io/weaviate/integration/ORMITest.java
Original file line number Diff line number Diff line change
Expand Up @@ -350,4 +350,8 @@ public void test_partialScan() throws IOException {
.returns(true, Song::hasAward)
.returns(null, Song::monthlyListeners);
}

@Test
public void test_nestedProperties() throws IOException {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public interface DataType {
public static final String DATE_ARRAY = "date[]";
public static final String UUID = "uuid";
public static final String UUID_ARRAY = "uuid[]";
public static final String OBJECT = "object";
public static final String OBJECT_ARRAY = "object[]";

/**
* Scalar/array types which Weaviate and WeaviateClient recognize.
Expand All @@ -31,6 +33,6 @@ public interface DataType {
* using {@link Property}'s factory classes.
*/
public static final Set<String> KNOWN_TYPES = ImmutableSet.of(
TEXT, INT, BLOB, BOOL, DATE, UUID, NUMBER,
TEXT_ARRAY, INT_ARRAY, NUMBER_ARRAY, BOOL_ARRAY, DATE_ARRAY, UUID_ARRAY);
TEXT, INT, BLOB, BOOL, DATE, UUID, NUMBER, OBJECT,
TEXT_ARRAY, INT_ARRAY, NUMBER_ARRAY, BOOL_ARRAY, DATE_ARRAY, UUID_ARRAY, OBJECT_ARRAY);
}
81 changes: 75 additions & 6 deletions src/main/java/io/weaviate/client6/v1/api/collections/Property.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.weaviate.client6.v1.api.collections;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;

Expand All @@ -17,7 +19,8 @@ public record Property(
@SerializedName("indexSearchable") Boolean indexSearchable,
@SerializedName("tokenization") Tokenization tokenization,
@SerializedName("skipVectorization") Boolean skipVectorization,
@SerializedName("vectorizePropertyName") Boolean vectorizePropertyName) {
@SerializedName("vectorizePropertyName") Boolean vectorizePropertyName,
@SerializedName("nestedProperties") List<Property> nestedProperties) {

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

/**
* Create a {@code bool} property.
* Create a {@code blob} property.
*
* @param name Property name.
*/
Expand Down Expand Up @@ -267,6 +270,44 @@ public static Property numberArray(String name, Function<Builder, ObjectBuilder<
return newProperty(name, DataType.NUMBER_ARRAY, fn);
}

/**
* Create a {@code object} property.
*
* @param name Property name.
*/
public static Property object(String name) {
return object(name, ObjectBuilder.identity());
}

/**
* Create a {@code object} property with additional configuration.
*
* @param name Property name.
* @param fn Lambda expression for optional parameters.
*/
public static Property object(String name, Function<Builder, ObjectBuilder<Property>> fn) {
return newProperty(name, DataType.OBJECT, fn);
}

/**
* Create a {@code object[]} property.
*
* @param name Property name.
*/
public static Property objectArray(String name) {
return objectArray(name, ObjectBuilder.identity());
}

/**
* Create a {@code objectArray[]} property with additional configuration.
*
* @param name Property name.
* @param fn Lambda expression for optional parameters.
*/
public static Property objectArray(String name, Function<Builder, ObjectBuilder<Property>> fn) {
return newProperty(name, DataType.OBJECT_ARRAY, fn);
}

private static Property newProperty(String name, String dataType, Function<Builder, ObjectBuilder<Property>> fn) {
return fn.apply(new Builder(name, dataType)).build();
}
Expand Down Expand Up @@ -329,7 +370,8 @@ public Property(Builder builder) {
builder.indexSearchable,
builder.tokenization,
builder.skipVectorization,
builder.vectorizePropertyName);
builder.vectorizePropertyName,
builder.nestedProperties.isEmpty() ? null : builder.nestedProperties);
}

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

// Optional parameters.
private List<String> dataTypes;
private String description;
private Boolean indexInverted;
private Boolean indexFilterable;
Expand All @@ -357,6 +399,7 @@ public static class Builder implements ObjectBuilder<Property> {
private Tokenization tokenization;
private Boolean skipVectorization;
private Boolean vectorizePropertyName;
private List<Property> nestedProperties = new ArrayList<>();

/**
* Create a scalar / array type property.
Expand All @@ -365,7 +408,7 @@ public static class Builder implements ObjectBuilder<Property> {
*/
public Builder(String propertyName, String dataType) {
this.propertyName = propertyName;
this.dataTypes = List.of(dataType);
this.dataTypes.add(dataType);
}

/**
Expand All @@ -375,7 +418,7 @@ public Builder(String propertyName, String dataType) {
*/
public Builder(String propertyName, List<String> dataTypes) {
this.propertyName = propertyName;
this.dataTypes = List.copyOf(dataTypes);
this.dataTypes.addAll(dataTypes);
}

/** Add property description. */
Expand Down Expand Up @@ -491,6 +534,32 @@ public Builder vectorizePropertyName(boolean vectorizePropertyName) {
return this;
}

/**
* Defined nested properties. This configuration is only applicable to a
* property of type {@code object} and {@code object[]}.
*
* <pre>{@code
* Property.object("address",
* p -> p.nestedProperties(
* Property.text("street"),
* Property.integer("building_nr")))
* }</pre>
*/
public Builder nestedProperties(Property... properties) {
return nestedProperties(Arrays.asList(properties));
}

/**
* Defined nested properties. This configuration is only applicable to a
* property of type {@code object} and {@code object[]}.
*
* @see Builder#nestedProperties(Property...)
*/
public Builder nestedProperties(List<Property> properties) {
this.nestedProperties.addAll(properties);
return this;
}

/** Convenience method to be used by {@link Property#edit}. */
Builder vectorizePropertyName(Boolean vectorizePropertyName) {
this.vectorizePropertyName = vectorizePropertyName;
Expand Down
Loading