summaryrefslogtreecommitdiff
path: root/internal/mapr/queryvariables.go
blob: 0612a810c6a450461ab06f25c656991d55503903 (plain)
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
package mapr

import (
	"sort"
	"strings"
)

// EffectiveLogFormat returns the log format parser name that will be used to
// evaluate the query. It centralises the parser-selection rule so that both the
// server (resolveParserName) and the client-side plan-time diagnostics agree on
// which parser a query targets.
//
// An explicit `logformat` clause always wins. Without one, a query lacking a
// `from TABLE` clause downgrades to the "generic" parser (which exposes only the
// common $-variables and no dynamic key=value fields). A query with a `from`
// clause uses configuredDefault, or "default" when configuredDefault is empty.
func (q *Query) EffectiveLogFormat(configuredDefault string) string {
	if configuredDefault == "" {
		configuredDefault = "default"
	}
	if q == nil {
		return configuredDefault
	}
	if q.LogFormat != "" {
		return q.LogFormat
	}
	if q.Table == "" {
		return "generic"
	}
	return configuredDefault
}

// ReferencedVariables returns the sorted, de-duplicated set of $-prefixed field
// variables the query references in its select, where, group-by and set clauses.
//
// Variables defined by the query itself via the `set` clause (the left-hand
// side names) are excluded, because they are legitimately produced at runtime
// even though no parser populates them. Bare (non-$) field names are dynamic
// key=value fields that legitimately vary per line and are never returned; only
// the reserved $-prefixed names are candidates for the unknown-variable
// diagnostic.
func (q *Query) ReferencedVariables() []string {
	if q == nil {
		return nil
	}

	produced := make(map[string]struct{}, len(q.Set))
	for _, sc := range q.Set {
		produced[sc.lString] = struct{}{}
	}

	referenced := make(map[string]struct{})
	add := func(name string) {
		if !strings.HasPrefix(name, "$") {
			return
		}
		if _, isProduced := produced[name]; isProduced {
			return
		}
		referenced[name] = struct{}{}
	}

	for _, sc := range q.Select {
		add(sc.Field)
	}
	for _, groupBy := range q.GroupBy {
		add(groupBy)
	}
	for _, wc := range q.Where {
		if wc.lType == Field {
			add(wc.lString)
		}
		if wc.rType == Field {
			add(wc.rString)
		}
	}
	for _, sc := range q.Set {
		if sc.rType == Field || sc.rType == FunctionStack {
			add(sc.rString)
		}
	}

	if len(referenced) == 0 {
		return nil
	}
	variables := make([]string, 0, len(referenced))
	for name := range referenced {
		variables = append(variables, name)
	}
	sort.Strings(variables)
	return variables
}