Merge pull request #1947 from nosqlbench/ms/qdrant

Qdrant driver - updates
This commit is contained in:
Madhavan 2024-05-14 09:20:30 -04:00 committed by GitHub
commit e9b40872c4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 1161 additions and 32 deletions

View File

@ -41,8 +41,6 @@
<PROG>nb5</PROG>
<maven.plugin.validation>VERBOSE</maven.plugin.validation>
<jacoco.version>0.8.12</jacoco.version>
</properties>
<name>${project.artifactId}</name>
@ -551,7 +549,7 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version>
<version>0.8.12</version>
<executions>
<execution>
<id>prepare-agent</id>
@ -785,7 +783,7 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version>
<version>0.8.12</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>

View File

@ -36,27 +36,27 @@
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<!-- <version>1.63.0</version> -->
<!-- Trying to match https://github.com/qdrant/java-client/blob/v1.9.0/build.gradle#L80 -->
<!-- If Trying to match https://github.com/qdrant/java-client/blob/v1.9.1/build.gradle#L80 -->
<version>1.59.0</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<!--<version>3.25.3</version>-->
<!-- Trying to match https://github.com/qdrant/java-client/blob/master/build.gradle#L81 -->
<!-- <version>3.25.3</version> -->
<!-- If Trying to match https://github.com/qdrant/java-client/blob/v1.9.1/build.gradle#L81 -->
<version>3.24.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<!--<version>33.1.0-jre</version>-->
<!-- Trying to match https://github.com/qdrant/java-client/blob/master/build.gradle#L93 -->
<version>30.1-jre</version>
<version>33.1.0-jre</version>
<!-- If Trying to match https://github.com/qdrant/java-client/blob/v1.9.1/build.gradle#L93, use below -->
<!-- <version>30.1-jre</version> -->
</dependency>
<dependency>
<groupId>io.qdrant</groupId>
<artifactId>client</artifactId>
<version>1.9.0</version>
<version>1.9.1</version>
</dependency>
</dependencies>
</project>

View File

@ -63,6 +63,12 @@ public class QdrantOpMapper implements OpMapper<QdrantBaseOp<?>> {
case search_points -> new QdrantSearchPointsOpDispenser(adapter, op, typeAndTarget.targetFunction);
case upsert_points -> new QdrantUpsertPointsOpDispenser(adapter, op, typeAndTarget.targetFunction);
case count_points -> new QdrantCountPointsOpDispenser(adapter, op, typeAndTarget.targetFunction);
case list_collections -> new QdrantListCollectionsOpDispenser(adapter, op, typeAndTarget.targetFunction);
case collection_info -> new QdrantCollectionInfoOpDispenser(adapter, op, typeAndTarget.targetFunction);
case collection_exists -> new QdrantCollectionExistsOpDispenser(adapter, op, typeAndTarget.targetFunction);
case list_collection_aliases ->
new QdrantListCollectionAliasesOpDispenser(adapter, op, typeAndTarget.targetFunction);
case list_snapshots -> new QdrantListSnapshotsOpDispenser(adapter, op, typeAndTarget.targetFunction);
// default -> throw new RuntimeException("Unrecognized op type '" + typeAndTarget.enumId.name() + "' while " +
// "mapping parsed op " + op);
};

View File

