Skip to content

Commit

Permalink
Hack in an example of analyzer
Browse files Browse the repository at this point in the history
  • Loading branch information
kderusso committed Mar 18, 2024
1 parent 853ee2c commit 544d9d7
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ static TransportVersion def(int id) {
public static final TransportVersion AGGS_EXCLUDED_DELETED_DOCS = def(8_609_00_0);
public static final TransportVersion ESQL_SERIALIZE_BIG_ARRAY = def(8_610_00_0);
public static final TransportVersion AUTO_SHARDING_ROLLOVER_CONDITION = def(8_611_00_0);
public static final TransportVersion QUERY_RULES_CRITERIA_METADATA_PROPERTIES_ADDED = def(8_612_00_0);

/*
* STOP! READ THIS FIRST! No, really,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,13 @@ public Request addTokenFilter(String tokenFilter) {
return this;
}

public Request addTokenFilters(List<String> tokenFilters) {
for (String tokenFilter : tokenFilters) {
this.tokenFilters.add(new NameOrDefinition(tokenFilter));
}
return this;
}

public Request addTokenFilter(Map<String, ?> tokenFilter) {
this.tokenFilters.add(new NameOrDefinition(tokenFilter));
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
package org.elasticsearch.xpack.application.rules;

import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.client.internal.Client;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.StreamInput;
Expand Down Expand Up @@ -277,7 +278,7 @@ public String toString() {
}

@SuppressWarnings("unchecked")
public AppliedQueryRules applyRule(AppliedQueryRules appliedRules, Map<String, Object> matchCriteria) {
public AppliedQueryRules applyRule(Client client, AppliedQueryRules appliedRules, Map<String, Object> matchCriteria) {
if (type != QueryRule.QueryRuleType.PINNED) {
throw new UnsupportedOperationException("Only pinned query rules are supported");
}
Expand All @@ -294,7 +295,7 @@ public AppliedQueryRules applyRule(AppliedQueryRules appliedRules, Map<String, O
final String criteriaMetadata = criterion.criteriaMetadata();

if (criteriaType == ALWAYS || (criteriaMetadata != null && criteriaMetadata.equals(match))) {
boolean singleCriterionMatches = criterion.isMatch(matchValue, criteriaType, false);
boolean singleCriterionMatches = criterion.isMatch(client, matchValue, criteriaType, false);
isRuleMatch = (isRuleMatch == null) ? singleCriterionMatches : isRuleMatch && singleCriterionMatches;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.TransportVersion;
import org.elasticsearch.TransportVersions;
import org.elasticsearch.client.internal.Client;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.StreamInput;
Expand All @@ -29,6 +30,7 @@

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static org.elasticsearch.xcontent.ConstructingObjectParser.constructorArg;
Expand All @@ -38,21 +40,31 @@
public class QueryRuleCriteria implements Writeable, ToXContentObject {

public static final TransportVersion CRITERIA_METADATA_VALUES_TRANSPORT_VERSION = TransportVersions.V_8_10_X;

public static final TransportVersion CRITERIA_METADATA_PROPERTIES_TRANSPORT_VERSION =
TransportVersions.QUERY_RULES_CRITERIA_METADATA_PROPERTIES_ADDED;
private final QueryRuleCriteriaType criteriaType;
private final String criteriaMetadata;
private final List<Object> criteriaValues;
private final Map<String, Object> criteriaProperties;

private static final Logger logger = LogManager.getLogger(QueryRuleCriteria.class);

/**
*
* @param criteriaType The {@link QueryRuleCriteriaType}, indicating how the criteria is matched
* @param criteriaMetadata The metadata for this identifier, indicating the criteria key of what is matched against.
* Required unless the CriteriaType is ALWAYS.
* @param criteriaValues The values to match against when evaluating {@link QueryRuleCriteria} against a {@link QueryRule}
* Required unless the CriteriaType is ALWAYS.
* @param criteriaType The {@link QueryRuleCriteriaType}, indicating how the criteria is matched
* @param criteriaMetadata The metadata for this identifier, indicating the criteria key of what is matched against.
* Required unless the CriteriaType is ALWAYS.
* @param criteriaValues The values to match against when evaluating {@link QueryRuleCriteria} against a {@link QueryRule}
* Required unless the CriteriaType is ALWAYS.
* @param criteriaProperties Additional configuration properties for this criteria, to override default criteria configuration.
*/
public QueryRuleCriteria(QueryRuleCriteriaType criteriaType, @Nullable String criteriaMetadata, @Nullable List<Object> criteriaValues) {
public QueryRuleCriteria(
QueryRuleCriteriaType criteriaType,
@Nullable String criteriaMetadata,
@Nullable List<Object> criteriaValues,
Map<String, Object> criteriaProperties
) {

Objects.requireNonNull(criteriaType);

Expand All @@ -69,6 +81,9 @@ public QueryRuleCriteria(QueryRuleCriteriaType criteriaType, @Nullable String cr
this.criteriaValues = criteriaValues;
this.criteriaType = criteriaType;

this.criteriaProperties = criteriaProperties == null ? Map.of() : criteriaProperties;
// TODO criteriaType.validateProperties(criteriaProperties);

}

public QueryRuleCriteria(StreamInput in) throws IOException {
Expand All @@ -80,6 +95,11 @@ public QueryRuleCriteria(StreamInput in) throws IOException {
this.criteriaMetadata = in.readString();
this.criteriaValues = List.of(in.readGenericValue());
}
if (in.getTransportVersion().onOrAfter(CRITERIA_METADATA_PROPERTIES_TRANSPORT_VERSION)) {
this.criteriaProperties = in.readGenericMap();
} else {
this.criteriaProperties = Map.of();
}
}

@Override
Expand All @@ -92,6 +112,9 @@ public void writeTo(StreamOutput out) throws IOException {
out.writeString(criteriaMetadata);
out.writeGenericValue(criteriaValues().get(0));
}
if (out.getTransportVersion().onOrAfter(CRITERIA_METADATA_PROPERTIES_TRANSPORT_VERSION)) {
out.writeGenericMap(criteriaProperties);
}
}

private static final ConstructingObjectParser<QueryRuleCriteria, String> PARSER = new ConstructingObjectParser<>(
Expand All @@ -102,18 +125,22 @@ public void writeTo(StreamOutput out) throws IOException {
final String metadata = params.length >= 3 ? (String) params[1] : null;
@SuppressWarnings("unchecked")
final List<Object> values = params.length >= 3 ? (List<Object>) params[2] : null;
return new QueryRuleCriteria(type, metadata, values);
@SuppressWarnings("unchecked")
final Map<String, Object> properties = params.length >= 4 ? (Map<String, Object>) params[3] : null;
return new QueryRuleCriteria(type, metadata, values, properties);
}
);

public static final ParseField TYPE_FIELD = new ParseField("type");
public static final ParseField METADATA_FIELD = new ParseField("metadata");
public static final ParseField VALUES_FIELD = new ParseField("values");
public static final ParseField PROPERTIES_FIELD = new ParseField("properties");

static {
PARSER.declareString(constructorArg(), TYPE_FIELD);
PARSER.declareStringOrNull(optionalConstructorArg(), METADATA_FIELD);
PARSER.declareStringArray(optionalConstructorArg(), VALUES_FIELD);
PARSER.declareObject(optionalConstructorArg(), (p, c) -> p.map(), PROPERTIES_FIELD);
}

/**
Expand Down Expand Up @@ -153,6 +180,9 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
if (criteriaValues != null) {
builder.array(VALUES_FIELD.getPreferredName(), criteriaValues.toArray());
}
if (criteriaProperties != null) {
builder.field(PROPERTIES_FIELD.getPreferredName(), criteriaProperties);
}
}
builder.endObject();
return builder;
Expand All @@ -170,31 +200,36 @@ public List<Object> criteriaValues() {
return criteriaValues;
}

public Map<String, Object> criteriaProperties() {
return criteriaProperties;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
QueryRuleCriteria that = (QueryRuleCriteria) o;
return criteriaType == that.criteriaType
&& Objects.equals(criteriaMetadata, that.criteriaMetadata)
&& Objects.equals(criteriaValues, that.criteriaValues);
&& Objects.equals(criteriaValues, that.criteriaValues)
&& Objects.equals(criteriaProperties, that.criteriaProperties);
}

@Override
public int hashCode() {
return Objects.hash(criteriaType, criteriaMetadata, criteriaValues);
return Objects.hash(criteriaType, criteriaMetadata, criteriaValues, criteriaProperties);
}

@Override
public String toString() {
return Strings.toString(this);
}

public boolean isMatch(Object matchValue, QueryRuleCriteriaType matchType) {
return isMatch(matchValue, matchType, true);
public boolean isMatch(Client client, Object matchValue, QueryRuleCriteriaType matchType) {
return isMatch(client, matchValue, matchType, true);
}

public boolean isMatch(Object matchValue, QueryRuleCriteriaType matchType, boolean throwOnInvalidInput) {
public boolean isMatch(Client client, Object matchValue, QueryRuleCriteriaType matchType, boolean throwOnInvalidInput) {
if (matchType == ALWAYS) {
return true;
}
Expand All @@ -204,7 +239,8 @@ public boolean isMatch(Object matchValue, QueryRuleCriteriaType matchType, boole
if (isValid == false) {
return false;
}
boolean matchFound = matchType.isMatch(matchString, criteriaValue);
QueryRulesAnalysisService analysisService = new QueryRulesAnalysisService(client);
boolean matchFound = matchType.isMatch(analysisService, matchString, criteriaValue, criteriaProperties);
if (matchFound) {
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,59 @@

import java.util.List;
import java.util.Locale;
import java.util.Map;

/**
* Defines the different types of query rule criteria and their rules for matching input against the criteria.
*/
public enum QueryRuleCriteriaType {

ALWAYS {
@Override
public boolean isMatch(Object input, Object criteriaValue) {
public boolean isMatch(Object input, Object criteriaValue, Map<String, Object> criteriaProperties) {
return true;
}
},
EXACT {
@Override
public boolean isMatch(Object input, Object criteriaValue) {
public boolean isMatch(Object input, Object criteriaValue, Map<String, Object> criteriaProperties) {
throw new UnsupportedOperationException("[" + this + "] criteria type requires analysis service");
}

@Override
public boolean isMatch(
QueryRulesAnalysisService analysisService,
Object input,
Object criteriaValue,
Map<String, Object> criteriaProperties
) {
if (input instanceof String && criteriaValue instanceof String) {
return input.equals(criteriaValue);

if (criteriaProperties.containsKey("analysis")) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> analysisChain = (List<Map<String, Object>>) criteriaProperties.get("analysis");
String analyzedInput = (String) input;
String analyzedCriteriaValue = (String) criteriaValue;
for (Map<String, Object> analysisConfig : analysisChain) {
String tokenizer = analysisConfig.containsKey("tokenizer") ? (String) analysisConfig.get("tokenizer") : "keyword";
String filter = analysisConfig.containsKey("filter") ? (String) analysisConfig.get("filter") : "lowercase";
QueryRulesAnalysisConfig config = new QueryRulesAnalysisConfig(null, tokenizer, List.of(filter));
analyzedInput = analysisService.analyze(analyzedInput, config);
analyzedCriteriaValue = analysisService.analyze(analyzedCriteriaValue, config);
}
return analyzedInput.equals(analyzedCriteriaValue);
} else {
return input.equals(criteriaValue);
}

} else {
return parseDouble(input) == parseDouble(criteriaValue);
}
}
},
FUZZY {
@Override
public boolean isMatch(Object input, Object criteriaValue) {
public boolean isMatch(Object input, Object criteriaValue, Map<String, Object> criteriaProperties) {
final LevenshteinDistance ld = new LevenshteinDistance();
if (input instanceof String && criteriaValue instanceof String) {
return ld.getDistance((String) input, (String) criteriaValue) > 0.5f;
Expand All @@ -44,43 +73,43 @@ public boolean isMatch(Object input, Object criteriaValue) {
},
PREFIX {
@Override
public boolean isMatch(Object input, Object criteriaValue) {
public boolean isMatch(Object input, Object criteriaValue, Map<String, Object> criteriaProperties) {
return ((String) input).startsWith((String) criteriaValue);
}
},
SUFFIX {
@Override
public boolean isMatch(Object input, Object criteriaValue) {
public boolean isMatch(Object input, Object criteriaValue, Map<String, Object> criteriaProperties) {
return ((String) input).endsWith((String) criteriaValue);
}
},
CONTAINS {
@Override
public boolean isMatch(Object input, Object criteriaValue) {
public boolean isMatch(Object input, Object criteriaValue, Map<String, Object> criteriaProperties) {
return ((String) input).contains((String) criteriaValue);
}
},
LT {
@Override
public boolean isMatch(Object input, Object criteriaValue) {
public boolean isMatch(Object input, Object criteriaValue, Map<String, Object> criteriaProperties) {
return parseDouble(input) < parseDouble(criteriaValue);
}
},
LTE {
@Override
public boolean isMatch(Object input, Object criteriaValue) {
public boolean isMatch(Object input, Object criteriaValue, Map<String, Object> criteriaProperties) {
return parseDouble(input) <= parseDouble(criteriaValue);
}
},
GT {
@Override
public boolean isMatch(Object input, Object criteriaValue) {
public boolean isMatch(Object input, Object criteriaValue, Map<String, Object> criteriaProperties) {
return parseDouble(input) > parseDouble(criteriaValue);
}
},
GTE {
@Override
public boolean isMatch(Object input, Object criteriaValue) {
public boolean isMatch(Object input, Object criteriaValue, Map<String, Object> criteriaProperties) {
validateInput(input);
return parseDouble(input) >= parseDouble(criteriaValue);
}
Expand All @@ -94,11 +123,20 @@ public boolean validateInput(Object input, boolean throwOnInvalidInput) {
return isValid;
}

public boolean validateInput(Object input) {
return validateInput(input, true);
public void validateInput(Object input) {
validateInput(input, true);
}

public abstract boolean isMatch(Object input, Object criteriaValue);
public abstract boolean isMatch(Object input, Object criteriaValue, Map<String, Object> criteriaProperties);

public boolean isMatch(
QueryRulesAnalysisService analysisService,
Object input,
Object criteriaValue,
Map<String, Object> criteriaProperties
) {
return isMatch(input, criteriaValue, criteriaProperties);
}

public static QueryRuleCriteriaType type(String criteriaType) {
for (QueryRuleCriteriaType type : values()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.application.rules;

import java.util.List;

public class QueryRulesAnalysisConfig {

private final String analyzer;
private final String tokenizer;
private final List<String> filters;

public QueryRulesAnalysisConfig(String analyzer, String tokenizer, List<String> filters) {
this.analyzer = analyzer;
this.tokenizer = tokenizer;
this.filters = filters;
}

public String analyzer() {
return analyzer;
}

public String tokenizer() {
return tokenizer;
}

public List<String> filters() {
return filters;
}

}
Loading

0 comments on commit 544d9d7

Please sign in to comment.