(Non-working) prototype for doing joins on unequal labels.

Allows you to do things like 'foo * on(bar==baz) blat'...

Signed-off-by: Tom Wilkie <tom@grafana.com>
join
Tom Wilkie 5 years ago
parent 820d7775eb
commit ac4594aaf4
No known key found for this signature in database
GPG Key ID: E32FC479235E15D8
  1. 18
      promql/ast.go
  2. 35
      promql/engine.go
  3. 34
      promql/generated_parser.y
  4. 700
      promql/generated_parser.y.go
  5. 6
      promql/lex.go
  6. 2
      promql/parse.go
  7. 4
      promql/printer.go

@ -232,7 +232,7 @@ type VectorMatching struct {
Card VectorMatchCardinality Card VectorMatchCardinality
// MatchingLabels contains the labels which define equality of a pair of // MatchingLabels contains the labels which define equality of a pair of
// elements from the Vectors. // elements from the Vectors.
MatchingLabels []string MatchingLabels []*labels.Matcher
// On includes the given label names from matching, // On includes the given label names from matching,
// rather than excluding them. // rather than excluding them.
On bool On bool
@ -241,6 +241,22 @@ type VectorMatching struct {
Include []string Include []string
} }
func justNames(in []*labels.Matcher) []string {
var result = make([]string, 0, len(in))
for _, i := range in {
result = append(result, i.Name)
}
return result
}
func justValues(in []*labels.Matcher) []string {
var result = make([]string, 0, len(in))
for _, i := range in {
result = append(result, i.Value)
}
return result
}
// Visitor allows visiting a Node and its child nodes. The Visit method is // Visitor allows visiting a Node and its child nodes. The Visit method is
// invoked for each node with the path leading to the node provided additionally. // invoked for each node with the path leading to the node provided additionally.
// If the result visitor w is not nil and no error, Walk visits each of the children // If the result visitor w is not nil and no error, Walk visits each of the children

