Skip to content

Commit cc7b2a7

Browse files
author
Codehardt
committed
feat: added exporter to ElasticSearch query
1 parent d54db32 commit cc7b2a7

File tree

2 files changed

+67
-42
lines changed

2 files changed

+67
-42
lines changed

export.go

Lines changed: 65 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,76 +4,99 @@ import (
44
"encoding/json"
55
)
66

7+
// ExportElasticSearchQuery exports the compiled query into an ElasticSearch query, e.g.
8+
// `"foo" OR "baz"` will be compiled to
9+
// {"bool":{"should":[{"wildcard":{"raw":{"case_insensitive":false,"value":"foo"}}},{"wildcard":{"raw":{"case_insensitive":false,"value":"bar"}}}]}}
710
func (e *Evalostic) ExportElasticSearchQuery(wildcardField string) string {
811
indexToStrings := make(map[int]string)
912
for k, v := range e.strings {
1013
indexToStrings[v] = k
1114
}
12-
query := e.exportElasticSearchQuery(wildcardField, indexToStrings, decisionTreeEntry{value: -1}, e.decisionTree)
15+
query := e.exportElasticSearchQuerySub(wildcardField, indexToStrings, decisionTreeEntry{value: -1}, e.decisionTree)
1316
if query == nil {
1417
return ""
1518
}
1619
b, _ := json.Marshal(query)
1720
return string(b)
1821
}
1922

20-
func (e *Evalostic) exportElasticSearchQuery(wildcardField string, indexToStrings map[int]string, entry decisionTreeEntry, node *decisionTreeNode) interface{} {
21-
var must []interface{}
22-
var mustNot []interface{}
23-
for entry, node := range node.children {
24-
subQuery := e.exportElasticSearchQuery(wildcardField, indexToStrings, entry, node)
25-
if subQuery != nil {
26-
must = append(must, subQuery)
27-
}
28-
}
29-
for entry, node := range node.notChildren {
30-
subQuery := e.exportElasticSearchQuery(wildcardField, indexToStrings, entry, node)
31-
if subQuery != nil {
32-
mustNot = append(mustNot, subQuery)
33-
}
34-
}
35-
var wildcard interface{}
36-
if entry.value != -1 {
37-
wildcard = map[string]interface{}{
38-
"wildcard": map[string]interface{}{
39-
wildcardField: map[string]interface{}{
40-
"value": indexToStrings[entry.value],
41-
"case_insensitive": entry.ci,
42-
},
23+
func (e *Evalostic) exportElasticSearchQuerySub(wildcardField string, indexToStrings map[int]string, entry decisionTreeEntry, node *decisionTreeNode) interface{} {
24+
25+
type Map map[string]interface{}
26+
27+
isLeaf := len(node.outputs) != 0
28+
wildcard := Map{
29+
"wildcard": Map{
30+
wildcardField: Map{
31+
"value": indexToStrings[entry.value],
32+
"case_insensitive": entry.ci,
4333
},
44-
}
34+
},
4535
}
46-
if len(node.outputs) != 0 {
36+
if entry.value == -1 {
37+
// special case: do not use root node as wildcard
38+
wildcard = nil
39+
}
40+
if isLeaf && wildcard != nil {
41+
// special case: if it's a leaf, we don't need to process the sub tree
4742
return wildcard
4843
}
49-
if len(must) == 0 && len(mustNot) == 0 {
50-
return nil
44+
45+
var should, shouldNot []interface{}
46+
47+
for subEntry, subNode := range node.children {
48+
if subQuery := e.exportElasticSearchQuerySub(wildcardField, indexToStrings, subEntry, subNode); subQuery != nil {
49+
should = append(should, subQuery)
50+
}
5151
}
52-
boolRes := make(map[string]interface{})
53-
if len(must) != 0 {
54-
boolRes["should"] = must
52+
for subEntry, subNode := range node.notChildren {
53+
if subQuery := e.exportElasticSearchQuerySub(wildcardField, indexToStrings, subEntry, subNode); subQuery != nil {
54+
shouldNot = append(shouldNot, subQuery)
55+
}
5556
}
56-
if len(mustNot) != 0 {
57-
if len(mustNot) == 1 {
58-
boolRes["must_not"] = mustNot
57+
58+
toQuery := func(should []interface{}, not bool) interface{} {
59+
if len(should) == 0 {
60+
return nil
61+
}
62+
var res interface{}
63+
if len(should) == 1 {
64+
res = should[0]
5965
} else {
60-
boolRes["must_not"] = map[string]interface{}{
61-
"bool": map[string]interface{}{
62-
"should": mustNot,
66+
res = Map{
67+
"bool": Map{
68+
"should": should,
69+
},
70+
}
71+
}
72+
if not {
73+
// wrap OR conditions with a NOT
74+
res = Map{
75+
"bool": Map{
76+
"must_not": []interface{}{res},
6377
},
6478
}
6579
}
80+
return res
81+
}
82+
83+
notChildQuery := toQuery(shouldNot, true)
84+
if notChildQuery != nil {
85+
should = append(should, notChildQuery)
86+
}
87+
childQuery := toQuery(should, false)
88+
if childQuery == nil {
89+
return nil
6690
}
6791
if wildcard == nil {
68-
return map[string]interface{}{"bool": boolRes}
92+
return childQuery
6993
}
70-
res := map[string]interface{}{
71-
"bool": map[string]interface{}{
94+
return Map{
95+
"bool": Map{
7296
"must": []interface{}{
7397
wildcard,
74-
map[string]interface{}{"bool": boolRes},
98+
childQuery,
7599
},
76100
},
77101
}
78-
return res
79102
}

export_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ func TestElasticSearchQueryExport(t *testing.T) {
88
`"foo" OR "bar"`,
99
`"foo" AND "bar"`,
1010
`"foo" AND NOT "bar"`,
11+
`"foo" OR ("bar" AND NOT "baz")`,
12+
`"foo" OR NOT ("bar" AND NOT "baz")`,
1113
`("foo" OR "bar") AND NOT ("bar" AND ("baz" OR "qux"))`,
1214
} {
1315
ev, err := New([]string{condition})

0 commit comments

Comments
 (0)