1package query
2
3import (
4	"context"
5
6	"github.com/influxdata/influxql"
7)
8
9type subqueryBuilder struct {
10	ic   IteratorCreator
11	stmt *influxql.SelectStatement
12}
13
14// buildAuxIterator constructs an auxiliary Iterator from a subquery.
15func (b *subqueryBuilder) buildAuxIterator(ctx context.Context, opt IteratorOptions) (Iterator, error) {
16	// Map the desired auxiliary fields from the substatement.
17	indexes := b.mapAuxFields(opt.Aux)
18
19	subOpt, err := newIteratorOptionsSubstatement(ctx, b.stmt, opt)
20	if err != nil {
21		return nil, err
22	}
23
24	cur, err := buildCursor(ctx, b.stmt, b.ic, subOpt)
25	if err != nil {
26		return nil, err
27	}
28
29	// Filter the cursor by a condition if one was given.
30	if opt.Condition != nil {
31		cur = newFilterCursor(cur, opt.Condition)
32	}
33
34	// Construct the iterators for the subquery.
35	itr := NewIteratorMapper(cur, nil, indexes, subOpt)
36	if len(opt.GetDimensions()) != len(subOpt.GetDimensions()) {
37		itr = NewTagSubsetIterator(itr, opt)
38	}
39	return itr, nil
40}
41
42func (b *subqueryBuilder) mapAuxFields(auxFields []influxql.VarRef) []IteratorMap {
43	indexes := make([]IteratorMap, len(auxFields))
44	for i, name := range auxFields {
45		m := b.mapAuxField(&name)
46		if m == nil {
47			// If this field doesn't map to anything, use the NullMap so it
48			// shows up as null.
49			m = NullMap{}
50		}
51		indexes[i] = m
52	}
53	return indexes
54}
55
56func (b *subqueryBuilder) mapAuxField(name *influxql.VarRef) IteratorMap {
57	offset := 0
58	for i, f := range b.stmt.Fields {
59		if f.Name() == name.Val {
60			return FieldMap{
61				Index: i + offset,
62				// Cast the result of the field into the desired type.
63				Type: name.Type,
64			}
65		} else if call, ok := f.Expr.(*influxql.Call); ok && (call.Name == "top" || call.Name == "bottom") {
66			// We may match one of the arguments in "top" or "bottom".
67			if len(call.Args) > 2 {
68				for j, arg := range call.Args[1 : len(call.Args)-1] {
69					if arg, ok := arg.(*influxql.VarRef); ok && arg.Val == name.Val {
70						return FieldMap{
71							Index: i + j + 1,
72							Type:  influxql.String,
73						}
74					}
75				}
76				// Increment the offset so we have the correct index for later fields.
77				offset += len(call.Args) - 2
78			}
79		}
80	}
81
82	// Unable to find this in the list of fields.
83	// Look within the dimensions and create a field if we find it.
84	for _, d := range b.stmt.Dimensions {
85		if d, ok := d.Expr.(*influxql.VarRef); ok && name.Val == d.Val {
86			return TagMap(d.Val)
87		}
88	}
89
90	// Unable to find any matches.
91	return nil
92}
93
94func (b *subqueryBuilder) buildVarRefIterator(ctx context.Context, expr *influxql.VarRef, opt IteratorOptions) (Iterator, error) {
95	// Look for the field or tag that is driving this query.
96	driver := b.mapAuxField(expr)
97	if driver == nil {
98		// Exit immediately if there is no driver. If there is no driver, there
99		// are no results. Period.
100		return nil, nil
101	}
102
103	// Map the auxiliary fields to their index in the subquery.
104	indexes := b.mapAuxFields(opt.Aux)
105	subOpt, err := newIteratorOptionsSubstatement(ctx, b.stmt, opt)
106	if err != nil {
107		return nil, err
108	}
109
110	cur, err := buildCursor(ctx, b.stmt, b.ic, subOpt)
111	if err != nil {
112		return nil, err
113	}
114
115	// Filter the cursor by a condition if one was given.
116	if opt.Condition != nil {
117		cur = newFilterCursor(cur, opt.Condition)
118	}
119
120	// Construct the iterators for the subquery.
121	itr := NewIteratorMapper(cur, driver, indexes, subOpt)
122	if len(opt.GetDimensions()) != len(subOpt.GetDimensions()) {
123		itr = NewTagSubsetIterator(itr, opt)
124	}
125	return itr, nil
126}
127