@ -1519,18 +1519,19 @@ func (ev *evaluator) VectorAnd(lhs, rhs Vector, matching *VectorMatching, enh *E
if matching.Card != CardManyToMany { if matching.Card != CardManyToMany {
panic("set operations must only use many-to-many matching") panic("set operations must only use many-to-many matching")
} }
sigf := enh.signatureFunc(matching.On, matching.MatchingLabels...) sigfl := enh.signatureFunc(matching.On, justNames(matching.MatchingLabels)...)
sigfr := enh.signatureFunc(matching.On, justValues(matching.MatchingLabels)...)
// The set of signatures for the right-hand side Vector. // The set of signatures for the right-hand side Vector.
rightSigs := map[uint64]struct{}{} rightSigs := map[uint64]struct{}{}
// Add all rhs samples to a map so we can easily find matches later. // Add all rhs samples to a map so we can easily find matches later.
for _, rs := range rhs { for _, rs := range rhs {
rightSigs[sigf(rs.Metric)] = struct{}{} rightSigs[sigfr(rs.Metric)] = struct{}{}
} }
for _, ls := range lhs { for _, ls := range lhs {
// If there's a matching entry in the right-hand side Vector, add the sample. // If there's a matching entry in the right-hand side Vector, add the sample.
if _, ok := rightSigs[sigf(ls.Metric)]; ok { if _, ok := rightSigs[sigfl(ls.Metric)]; ok {
enh.out = append(enh.out, ls) enh.out = append(enh.out, ls)
} }
} }
@ -1541,17 +1542,18 @@ func (ev *evaluator) VectorOr(lhs, rhs Vector, matching *VectorMatching, enh *Ev
if matching.Card != CardManyToMany { if matching.Card != CardManyToMany {
panic("set operations must only use many-to-many matching") panic("set operations must only use many-to-many matching")
} }
sigf := enh.signatureFunc(matching.On, matching.MatchingLabels...) sigfl := enh.signatureFunc(matching.On, justNames(matching.MatchingLabels)...)
sigfr := enh.signatureFunc(matching.On, justValues(matching.MatchingLabels)...)
leftSigs := map[uint64]struct{}{} leftSigs := map[uint64]struct{}{}
// Add everything from the left-hand-side Vector. // Add everything from the left-hand-side Vector.
for _, ls := range lhs { for _, ls := range lhs {
leftSigs[sigf(ls.Metric)] = struct{}{} leftSigs[sigfl(ls.Metric)] = struct{}{}
enh.out = append(enh.out, ls) enh.out = append(enh.out, ls)
} }
// Add all right-hand side elements which have not been added from the left-hand side. // Add all right-hand side elements which have not been added from the left-hand side.
for _, rs := range rhs { for _, rs := range rhs {
if _, ok := leftSigs[sigf(rs.Metric)]; !ok { if _, ok := leftSigs[sigfr(rs.Metric)]; !ok {
enh.out = append(enh.out, rs) enh.out = append(enh.out, rs)
} }
} }
@ -1562,15 +1564,16 @@ func (ev *evaluator) VectorUnless(lhs, rhs Vector, matching *VectorMatching, enh
if matching.Card != CardManyToMany { if matching.Card != CardManyToMany {
panic("set operations must only use many-to-many matching") panic("set operations must only use many-to-many matching")
} }
sigf := enh.signatureFunc(matching.On, matching.MatchingLabels...) sigfl := enh.signatureFunc(matching.On, justNames(matching.MatchingLabels)...)
sigfr := enh.signatureFunc(matching.On, justValues(matching.MatchingLabels)...)
rightSigs := map[uint64]struct{}{} rightSigs := map[uint64]struct{}{}
for _, rs := range rhs { for _, rs := range rhs {
rightSigs[sigf(rs.Metric)] = struct{}{} rightSigs[sigfr(rs.Metric)] = struct{}{}
} }
for _, ls := range lhs { for _, ls := range lhs {
if _, ok := rightSigs[sigf(ls.Metric)]; !ok { if _, ok := rightSigs[sigfl(ls.Metric)]; !ok {
enh.out = append(enh.out, ls) enh.out = append(enh.out, ls)
} }
} }
@ -1582,12 +1585,14 @@ func (ev *evaluator) VectorBinop(op ItemType, lhs, rhs Vector, matching *VectorM
if matching.Card == CardManyToMany { if matching.Card == CardManyToMany {
panic("many-to-many only allowed for set operators") panic("many-to-many only allowed for set operators")
} }
sigf := enh.signatureFunc(matching.On, matching.MatchingLabels...) sigfl := enh.signatureFunc(matching.On, justNames(matching.MatchingLabels)...)
sigfr := enh.signatureFunc(matching.On, justValues(matching.MatchingLabels)...)
// The control flow below handles one-to-one or many-to-one matching. // The control flow below handles one-to-one or many-to-one matching.
// For one-to-many, swap sidedness and account for the swap when calculating // For one-to-many, swap sidedness and account for the swap when calculating
// values. // values.
if matching.Card == CardOneToMany { if matching.Card == CardOneToMany {
sigfl, sigfr = sigfr, sigfl
lhs, rhs = rhs, lhs lhs, rhs = rhs, lhs
} }
@ -1603,7 +1608,7 @@ func (ev *evaluator) VectorBinop(op ItemType, lhs, rhs Vector, matching *VectorM
// Add all rhs samples to a map so we can easily find matches later. // Add all rhs samples to a map so we can easily find matches later.
for _, rs := range rhs { for _, rs := range rhs {
sig := sigf(rs.Metric) sig := sigfr(rs.Metric)
// The rhs is guaranteed to be the 'one' side. Having multiple samples // The rhs is guaranteed to be the 'one' side. Having multiple samples
// with the same signature means that the matching is many-to-many. // with the same signature means that the matching is many-to-many.
if duplSample, found := rightSigs[sig]; found { if duplSample, found := rightSigs[sig]; found {
@ -1612,7 +1617,7 @@ func (ev *evaluator) VectorBinop(op ItemType, lhs, rhs Vector, matching *VectorM
if matching.Card == CardOneToMany { if matching.Card == CardOneToMany {
oneSide = "left" oneSide = "left"
} }
matchedLabels := rs.Metric.MatchLabels(matching.On, matching.MatchingLabels...) matchedLabels := rs.Metric.MatchLabels(matching.On, justNames(matching.MatchingLabels)...)
// Many-to-many matching not allowed. // Many-to-many matching not allowed.
ev.errorf("found duplicate series for the match group %s on the %s hand-side of the operation: [%s, %s]"+ ev.errorf("found duplicate series for the match group %s on the %s hand-side of the operation: [%s, %s]"+
";many-to-many matching not allowed: matching labels must be unique on one side", matchedLabels.String(), oneSide, rs.Metric.String(), duplSample.Metric.String()) ";many-to-many matching not allowed: matching labels must be unique on one side", matchedLabels.String(), oneSide, rs.Metric.String(), duplSample.Metric.String())
@ -1634,7 +1639,7 @@ func (ev *evaluator) VectorBinop(op ItemType, lhs, rhs Vector, matching *VectorM
// For all lhs samples find a respective rhs sample and perform // For all lhs samples find a respective rhs sample and perform
// the binary operation. // the binary operation.
for _, ls := range lhs { for _, ls := range lhs {
sig := sigf(ls.Metric) sig := sigfl(ls.Metric)
rs, found := rightSigs[sig] // Look for a match in the rhs Vector. rs, found := rightSigs[sig] // Look for a match in the rhs Vector.
if !found { if !found {
@ -1730,14 +1735,14 @@ func resultMetric(lhs, rhs labels.Labels, op ItemType, matching *VectorMatching,
Outer: Outer:
for _, l := range lhs { for _, l := range lhs {
for _, n := range matching.MatchingLabels { for _, n := range matching.MatchingLabels {
if l.Name == n { if l.Name == n.Name {
continue Outer continue Outer
} }
} }
lb.Del(l.Name) lb.Del(l.Name)
} }
} else { } else {
lb.Del(matching.MatchingLabels...) lb.Del(justNames(matching.MatchingLabels)...)
} }
} }
for _, ln := range matching.Include { for _, ln := range matching.Include {

@ -129,11 +129,13 @@ START_METRIC_SELECTOR
%type <matchers> label_match_list %type <matchers> label_match_list
%type <matcher> label_matcher %type <matcher> label_matcher
%type <item> aggregate_op grouping_label match_op maybe_label metric_identifier unary_op %type <item> aggregate_op match_op maybe_label metric_identifier unary_op
%type <matcher> grouping_label
%type <labels> label_set label_set_list metric %type <labels> label_set label_set_list metric
%type <label> label_set_item %type <label> label_set_item
%type <strings> grouping_label_list grouping_labels maybe_grouping_labels %type <matchers> grouping_label_list grouping_labels maybe_grouping_labels
%type <series> series_item series_values %type <series> series_item series_values
%type <uint> uint %type <uint> uint
%type <float> number series_value signed_number %type <float> number series_value signed_number
@ -206,13 +208,13 @@ aggregate_modifier:
BY grouping_labels BY grouping_labels
{ {
$$ = &AggregateExpr{ $$ = &AggregateExpr{
Grouping: $2, Grouping: justNames($2),
} }
} }
| WITHOUT grouping_labels | WITHOUT grouping_labels
{ {
$$ = &AggregateExpr{ $$ = &AggregateExpr{
Grouping: $2, Grouping: justNames($2),
Without: true, Without: true,
} }
} }
@ -276,13 +278,13 @@ group_modifiers: bool_modifier /* empty */
{ {
$$ = $1 $$ = $1
$$.(*BinaryExpr).VectorMatching.Card = CardManyToOne $$.(*BinaryExpr).VectorMatching.Card = CardManyToOne
$$.(*BinaryExpr).VectorMatching.Include = $3 $$.(*BinaryExpr).VectorMatching.Include = justNames($3)
} }
| on_or_ignoring GROUP_RIGHT maybe_grouping_labels | on_or_ignoring GROUP_RIGHT maybe_grouping_labels
{ {
$$ = $1 $$ = $1
$$.(*BinaryExpr).VectorMatching.Card = CardOneToMany $$.(*BinaryExpr).VectorMatching.Card = CardOneToMany
$$.(*BinaryExpr).VectorMatching.Include = $3 $$.(*BinaryExpr).VectorMatching.Include = justNames($3)
} }
; ;
@ -292,7 +294,7 @@ grouping_labels : LEFT_PAREN grouping_label_list RIGHT_PAREN
| LEFT_PAREN grouping_label_list COMMA RIGHT_PAREN | LEFT_PAREN grouping_label_list COMMA RIGHT_PAREN
{ $$ = $2 } { $$ = $2 }
| LEFT_PAREN RIGHT_PAREN | LEFT_PAREN RIGHT_PAREN
{ $$ = []string{} } { $$ = []*labels.Matcher{} }
| error | error
{ yylex.(*parser).unexpected("grouping opts", "\"(\""); $$ = nil } { yylex.(*parser).unexpected("grouping opts", "\"(\""); $$ = nil }
; ;
@ -300,9 +302,9 @@ grouping_labels : LEFT_PAREN grouping_label_list RIGHT_PAREN
grouping_label_list: grouping_label_list:
grouping_label_list COMMA grouping_label grouping_label_list COMMA grouping_label
{ $$ = append($1, $3.Val) } { $$ = append($1, $3) }
| grouping_label | grouping_label
{ $$ = []string{$1.Val} } { $$ = []*labels.Matcher{$1} }
| grouping_label_list error | grouping_label_list error
{ yylex.(*parser).unexpected("grouping opts", "\",\" or \")\""); $$ = $1 } { yylex.(*parser).unexpected("grouping opts", "\",\" or \")\""); $$ = $1 }
; ;
@ -312,10 +314,20 @@ grouping_label : maybe_label
if !isLabel($1.Val) { if !isLabel($1.Val) {
yylex.(*parser).unexpected("grouping opts", "label") yylex.(*parser).unexpected("grouping opts", "label")
} }
$$ = $1 $$ = yylex.(*parser).newLabelMatcher($1, Item{Typ:EQL}, $1.Quote());
}
| maybe_label EQL maybe_label
{
if !isLabel($1.Val) {
yylex.(*parser).unexpected("grouping opts", "label")
}
if !isLabel($3.Val) {
yylex.(*parser).unexpected("grouping opts", "label")
}
$$ = yylex.(*parser).newLabelMatcher($1, Item{Typ:EQL}, $3.Quote());
} }
| error | error
{ yylex.(*parser).unexpected("grouping opts", "label"); $$ = Item{} } { yylex.(*parser).unexpected("grouping opts", "label"); $$ = nil }
; ;
/* /*

File diff suppressed because it is too large Load Diff

@ -15,6 +15,7 @@ package promql
import ( import (
"fmt" "fmt"
"strconv"
"strings" "strings"
"unicode" "unicode"
"unicode/utf8" "unicode/utf8"
@ -27,6 +28,11 @@ type Item struct {
Val string // The value of this Item. Val string // The value of this Item.
} }
func (i Item) Quote() Item {
i.Val = strconv.Quote(i.Val)
return i
}
// String returns a descriptive string for the Item. // String returns a descriptive string for the Item.
func (i Item) String() string { func (i Item) String() string {
switch { switch {

@ -490,7 +490,7 @@ func (p *parser) checkAST(node Node) (typ ValueType) {
for _, l1 := range n.VectorMatching.MatchingLabels { for _, l1 := range n.VectorMatching.MatchingLabels {
for _, l2 := range n.VectorMatching.Include { for _, l2 := range n.VectorMatching.Include {
if l1 == l2 && n.VectorMatching.On { if l1.Name == l2 && n.VectorMatching.On {
p.addParseErrf(opRange(), "label %q must not occur in ON and GROUP clause at once", l1) p.addParseErrf(opRange(), "label %q must not occur in ON and GROUP clause at once", l1)
} }
} }

@ -90,9 +90,9 @@ func (node *BinaryExpr) String() string {
vm := node.VectorMatching vm := node.VectorMatching
if vm != nil && (len(vm.MatchingLabels) > 0 || vm.On) { if vm != nil && (len(vm.MatchingLabels) > 0 || vm.On) {
if vm.On { if vm.On {
matching = fmt.Sprintf(" on(%s)", strings.Join(vm.MatchingLabels, ", ")) matching = fmt.Sprintf(" on(%s)", strings.Join(justNames(vm.MatchingLabels), ", "))
} else { } else {
matching = fmt.Sprintf(" ignoring(%s)", strings.Join(vm.MatchingLabels, ", ")) matching = fmt.Sprintf(" ignoring(%s)", strings.Join(justNames(vm.MatchingLabels), ", "))
} }
if vm.Card == CardManyToOne || vm.Card == CardOneToMany { if vm.Card == CardManyToOne || vm.Card == CardOneToMany {
matching += " group_" matching += " group_"

Loading…
Cancel
Save