All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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