@@ -108,20 +108,14 @@ func (s *PromQLSmith) walkBinaryExpr(depth int, valueTypes ...parser.ValueType)
108108 valueTypes = []parser.ValueType {parser .ValueTypeVector }
109109 expr .VectorMatching .Card = parser .CardManyToMany
110110 }
111- expr .LHS = wrapParenExpr (s .walk (depth - 1 , valueTypes ... ))
112- expr .RHS = wrapParenExpr (s .walk (depth - 1 , valueTypes ... ))
113- lvt := expr .LHS .Type ()
114- rvt := expr .RHS .Type ()
115- // ReturnBool can only be set for comparison operator. It is
116- // required to set to true if both expressions are scalar type.
117- if expr .Op .IsComparisonOperator () {
118- if lvt == parser .ValueTypeScalar && rvt == parser .ValueTypeScalar || s .rnd .Intn (2 ) == 0 {
119- expr .ReturnBool = true
120- }
121- }
122111
123- if ! expr .Op .IsSetOperator () && s .enableVectorMatching && lvt == parser .ValueTypeVector &&
124- rvt == parser .ValueTypeVector && s .rnd .Intn (2 ) == 0 {
112+ // Generate vector matching only if we know it asks for vector value type.
113+ if ! expr .Op .IsSetOperator () && len (valueTypes ) == 1 && valueTypes [0 ] == parser .ValueTypeVector && s .enableVectorMatching && s .rnd .Float64 () > 0.8 {
114+ lhs , _ := s .walkExpr (VectorSelector , depth - 1 , valueTypes ... )
115+ expr .LHS = wrapParenExpr (lhs )
116+ rhs , _ := s .walkExpr (VectorSelector , depth - 1 , valueTypes ... )
117+ expr .RHS = wrapParenExpr (rhs )
118+
125119 leftSeriesSet , stop := getOutputSeries (expr .LHS )
126120 if stop {
127121 return expr
@@ -130,12 +124,25 @@ func (s *PromQLSmith) walkBinaryExpr(depth int, valueTypes ...parser.ValueType)
130124 if stop {
131125 return expr
132126 }
133- s .walkVectorMatching (expr , leftSeriesSet , rightSeriesSet , s .rnd .Intn (4 ) == 0 )
127+ s .walkVectorMatching (expr , leftSeriesSet , rightSeriesSet , s .rnd .Intn (2 ) == 0 , s .rnd .Intn (4 ) == 0 )
128+ } else {
129+ expr .LHS = wrapParenExpr (s .walk (depth - 1 , valueTypes ... ))
130+ expr .RHS = wrapParenExpr (s .walk (depth - 1 , valueTypes ... ))
131+ }
132+
133+ lvt := expr .LHS .Type ()
134+ rvt := expr .RHS .Type ()
135+ // ReturnBool can only be set for comparison operator. It is
136+ // required to set to true if both expressions are scalar type.
137+ if expr .Op .IsComparisonOperator () {
138+ if lvt == parser .ValueTypeScalar && rvt == parser .ValueTypeScalar || s .rnd .Intn (2 ) == 0 {
139+ expr .ReturnBool = true
140+ }
134141 }
135142 return expr
136143}
137144
138- func (s * PromQLSmith ) walkVectorMatching (expr * parser.BinaryExpr , seriesSetA []labels.Labels , seriesSetB []labels.Labels , includeLabels bool ) {
145+ func (s * PromQLSmith ) walkVectorMatching (expr * parser.BinaryExpr , seriesSetA []labels.Labels , seriesSetB []labels.Labels , on , includeLabels bool ) {
139146 sa := make (map [string ]struct {})
140147 for _ , series := range seriesSetA {
141148 series .Range (func (lbl labels.Label ) {
@@ -155,48 +162,129 @@ func (s *PromQLSmith) walkVectorMatching(expr *parser.BinaryExpr, seriesSetA []l
155162 sb [lbl .Name ] = struct {}{}
156163 })
157164 }
158- expr .VectorMatching .On = true
159- matchedLabels := make ([]string , 0 )
165+
166+ // Find all matching labels
167+ allMatchedLabels := make ([]string , 0 )
160168 for key := range sb {
161169 if _ , ok := sa [key ]; ok {
162- matchedLabels = append (matchedLabels , key )
170+ allMatchedLabels = append (allMatchedLabels , key )
163171 }
164172 }
173+ // If there is no matching labels, we don't need to do vector matching.
174+ if len (allMatchedLabels ) == 0 {
175+ return
176+ }
177+
178+ // Randomly select a subset of matched labels
179+ sort .Strings (allMatchedLabels ) // Sort for deterministic selection
180+ numLabels := s .rnd .Intn (len (allMatchedLabels )) + 1 // Select at least 1 label
181+ selectedIndices := s .rnd .Perm (len (allMatchedLabels ))[:numLabels ]
182+ sort .Ints (selectedIndices ) // Sort indices for consistent order
183+
184+ matchedLabels := make ([]string , numLabels )
185+ for i , idx := range selectedIndices {
186+ matchedLabels [i ] = allMatchedLabels [idx ]
187+ }
188+
189+ expr .VectorMatching .On = on
190+
165191 // We are doing a very naive approach of guessing side cardinalities
166192 // by checking number of series each side.
167193 oneSideLabelsSet := sa
168- if len ( seriesSetA ) > len ( seriesSetB ) {
194+ if expr . VectorMatching . On {
169195 expr .VectorMatching .MatchingLabels = matchedLabels
196+ } else {
197+ // For 'ignoring', we need to use all labels except the matched ones
198+ expr .VectorMatching .MatchingLabels = getDifference (getAllLabels (sa ), matchedLabels )
199+ }
200+
201+ if len (seriesSetA ) > len (seriesSetB ) {
170202 expr .VectorMatching .Card = parser .CardManyToOne
171203 oneSideLabelsSet = sb
172204 } else if len (seriesSetA ) < len (seriesSetB ) {
173- expr .VectorMatching .MatchingLabels = matchedLabels
174205 expr .VectorMatching .Card = parser .CardOneToMany
175206 }
207+
176208 // Otherwise we do 1:1 match.
177209
178- // For simplicity, we always include all labels on the one side.
179210 if expr .VectorMatching .Card != parser .CardOneToOne && includeLabels {
180- includeLabels := getIncludeLabels ( oneSideLabelsSet , matchedLabels )
211+ includeLabels := getRandomIncludeLabels ( s . rnd , oneSideLabelsSet , expr . VectorMatching . MatchingLabels )
181212 expr .VectorMatching .Include = includeLabels
182213 }
183214}
184215
216+ // Helper function to get all labels from a map
217+ func getAllLabels (labelSet map [string ]struct {}) []string {
218+ labels := make ([]string , 0 , len (labelSet ))
219+ for label := range labelSet {
220+ labels = append (labels , label )
221+ }
222+ sort .Strings (labels )
223+ return labels
224+ }
225+
226+ // Helper function to get the difference between two sorted string slices
227+ func getDifference (all , exclude []string ) []string {
228+ result := make ([]string , 0 )
229+ excludeMap := make (map [string ]struct {})
230+ for _ , e := range exclude {
231+ excludeMap [e ] = struct {}{}
232+ }
233+
234+ for _ , label := range all {
235+ if _ , exists := excludeMap [label ]; ! exists {
236+ result = append (result , label )
237+ }
238+ }
239+ return result
240+ }
241+
242+ // Helper function to get all eligible labels that aren't in the matched set
185243func getIncludeLabels (labelNameSet map [string ]struct {}, matchedLabels []string ) []string {
244+ // Create a map of matched labels for quick lookup
245+ matchedSet := make (map [string ]struct {})
246+ for _ , label := range matchedLabels {
247+ matchedSet [label ] = struct {}{}
248+ }
249+
250+ // Collect all eligible labels that aren't in the matched set
186251 output := make ([]string , 0 )
187- OUTER:
188252 for lbl := range labelNameSet {
189- for _ , matchedLabel := range matchedLabels {
190- if lbl == matchedLabel {
191- continue OUTER
192- }
253+ if _ , matched := matchedSet [lbl ]; ! matched {
254+ output = append (output , lbl )
193255 }
194- output = append (output , lbl )
195256 }
257+
258+ // Sort for deterministic output
196259 sort .Strings (output )
197260 return output
198261}
199262
263+ // Helper function to randomly select a subset of include labels
264+ func getRandomIncludeLabels (rnd * rand.Rand , labelNameSet map [string ]struct {}, matchedLabels []string ) []string {
265+ eligible := getIncludeLabels (labelNameSet , matchedLabels )
266+ if len (eligible ) == 0 {
267+ return nil
268+ }
269+
270+ // Pick a random number of labels to include (at least 1 if available)
271+ numLabels := rnd .Intn (len (eligible )) + 1
272+ if numLabels > len (eligible ) {
273+ numLabels = len (eligible )
274+ }
275+
276+ // Randomly select the labels
277+ indices := rnd .Perm (len (eligible ))[:numLabels ]
278+ sort .Ints (indices )
279+
280+ // Create the final selection
281+ result := make ([]string , numLabels )
282+ for i , idx := range indices {
283+ result [i ] = eligible [idx ]
284+ }
285+ return result
286+ }
287+
200288// Walk binary op based on whether vector value type is allowed or not.
201289// Since Set operator only works with vector so if vector is disallowed
202290// we will choose comparison operator that works both for scalar and vector.
0 commit comments