@ -16,16 +16,31 @@
package io.nosqlbench.adapter.qdrant.opdispensers;
import com.google.protobuf.Timestamp;
import io.nosqlbench.adapter.qdrant.QdrantDriverAdapter;
import io.nosqlbench.adapter.qdrant.QdrantSpace;
import io.nosqlbench.adapter.qdrant.ops.QdrantBaseOp;
import io.nosqlbench.adapters.api.activityimpl.BaseOpDispenser;
import io.nosqlbench.adapters.api.activityimpl.uniform.DriverAdapter;
import io.nosqlbench.adapters.api.templating.ParsedOp;
import io.nosqlbench.nb.api.errors.OpConfigError;
import io.qdrant.client.PointIdFactory;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.grpc.Points;
import io.qdrant.client.grpc.Points.Condition;
import io.qdrant.client.grpc.Points.DatetimeRange;
import io.qdrant.client.grpc.Points.Filter;
import io.qdrant.client.grpc.Points.Range;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.LongFunction;
import static io.qdrant.client.ConditionFactory.*;
public abstract class QdrantBaseOpDispenser<T> extends BaseOpDispenser<QdrantBaseOp<T>, QdrantSpace> {
protected final LongFunction<QdrantSpace> qdrantSpaceFunction;
@ -61,4 +76,456 @@ public abstract class QdrantBaseOpDispenser<T> extends BaseOpDispenser<QdrantBas
public QdrantBaseOp<T> getOp(long value) {
return opF.apply(value);
}
/**
* Builds the complete {@link Filter.Builder} from the {@code ParsedOp}
*
* @param {@link ParsedOp}
* @return {@code LongFunction<Filter.Builder>}
* @see <a href="https://qdrant.tech/documentation/concepts/filtering">Filtering</a>
*/
protected LongFunction<Filter.Builder> getFilterFromOp(ParsedOp op) {
Optional<LongFunction<List>> filterFunc = op.getAsOptionalFunction("filter", List.class);
return filterFunc.<LongFunction<Filter.Builder>>map(filterListLongF -> l -> {
Filter.Builder filterBuilder = Filter.newBuilder();
List<Map<String, Object>> filters = filterListLongF.apply(l);
return constructFilterBuilder(filters, filterBuilder);
}).orElse(null);
}
private Filter.Builder constructFilterBuilder(List<Map<String, Object>> filters, Filter.Builder filterBuilder) {
List<Condition> mustClauseList = new ArrayList<>();
List<Condition> mustNotClauseList = new ArrayList<>();
List<Condition> shouldClauseList = new ArrayList<>();
for (Map<String, Object> filterFields : filters) {
switch ((String) filterFields.get("clause")) {
case "must" -> {
logger.debug("[QdrantBaseOpDispenser] - Building 'must' filter clause");
switch (filterFields.get("condition").toString()) {
case "match" -> //filterBuilder.addMust(getMatchFilterCondition(filterFields));
mustClauseList.add(getMatchFilterCondition(filterFields));
case "match_any" -> mustClauseList.add(getMatchAnyExceptCondition(filterFields, "match_any"));
case "match_except" ->
mustClauseList.add(getMatchAnyExceptCondition(filterFields, "match_except"));
case "text" -> mustClauseList.add(getMatchTextCondition(filterFields));
case "range" -> mustClauseList.add(getRangeCondition(filterFields));
case "geo_bounding_box" -> mustClauseList.add(getGeoBoundingBoxCondition(filterFields));
case "geo_radius" -> mustClauseList.add(getGeoRadiusCondition(filterFields));
case "geo_polygon" -> mustClauseList.add(getGeoPolygonCondition(filterFields));
case "values_count" -> mustClauseList.add(getValuesCountCondition(filterFields));
case "is_empty" -> mustClauseList.add(getIsEmptyCondition(filterFields));
case "is_null" -> mustClauseList.add(getIsNullCondition(filterFields));
case "has_id" -> mustClauseList.add(getHasIdCondition(filterFields));
case "nested" -> mustClauseList.add(getNestedCondition(filterFields));
default -> logger.warn("Filter condition '{}' is not supported", filterFields.get("condition"));
}
}
case "must_not" -> {
logger.debug("[QdrantBaseOpDispenser] - Building 'must_not' filter clause");
switch (filterFields.get("condition").toString()) {
case "match" -> mustNotClauseList.add(getMatchFilterCondition(filterFields));
case "match_any" ->
mustNotClauseList.add(getMatchAnyExceptCondition(filterFields, "match_any"));
case "match_except" ->
mustNotClauseList.add(getMatchAnyExceptCondition(filterFields, "match_except"));
case "range" -> mustNotClauseList.add(getRangeCondition(filterFields));
case "geo_bounding_box" -> mustNotClauseList.add(getGeoBoundingBoxCondition(filterFields));
case "geo_radius" -> mustNotClauseList.add(getGeoRadiusCondition(filterFields));
case "values_count" -> mustNotClauseList.add(getValuesCountCondition(filterFields));
case "is_empty" -> mustNotClauseList.add(getIsEmptyCondition(filterFields));
case "is_null" -> mustNotClauseList.add(getIsNullCondition(filterFields));
case "has_id" -> mustClauseList.add(getHasIdCondition(filterFields));
default -> logger.warn("Filter condition '{}' is not supported", filterFields.get("condition"));
}
}
case "should" -> {
logger.debug("[QdrantBaseOpDispenser] - Building 'should' filter clause");
switch (filterFields.get("condition").toString()) {
case "match" -> shouldClauseList.add(getMatchFilterCondition(filterFields));
case "match_any" -> shouldClauseList.add(getMatchAnyExceptCondition(filterFields, "match_any"));
case "match_except" ->
shouldClauseList.add(getMatchAnyExceptCondition(filterFields, "match_except"));
case "range" -> shouldClauseList.add(getRangeCondition(filterFields));
case "geo_bounding_box" -> shouldClauseList.add(getGeoBoundingBoxCondition(filterFields));
case "geo_radius" -> shouldClauseList.add(getGeoRadiusCondition(filterFields));
case "values_count" -> shouldClauseList.add(getValuesCountCondition(filterFields));
case "is_empty" -> shouldClauseList.add(getIsEmptyCondition(filterFields));
case "is_null" -> shouldClauseList.add(getIsNullCondition(filterFields));
case "has_id" -> mustClauseList.add(getHasIdCondition(filterFields));
default -> logger.warn("Filter condition '{}' is not supported", filterFields.get("condition"));
}
}
default -> logger.error("Clause '{}' is not supported", filterFields.get("clause"));
}
}
if (!mustClauseList.isEmpty()) {
filterBuilder.addAllMust(mustClauseList);
}
if (!mustNotClauseList.isEmpty()) {
filterBuilder.addAllMustNot(mustNotClauseList);
}
if (!shouldClauseList.isEmpty()) {
filterBuilder.addAllShould(shouldClauseList);
}
return filterBuilder;
}
private Condition getMatchFilterCondition(Map<String, Object> filterFields) {
logger.debug("[QdrantBaseOpDispenser] - Building 'match' filter condition");
if (filterFields.get("value") instanceof String) {
return matchKeyword((String) filterFields.get("key"), (String) filterFields.get("value"));
} else if (filterFields.get("value") instanceof Number) {
return match((String) filterFields.get("key"), ((Number) filterFields.get("value")).intValue());
} else if (filterFields.get("value") instanceof Boolean) {
return match((String) filterFields.get("key"), ((Boolean) filterFields.get("value")));
} else if (filterFields.containsKey("text")) {
// special case of 'match'
// https://qdrant.tech/documentation/concepts/filtering/#full-text-match
return getMatchTextCondition(filterFields);
} else {
throw new OpConfigError("Unsupported value type [" + filterFields.get("value").getClass().getSimpleName() + "] for 'match' condition");
}
}
private Condition getMatchAnyExceptCondition(Map<String, Object> filterFields, String filterCondition) {
logger.debug("[QdrantBaseOpDispenser] - Building 'match_any'/'match_except' filter condition");
if (filterFields.get("value") instanceof List) {
switch (((List<?>) filterFields.get("value")).getFirst().getClass().getSimpleName()) {
case "String" -> {
if ("match_any".equals(filterCondition)) {
return matchKeywords(filterFields.get("key").toString(), (List<String>) filterFields.get("value"));
} else if ("match_except".equals(filterCondition)) {
return matchExceptKeywords(filterFields.get("key").toString(), (List<String>) filterFields.get("value"));
} else {
throw new OpConfigError("Unsupported filter condition [" + filterCondition + "]");
}
}
case "Long" -> {
if ("match_any".equals(filterCondition)) {
return matchValues(filterFields.get("key").toString(), (List<Long>) filterFields.get("value"));
} else if ("match_except".equals(filterCondition)) {
return matchExceptValues(filterFields.get("key").toString(), (List<Long>) filterFields.get("value"));
} else {
throw new OpConfigError("Unsupported filter condition [" + filterCondition + "]");
}
}
case "Integer" -> {
List<Long> convertedIntToLongValues = new ArrayList<>();
for (Integer intVal : (List<Integer>) filterFields.get("value")) {
convertedIntToLongValues.add(intVal.longValue());
}
if ("match_any".equals(filterCondition)) {
return matchValues(filterFields.get("key").toString(), convertedIntToLongValues);
} else if ("match_except".equals(filterCondition)) {
return matchExceptValues(filterFields.get("key").toString(), convertedIntToLongValues);
} else {
throw new OpConfigError("Unsupported filter condition [" + filterCondition + "]");
}
}
default -> throw new OpConfigError("Unsupported value type [" +
filterFields.get("value").getClass().getSimpleName() +
"] within value list for 'match_any'/'match_except' condition.\n" + filterFields.get("value"));
}
} else {
throw new OpConfigError("Unsupported value type [" + filterFields.get("value").getClass().getSimpleName() +
"] for 'match_any'/'match_except' condition");
}
}
private Condition getMatchTextCondition(Map<String, Object> filterFields) {
logger.debug("[QdrantBaseOpDispenser] - Building 'match_text' filter condition");
if (filterFields.get("text") instanceof String) {
return matchText((String) filterFields.get("key"), (String) filterFields.get("text"));
} else {
throw new OpConfigError("Unsupported value type [" +
filterFields.get("text").getClass().getSimpleName() + "] for 'match -> text' condition");
}
}
private Condition getRangeCondition(Map<String, Object> filterFields) {
logger.debug("[QdrantBaseOpDispenser] - Building 'range' filter condition");
if (filterFields.get("value") instanceof Map) {
if (((Map<String, ?>) filterFields.get("value")).keySet().stream().noneMatch(key ->
key.equals("gte") || key.equals("lte") || key.equals("lt") || key.equals("gt"))) {
throw new OpConfigError("Only gte/gt/lte/lt is expected for range condition, but received: "
+ ((Map<String, ?>) filterFields.get("value")).keySet());
}
Range.Builder rangeBuilder = Range.newBuilder();
DatetimeRange.Builder datetimerangeBuilder = DatetimeRange.newBuilder();
((Map<?, ?>) filterFields.get("value")).forEach((rKey, rValue) -> {
if (rValue != null) {
if (rValue instanceof Number) {
switch ((String) rKey) {
case "gte" -> rangeBuilder.setGte(((Number) rValue).doubleValue());
case "gt" -> rangeBuilder.setGt(((Number) rValue).doubleValue());
case "lte" -> rangeBuilder.setLte(((Number) rValue).doubleValue());
case "lt" -> rangeBuilder.setLt(((Number) rValue).doubleValue());
}
} else if (rValue instanceof String) {
// This is now a https://qdrant.tech/documentation/concepts/filtering/#datetime-range type
long rVal = Instant.parse(String.valueOf(rValue)).getEpochSecond();
Timestamp.Builder timestampBuilder = Timestamp.newBuilder().setSeconds(rVal);
switch ((String) rKey) {
case "gte" -> datetimerangeBuilder.setGte(timestampBuilder);
case "gt" -> datetimerangeBuilder.setGt(timestampBuilder);
case "lte" -> datetimerangeBuilder.setLte(timestampBuilder);
case "lt" -> datetimerangeBuilder.setLt(timestampBuilder);
}
} else {
logger.warn("Unsupported value [{}] ignored for 'range' filter condition.", rValue);
}
}
});
if (datetimerangeBuilder.hasGt() || datetimerangeBuilder.hasGte()
|| datetimerangeBuilder.hasLte() || datetimerangeBuilder.hasLt()) {
return datetimeRange((String) filterFields.get("key"), datetimerangeBuilder.build());
} else {
// we assume here this is Range type
if (rangeBuilder.hasGt() || rangeBuilder.hasGte() || rangeBuilder.hasLte() || rangeBuilder.hasLt())
return range((String) filterFields.get("key"), rangeBuilder.build());
}
} else {
throw new OpConfigError("Unsupported value type [" + filterFields.get("value").getClass().getSimpleName() +
"] for 'range' condition. Needs a list containing gt/gte/lt/lte");
}
return null;
}
private Condition getGeoBoundingBoxCondition(Map<String, Object> filterFields) {
logger.debug("[QdrantBaseOpDispenser] - Building 'geo_bounding_box' filter condition");
if (filterFields.get("value") instanceof Map) {
Map<String, ?> valueMap = (Map<String, ?>) filterFields.get("value");
if (valueMap.keySet().stream().noneMatch(key -> key.equals("bottom_right") || key.equals("top_left"))) {
throw new OpConfigError("Both top_left & bottom_right are expected for geo_bounding_box condition, " +
"but received: " + valueMap.keySet());
}
valueMap.forEach((rKey, rValue) -> {
if (rValue instanceof Map) {
if (((Map<String, Number>) rValue).keySet().stream().noneMatch(geoLatLon ->
geoLatLon.equals("lat") || geoLatLon.equals("lon"))) {
throw new OpConfigError("Both 'top_left' & 'bottom_right' for 'geo_bounding_box' are expected" +
" to have both 'lat' & 'lon' fields");
}
} else {
throw new OpConfigError("Unsupported value [" + rValue + "] ignored for 'geo_bounding_box' filter condition.");
}
});
double topLeftLat = (((Map<String, Number>) valueMap.get("top_left")).get("lat")).doubleValue();
double topLeftLon = (((Map<String, Number>) valueMap.get("top_left")).get("lon")).doubleValue();
double bottomRightLat = (((Map<String, Number>) valueMap.get("bottom_right")).get("lat")).doubleValue();
double bottomRightLon = (((Map<String, Number>) valueMap.get("bottom_right")).get("lon")).doubleValue();
return Condition.newBuilder()
.setField(Points.FieldCondition.newBuilder()
.setKey((String) filterFields.get("key"))
.setGeoBoundingBox(Points.GeoBoundingBox.newBuilder()
.setTopLeft(Points.GeoPoint.newBuilder()
.setLat(topLeftLat)
.setLon(topLeftLon)
.build())
.setBottomRight(Points.GeoPoint.newBuilder()
.setLat(bottomRightLat)
.setLon(bottomRightLon)
.build())
.build())
.build())
.build();
} else {
throw new OpConfigError("Unsupported value type [" + filterFields.get("value").getClass().getSimpleName() + "]" +
" for 'geo_bounding_box' condition. Needs a map containing 'top_left' & 'bottom_right' with 'lat' & 'lon'");
}
}
private Condition getGeoRadiusCondition(Map<String, Object> filterFields) {
logger.debug("[QdrantBaseOpDispenser] - Building 'geo_radius' filter condition");
if (filterFields.get("value") instanceof Map) {
Map<String, ?> valueMap = (Map<String, ?>) filterFields.get("value");
if (valueMap.keySet().stream().noneMatch(key -> key.equals("center") || key.equals("radius"))) {
throw new OpConfigError("Both 'center' & 'radius' are expected for 'geo_radius' condition, " +
"but received: " + valueMap.keySet());
}
valueMap.forEach((rKey, rValue) -> {
if (rKey.equals("center")) {
if (rValue instanceof Map) {
if (((Map<String, Number>) rValue).keySet().stream().noneMatch(geoLatLon ->
geoLatLon.equals("lat") || geoLatLon.equals("lon"))) {
throw new OpConfigError("Both 'lat' & 'lon' within 'center' are expected" +
" for the 'geo_radius' condition");
}
} else {
throw new OpConfigError("Unsupported value [" + rValue + "] ignored for 'geo_radius'" +
" -> 'center' filter condition.");
}
}
});
double centerLat = (((Map<String, Number>) valueMap.get("center")).get("lat")).doubleValue();
double centerLon = (((Map<String, Number>) valueMap.get("center")).get("lon")).doubleValue();
float radius = ((Number) valueMap.get("radius")).floatValue();
return Condition.newBuilder()
.setField(Points.FieldCondition.newBuilder()
.setKey((String) filterFields.get("key"))
.setGeoRadius(Points.GeoRadius.newBuilder()
.setCenter(Points.GeoPoint.newBuilder()
.setLat(centerLat)
.setLon(centerLon)
.build())
.setRadius(radius)
.build())
.build())
.build();
} else {
throw new OpConfigError("Unsupported value type [" + filterFields.get("value").getClass().getSimpleName() + "]" +
" for 'geo_radius' condition. Needs a map containing 'center' ('lat' & 'lon') and 'radius' fields");
}
}
private Condition getGeoPolygonCondition(Map<String, Object> filterFields) {
logger.debug("[QdrantBaseOpDispenser] - Building 'geo_polygon' filter condition");
if (filterFields.get("value") instanceof Map) {
Map<String, ?> valueMap = (Map<String, ?>) filterFields.get("value");
if (valueMap.keySet().stream().noneMatch(key -> key.equals("exterior_points") || key.equals("interior_points"))) {
throw new OpConfigError("Both 'exterior_points' & 'interior_points' with lat/lon array is required" +
" for 'geo_polygon' filter condition");
}
Points.GeoLineString.Builder exteriorPoints = Points.GeoLineString.newBuilder();
List<Points.GeoPoint> extPoints = new ArrayList<>();
Points.GeoLineString.Builder interiorPoints = Points.GeoLineString.newBuilder();
List<Points.GeoPoint> intPoints = new ArrayList<>();
valueMap.forEach((gpKey, gpVal) -> {
switch ((String) gpKey) {
case "exterior_points" -> {
((List<Map<String, Number>>) gpVal).forEach((endEp) -> {
extPoints.add(Points.GeoPoint.newBuilder()
.setLat((endEp).get("lat").doubleValue())
.setLon((endEp).get("lon").doubleValue())
.build());
});
}
case "interior_points" -> {
((List<Map<String, Number>>) gpVal).forEach((endIp) -> {
intPoints.add(Points.GeoPoint.newBuilder()
.setLat((endIp).get("lat").doubleValue())
.setLon((endIp).get("lon").doubleValue())
.build());
});
}
}
});
exteriorPoints.addAllPoints(extPoints);
interiorPoints.addAllPoints(intPoints);
return geoPolygon((String) filterFields.get("key"), exteriorPoints.build(), List.of(interiorPoints.build()));
} else {
throw new OpConfigError("Unsupported type [" + filterFields.get("value").getClass().getSimpleName() +
"] passed for 'geo_polygon' filter condition");
}
}
private Condition getValuesCountCondition(Map<String, Object> filterFields) {
logger.debug("[QdrantBaseOpDispenser] - Building 'values_count' filter condition");
if (filterFields.get("value") instanceof Map) {
Map<String, ?> valueMap = (Map<String, ?>) filterFields.get("value");
if (valueMap.keySet().stream().noneMatch(key -> key.equals("gte") || key.equals("gt")
|| key.equals("lte") || key.equals("lt"))) {
throw new OpConfigError("Only 'gte', 'gt', 'lte' or 'lt' is expected for 'values_count' condition, " +
"but received: " + valueMap.keySet());
}
Points.ValuesCount.Builder valCntBuilder = Points.ValuesCount.newBuilder();
valueMap.forEach((vcKey, vcValue) -> {
if (vcValue != null) {
switch (vcKey) {
case "gte" -> valCntBuilder.setGte(((Number) vcValue).longValue());
case "gt" -> valCntBuilder.setGt(((Number) vcValue).longValue());
case "lte" -> valCntBuilder.setLte(((Number) vcValue).longValue());
case "lt" -> valCntBuilder.setLt(((Number) vcValue).longValue());
}
}
});
return Condition.newBuilder()
.setField(Points.FieldCondition.newBuilder()
.setKey((String) filterFields.get("key"))
.setValuesCount(valCntBuilder)
.build())
.build();
} else {
throw new OpConfigError("Unsupported type [" + filterFields.get("value").getClass().getSimpleName() + "]" +
" for 'values_count' filter condition");
}
}
private Condition getIsNullCondition(Map<String, Object> filterFields) {
logger.debug("[QdrantBaseOpDispenser] - Building 'is_null' filter condition");
return Condition.newBuilder()
.setIsNull(Points.IsNullCondition.newBuilder()
.setKey((String) filterFields.get("key"))
.build())
.build();
}
private Condition getIsEmptyCondition(Map<String, Object> filterFields) {
logger.debug("[QdrantBaseOpDispenser] - Building 'is_empty' filter condition");
return Condition.newBuilder()
.setIsEmpty(Points.IsEmptyCondition.newBuilder()
.setKey((String) filterFields.get("key"))
.build())
.build();
}
/**
* This {@link nested} is only valid within a 'must' filter condition.
*
* @param filterFields
* @return
* @see <a href="https://qdrant.tech/documentation/concepts/filtering/#nested-object-filter">Nested Object Filter</a>
* @see <a href="https://github.com/qdrant/java-client/blob/v1.9.1/src/main/java/io/qdrant/client/ConditionFactory.java#L220-L249">code</a>
*/
private Condition getNestedCondition(Map<String, Object> filterFields) {
logger.debug("[QdrantBaseOpDispenser] - Building 'nested' filter condition");
if (filterFields.get("nested") instanceof List) {
List<Condition> mustClauseList = new ArrayList<>();
Filter.Builder nestedFilter = Filter.newBuilder();
((List<Map<String, Object>>) filterFields.get("nested")).forEach((nList) -> {
mustClauseList.add(getMatchFilterCondition(nList));
});
nestedFilter.addAllMust(mustClauseList);
return Condition.newBuilder()
.setNested(Points.NestedCondition.newBuilder()
.setKey((String) filterFields.get("key"))
.setFilter(nestedFilter)
.build())
.build();
} else {
throw new OpConfigError("Unsupported type [" + filterFields.get("nested").getClass().getSimpleName() + "]" +
" for 'nested' filter condition");
}
}
private Condition getHasIdCondition(Map<String, Object> filterFields) {
if (filterFields.get("value") instanceof List) {
List<Points.PointId> pointIds = new ArrayList<>();
((List<Object>) filterFields.get("value")).forEach((pId) -> {
if (pId instanceof Number) {
pointIds.add(PointIdFactory.id(((Number) pId).longValue()));
} else if (pId instanceof String) {
pointIds.add(Points.PointId.newBuilder().setUuid(pId.toString()).build());
} else {
throw new OpConfigError("Unsupported type for 'id' specified [" + pId.getClass().getSimpleName() + "]");
}
});
return Condition.newBuilder()
.setHasId(Points.HasIdCondition.newBuilder()
.addAllHasId(pointIds)
.build())
.build();
} else {
throw new OpConfigError("Unsupported type [" + filterFields.get("value").getClass().getSimpleName() + "]" +
" for 'has_id' filter condition");
}
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright (c) 2020-2024 nosqlbench
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.nosqlbench.adapter.qdrant.opdispensers;
import io.nosqlbench.adapter.qdrant.QdrantDriverAdapter;
import io.nosqlbench.adapter.qdrant.ops.QdrantBaseOp;
import io.nosqlbench.adapter.qdrant.ops.QdrantCollectionExistsOp;
import io.nosqlbench.adapters.api.templating.ParsedOp;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.grpc.Collections.CollectionExistsRequest;
import java.util.function.LongFunction;
public class QdrantCollectionExistsOpDispenser extends QdrantBaseOpDispenser<CollectionExistsRequest> {
public QdrantCollectionExistsOpDispenser(QdrantDriverAdapter adapter, ParsedOp op, LongFunction<String> targetFunction) {
super(adapter, op, targetFunction);
}
@Override
public LongFunction<CollectionExistsRequest> getParamFunc(
LongFunction<QdrantClient> clientF, ParsedOp op, LongFunction<String> targetF) {
LongFunction<CollectionExistsRequest.Builder> ebF =
l -> CollectionExistsRequest.newBuilder().setCollectionName(targetF.apply(l));
final LongFunction<CollectionExistsRequest.Builder> lastF = ebF;
return l -> lastF.apply(l).build();
}
@Override
public LongFunction<QdrantBaseOp<CollectionExistsRequest>> createOpFunc(
LongFunction<CollectionExistsRequest> paramF,
LongFunction<QdrantClient> clientF,
ParsedOp op,
LongFunction<String> targetF) {
return l -> new QdrantCollectionExistsOp(clientF.apply(l), paramF.apply(l));
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (c) 2020-2024 nosqlbench
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.nosqlbench.adapter.qdrant.opdispensers;
import io.nosqlbench.adapter.qdrant.QdrantDriverAdapter;
import io.nosqlbench.adapter.qdrant.ops.QdrantBaseOp;
import io.nosqlbench.adapter.qdrant.ops.QdrantCollectionInfoOp;
import io.nosqlbench.adapters.api.templating.ParsedOp;
import io.qdrant.client.QdrantClient;
import java.util.function.LongFunction;
public class QdrantCollectionInfoOpDispenser extends QdrantBaseOpDispenser<String> {
public QdrantCollectionInfoOpDispenser(QdrantDriverAdapter adapter, ParsedOp op, LongFunction<String> targetFunction) {
super(adapter, op, targetFunction);
}
@Override
public LongFunction<String> getParamFunc(
LongFunction<QdrantClient> clientF, ParsedOp op, LongFunction<String> targetF) {
return l -> targetF.apply(l);
}
@Override
public LongFunction<QdrantBaseOp<String>> createOpFunc(
LongFunction<String> paramF,
LongFunction<QdrantClient> clientF,
ParsedOp op,
LongFunction<String> targetF) {
return l -> new QdrantCollectionInfoOp(clientF.apply(l), paramF.apply(l));
}
}

View File

@ -22,6 +22,7 @@ import io.nosqlbench.adapter.qdrant.ops.QdrantCountPointsOp;
import io.nosqlbench.adapters.api.templating.ParsedOp;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.grpc.Points.CountPoints;
import io.qdrant.client.grpc.Points.Filter;
import java.util.function.LongFunction;
@ -36,6 +37,14 @@ public class QdrantCountPointsOpDispenser extends QdrantBaseOpDispenser<CountPoi
LongFunction<CountPoints.Builder> ebF =
l -> CountPoints.newBuilder().setCollectionName(targetF.apply(l));
ebF = op.enhanceFuncOptionally(ebF, "exact", Boolean.class, CountPoints.Builder::setExact);
LongFunction<Filter.Builder> filterBuilder = getFilterFromOp(op);
if (filterBuilder != null) {
final LongFunction<CountPoints.Builder> filterF = ebF;
ebF = l -> filterF.apply(l).setFilter(filterBuilder.apply(l));
}
final LongFunction<CountPoints.Builder> lastF = ebF;
return l -> lastF.apply(l).build();
}

View File

@ -18,14 +18,18 @@ package io.nosqlbench.adapter.qdrant.opdispensers;
import io.nosqlbench.adapter.qdrant.QdrantDriverAdapter;
import io.nosqlbench.adapter.qdrant.ops.QdrantBaseOp;
import io.nosqlbench.adapter.qdrant.ops.QdrantPayloadIndexOp;
import io.nosqlbench.adapter.qdrant.ops.QdrantCreatePayloadIndexOp;
import io.nosqlbench.adapters.api.templating.ParsedOp;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.grpc.Collections.PayloadIndexParams;
import io.qdrant.client.grpc.Points.CreateFieldIndexCollection;
import io.qdrant.client.grpc.Points.FieldType;
import io.qdrant.client.grpc.Points.WriteOrdering;
import io.qdrant.client.grpc.Points.WriteOrderingType;
import java.util.Optional;
import java.util.function.LongFunction;
public class QdrantCreatePayloadIndexOpDispenser extends QdrantBaseOpDispenser<PayloadIndexParams> {
public class QdrantCreatePayloadIndexOpDispenser extends QdrantBaseOpDispenser<CreateFieldIndexCollection> {
public QdrantCreatePayloadIndexOpDispenser(
QdrantDriverAdapter adapter,
ParsedOp op,
@ -34,21 +38,44 @@ public class QdrantCreatePayloadIndexOpDispenser extends QdrantBaseOpDispenser<P
}
@Override
public LongFunction<PayloadIndexParams> getParamFunc(
public LongFunction<CreateFieldIndexCollection> getParamFunc(
LongFunction<QdrantClient> clientF,
ParsedOp op,
LongFunction<String> targetF) {
LongFunction<PayloadIndexParams.Builder> ebF =
l -> PayloadIndexParams.newBuilder().setField(null, targetF.apply(l));
return l -> ebF.apply(l).build();
LongFunction<CreateFieldIndexCollection.Builder> ebF =
l -> CreateFieldIndexCollection.newBuilder().setCollectionName(targetF.apply(l));
// https://github.com/qdrant/java-client/blob/v1.9.1/src/main/java/io/qdrant/client/QdrantClient.java#L2240-L2248
ebF = op.enhanceFuncOptionally(ebF, "field_name", String.class, CreateFieldIndexCollection.Builder::setFieldName);
LongFunction<String> fieldTypeF = op.getAsRequiredFunction("field_type", String.class);
final LongFunction<CreateFieldIndexCollection.Builder> ftF = ebF;
ebF = l -> ftF.apply(l).setFieldType(FieldType.valueOf(fieldTypeF.apply(l)));
Optional<LongFunction<String>> writeOrderingF = op.getAsOptionalFunction("ordering", String.class);
if (writeOrderingF.isPresent()) {
LongFunction<CreateFieldIndexCollection.Builder> woF = ebF;
LongFunction<WriteOrdering> writeOrdrF = buildWriteOrderingFunc(writeOrderingF.get());
ebF = l -> woF.apply(l).setOrdering(writeOrdrF.apply(l));
}
ebF = op.enhanceFuncOptionally(ebF, "wait", Boolean.class, CreateFieldIndexCollection.Builder::setWait);
final LongFunction<CreateFieldIndexCollection.Builder> lastF = ebF;
return l -> lastF.apply(l).build();
}
@Override
public LongFunction<QdrantBaseOp<PayloadIndexParams>> createOpFunc(
LongFunction<PayloadIndexParams> paramF,
public LongFunction<QdrantBaseOp<CreateFieldIndexCollection>> createOpFunc(
LongFunction<CreateFieldIndexCollection> paramF,
LongFunction<QdrantClient> clientF,
ParsedOp op,
LongFunction<String> targetF) {
return l -> new QdrantPayloadIndexOp(clientF.apply(l), paramF.apply(l));
return l -> new QdrantCreatePayloadIndexOp(clientF.apply(l), paramF.apply(l));
}
private LongFunction<WriteOrdering> buildWriteOrderingFunc(LongFunction<String> stringLongFunction) {
return l -> {
return WriteOrdering.newBuilder().setType(WriteOrderingType.valueOf(stringLongFunction.apply(l))).build();
};
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright (c) 2020-2024 nosqlbench
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.nosqlbench.adapter.qdrant.opdispensers;
import io.nosqlbench.adapter.qdrant.QdrantDriverAdapter;
import io.nosqlbench.adapter.qdrant.ops.QdrantBaseOp;
import io.nosqlbench.adapter.qdrant.ops.QdrantListCollectionAliasesOp;
import io.nosqlbench.adapters.api.templating.ParsedOp;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.grpc.Collections.ListCollectionAliasesRequest;
import java.util.function.LongFunction;
public class QdrantListCollectionAliasesOpDispenser extends QdrantBaseOpDispenser<ListCollectionAliasesRequest> {
public QdrantListCollectionAliasesOpDispenser(QdrantDriverAdapter adapter, ParsedOp op,
LongFunction<String> targetFunction) {
super(adapter, op, targetFunction);
}
@Override
public LongFunction<ListCollectionAliasesRequest> getParamFunc(LongFunction<QdrantClient> clientF, ParsedOp op,
LongFunction<String> targetF) {
LongFunction<ListCollectionAliasesRequest.Builder> ebF =
l -> ListCollectionAliasesRequest.newBuilder().setCollectionName(targetF.apply(l));
final LongFunction<ListCollectionAliasesRequest.Builder> lastF = ebF;
return l -> lastF.apply(l).build();
}
@Override
public LongFunction<QdrantBaseOp<ListCollectionAliasesRequest>> createOpFunc(
LongFunction<ListCollectionAliasesRequest> paramF,
LongFunction<QdrantClient> clientF, ParsedOp op, LongFunction<String> targetF) {
return l -> new QdrantListCollectionAliasesOp(clientF.apply(l), paramF.apply(l));
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) 2020-2024 nosqlbench
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.nosqlbench.adapter.qdrant.opdispensers;
import io.nosqlbench.adapter.qdrant.QdrantDriverAdapter;
import io.nosqlbench.adapter.qdrant.ops.QdrantBaseOp;
import io.nosqlbench.adapter.qdrant.ops.QdrantListCollectionsOp;
import io.nosqlbench.adapters.api.templating.ParsedOp;
import io.qdrant.client.QdrantClient;
import java.util.function.LongFunction;
public class QdrantListCollectionsOpDispenser extends QdrantBaseOpDispenser<Object> {
public QdrantListCollectionsOpDispenser(QdrantDriverAdapter adapter,
ParsedOp op, LongFunction<String> targetFunction) {
super(adapter, op, targetFunction);
}
@Override
public LongFunction<Object> getParamFunc(LongFunction<QdrantClient> clientF, ParsedOp op, LongFunction<String> targetF) {
return l -> "";
}
@Override
public LongFunction<QdrantBaseOp<Object>> createOpFunc(
LongFunction<Object> paramF,
LongFunction<QdrantClient> clientF,
ParsedOp op, LongFunction<String> targetF) {
return l -> new QdrantListCollectionsOp(clientF.apply(l), null);
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) 2020-2024 nosqlbench
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.nosqlbench.adapter.qdrant.opdispensers;
import io.nosqlbench.adapter.qdrant.QdrantDriverAdapter;
import io.nosqlbench.adapter.qdrant.ops.QdrantBaseOp;
import io.nosqlbench.adapter.qdrant.ops.QdrantListSnapshotsOp;
import io.nosqlbench.adapters.api.templating.ParsedOp;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.grpc.SnapshotsService.ListSnapshotsRequest;
import java.util.function.LongFunction;
public class QdrantListSnapshotsOpDispenser extends QdrantBaseOpDispenser<ListSnapshotsRequest> {
public QdrantListSnapshotsOpDispenser(QdrantDriverAdapter adapter, ParsedOp op, LongFunction<String> targetFunction) {
super(adapter, op, targetFunction);
}
@Override
public LongFunction<ListSnapshotsRequest> getParamFunc(LongFunction<QdrantClient> clientF,
ParsedOp op, LongFunction<String> targetF) {
LongFunction<ListSnapshotsRequest.Builder> ebF =
l -> ListSnapshotsRequest.newBuilder().setCollectionName(targetF.apply(l));
final LongFunction<ListSnapshotsRequest.Builder> lastF = ebF;
return l -> lastF.apply(l).build();
}
@Override
public LongFunction<QdrantBaseOp<ListSnapshotsRequest>> createOpFunc(LongFunction<ListSnapshotsRequest> paramF,
LongFunction<QdrantClient> clientF,
ParsedOp op, LongFunction<String> targetF) {
return l -> new QdrantListSnapshotsOp(clientF.apply(l), paramF.apply(l));
}
}

View File

@ -19,7 +19,7 @@ package io.nosqlbench.adapter.qdrant.opdispensers;
import io.nosqlbench.adapter.qdrant.QdrantDriverAdapter;
import io.nosqlbench.adapter.qdrant.ops.QdrantBaseOp;
import io.nosqlbench.adapter.qdrant.ops.QdrantSearchPointsOp;
import io.nosqlbench.adapter.qdrant.pojo.SearchPointsHelper;
import io.nosqlbench.adapter.qdrant.pojos.SearchPointsHelper;
import io.nosqlbench.adapters.api.templating.ParsedOp;
import io.nosqlbench.nb.api.errors.OpConfigError;
import io.qdrant.client.QdrantClient;
@ -74,7 +74,7 @@ public class QdrantSearchPointsOpDispenser extends QdrantBaseOpDispenser<SearchP
ebF = l -> detailsOfNamedVectorsF.apply(l)
.setVectorName(searchPointsHelperF.apply(l).getVectorName())
.addAllVector(searchPointsHelperF.apply(l).getVectorValues());
//.setSparseIndices(searchPointsHelperF.apply(l).getSparseIndices()); throws NPE at their driver and hence below
//.setSparseIndices(searchPointsHelperF.apply(l).getSparseIndices()); throws NPE at Qdrant driver and hence below
final LongFunction<SearchPoints.Builder> sparseIndicesF = ebF;
ebF = l -> {
SearchPoints.Builder builder = sparseIndicesF.apply(l);
@ -106,7 +106,18 @@ public class QdrantSearchPointsOpDispenser extends QdrantBaseOpDispenser<SearchP
ebF = l -> withVectorFunc.apply(l).setWithVectors(builtWithVector.apply(l));
}
// TODO - Implement filter, params
Optional<LongFunction<Map>> optionalParams = op.getAsOptionalFunction("params", Map.class);
if (optionalParams.isPresent()) {
LongFunction<SearchPoints.Builder> paramsF = ebF;
LongFunction<SearchParams> params = buildSearchParams(optionalParams.get());
ebF = l -> paramsF.apply(l).setParams(params.apply(l));
}
LongFunction<Filter.Builder> filterBuilder = getFilterFromOp(op);
if (filterBuilder != null) {
final LongFunction<SearchPoints.Builder> filterF = ebF;
ebF = l -> filterF.apply(l).setFilter(filterBuilder.apply(l));
}
final LongFunction<SearchPoints.Builder> lastF = ebF;
return l -> lastF.apply(l).build();
@ -216,4 +227,37 @@ public class QdrantSearchPointsOpDispenser extends QdrantBaseOpDispenser<SearchP
}
};
}
private LongFunction<SearchParams> buildSearchParams(LongFunction<Map> mapLongFunction) {
return l -> {
SearchParams.Builder searchParamsBuilder = SearchParams.newBuilder();
mapLongFunction.apply(l).forEach((key, val) -> {
if ("hnsw_config".equals(key)) {
searchParamsBuilder.setHnswEf(((Number) val).longValue());
}
if ("exact".equals(key)) {
searchParamsBuilder.setExact((Boolean) val);
}
if ("indexed_only".equals(key)) {
searchParamsBuilder.setIndexedOnly((Boolean) val);
}
if ("quantization".equals(key)) {
QuantizationSearchParams.Builder qsBuilder = QuantizationSearchParams.newBuilder();
((Map<String, Object>) val).forEach((qKey, qVal) -> {
if ("ignore".equals(qKey)) {
qsBuilder.setIgnore((Boolean) qVal);
}
if ("rescore".equals(qKey)) {
qsBuilder.setRescore((Boolean) qVal);
}
if ("oversampling".equals(qKey)) {
qsBuilder.setOversampling(((Number) qVal).doubleValue());
}
});
searchParamsBuilder.setQuantization(qsBuilder);
}
});
return searchParamsBuilder.build();
};
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2020-2024 nosqlbench
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.nosqlbench.adapter.qdrant.ops;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.grpc.Collections.CollectionExistsRequest;
import java.time.Duration;
public class QdrantCollectionExistsOp extends QdrantBaseOp<CollectionExistsRequest> {
public QdrantCollectionExistsOp(QdrantClient client, CollectionExistsRequest request) {
super(client, request);
}
@Override
public Object applyOp(long value) {
Boolean response;
try {
response = client.collectionExistsAsync(request.getCollectionName(), Duration.ofSeconds(600)).get();
logger.info("Collection {} exists: {}", request.getCollectionName(), response);
} catch (Exception e) {
throw new RuntimeException(e);
}
return response;
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2020-2024 nosqlbench
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.nosqlbench.adapter.qdrant.ops;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.grpc.Collections.CollectionInfo;
import java.time.Duration;
public class QdrantCollectionInfoOp extends QdrantBaseOp<String> {
public QdrantCollectionInfoOp(QdrantClient client, String request) {
super(client, request);
}
@Override
public Object applyOp(long value) {
CollectionInfo response;
try {
response = client.getCollectionInfoAsync(request, Duration.ofSeconds(600)).get();
logger.debug("Collection info: {}", response.toString());
} catch (Exception e) {
throw new RuntimeException(e);
}
return response;
}
}

View File

@ -31,9 +31,11 @@ public class QdrantCountPointsOp extends QdrantBaseOp<CountPoints> {
public Object applyOp(long value) {
long result;
try {
boolean hasFilters = request.getFilter() != null && (request.getFilter().getMustCount() > 0
|| request.getFilter().getMustNotCount() > 0 || request.getFilter().getShouldCount() > 0);
result = client.countAsync(
request.getCollectionName(),
request.getFilter(),
(hasFilters) ? request.getFilter() : null,
request.getExact(),
Duration.ofMinutes(5) // opinionated default of 5 minutes for timeout
).get();

View File

@ -0,0 +1,50 @@
/*
* Copyright (c) 2020-2024 nosqlbench
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.nosqlbench.adapter.qdrant.ops;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.grpc.Collections.PayloadSchemaType;
import io.qdrant.client.grpc.Points.CreateFieldIndexCollection;
import io.qdrant.client.grpc.Points.UpdateResult;
import java.time.Duration;
public class QdrantCreatePayloadIndexOp extends QdrantBaseOp<CreateFieldIndexCollection> {
public QdrantCreatePayloadIndexOp(QdrantClient client, CreateFieldIndexCollection request) {
super(client, request);
}
@Override
public Object applyOp(long value) {
UpdateResult response;
try {
response = client.createPayloadIndexAsync(
request.getCollectionName(),
request.getFieldName(),
PayloadSchemaType.forNumber(request.getFieldTypeValue()),
request.getFieldIndexParams(),
request.getWait(),
request.getOrdering().getType(),
Duration.ofSeconds(60000))
.get();
logger.debug("[QdrantCreatePayloadIndexOp] Response is {}", response);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
return response;
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright (c) 2020-2024 nosqlbench
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.nosqlbench.adapter.qdrant.ops;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.grpc.Collections.ListCollectionAliasesRequest;
import java.time.Duration;
import java.util.List;
public class QdrantListCollectionAliasesOp extends QdrantBaseOp<ListCollectionAliasesRequest> {
public QdrantListCollectionAliasesOp(QdrantClient client, ListCollectionAliasesRequest request) {
super(client, request);
}
@Override
public Object applyOp(long value) {
List<String> response;
try {
response = client.listCollectionAliasesAsync(request.getCollectionName(), Duration.ofSeconds(600)).get();
logger.info("Collection aliases are {}", response);
} catch (Exception e) {
throw new RuntimeException(e);
}
return response;
}
}

View File

@ -17,16 +17,23 @@
package io.nosqlbench.adapter.qdrant.ops;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.grpc.Collections.PayloadIndexParams;
public class QdrantPayloadIndexOp extends QdrantBaseOp<PayloadIndexParams> {
public QdrantPayloadIndexOp(QdrantClient client, PayloadIndexParams request) {
import java.time.Duration;
import java.util.List;
public class QdrantListCollectionsOp extends QdrantBaseOp<Object> {
public QdrantListCollectionsOp(QdrantClient client, Object request) {
super(client, request);
}
@Override
public Object applyOp(long value) {
//client.createPayloadIndexAsync(PayloadIndexParams.get);
return null;
List<String> response;
try {
response = client.listCollectionsAsync(Duration.ofSeconds(300)).get();
logger.info("[QdrantListCollectionsOp] Collections: {}", response);
} catch (Exception e) {
throw new RuntimeException(e);
}
return response;
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright (c) 2020-2024 nosqlbench
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.nosqlbench.adapter.qdrant.ops;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.grpc.SnapshotsService.ListSnapshotsRequest;
import io.qdrant.client.grpc.SnapshotsService.SnapshotDescription;
import java.time.Duration;
import java.util.List;
public class QdrantListSnapshotsOp extends QdrantBaseOp<ListSnapshotsRequest> {
public QdrantListSnapshotsOp(QdrantClient client, ListSnapshotsRequest request) {
super(client, request);
}
@Override
public Object applyOp(long value) {
List<SnapshotDescription> response;
try {
response = client.listSnapshotAsync(request.getCollectionName(), Duration.ofSeconds(600)).get();
response.forEach(s -> logger.info("[QdrantListSnapshotsOp] Snapshot: " + s.toString()));
} catch(Exception e) {
throw new RuntimeException(e);
}
return response;
}
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.nosqlbench.adapter.qdrant.pojo;
package io.nosqlbench.adapter.qdrant.pojos;
import io.qdrant.client.grpc.Points.SparseIndices;

View File

@ -28,4 +28,9 @@ public enum QdrantOpType {
// https://qdrant.github.io/qdrant/redoc/index.html#tag/points/operation/count_points
// https://qdrant.tech/documentation/concepts/points/#counting-points
count_points,
list_collections,
collection_info,
collection_exists,
list_collection_aliases,
list_snapshots,
}

View File

@ -178,9 +178,113 @@ blocks:
relevancy.accept({relevant_indices_TEMPLATE(filetype)},actual_indices);
// because we are "verifying" although this needs to be reorganized
return true;
# More complex filtering available. See 'count_points' below for an example filter structure
create_payload_index:
ops:
create_payload_index_op:
create_payload_index: "TEMPLATE(collection)"
field_name: "field17"
field_type: "Keyword"
ordering: "Strong"
wait: true
# https://github.com/qdrant/qdrant/blob/v1.9.2/lib/api/src/grpc/proto/collections.proto#L395-L400
count_vectors:
ops:
count_points_op:
count_points: "TEMPLATE(collection)"
exact: true
# More complex filtering logic could be provided as follows
#filter:
# - clause: "must"
# condition: "match"
# key: "field1"
# value: "abc1"
# - clause: "must_not"
# condition: "match_any"
# key: "field2"
# value:
# - "abc2"
# - "abc3"
# - clause: "should"
# condition: "range"
# key: "field3"
# # any one of below
# value:
# gte: 10
# lte: 20
# gt: null
# lt: null
# - clause: "must"
# condition: "nested"
# key: "field4"
# nested:
# - condition: "match"
# key: "field5[].whatsup"
# value: "ni24maddy"
# - condition: "match"
# key: "field6"
# value: true
# - clause: "should"
# condition: "has_id"
# # no other keys are supported for this type
# key: "id"
# value:
# - 1
# - 2
# - 3
# - clause: "should"
# condition: "match"
# key: "field7"
# # special case of match is text
# text: "abc7"
# - clause: "should"
# condition: "geo_bounding_box"
# key: "field8"
# value:
# top_left:
# lat: 40.7128
# lon: -74.0060
# bottom_right:
# lat: 40.7128
# lon: -74.0060
# - clause: "must_not"
# condition: "geo_radius"
# key: "field9"
# value:
# center:
# lat: 40.7128
# lon: -74.0060
# radius: 100.0
# - clause: "must"
# condition: "geo_polygon"
# key: "field10"
# value:
# exterior_points:
# - lat: 30.7128
# lon: -34.0060
# interior_points:
# - lat: 42.7128
# lon: -54.0060
# - clause: "should"
# condition: "values_count"
# key: "field11"
# # Any one of below
# value:
# gte: 1
# lte: 10
# gt: null
# lt: null
# - clause: "must_not"
# condition: "is_empty"
# key: "field12"
# - clause: "must"
# condition: "is_null"
# key: "field13"
# - clause: "must"
# condition: "match_except"
# key: "field14"
# value:
# - 1
# - 2

View File

@ -33,6 +33,12 @@ The following are a couple high level API operations.
* Count Points
* Drop Collection
* Search Points (vectors)
* Create Payload Index
* List Collections
* List Collection Aliases
* List Snapshots
* Collection Info
* Collection Exists
## Examples