Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Zulia Set Syntax #114

Merged
merged 11 commits into from
Jul 10, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ else if (command instanceof MultiIndexRoutableCommand) {
throw new Exception(grpcCommand.getClass().getSimpleName() + ": " + errorMessage);
}
else {
throw new IllegalArgumentException(grpcCommand.getClass().getSimpleName() + ":" + errorMessage);
throw new IllegalArgumentException(grpcCommand.getClass().getSimpleName() + ": " + errorMessage);
}
}
else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package io.zulia.server.search.queryparser;

import io.zulia.message.ZuliaIndex;
import io.zulia.server.config.IndexFieldInfo;
import io.zulia.server.field.FieldTypeUtil;
import org.apache.lucene.document.DoublePoint;
import org.apache.lucene.document.FloatPoint;
import org.apache.lucene.document.IntPoint;
import org.apache.lucene.document.LongPoint;
import org.apache.lucene.document.SortedNumericDocValuesField;
import org.apache.lucene.search.IndexOrDocValuesQuery;
import org.apache.lucene.search.MultiTermQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermInSetQuery;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.NumericUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;

public class SetQueryHelper {

public static Query getNumericSetQuery(String field, IndexFieldInfo indexFieldInfo, Supplier<List<Integer>> intSupplier, Supplier<List<Long>> longSupplier,
Supplier<List<Float>> floatSupplier, Supplier<List<Double>> doubleSupplier) {
ZuliaIndex.FieldConfig.FieldType fieldType = indexFieldInfo.getFieldType();
String searchField = indexFieldInfo.getInternalFieldName();
String sortField = indexFieldInfo.getInternalSortFieldName();

if (fieldType == null) {
throw new IllegalArgumentException("Field <" + field + "> is not indexed");
}
else {
if (FieldTypeUtil.isNumericIntFieldType(fieldType)) {
List<Integer> integerValueList = intSupplier.get();
if (integerValueList.isEmpty()) {
throw new IllegalArgumentException("No integer values for integer field <" + field + "> for numeric set query");
}

Query pointQuery = IntPoint.newSetQuery(searchField, integerValueList);
if (sortField == null) {
return pointQuery;
}
long[] pointsArray = integerValueList.stream().mapToLong(Integer::intValue).toArray();
return new IndexOrDocValuesQuery(pointQuery, SortedNumericDocValuesField.newSlowSetQuery(sortField, pointsArray));
}
else if (FieldTypeUtil.isNumericLongFieldType(fieldType)) {
List<Long> longValueList = longSupplier.get();
if (longValueList.isEmpty()) {
throw new IllegalArgumentException("No long values for long field <" + field + "> for numeric set query");
}

Query pointQuery = LongPoint.newSetQuery(searchField, longValueList);
if (sortField == null) {
return pointQuery;
}
long[] pointsArray = longValueList.stream().mapToLong(Long::longValue).toArray();
return new IndexOrDocValuesQuery(pointQuery, SortedNumericDocValuesField.newSlowSetQuery(sortField, pointsArray));
}
else if (FieldTypeUtil.isNumericFloatFieldType(fieldType)) {
List<Float> floatValueList = floatSupplier.get();
if (floatValueList.isEmpty()) {
throw new IllegalArgumentException("No float values for float field <" + field + "> for numeric set query");
}

Query pointQuery = FloatPoint.newSetQuery(searchField, floatValueList);
if (sortField == null) {
return pointQuery;
}
long[] pointsArray = floatValueList.stream().mapToLong(NumericUtils::floatToSortableInt).toArray();
return new IndexOrDocValuesQuery(pointQuery, SortedNumericDocValuesField.newSlowSetQuery(sortField, pointsArray));
}
else if (FieldTypeUtil.isNumericDoubleFieldType(fieldType)) {
List<Double> doubleValueList = doubleSupplier.get();
if (doubleValueList.isEmpty()) {
throw new IllegalArgumentException("No double values for double field <" + field + "> for numeric set query");
}

Query pointQuery = DoublePoint.newSetQuery(searchField, doubleValueList);
if (sortField == null) {
return pointQuery;
}
long[] pointsArray = doubleValueList.stream().mapToLong(NumericUtils::doubleToSortableLong).toArray();
return new IndexOrDocValuesQuery(pointQuery, SortedNumericDocValuesField.newSlowSetQuery(sortField, pointsArray));
}
}
throw new IllegalArgumentException("No field type of <" + fieldType + "> is not supported for numeric set queries");
}

public static Query getTermInSetQuery(List<String> terms, String field, IndexFieldInfo indexFieldInfo) {
List<BytesRef> termBytesRef = new ArrayList<>();
for (String term : terms) {
termBytesRef.add(new BytesRef(term));
}

if (FieldTypeUtil.isStringFieldType(indexFieldInfo.getFieldType())) {
String sortField = indexFieldInfo.getInternalSortFieldName();

if (sortField != null) {
Query indexQuery = new TermInSetQuery(field, termBytesRef);
Query dvQuery = new TermInSetQuery(MultiTermQuery.DOC_VALUES_REWRITE, sortField, termBytesRef);
return new IndexOrDocValuesQuery(indexQuery, dvQuery);
}

return new TermInSetQuery(field, termBytesRef);
}
throw new IllegalArgumentException(
"Field type of <" + indexFieldInfo.getFieldType() + "> is not supported for term queries. Only STRING is supported.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.zulia.server.search.queryparser.builder;

import io.zulia.server.search.queryparser.node.ZuliaFieldableQueryNode;
import org.apache.lucene.queryparser.flexible.core.QueryNodeException;
import org.apache.lucene.queryparser.flexible.core.nodes.QueryNode;
import org.apache.lucene.queryparser.flexible.standard.builders.StandardQueryBuilder;
import org.apache.lucene.search.Query;

public class ZuliaFieldableQueryNodeBuilder implements StandardQueryBuilder {

/**
* Constructs a {@link ZuliaFieldableQueryNodeBuilder} object.
*/
public ZuliaFieldableQueryNodeBuilder() {
// empty constructor
}

@Override
public Query build(QueryNode queryNode) throws QueryNodeException {
ZuliaFieldableQueryNode fieldableQueryNode = (ZuliaFieldableQueryNode) queryNode;
return fieldableQueryNode.getQuery();
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package io.zulia.server.search.queryparser.builder;

import io.zulia.server.search.queryparser.node.ZuliaFieldableQueryNode;
import io.zulia.server.search.queryparser.node.ZuliaPointRangeQueryNode;
import org.apache.lucene.queryparser.flexible.standard.builders.StandardQueryTreeBuilder;

public class ZuliaQueryTreeBuilder extends StandardQueryTreeBuilder {

public ZuliaQueryTreeBuilder() {
setBuilder(ZuliaPointRangeQueryNode.class, new ZuliaPointRangeQueryNodeBuilder());
setBuilder(ZuliaFieldableQueryNode.class, new ZuliaFieldableQueryNodeBuilder());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.zulia.server.search.queryparser.node;

import io.zulia.server.config.IndexFieldInfo;
import org.apache.lucene.queryparser.flexible.core.nodes.FieldableNode;
import org.apache.lucene.queryparser.flexible.core.nodes.QueryNodeImpl;
import org.apache.lucene.search.Query;

public abstract class ZuliaFieldableQueryNode extends QueryNodeImpl implements FieldableNode {
private CharSequence field;
private IndexFieldInfo indexFieldInfo;

@Override
public CharSequence getField() {
return field;
}

@Override
public void setField(CharSequence fieldName) {
this.field = fieldName != null ? fieldName.toString() : null;
}

public abstract Query getQuery();

public void setIndexFieldInfo(IndexFieldInfo indexFieldInfo) {
this.indexFieldInfo = indexFieldInfo;
}

public IndexFieldInfo getIndexFieldInfo() {
return indexFieldInfo;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.zulia.server.search.queryparser.node;

import io.zulia.server.search.queryparser.SetQueryHelper;
import org.apache.lucene.queryparser.flexible.core.parser.EscapeQuerySyntax;
import org.apache.lucene.queryparser.flexible.standard.parser.EscapeQuerySyntaxImpl;
import org.apache.lucene.search.Query;

import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Supplier;
import java.util.stream.Collectors;

public class ZuliaNumericSetQueryNode extends ZuliaFieldableQueryNode {
private final List<CharSequence> terms;

public ZuliaNumericSetQueryNode(CharSequence field, List<CharSequence> terms) {
setField(field);
this.terms = Objects.requireNonNull(terms);
}

public Query getQuery() {
Objects.requireNonNull(getField(), "Field must not be null for numeric set queries");
Objects.requireNonNull(getIndexFieldInfo(), "Field <" + getField() + "> must be indexed for numeric set queries");
return SetQueryHelper.getNumericSetQuery(getField().toString(), getIndexFieldInfo(), intTerms(), longTerms(), floatTerms(), doubleTerms());
}

public Supplier<List<Integer>> intTerms() {
return () -> terms.stream().map(Object::toString).map(Integer::parseInt).collect(Collectors.toList());
}

public Supplier<List<Long>> longTerms() {
return () -> terms.stream().map(Object::toString).map(Long::parseLong).collect(Collectors.toList());
}

public Supplier<List<Float>> floatTerms() {
return () -> terms.stream().map(Object::toString).map(Float::parseFloat).collect(Collectors.toList());
}

public Supplier<List<Double>> doubleTerms() {
return () -> terms.stream().map(Object::toString).map(Double::parseDouble).collect(Collectors.toList());
}

@Override
public String toQueryString(EscapeQuerySyntax escapeSyntaxParser) {
return String.format(Locale.ROOT, "%s:numericSet(%s)", getField(), terms);
}

@Override
public String toString() {
return toQueryString(new EscapeQuerySyntaxImpl());
}

@Override
public ZuliaNumericSetQueryNode cloneTree() {
return new ZuliaNumericSetQueryNode(getField(), terms);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.zulia.server.search.queryparser.node;

import io.zulia.server.search.queryparser.SetQueryHelper;
import org.apache.lucene.queryparser.flexible.core.parser.EscapeQuerySyntax;
import org.apache.lucene.queryparser.flexible.standard.parser.EscapeQuerySyntaxImpl;
import org.apache.lucene.search.Query;

import java.util.List;
import java.util.Locale;
import java.util.Objects;

public class ZuliaTermsInSetQueryNode extends ZuliaFieldableQueryNode {
private final List<CharSequence> terms;

public ZuliaTermsInSetQueryNode(CharSequence field, List<CharSequence> terms) {
setField(field);
this.terms = Objects.requireNonNull(terms);
}

public Query getQuery() {
Objects.requireNonNull(getField(), "Field must not be null for term set queries.");
Objects.requireNonNull(getIndexFieldInfo(), "Field <" + getField() + "> must be indexed for term set queries");
return SetQueryHelper.getTermInSetQuery(terms.stream().map(Object::toString).toList(), getField().toString(), getIndexFieldInfo());
}

@Override
public String toQueryString(EscapeQuerySyntax escapeSyntaxParser) {
return String.format(Locale.ROOT, "%s:termQuery(%s)", getField(), terms);
}

@Override
public String toString() {
return toQueryString(new EscapeQuerySyntaxImpl());
}

@Override
public ZuliaTermsInSetQueryNode cloneTree() {
return new ZuliaTermsInSetQueryNode(getField(), terms);
}

}
Loading