forked from dolthub/go-mysql-server
-
Notifications
You must be signed in to change notification settings - Fork 0
/
batch.go
156 lines (134 loc) · 4.67 KB
/
batch.go
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// Copyright 2020-2021 Dolthub, Inc.
//
// Licensed 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 analyzer
import (
"reflect"
"strconv"
"github.com/gabereiser/go-mysql-server/sql/transform"
"github.com/gabereiser/go-mysql-server/sql"
)
// RuleFunc is the function to be applied in a rule.
type RuleFunc func(*sql.Context, *Analyzer, sql.Node, *Scope, RuleSelector) (sql.Node, transform.TreeIdentity, error)
// RuleSelector filters analysis rules by id
type RuleSelector func(RuleId) bool
// Rule to transform nodes.
type Rule struct {
// Name of the rule.
Id RuleId
// Apply transforms a node.
Apply RuleFunc
}
// BatchSelector filters analysis batches by name
type BatchSelector func(string) bool
// Batch executes a set of rules a specific number of times.
// When this number of times is reached, the actual node
// and ErrMaxAnalysisIters is returned.
type Batch struct {
Desc string
Iterations int
Rules []Rule
}
// Eval executes the rules of the batch. On any error, the partially transformed node is returned along with the error.
// If the batch's max number of iterations is reached without achieving stabilization (batch evaluation no longer
// changes the node), then this method returns ErrMaxAnalysisIters.
func (b *Batch) Eval(ctx *sql.Context, a *Analyzer, n sql.Node, scope *Scope, sel RuleSelector) (sql.Node, transform.TreeIdentity, error) {
return b.EvalWithSelector(ctx, a, n, scope, sel)
}
func (b *Batch) EvalWithSelector(ctx *sql.Context, a *Analyzer, n sql.Node, scope *Scope, sel RuleSelector) (sql.Node, transform.TreeIdentity, error) {
if b.Iterations == 0 {
return n, transform.SameTree, nil
}
prev := n
a.PushDebugContext("0")
cur, _, err := b.evalOnce(ctx, a, n, scope, sel)
a.PopDebugContext()
if err != nil {
return cur, transform.SameTree, err
}
nodesEq := nodesEqual(prev, cur)
same := transform.TreeIdentity(nodesEq)
if b.Iterations == 1 {
return cur, transform.TreeIdentity(nodesEq), nil
}
for i := 1; !nodesEq; {
a.Log("Nodes not equal, re-running batch")
a.LogDiff(prev, cur)
if i >= b.Iterations {
return cur, transform.SameTree, ErrMaxAnalysisIters.New(b.Iterations)
}
prev = cur
a.PushDebugContext(strconv.Itoa(i))
cur, _, err = b.evalOnce(ctx, a, cur, scope, sel)
a.PopDebugContext()
if err != nil {
return cur, transform.SameTree, err
}
//todo(max): Use nodesEqual until all rules can reliably report
// modifications. False positives, where a rule incorrectly states
// report sql.NewTree, are the primary barrier.
nodesEq = nodesEqual(prev, cur)
same = same && transform.TreeIdentity(nodesEq)
i++
}
return cur, same, nil
}
// evalOnce returns the result of evaluating a batch of rules on the node given. In the result of an error, the result
// of the last successful transformation is returned along with the error. If no transformation was successful, the
// input node is returned as-is.
func (b *Batch) evalOnce(ctx *sql.Context, a *Analyzer, n sql.Node, scope *Scope, sel RuleSelector) (sql.Node, transform.TreeIdentity, error) {
var (
same = transform.SameTree
allSame = transform.SameTree
next sql.Node
prev = n
)
for _, rule := range b.Rules {
if !sel(rule.Id) {
a.Log("Skipping rule %s", rule.Id)
continue
}
var err error
a.Log("Evaluating rule %s", rule.Id)
a.PushDebugContext(rule.Id.String())
next, same, err = rule.Apply(ctx, a, prev, scope, sel)
allSame = same && allSame
if next != nil && !same {
a.LogNode(next)
// We should only do this if the result has changed, but some rules currently misbehave and falsely report nothing
// changed
a.LogDiff(prev, next)
}
a.PopDebugContext()
if err != nil {
// Returning the last node before the error is important. This is non-idiomatic, but in the case of partial
// resolution before an error we want the last successful transformation result. Very important for resolving
// subqueries.
return prev, allSame, err
}
prev = next
}
return prev, allSame, nil
}
func nodesEqual(a, b sql.Node) bool {
if e, ok := a.(equaler); ok {
return e.Equal(b)
}
if e, ok := b.(equaler); ok {
return e.Equal(a)
}
return reflect.DeepEqual(a, b)
}
type equaler interface {
Equal(sql.Node) bool
}