|
| 1 | +/* |
| 2 | + * SPDX-License-Identifier: Apache-2.0 |
| 3 | + * |
| 4 | + * The OpenSearch Contributors require contributions made to |
| 5 | + * this file be licensed under the Apache-2.0 license or a |
| 6 | + * compatible open source license. |
| 7 | + */ |
| 8 | +package org.opensearch.plugin.transport.grpc.proto.request.search.query; |
| 9 | + |
| 10 | +import com.google.protobuf.ProtocolStringList; |
| 11 | +import org.apache.lucene.util.BytesRef; |
| 12 | +import org.opensearch.core.common.bytes.BytesArray; |
| 13 | +import org.opensearch.core.xcontent.XContentParser; |
| 14 | +import org.opensearch.index.query.AbstractQueryBuilder; |
| 15 | +import org.opensearch.index.query.TermsQueryBuilder; |
| 16 | +import org.opensearch.indices.TermsLookup; |
| 17 | +import org.opensearch.protobufs.TermsLookupField; |
| 18 | +import org.opensearch.protobufs.TermsLookupFieldStringArrayMap; |
| 19 | +import org.opensearch.protobufs.TermsQueryField; |
| 20 | +import org.opensearch.protobufs.ValueType; |
| 21 | + |
| 22 | +import java.util.ArrayList; |
| 23 | +import java.util.Base64; |
| 24 | +import java.util.List; |
| 25 | +import java.util.Map; |
| 26 | + |
| 27 | +import static org.opensearch.index.query.AbstractQueryBuilder.maybeConvertToBytesRef; |
| 28 | + |
| 29 | +/** |
| 30 | + * Utility class for converting TermQuery Protocol Buffers to OpenSearch objects. |
| 31 | + * This class provides methods to transform Protocol Buffer representations of term queries |
| 32 | + * into their corresponding OpenSearch TermQueryBuilder implementations for search operations. |
| 33 | + */ |
| 34 | +public class TermsQueryBuilderProtoUtils { |
| 35 | + |
| 36 | + private TermsQueryBuilderProtoUtils() { |
| 37 | + // Utility class, no instances |
| 38 | + } |
| 39 | + |
| 40 | + /** |
| 41 | + * Converts a Protocol Buffer TermQuery map to an OpenSearch TermQueryBuilder. |
| 42 | + * Similar to {@link TermsQueryBuilder#fromXContent(XContentParser)}, this method |
| 43 | + * parses the Protocol Buffer representation and creates a properly configured |
| 44 | + * TermQueryBuilder with the appropriate field name, value, boost, query name, |
| 45 | + * and case sensitivity settings. |
| 46 | + * |
| 47 | + * @param termsQueryProto The map of field names to Protocol Buffer TermsQuery objects |
| 48 | + * @return A configured TermQueryBuilder instance |
| 49 | + * @throws IllegalArgumentException if the term query map has more than one element, |
| 50 | + * if the field value type is not supported, or if the term query field value is not recognized |
| 51 | + */ |
| 52 | + protected static TermsQueryBuilder fromProto(TermsQueryField termsQueryProto) { |
| 53 | + |
| 54 | + String fieldName = null; |
| 55 | + List<Object> values = null; |
| 56 | + TermsLookup termsLookup = null; |
| 57 | + |
| 58 | + String queryName = null; |
| 59 | + float boost = AbstractQueryBuilder.DEFAULT_BOOST; |
| 60 | + String valueTypeStr = TermsQueryBuilder.ValueType.DEFAULT.name(); |
| 61 | + |
| 62 | + if (termsQueryProto.hasBoost()) { |
| 63 | + boost = termsQueryProto.getBoost(); |
| 64 | + } |
| 65 | + |
| 66 | + if (termsQueryProto.hasName()) { |
| 67 | + queryName = termsQueryProto.getName(); |
| 68 | + } |
| 69 | + |
| 70 | + // TODO: remove this parameter when backporting to under OS 2.17 |
| 71 | + if (termsQueryProto.hasValueType()) { |
| 72 | + valueTypeStr = parseValueType(termsQueryProto.getValueType()).name(); |
| 73 | + } |
| 74 | + |
| 75 | + if (termsQueryProto.getTermsLookupFieldStringArrayMapMap().size() > 1) { |
| 76 | + throw new IllegalArgumentException("[" + TermsQueryBuilder.NAME + "] query does not support more than one field. "); |
| 77 | + } |
| 78 | + |
| 79 | + for (Map.Entry<String, TermsLookupFieldStringArrayMap> entry : termsQueryProto.getTermsLookupFieldStringArrayMapMap().entrySet()) { |
| 80 | + fieldName = entry.getKey(); |
| 81 | + TermsLookupFieldStringArrayMap termsLookupFieldStringArrayMap = entry.getValue(); |
| 82 | + |
| 83 | + if (termsLookupFieldStringArrayMap.hasTermsLookupField()) { |
| 84 | + TermsLookupField termsLookupField = termsLookupFieldStringArrayMap.getTermsLookupField(); |
| 85 | + termsLookup = TermsLookupProtoUtils.parseTermsLookup(termsLookupField); |
| 86 | + } else if (termsLookupFieldStringArrayMap.hasStringArray()) { |
| 87 | + values = parseValues(termsLookupFieldStringArrayMap.getStringArray().getStringArrayList()); |
| 88 | + } else { |
| 89 | + throw new IllegalArgumentException("termsLookupField and stringArray fields cannot both be null"); |
| 90 | + } |
| 91 | + } |
| 92 | + |
| 93 | + TermsQueryBuilder.ValueType valueType = TermsQueryBuilder.ValueType.fromString(valueTypeStr); |
| 94 | + |
| 95 | + if (valueType == TermsQueryBuilder.ValueType.BITMAP) { |
| 96 | + if (values != null && values.size() == 1 && values.get(0) instanceof BytesRef) { |
| 97 | + values.set(0, new BytesArray(Base64.getDecoder().decode(((BytesRef) values.get(0)).utf8ToString()))); |
| 98 | + } else if (termsLookup == null) { |
| 99 | + throw new IllegalArgumentException( |
| 100 | + "Invalid value for bitmap type: Expected a single-element array with a base64 encoded serialized bitmap." |
| 101 | + ); |
| 102 | + } |
| 103 | + } |
| 104 | + |
| 105 | + TermsQueryBuilder termsQueryBuilder; |
| 106 | + if (values == null) { |
| 107 | + termsQueryBuilder = new TermsQueryBuilder(fieldName, termsLookup); |
| 108 | + } else if (termsLookup == null) { |
| 109 | + termsQueryBuilder = new TermsQueryBuilder(fieldName, values); |
| 110 | + } else { |
| 111 | + throw new IllegalArgumentException("values and termsLookup cannot both be null"); |
| 112 | + } |
| 113 | + |
| 114 | + return termsQueryBuilder.boost(boost).queryName(queryName).valueType(valueType); |
| 115 | + } |
| 116 | + |
| 117 | + /** |
| 118 | + * Parses a protobuf ScriptLanguage to a String representation |
| 119 | + * |
| 120 | + * See {@link org.opensearch.index.query.TermsQueryBuilder.ValueType#fromString(String)} } |
| 121 | + * * |
| 122 | + * @param valueType the Protocol Buffer ValueType to convert |
| 123 | + * @return the string representation of the script language |
| 124 | + * @throws UnsupportedOperationException if no language was specified |
| 125 | + */ |
| 126 | + public static TermsQueryBuilder.ValueType parseValueType(ValueType valueType) { |
| 127 | + switch (valueType) { |
| 128 | + case VALUE_TYPE_BITMAP: |
| 129 | + return TermsQueryBuilder.ValueType.BITMAP; |
| 130 | + case VALUE_TYPE_DEFAULT: |
| 131 | + return TermsQueryBuilder.ValueType.DEFAULT; |
| 132 | + case VALUE_TYPE_UNSPECIFIED: |
| 133 | + default: |
| 134 | + return TermsQueryBuilder.ValueType.DEFAULT; |
| 135 | + } |
| 136 | + } |
| 137 | + |
| 138 | + /** |
| 139 | + * Similar to {@link TermsQueryBuilder#parseValues(XContentParser)} |
| 140 | + * @param termsLookupFieldStringArray |
| 141 | + * @return |
| 142 | + * @throws IllegalArgumentException |
| 143 | + */ |
| 144 | + static List<Object> parseValues(ProtocolStringList termsLookupFieldStringArray) throws IllegalArgumentException { |
| 145 | + List<Object> values = new ArrayList<>(); |
| 146 | + |
| 147 | + for (Object value : termsLookupFieldStringArray) { |
| 148 | + Object convertedValue = maybeConvertToBytesRef(value); |
| 149 | + if (value == null) { |
| 150 | + throw new IllegalArgumentException("No value specified for terms query"); |
| 151 | + } |
| 152 | + values.add(convertedValue); |
| 153 | + } |
| 154 | + return values; |
| 155 | + } |
| 156 | +} |
0 commit comments