aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/query/optimizer.go
blob: 16f36dc08f97b53bb8fdee28827193c7e6744a31 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
package query

import (
	"slices"
)

type Optimizer struct{}

func StatementCmp(a Statement, b Statement) int {
	catDiff := int(a.Category - b.Category)
	opDiff := int(a.Operator - b.Operator)
	negatedDiff := 0
	if a.Negated && !b.Negated {
		negatedDiff = 1
	} else if !a.Negated && b.Negated {
		negatedDiff = -1
	}

	return catDiff*100_000 + opDiff*100 + negatedDiff*10 + a.Value.Compare(b.Value)
}

func StatementEq(a Statement, b Statement) bool {
	a.Simplify()
	b.Simplify()
	return a.Category == b.Category && a.Operator == b.Operator && a.Negated == b.Negated && a.Value.Compare(b.Value) == 0
}

// Merge child clauses with their parents when applicable
func (o Optimizer) Flatten(root *Clause) {
	stack := make([]*Clause, 0, len(root.Clauses))
	stack = append(stack, root)
	for len(stack) != 0 {
		top := len(stack) - 1
		node := stack[top]
		stack = stack[:top]

		hasMerged := false

		// merge if only child clause
		if len(node.Statements) == 0 && len(node.Clauses) == 1 {
			child := node.Clauses[0]

			node.Operator = child.Operator
			node.Statements = child.Statements
			node.Clauses = child.Clauses
		}

		// cannot be "modernized", node.Clauses is modified in loop
		for i := 0; i < len(node.Clauses); i++ {
			child := node.Clauses[i]

			// merge because of commutativity
			if node.Operator == child.Operator {
				hasMerged = true
				node.Statements = append(node.Statements, child.Statements...)
				node.Clauses = append(node.Clauses, child.Clauses...)
			} else {
				stack = append(stack, child)
			}
		}

		if hasMerged {
			numChildren := len(stack) - top
			if numChildren > 0 {
				node.Clauses = slices.Grow(node.Clauses, numChildren)
				node.Clauses = node.Clauses[:numChildren]
				copy(node.Clauses, stack[top:top+numChildren])
			} else {
				node.Clauses = nil
			}
		}
	}
}

func (o Optimizer) Compact(c *Clause) {
	for clause := range c.DFS() {
		clause.Statements = slices.CompactFunc(c.Statements, StatementEq)
	}
}

// if any claus is a strict equality/inequality noop all fuzzy operations
func strictEquality(clause Clause) error {
	isStrict := slices.ContainsFunc(clause.Statements, func(stmt Statement) bool {
		if stmt.Operator == OP_EQ || stmt.Operator == OP_NE {
			return true
		}
		return false
	})

	if isStrict {
		for i := range clause.Statements {
			stmt := clause.Statements[i]
			if stmt.Operator != OP_EQ && stmt.Operator != OP_NE {
				clause.Statements[i] = Statement{}
			}
		}
	}

	return nil
}