Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 190d39e

Browse files
Christopher M. Wolffjlapacik
Christopher M. Wolff
authored and
jlapacik
committedOct 26, 2018
Add unit tests that exercise rules; fix bugs found along the way (#140)
1 parent dfbf1be commit 190d39e

File tree

8 files changed

+326
-12
lines changed

8 files changed

+326
-12
lines changed
 

‎functions/transformations/range.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,9 @@ func (s *RangeProcedureSpec) Kind() plan.ProcedureKind {
128128
func (s *RangeProcedureSpec) Copy() plan.ProcedureSpec {
129129
ns := new(RangeProcedureSpec)
130130
ns.Bounds = s.Bounds
131+
ns.TimeCol = s.TimeCol
132+
ns.StartCol = s.StartCol
133+
ns.StopCol = s.StopCol
131134
return ns
132135
}
133136

‎planner/heuristic_planner.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@ func (p *heuristicPlanner) matchRules(node PlanNode) (PlanNode, bool) {
3131
}
3232

3333
for _, rule := range p.rules[node.Kind()] {
34-
newNode, changed := rule.Rewrite(node)
35-
anyChanged = anyChanged || changed
36-
node = newNode
34+
if rule.Pattern().Match(node) {
35+
newNode, changed := rule.Rewrite(node)
36+
anyChanged = anyChanged || changed
37+
node = newNode
38+
}
3739
}
3840

3941
return node, anyChanged
@@ -103,12 +105,15 @@ func updateSuccessors(plan *PlanSpec, oldNode, newNode PlanNode) {
103105
}
104106

105107
for _, succ := range oldNode.Successors() {
106-
i := 0
107-
for ; i < len(succ.Predecessors()); i++ {
108-
succ.Predecessors()[i] = newNode
108+
found := false
109+
for i, succPred := range succ.Predecessors() {
110+
if succPred == oldNode {
111+
found = true
112+
succ.Predecessors()[i] = newNode
113+
}
109114
}
110115

111-
if i == len(succ.Predecessors()) {
116+
if !found {
112117
panic("Inconsistent plan graph: successor does not have edge back to predecessor")
113118
}
114119
}

‎planner/logical.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,14 @@ func (lpn *LogicalPlanNode) ProcedureSpec() ProcedureSpec {
107107
return lpn.Spec
108108
}
109109

110+
func (lpn *LogicalPlanNode) ShallowCopy() PlanNode {
111+
newNode := new(LogicalPlanNode)
112+
newNode.edges = lpn.edges.shallowCopy()
113+
newNode.id = lpn.id + "_copy"
114+
newNode.Spec = lpn.Spec.Copy()
115+
return newNode
116+
}
117+
110118
// createLogicalPlan creates a logical query plan from a flux spec
111119
func createLogicalPlan(spec *flux.Spec, a Administration) (*PlanSpec, error) {
112120
nodes := make(map[flux.OperationID]PlanNode, len(spec.Operations))

‎planner/logical_test.go

Lines changed: 201 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,19 @@ import (
66
"time"
77

88
"github.com/influxdata/flux"
9+
"github.com/influxdata/flux/ast"
910
"github.com/influxdata/flux/functions/inputs"
1011
"github.com/influxdata/flux/functions/transformations"
1112
"github.com/influxdata/flux/planner"
1213
"github.com/influxdata/flux/planner/plantest"
1314
"github.com/influxdata/flux/semantic"
1415
)
1516

17+
func compile(fluxText string) (*flux.Spec, error) {
18+
now := time.Now().UTC()
19+
return flux.Compile(context.Background(), fluxText, now)
20+
}
21+
1622
// Test the translation of Flux query to logical plan
1723
func TestFluxSpecToLogicalPlan(t *testing.T) {
1824
testcases := []struct {
@@ -99,14 +105,12 @@ func TestFluxSpecToLogicalPlan(t *testing.T) {
99105
t.Run(tc.name, func(t *testing.T) {
100106
t.Parallel()
101107

102-
now := time.Now().UTC()
103-
spec, err := flux.Compile(context.Background(), tc.query, now)
108+
spec, err := compile(tc.query)
104109

105110
if err != nil {
106111
t.Fatal(err)
107112
}
108113

109-
tc.spec.Now = now
110114
want := plantest.CreateLogicalPlanSpec(tc.spec)
111115

112116
thePlanner := planner.NewLogicalPlanner()
@@ -125,3 +129,197 @@ func TestFluxSpecToLogicalPlan(t *testing.T) {
125129
})
126130
}
127131
}
132+
133+
type MergeFiltersRule struct {
134+
}
135+
136+
func (MergeFiltersRule) Name() string {
137+
return "mergeFilters"
138+
}
139+
140+
func (MergeFiltersRule) Pattern() planner.Pattern {
141+
return planner.Pat(transformations.FilterKind,
142+
planner.Pat(transformations.FilterKind,
143+
planner.Any()))
144+
}
145+
146+
func (MergeFiltersRule) Rewrite(pn planner.PlanNode) (planner.PlanNode, bool) {
147+
specTop := pn.ProcedureSpec()
148+
149+
filterSpecTop := specTop.(*transformations.FilterProcedureSpec)
150+
filterSpecBottom := pn.Predecessors()[0].ProcedureSpec().(*transformations.FilterProcedureSpec)
151+
mergedFilterSpec := mergeFilterSpecs(filterSpecTop, filterSpecBottom)
152+
153+
return planner.MergeLogicalPlanNodes(pn, pn.Predecessors()[0], mergedFilterSpec), true
154+
}
155+
156+
func mergeFilterSpecs(a, b *transformations.FilterProcedureSpec) planner.ProcedureSpec {
157+
fn := a.Fn.Copy().(*semantic.FunctionExpression)
158+
159+
aExp, aOK := a.Fn.Body.(semantic.Expression)
160+
bExp, bOK := b.Fn.Body.(semantic.Expression)
161+
162+
if !aOK || !bOK {
163+
// Note that this is just a unit test, so "return" statements are not handled.
164+
panic("function body not expression")
165+
}
166+
167+
fn.Body = &semantic.LogicalExpression{
168+
Operator: ast.AndOperator,
169+
Left: aExp,
170+
Right: bExp,
171+
}
172+
173+
return &transformations.FilterProcedureSpec{
174+
Fn: fn,
175+
}
176+
}
177+
178+
type PushFilterThroughMapRule struct {
179+
}
180+
181+
func (PushFilterThroughMapRule) Name() string {
182+
return "pushFilterThroughMap"
183+
}
184+
185+
func (PushFilterThroughMapRule) Pattern() planner.Pattern {
186+
return planner.Pat(transformations.FilterKind,
187+
planner.Pat(transformations.MapKind,
188+
planner.Any()))
189+
}
190+
191+
func (PushFilterThroughMapRule) Rewrite(pn planner.PlanNode) (planner.PlanNode, bool) {
192+
// It will not always be possible to push a filter through a map... but this is just a unit test.
193+
return planner.SwapPlanNodes(pn, pn.Predecessors()[0]), true
194+
}
195+
196+
func init() {
197+
planner.RegisterLogicalRule(MergeFiltersRule{})
198+
planner.RegisterLogicalRule(PushFilterThroughMapRule{})
199+
}
200+
201+
func TestLogicalPlanner(t *testing.T) {
202+
testcases := []struct {
203+
name string
204+
flux string
205+
wantPlan plantest.LogicalPlanSpec
206+
}{{
207+
name: "with merge-able filters",
208+
flux: `
209+
from(bucket: "telegraf") |>
210+
filter(fn: (r) => r._measurement == "cpu") |>
211+
filter(fn: (r) => r._value > 0.5) |>
212+
filter(fn: (r) => r._value < 0.9) |>
213+
yield(name: "result")`,
214+
wantPlan: plantest.LogicalPlanSpec{
215+
Nodes: []planner.PlanNode{
216+
planner.CreateLogicalNode("from0", &inputs.FromProcedureSpec{Bucket: "telegraf"}),
217+
planner.CreateLogicalNode("merged_filter1_merged_filter2_filter3", &transformations.FilterProcedureSpec{Fn: &semantic.FunctionExpression{
218+
Params: []*semantic.FunctionParam{{Key: &semantic.Identifier{Name: "r"}}},
219+
Body: &semantic.LogicalExpression{Operator: ast.AndOperator,
220+
Left: &semantic.LogicalExpression{Operator: ast.AndOperator,
221+
Left: &semantic.BinaryExpression{Operator: ast.LessThanOperator,
222+
Left: &semantic.MemberExpression{Object: &semantic.IdentifierExpression{Name: "r"}, Property: "_value"},
223+
Right: &semantic.FloatLiteral{Value: 0.9}},
224+
Right: &semantic.BinaryExpression{Operator: ast.GreaterThanOperator,
225+
Left: &semantic.MemberExpression{Object: &semantic.IdentifierExpression{Name: "r"}, Property: "_value"},
226+
Right: &semantic.FloatLiteral{Value: 0.5}}},
227+
Right: &semantic.BinaryExpression{Operator: ast.EqualOperator,
228+
Left: &semantic.MemberExpression{Object: &semantic.IdentifierExpression{Name: "r"}, Property: "_measurement"},
229+
Right: &semantic.StringLiteral{Value: "cpu"}}}},
230+
}),
231+
planner.CreateLogicalNode("yield4", &transformations.YieldProcedureSpec{Name: "result"}),
232+
},
233+
Edges: [][2]int{
234+
{0, 1},
235+
{1, 2},
236+
},
237+
},
238+
},
239+
{
240+
name: "with swappable map and filter",
241+
flux: `from(bucket: "telegraf") |> map(fn: (r) => r._value * 2.0) |> filter(fn: (r) => r._value < 10.0) |> yield(name: "result")`,
242+
wantPlan: plantest.LogicalPlanSpec{
243+
Nodes: []planner.PlanNode{
244+
planner.CreateLogicalNode("from0", &inputs.FromProcedureSpec{Bucket: "telegraf"}),
245+
planner.CreateLogicalNode("filter2_copy", &transformations.FilterProcedureSpec{Fn: &semantic.FunctionExpression{
246+
Params: []*semantic.FunctionParam{{Key: &semantic.Identifier{Name: "r"}}},
247+
Body: &semantic.BinaryExpression{Operator: ast.LessThanOperator,
248+
Left: &semantic.MemberExpression{Object: &semantic.IdentifierExpression{Name: "r"}, Property: "_value"},
249+
Right: &semantic.FloatLiteral{Value: 10}},
250+
}}),
251+
planner.CreateLogicalNode("map1", &transformations.MapProcedureSpec{
252+
Fn: &semantic.FunctionExpression{
253+
Params: []*semantic.FunctionParam{{Key: &semantic.Identifier{Name: "r"}}},
254+
Body: &semantic.BinaryExpression{Operator: ast.MultiplicationOperator,
255+
Left: &semantic.MemberExpression{Object: &semantic.IdentifierExpression{Name: "r"}, Property: "_value"},
256+
Right: &semantic.FloatLiteral{Value: 2}}},
257+
MergeKey: true,
258+
}),
259+
planner.CreateLogicalNode("yield3", &transformations.YieldProcedureSpec{Name: "result"}),
260+
},
261+
Edges: [][2]int{
262+
{0, 1},
263+
{1, 2},
264+
{2, 3},
265+
},
266+
}},
267+
{
268+
name: "rules working together",
269+
flux: `
270+
from(bucket: "telegraf") |>
271+
filter(fn: (r) => r._value != 0) |>
272+
map(fn: (r) => r._value * 10) |>
273+
filter(fn: (r) => f._value < 100) |>
274+
yield(name: "result")`,
275+
wantPlan: plantest.LogicalPlanSpec{
276+
Nodes: []planner.PlanNode{
277+
planner.CreateLogicalNode("from0", &inputs.FromProcedureSpec{Bucket: "telegraf"}),
278+
planner.CreateLogicalNode("merged_filter1_filter3_copy", &transformations.FilterProcedureSpec{Fn: &semantic.FunctionExpression{
279+
Params: []*semantic.FunctionParam{{Key: &semantic.Identifier{Name: "r"}}},
280+
Body: &semantic.LogicalExpression{Operator: ast.AndOperator,
281+
Left: &semantic.BinaryExpression{Operator: ast.LessThanOperator,
282+
Left: &semantic.MemberExpression{Object: &semantic.IdentifierExpression{Name: "f"}, Property: "_value"},
283+
Right: &semantic.IntegerLiteral{Value: 100}},
284+
Right: &semantic.BinaryExpression{Operator: ast.NotEqualOperator,
285+
Left: &semantic.MemberExpression{Object: &semantic.IdentifierExpression{Name: "r"}, Property: "_value"},
286+
Right: &semantic.IntegerLiteral{}}},
287+
}}),
288+
planner.CreateLogicalNode("map2", &transformations.MapProcedureSpec{Fn: &semantic.FunctionExpression{
289+
Params: []*semantic.FunctionParam{{Key: &semantic.Identifier{Name: "r"}}},
290+
Body: &semantic.BinaryExpression{Operator: ast.MultiplicationOperator,
291+
Left: &semantic.MemberExpression{Object: &semantic.IdentifierExpression{Name: "r"}, Property: "_value"},
292+
Right: &semantic.IntegerLiteral{Value: 10}}},
293+
MergeKey: true,
294+
}),
295+
planner.CreateLogicalNode("yield4", &transformations.YieldProcedureSpec{Name: "result"}),
296+
},
297+
Edges: [][2]int{
298+
{0, 1},
299+
{1, 2},
300+
{2, 3},
301+
},
302+
},
303+
},
304+
}
305+
306+
for _, tc := range testcases {
307+
tc := tc
308+
t.Run(tc.name, func(t *testing.T) {
309+
t.Parallel()
310+
311+
fluxSpec, err := compile(tc.flux)
312+
if err != nil {
313+
t.Fatalf("could not compile flux query: %v", err)
314+
}
315+
316+
logicalPlanner := planner.NewLogicalPlanner()
317+
logicalPlan, err := logicalPlanner.Plan(fluxSpec)
318+
319+
wantPlan := plantest.CreateLogicalPlanSpec(&tc.wantPlan)
320+
if err := plantest.ComparePlans(wantPlan, logicalPlan, plantest.CompareLogicalPlanNodes); err != nil {
321+
t.Error(err)
322+
}
323+
})
324+
}
325+
}

‎planner/physical.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,15 @@ func (ppn *PhysicalPlanNode) Kind() ProcedureKind {
157157
return ppn.Spec.Kind()
158158
}
159159

160+
func (ppn *PhysicalPlanNode) ShallowCopy() PlanNode {
161+
newNode := new(PhysicalPlanNode)
162+
newNode.edges = ppn.edges.shallowCopy()
163+
newNode.id = ppn.id + "_copy"
164+
// TODO: the type assertion below... is it needed?
165+
newNode.Spec = ppn.Spec.Copy().(PhysicalProcedureSpec)
166+
return newNode
167+
}
168+
160169
// Cost provides the self-cost (i.e., does not include the cost of its predecessors) for
161170
// this plan node. Caller must provide statistics of predecessors to this node.
162171
func (ppn *PhysicalPlanNode) Cost(inStats []Statistics) (cost Cost, outStats Statistics) {

‎planner/plantest/cmp.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package plantest
22

33
import (
44
"fmt"
5-
65
"github.com/google/go-cmp/cmp"
76
"github.com/influxdata/flux/planner"
87
"github.com/influxdata/flux/semantic/semantictest"
@@ -95,7 +94,7 @@ func cmpPlanNode(p, q planner.PlanNode) error {
9594
// The specifications of both procedures must be the same
9695
if !cmp.Equal(p.ProcedureSpec(), q.ProcedureSpec(), semantictest.CmpOptions...) {
9796
return fmt.Errorf("procedure specs not equal -want(%s)/+got(%s) %s",
98-
p.ID(), q.ID(), cmp.Diff(p.ProcedureSpec(), q.ProcedureSpec()))
97+
p.ID(), q.ID(), cmp.Diff(p.ProcedureSpec(), q.ProcedureSpec(), semantictest.CmpOptions...))
9998
}
10099

101100
return nil

0 commit comments

Comments
 (0)
Please sign in to comment.