com.yahoo.search.predicate.PredicateQueryParser Maven / Gradle / Ivy
The newest version!
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.predicate;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import java.io.IOException;
import java.util.Arrays;
/**
* Parses predicate queries from JSON.
* Input JSON is assumed to have the following format:
* {
* "features": [
* {"k": "key-name", "v":"value", "s":"0xDEADBEEFDEADBEEF"}
* ],
* "rangeFeatures": [
* {"k": "key-name", "v":42, "s":"0xDEADBEEFDEADBEEF"}
* ]
* }
*
* @author bjorncs
*/
public class PredicateQueryParser {
private final JsonFactory factory = new JsonFactory();
@FunctionalInterface
public interface FeatureHandler {
void accept(String key, V value, long subqueryBitmap);
}
/**
* Parses predicate query from JSON.
* @param json JSON input.
* @param featureHandler The handler is invoked when a feature is parsed.
* @param rangeFeatureHandler The handler is invoked when a range feature is parsed.
* @throws IllegalArgumentException If JSON is invalid.
*/
public void parseJsonQuery(
String json, FeatureHandler featureHandler, FeatureHandler rangeFeatureHandler)
throws IllegalArgumentException {
try (JsonParser parser = factory.createParser(json)) {
skipToken(parser, JsonToken.START_OBJECT);
while (parser.nextToken() != JsonToken.END_OBJECT) {
String fieldName = parser.currentName();
switch (fieldName) {
case "features":
parseFeatures(parser, JsonParser::getText, featureHandler);
break;
case "rangeFeatures":
parseFeatures(parser, JsonParser::getLongValue, rangeFeatureHandler);
break;
default:
throw new IllegalArgumentException("Invalid field name: " + fieldName);
}
}
} catch (IllegalArgumentException e) {
throw e;
} catch (IOException e) {
throw new AssertionError("This should never happen when parsing from a String", e);
} catch (Exception e) {
throw new IllegalArgumentException(String.format("Parsing query from JSON failed: '%s'", json), e);
}
}
private static void parseFeatures(
JsonParser parser, ValueParser valueParser, FeatureHandler featureHandler) throws IOException {
skipToken(parser, JsonToken.START_ARRAY);
while (parser.nextToken() != JsonToken.END_ARRAY) {
parseFeature(parser, valueParser, featureHandler);
}
}
private static void parseFeature(
JsonParser parser, ValueParser valueParser, FeatureHandler featureHandler) throws IOException {
String key = null;
V value = null;
long subqueryBitmap = SubqueryBitmap.DEFAULT_VALUE; // Specifying subquery bitmap is optional.
while (parser.nextToken() != JsonToken.END_OBJECT) {
String fieldName = parser.currentName();
skipToken(parser, JsonToken.VALUE_STRING, JsonToken.VALUE_NUMBER_INT);
switch (fieldName) {
case "k":
key = parser.getText();
break;
case "v":
value = valueParser.parse(parser);
break;
case "s":
subqueryBitmap = fromHexString(parser.getText());
break;
default:
throw new IllegalArgumentException("Invalid field name: " + fieldName);
}
}
if (key == null) {
throw new IllegalArgumentException(
String.format("Feature key is missing! (%s)", parser.currentLocation()));
}
if (value == null) {
throw new IllegalArgumentException(
String.format("Feature value is missing! (%s)", parser.currentLocation()));
}
featureHandler.accept(key, value, subqueryBitmap);
}
private static void skipToken(JsonParser parser, JsonToken... expected) throws IOException {
JsonToken actual = parser.nextToken();
if (Arrays.stream(expected).noneMatch(e -> e.equals(actual))) {
throw new IllegalArgumentException(
String.format("Expected a token in %s, got %s (%s).",
Arrays.toString(expected), actual, parser.currentTokenLocation()));
}
}
private static long fromHexString(String subqueryBitmap) {
if (!subqueryBitmap.startsWith("0x")) {
throw new IllegalArgumentException("Not a valid subquery bitmap ('0x' prefix missing): " + subqueryBitmap);
}
return Long.parseUnsignedLong(subqueryBitmap.substring(2), 16);
}
@FunctionalInterface
private interface ValueParser {
V parse(JsonParser parser) throws IOException;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy