Skip to content

Commit

Permalink
Zulia Set Syntax (#114)
Browse files Browse the repository at this point in the history
* grammar changes for adding field:zl:ns and field:zl:tq syntax
* generated code changes for adding field:zl:ns and field:zl:tq syntax
* refactor out term query and numeric set query code into SetQuery Helper
* implement new query syntax for field:zl:ns and field:zl:tq syntax in actual parser
* upgrade flapdoodle to 4.7.0 from 3.5.2
* fix spacing in exception
* fix exception when field is not set in the directly query (set via default fields)
* numeric set syntax tests
* add tests for querying fields with names zl and fl
* add term query syntax test
* add quoted example
  • Loading branch information
mdavis95 authored Jul 10, 2023
1 parent 7d92e52 commit b42a79c
Show file tree
Hide file tree
Showing 16 changed files with 1,570 additions and 721 deletions.
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

0 comments on commit b42a79c

Please sign in to comment.