I want to retrieve documents based on a combination of 3 field values:
- canonicalForm
- grammar
- meaning
Here is how I do it now.
String canonicalForm = "tut";
String grammar = "verb";
String meaning = "to land";
BoolQuery bool = BoolQuery.of(q -> q
.must(m -> m
.match(mt -> mt
.field("descr.canonicalForm")
.query(canonicalForm)
)
)
.must(m -> m
.match(mt -> mt
.field("descr.grammar")
.query(grammar)
)
)
.must(m -> m
.match(mt -> mt
.field("descr.meaning")
.query(meaning)
)
)
);
This works as long as I provide a value for all three fields. But sometimes I want to search using only 1 or two of the fields.
I tried setting the "absent" field values to null, but that raises an exception.
I also tried setting the "absent" value to the empty string but that always returns 0 hits.
Another solution would be to only add a match() clause for a field if the provided value is not null, but I can't figure out how to insert this kind of conditionals in the fluent DSL builder pattern.
CodePudding user response:
I believe that you must use the should clausule.
{
"query": {
"bool": {
"minimum_should_match": 1,
"should": [
{
"bool": {
"must": [
{
"exists": {
"field": "descr.grammar"
}
},
{
"bool": {
"must_not": [
{
"exists": {
"field": "descr.canonicalForm"
}
}
]
}
},
{
"bool": {
"must_not": [
{
"exists": {
"field": "descr.meaning"
}
}
]
}
},
{
"match": {
"descr.grammar": "verb"
}
}
]
}
},
{
"bool": {
"must": [
{
"exists": {
"field": "descr.grammar"
}
},
{
"exists": {
"field": "descr.canonicalForm"
}
},
{
"bool": {
"must_not": [
{
"exists": {
"field": "descr.meaning"
}
}
]
}
},
{
"match": {
"descr.grammar": "verb"
}
},
{
"match": {
"descr.canonicalForm": "tut"
}
}
]
}
},
{
"bool": {
"must": [
{
"exists": {
"field": "descr.grammar"
}
},
{
"exists": {
"field": "descr.canonicalForm"
}
},
{
"exists": {
"field": "descr.meaning"
}
},
{
"match": {
"descr.grammar": "verb"
}
},
{
"match": {
"descr.canonicalForm": "tut"
}
},
{
"match": {
"descr.meaning": "meaning"
}
}
]
}
}
]
}
}
}
CodePudding user response:
I found a solution, but it's awkward as hell. If someone has a more fluent solution to suggest, please let me know.
The solution I came up with is to:
- Create the query as a JSONObject
- Transform that JSONObject to an InputStream
- Feed that InputStream to .query(q -> q.withJson()) method
Here is an example below:
// Say we have these input field values
String canonicalForm = "tut";
String grammar = "verb";
String meaning = null; // This means we don't want to query on field 'meaning'
// Build a JSONArray that will contain the "match" criteria for the non-null
// input field values
//
JSONArray mustArr = new JSONArray();
if (canonicalForm != null) {
mustArr.put(new JSONObject()
.put("match", new JSONObject()
.put("descr.canonicalForm", new JSONObject()
.put("query", canonicalForm)
)
)
);
}
if (grammar != null) {
mustArr.put(new JSONObject()
.put("match", new JSONObject()
.put("descr.grammar", new JSONObject()
.put("query", grammar)
)
)
);
}
if (meaning != null) {
mustArr.put(new JSONObject()
.put("match", new JSONObject()
.put("descr.meaning", new JSONObject()
.put("query", meaning)
)
)
);
}
// Build a "bool" query object, feeding it the "must" array.
JSONObject queryJObj = new JSONObject()
.put("bool", new JSONObject()
.put("must", mustArr)
)
;
// Convert the "query" object to an InputStream
String queryJsonStr = queryJObj.toString();
InputStream queryIS = new ByteArrayInputStream(queryJsonStr.getBytes(StandardCharsets.UTF_8));
SearchRequest sr = SearchRequest.of(s -> s
.index("morphemes")
.query(q -> q
.withJson(queryIS)
)
);