github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/parser/promql/parse_test.go (about)

     1  // Copyright (c) 2018 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package promql
    22  
    23  import (
    24  	"fmt"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/m3db/m3/src/query/functions"
    29  	"github.com/m3db/m3/src/query/functions/aggregation"
    30  	"github.com/m3db/m3/src/query/functions/binary"
    31  	"github.com/m3db/m3/src/query/functions/lazy"
    32  	"github.com/m3db/m3/src/query/functions/linear"
    33  	"github.com/m3db/m3/src/query/functions/scalar"
    34  	"github.com/m3db/m3/src/query/functions/tag"
    35  	"github.com/m3db/m3/src/query/functions/temporal"
    36  	"github.com/m3db/m3/src/query/models"
    37  	"github.com/m3db/m3/src/query/parser"
    38  
    39  	pql "github.com/prometheus/prometheus/promql/parser"
    40  	"github.com/stretchr/testify/assert"
    41  	"github.com/stretchr/testify/require"
    42  )
    43  
    44  func TestDAGWithCountOp(t *testing.T) {
    45  	q := "count(http_requests_total{method=\"GET\"}) by (service)"
    46  	p, err := Parse(q, time.Second, models.NewTagOptions(), NewParseOptions())
    47  	require.NoError(t, err)
    48  	transforms, edges, err := p.DAG()
    49  	require.NoError(t, err)
    50  	assert.Len(t, transforms, 2)
    51  	assert.Equal(t, transforms[0].Op.OpType(), functions.FetchType)
    52  	assert.Equal(t, transforms[0].ID, parser.NodeID("0"))
    53  	assert.Equal(t, transforms[1].ID, parser.NodeID("1"))
    54  	assert.Equal(t, transforms[1].Op.OpType(), aggregation.CountType)
    55  	assert.Len(t, edges, 1)
    56  	assert.Equal(t, edges[0].ParentID, parser.NodeID("0"),
    57  		"fetch should be the parent")
    58  	assert.Equal(t, edges[0].ChildID, parser.NodeID("1"),
    59  		"aggregation should be the child")
    60  }
    61  
    62  func TestDAGWithOffset(t *testing.T) {
    63  	q := "up offset 2m"
    64  	p, err := Parse(q, time.Second, models.NewTagOptions(), NewParseOptions())
    65  	require.NoError(t, err)
    66  	transforms, edges, err := p.DAG()
    67  	require.NoError(t, err)
    68  	assert.Len(t, transforms, 2)
    69  	assert.Equal(t, transforms[0].Op.OpType(), functions.FetchType)
    70  	assert.Equal(t, transforms[0].ID, parser.NodeID("0"))
    71  	assert.Equal(t, transforms[1].ID, parser.NodeID("1"))
    72  	assert.Equal(t, transforms[1].Op.OpType(), lazy.OffsetType)
    73  	assert.Len(t, edges, 1)
    74  	assert.Equal(t, edges[0].ParentID, parser.NodeID("0"),
    75  		"fetch should be the parent")
    76  	assert.Equal(t, edges[0].ChildID, parser.NodeID("1"),
    77  		"offset should be the child")
    78  }
    79  
    80  func TestInvalidOffset(t *testing.T) {
    81  	q := "up offset -2m"
    82  	_, err := Parse(q, time.Second, models.NewTagOptions(), NewParseOptions())
    83  	require.NoError(t, err)
    84  }
    85  
    86  func TestNegativeUnary(t *testing.T) {
    87  	q := "-up"
    88  	p, err := Parse(q, time.Second, models.NewTagOptions(), NewParseOptions())
    89  	require.NoError(t, err)
    90  	transforms, edges, err := p.DAG()
    91  	require.NoError(t, err)
    92  	assert.Len(t, transforms, 2)
    93  	assert.Equal(t, transforms[0].Op.OpType(), functions.FetchType)
    94  	assert.Equal(t, transforms[0].ID, parser.NodeID("0"))
    95  	assert.Equal(t, transforms[1].Op.OpType(), lazy.UnaryType)
    96  	assert.Equal(t, transforms[1].ID, parser.NodeID("1"))
    97  	assert.Len(t, edges, 1)
    98  	assert.Equal(t, edges[0].ParentID, parser.NodeID("0"))
    99  	assert.Equal(t, edges[0].ChildID, parser.NodeID("1"))
   100  }
   101  
   102  func TestPositiveUnary(t *testing.T) {
   103  	q := "+up"
   104  	p, err := Parse(q, time.Second, models.NewTagOptions(), NewParseOptions())
   105  	require.NoError(t, err)
   106  	transforms, edges, err := p.DAG()
   107  	require.NoError(t, err)
   108  	assert.Len(t, transforms, 1) // "+" defaults to just a fetch operation
   109  	assert.Equal(t, transforms[0].Op.OpType(), functions.FetchType)
   110  	assert.Equal(t, transforms[0].ID, parser.NodeID("0"))
   111  	assert.Len(t, edges, 0)
   112  }
   113  
   114  func TestInvalidUnary(t *testing.T) {
   115  	q := "*up"
   116  	_, err := Parse(q, time.Second, models.NewTagOptions(), NewParseOptions())
   117  	require.Error(t, err)
   118  }
   119  
   120  func TestGetUnaryOpType(t *testing.T) {
   121  	unaryOpType, err := getUnaryOpType(pql.ADD)
   122  	require.NoError(t, err)
   123  	assert.Equal(t, binary.PlusType, unaryOpType)
   124  
   125  	_, err = getUnaryOpType(pql.EQL)
   126  	require.Error(t, err)
   127  }
   128  
   129  func TestDAGWithEmptyExpression(t *testing.T) {
   130  	q := ""
   131  	_, err := Parse(q, time.Second, models.NewTagOptions(), NewParseOptions())
   132  	require.Error(t, err)
   133  }
   134  
   135  func TestDAGWithFakeOp(t *testing.T) {
   136  	q := "fake(http_requests_total{method=\"GET\"})"
   137  	_, err := Parse(q, time.Second, models.NewTagOptions(), NewParseOptions())
   138  	require.Error(t, err)
   139  }
   140  
   141  var aggregateParseTests = []struct {
   142  	q            string
   143  	expectedType string
   144  }{
   145  	{"sum(up)", aggregation.SumType},
   146  	{"min(up)", aggregation.MinType},
   147  	{"max(up)", aggregation.MaxType},
   148  	{"avg(up)", aggregation.AverageType},
   149  	{"stddev(up)", aggregation.StandardDeviationType},
   150  	{"stdvar(up)", aggregation.StandardVarianceType},
   151  	{"count(up)", aggregation.CountType},
   152  
   153  	{"topk(3, up)", aggregation.TopKType},
   154  	{"bottomk(3, up)", aggregation.BottomKType},
   155  	{"quantile(3, up)", aggregation.QuantileType},
   156  	{"count_values(\"some_name\", up)", aggregation.CountValuesType},
   157  
   158  	{"absent(up)", aggregation.AbsentType},
   159  }
   160  
   161  func TestAggregateParses(t *testing.T) {
   162  	for _, tt := range aggregateParseTests {
   163  		t.Run(tt.q, func(t *testing.T) {
   164  			q := tt.q
   165  			p, err := Parse(q, time.Second, models.NewTagOptions(), NewParseOptions())
   166  			require.NoError(t, err)
   167  			transforms, edges, err := p.DAG()
   168  			require.NoError(t, err)
   169  			assert.Len(t, transforms, 2)
   170  			assert.Equal(t, transforms[0].Op.OpType(), functions.FetchType)
   171  			assert.Equal(t, transforms[0].ID, parser.NodeID("0"))
   172  			assert.Equal(t, transforms[1].Op.OpType(), tt.expectedType)
   173  			assert.Equal(t, transforms[1].ID, parser.NodeID("1"))
   174  			assert.Len(t, edges, 1)
   175  			assert.Equal(t, edges[0].ParentID, parser.NodeID("0"))
   176  			assert.Equal(t, edges[0].ChildID, parser.NodeID("1"))
   177  		})
   178  	}
   179  }
   180  
   181  var aggregationWithTagListTests = []string{
   182  	// different number of tags
   183  	"sum(up) by (t1,)",
   184  	"sum(up) by (t1,t2)",
   185  	"sum(up) without (t1)",
   186  	"sum(up) without (t1, t2, t3)",
   187  
   188  	// trailing comma in tag list
   189  	"sum(up) by (t1,)",
   190  	"sum(up) without (t1, t2,)",
   191  
   192  	// alternative form
   193  	"sum by (t) (up)",
   194  	"sum by (t,) (up)",
   195  	"sum without (t) (up)",
   196  	"sum without (t,) (up)",
   197  }
   198  
   199  func TestAggregationWithTagListDoesNotError(t *testing.T) {
   200  	for _, q := range aggregationWithTagListTests {
   201  		t.Run(q, func(t *testing.T) {
   202  			p, err := Parse(q, time.Second, models.NewTagOptions(), NewParseOptions())
   203  			require.NoError(t, err)
   204  			_, _, err = p.DAG()
   205  			require.NoError(t, err)
   206  		})
   207  	}
   208  }
   209  
   210  var linearParseTests = []struct {
   211  	q            string
   212  	expectedType string
   213  }{
   214  	{"abs(up)", linear.AbsType},
   215  	{"ceil(up)", linear.CeilType},
   216  	{"clamp_min(up, 1)", linear.ClampMinType},
   217  	{"clamp_max(up, 1)", linear.ClampMaxType},
   218  	{"exp(up)", linear.ExpType},
   219  	{"floor(up)", linear.FloorType},
   220  	{"ln(up)", linear.LnType},
   221  	{"log2(up)", linear.Log2Type},
   222  	{"log10(up)", linear.Log10Type},
   223  	{"sqrt(up)", linear.SqrtType},
   224  	{"round(up)", linear.RoundType},
   225  	{"round(up, 10)", linear.RoundType},
   226  
   227  	{"day_of_month(up)", linear.DayOfMonthType},
   228  	{"day_of_week(up)", linear.DayOfWeekType},
   229  	{"day_of_month(up)", linear.DayOfMonthType},
   230  	{"days_in_month(up)", linear.DaysInMonthType},
   231  
   232  	{"hour(up)", linear.HourType},
   233  	{"minute(up)", linear.MinuteType},
   234  	{"month(up)", linear.MonthType},
   235  	{"year(up)", linear.YearType},
   236  
   237  	{"histogram_quantile(1,up)", linear.HistogramQuantileType},
   238  }
   239  
   240  func TestLinearParses(t *testing.T) {
   241  	for _, tt := range linearParseTests {
   242  		t.Run(tt.q, func(t *testing.T) {
   243  			q := tt.q
   244  			p, err := Parse(q, time.Second, models.NewTagOptions(), NewParseOptions())
   245  			require.NoError(t, err)
   246  			transforms, edges, err := p.DAG()
   247  			require.NoError(t, err)
   248  			require.Len(t, transforms, 2)
   249  			assert.Equal(t, transforms[0].Op.OpType(), functions.FetchType)
   250  			assert.Equal(t, transforms[0].ID, parser.NodeID("0"))
   251  			assert.Equal(t, transforms[1].Op.OpType(), tt.expectedType)
   252  			assert.Equal(t, transforms[1].ID, parser.NodeID("1"))
   253  			assert.Len(t, edges, 1)
   254  			assert.Equal(t, edges[0].ParentID, parser.NodeID("0"))
   255  			assert.Equal(t, edges[0].ChildID, parser.NodeID("1"))
   256  		})
   257  	}
   258  }
   259  
   260  var variadicTests = []struct {
   261  	q            string
   262  	expectedType string
   263  }{
   264  	{"day_of_month()", linear.DayOfMonthType},
   265  	{"day_of_week()", linear.DayOfWeekType},
   266  	{"day_of_month()", linear.DayOfMonthType},
   267  	{"days_in_month()", linear.DaysInMonthType},
   268  
   269  	{"hour()", linear.HourType},
   270  	{"minute()", linear.MinuteType},
   271  	{"month()", linear.MonthType},
   272  	{"year()", linear.YearType},
   273  }
   274  
   275  func TestVariadicParses(t *testing.T) {
   276  	for _, tt := range variadicTests {
   277  		t.Run(tt.q, func(t *testing.T) {
   278  			q := tt.q
   279  			p, err := Parse(q, time.Second, models.NewTagOptions(), NewParseOptions())
   280  			require.NoError(t, err)
   281  			transforms, _, err := p.DAG()
   282  			require.NoError(t, err)
   283  			require.Len(t, transforms, 1)
   284  			assert.Equal(t, transforms[0].Op.OpType(), tt.expectedType)
   285  			assert.Equal(t, transforms[0].ID, parser.NodeID("0"))
   286  		})
   287  	}
   288  }
   289  
   290  var sortTests = []struct {
   291  	q            string
   292  	expectedType string
   293  }{
   294  	{"sort(up)", linear.SortType},
   295  	{"sort_desc(up)", linear.SortDescType},
   296  }
   297  
   298  func TestSort(t *testing.T) {
   299  	for _, tt := range sortTests {
   300  		t.Run(tt.q, func(t *testing.T) {
   301  			q := tt.q
   302  			p, err := Parse(q, time.Second, models.NewTagOptions(), NewParseOptions())
   303  			require.NoError(t, err)
   304  			transforms, edges, err := p.DAG()
   305  			require.NoError(t, err)
   306  			assert.Len(t, transforms, 2)
   307  			assert.Equal(t, transforms[0].Op.OpType(), functions.FetchType)
   308  			assert.Equal(t, transforms[0].ID, parser.NodeID("0"))
   309  			assert.Equal(t, transforms[1].Op.OpType(), tt.expectedType)
   310  			assert.Equal(t, transforms[1].ID, parser.NodeID("1"))
   311  			assert.Len(t, edges, 1)
   312  		})
   313  	}
   314  }
   315  
   316  func TestScalar(t *testing.T) {
   317  	p, err := Parse("scalar(up)", time.Second,
   318  		models.NewTagOptions(), NewParseOptions())
   319  	require.NoError(t, err)
   320  	transforms, edges, err := p.DAG()
   321  	require.NoError(t, err)
   322  	assert.Len(t, transforms, 1)
   323  	assert.Equal(t, transforms[0].Op.OpType(), functions.FetchType)
   324  	assert.Equal(t, transforms[0].ID, parser.NodeID("0"))
   325  	assert.Len(t, edges, 0)
   326  }
   327  
   328  func TestVector(t *testing.T) {
   329  	vectorExprs := []string{
   330  		"vector(12)",
   331  		"vector(scalar(up))",
   332  		"vector(12 - scalar(vector(100)-2))",
   333  	}
   334  
   335  	for _, expr := range vectorExprs {
   336  		t.Run(expr, func(t *testing.T) {
   337  			p, err := Parse(expr, time.Second,
   338  				models.NewTagOptions(), NewParseOptions())
   339  			require.NoError(t, err)
   340  			transforms, edges, err := p.DAG()
   341  			require.NoError(t, err)
   342  			assert.Len(t, transforms, 1)
   343  			assert.Equal(t, transforms[0].Op.OpType(), scalar.ScalarType)
   344  			assert.Equal(t, transforms[0].ID, parser.NodeID("0"))
   345  			assert.Len(t, edges, 0)
   346  		})
   347  	}
   348  }
   349  
   350  func TestTimeTypeParse(t *testing.T) {
   351  	q := "time()"
   352  	p, err := Parse(q, time.Second, models.NewTagOptions(), NewParseOptions())
   353  	require.NoError(t, err)
   354  	transforms, edges, err := p.DAG()
   355  	require.NoError(t, err)
   356  	assert.Len(t, transforms, 1)
   357  	assert.Equal(t, transforms[0].Op.OpType(), scalar.TimeType)
   358  	assert.Equal(t, transforms[0].ID, parser.NodeID("0"))
   359  	assert.Len(t, edges, 0)
   360  }
   361  
   362  var binaryParseTests = []struct {
   363  	q                string
   364  	LHSType, RHSType string
   365  	expectedType     string
   366  }{
   367  	// Arithmetic
   368  	{"up / up", functions.FetchType, functions.FetchType, binary.DivType},
   369  	{"up ^ 10", functions.FetchType, scalar.ScalarType, binary.ExpType},
   370  	{"10 - up", scalar.ScalarType, functions.FetchType, binary.MinusType},
   371  	{"10 + 10", scalar.ScalarType, scalar.ScalarType, binary.PlusType},
   372  	{"up % up", functions.FetchType, functions.FetchType, binary.ModType},
   373  	{"up * 10", functions.FetchType, scalar.ScalarType, binary.MultiplyType},
   374  
   375  	// Equality
   376  	{"up == up", functions.FetchType, functions.FetchType, binary.EqType},
   377  	{"up != 10", functions.FetchType, scalar.ScalarType, binary.NotEqType},
   378  	{"up > up", functions.FetchType, functions.FetchType, binary.GreaterType},
   379  	{"10 < up", scalar.ScalarType, functions.FetchType, binary.LesserType},
   380  	{"up >= 10", functions.FetchType, scalar.ScalarType, binary.GreaterEqType},
   381  	{"up <= 10", functions.FetchType, scalar.ScalarType, binary.LesserEqType},
   382  
   383  	// Logical
   384  	{"up and up", functions.FetchType, functions.FetchType, binary.AndType},
   385  	{"up or up", functions.FetchType, functions.FetchType, binary.OrType},
   386  	{"up unless up", functions.FetchType, functions.FetchType, binary.UnlessType},
   387  
   388  	// Various spacing
   389  	{"up/ up", functions.FetchType, functions.FetchType, binary.DivType},
   390  	{"up-up", functions.FetchType, functions.FetchType, binary.MinusType},
   391  	{"10 -up", scalar.ScalarType, functions.FetchType, binary.MinusType},
   392  	{"up*10", functions.FetchType, scalar.ScalarType, binary.MultiplyType},
   393  	{"up!=10", functions.FetchType, scalar.ScalarType, binary.NotEqType},
   394  	{"10   <up", scalar.ScalarType, functions.FetchType, binary.LesserType},
   395  	{"up>=   10", functions.FetchType, scalar.ScalarType, binary.GreaterEqType},
   396  }
   397  
   398  func TestBinaryParses(t *testing.T) {
   399  	for _, tt := range binaryParseTests {
   400  		t.Run(tt.q, func(t *testing.T) {
   401  			p, err := Parse(tt.q, time.Second,
   402  				models.NewTagOptions(), NewParseOptions())
   403  
   404  			require.NoError(t, err)
   405  			transforms, edges, err := p.DAG()
   406  			require.NoError(t, err)
   407  			require.Len(t, transforms, 3)
   408  			assert.Equal(t, transforms[0].Op.OpType(), tt.LHSType)
   409  			assert.Equal(t, transforms[0].ID, parser.NodeID("0"))
   410  			assert.Equal(t, transforms[1].Op.OpType(), tt.RHSType)
   411  			assert.Equal(t, transforms[1].ID, parser.NodeID("1"))
   412  			assert.Equal(t, transforms[2].Op.OpType(), tt.expectedType)
   413  			assert.Equal(t, transforms[2].ID, parser.NodeID("2"))
   414  			assert.Len(t, edges, 2)
   415  			assert.Equal(t, edges[0].ParentID, parser.NodeID("0"))
   416  			assert.Equal(t, edges[0].ChildID, parser.NodeID("2"))
   417  			assert.Equal(t, edges[1].ParentID, parser.NodeID("1"))
   418  			assert.Equal(t, edges[1].ChildID, parser.NodeID("2"))
   419  		})
   420  	}
   421  }
   422  
   423  func TestParenPrecedenceParses(t *testing.T) {
   424  	p, err := Parse("(5^(up-6))", time.Second,
   425  		models.NewTagOptions(), NewParseOptions())
   426  	require.NoError(t, err)
   427  	transforms, edges, err := p.DAG()
   428  	require.NoError(t, err)
   429  	require.Len(t, transforms, 5)
   430  	// 5
   431  	assert.Equal(t, transforms[0].Op.OpType(), scalar.ScalarType)
   432  	assert.Equal(t, transforms[0].ID, parser.NodeID("0"))
   433  	// up
   434  	assert.Equal(t, transforms[1].Op.OpType(), functions.FetchType)
   435  	assert.Equal(t, transforms[1].ID, parser.NodeID("1"))
   436  	// 6
   437  	assert.Equal(t, transforms[2].Op.OpType(), scalar.ScalarType)
   438  	assert.Equal(t, transforms[2].ID, parser.NodeID("2"))
   439  	// -
   440  	assert.Equal(t, transforms[3].Op.OpType(), binary.MinusType)
   441  	assert.Equal(t, transforms[3].ID, parser.NodeID("3"))
   442  	// ^
   443  	assert.Equal(t, transforms[4].Op.OpType(), binary.ExpType)
   444  	assert.Equal(t, transforms[4].ID, parser.NodeID("4"))
   445  
   446  	assert.Len(t, edges, 4)
   447  	// up -
   448  	assert.Equal(t, edges[0].ParentID, parser.NodeID("1"))
   449  	assert.Equal(t, edges[0].ChildID, parser.NodeID("3"))
   450  	// 6 -
   451  	assert.Equal(t, edges[1].ParentID, parser.NodeID("2"))
   452  	assert.Equal(t, edges[1].ChildID, parser.NodeID("3"))
   453  	// 5 ^
   454  	assert.Equal(t, edges[2].ParentID, parser.NodeID("0"))
   455  	assert.Equal(t, edges[2].ChildID, parser.NodeID("4"))
   456  	// (up -6) ^
   457  	assert.Equal(t, edges[3].ParentID, parser.NodeID("3"))
   458  	assert.Equal(t, edges[3].ChildID, parser.NodeID("4"))
   459  }
   460  
   461  var temporalParseTests = []struct {
   462  	q            string
   463  	expectedType string
   464  }{
   465  	{"avg_over_time(up[5m])", temporal.AvgType},
   466  	{"count_over_time(up[5m])", temporal.CountType},
   467  	{"min_over_time(up[5m])", temporal.MinType},
   468  	{"max_over_time(up[5m])", temporal.MaxType},
   469  	{"sum_over_time(up[5m])", temporal.SumType},
   470  	{"stddev_over_time(up[5m])", temporal.StdDevType},
   471  	{"stdvar_over_time(up[5m])", temporal.StdVarType},
   472  	{"last_over_time(up[5m])", temporal.LastType},
   473  	{"quantile_over_time(0.2, up[5m])", temporal.QuantileType},
   474  	{"irate(up[5m])", temporal.IRateType},
   475  	{"idelta(up[5m])", temporal.IDeltaType},
   476  	{"rate(up[5m])", temporal.RateType},
   477  	{"delta(up[5m])", temporal.DeltaType},
   478  	{"increase(up[5m])", temporal.IncreaseType},
   479  	{"resets(up[5m])", temporal.ResetsType},
   480  	{"changes(up[5m])", temporal.ChangesType},
   481  	{"holt_winters(up[5m], 0.2, 0.3)", temporal.HoltWintersType},
   482  	{"predict_linear(up[5m], 100)", temporal.PredictLinearType},
   483  	{"deriv(up[5m])", temporal.DerivType},
   484  }
   485  
   486  func TestTemporalParses(t *testing.T) {
   487  	for _, tt := range temporalParseTests {
   488  		t.Run(tt.q, func(t *testing.T) {
   489  			q := tt.q
   490  			p, err := Parse(q, time.Second, models.NewTagOptions(), NewParseOptions())
   491  			require.NoError(t, err)
   492  			transforms, edges, err := p.DAG()
   493  			require.NoError(t, err)
   494  			assert.Len(t, transforms, 2)
   495  			assert.Equal(t, transforms[0].Op.OpType(), functions.FetchType)
   496  			assert.Equal(t, transforms[0].ID, parser.NodeID("0"))
   497  			assert.Equal(t, transforms[1].Op.OpType(), tt.expectedType)
   498  			assert.Equal(t, transforms[1].ID, parser.NodeID("1"))
   499  			assert.Len(t, edges, 1)
   500  			assert.Equal(t, edges[0].ParentID, parser.NodeID("0"))
   501  			assert.Equal(t, edges[0].ChildID, parser.NodeID("1"))
   502  		})
   503  	}
   504  }
   505  
   506  var tagParseTests = []struct {
   507  	q            string
   508  	expectedType string
   509  }{
   510  	{`label_join(up, "foo", ",", "s1","s2","s4")`, tag.TagJoinType},
   511  	{`label_replace(up, "foo", "$1", "tagname","(.*):.*")`, tag.TagReplaceType},
   512  }
   513  
   514  func TestTagParses(t *testing.T) {
   515  	for _, tt := range tagParseTests {
   516  		t.Run(tt.q, func(t *testing.T) {
   517  			q := tt.q
   518  			p, err := Parse(q, time.Second, models.NewTagOptions(), NewParseOptions())
   519  			require.NoError(t, err)
   520  			transforms, edges, err := p.DAG()
   521  			require.NoError(t, err)
   522  			assert.Len(t, transforms, 2)
   523  			assert.Equal(t, transforms[0].Op.OpType(), functions.FetchType)
   524  			assert.Equal(t, transforms[0].ID, parser.NodeID("0"))
   525  			assert.Equal(t, transforms[1].Op.OpType(), tt.expectedType)
   526  			assert.Equal(t, transforms[1].ID, parser.NodeID("1"))
   527  			assert.Len(t, edges, 1)
   528  			assert.Equal(t, edges[0].ParentID, parser.NodeID("0"))
   529  			assert.Equal(t, edges[0].ChildID, parser.NodeID("1"))
   530  		})
   531  	}
   532  }
   533  
   534  func TestFailedTemporalParse(t *testing.T) {
   535  	q := "unknown_over_time(http_requests_total[5m])"
   536  	_, err := Parse(q, time.Second, models.NewTagOptions(), NewParseOptions())
   537  	require.Error(t, err)
   538  }
   539  
   540  func TestMissingTagsDoNotPanic(t *testing.T) {
   541  	q := `label_join(up, "foo", ",")`
   542  	p, err := Parse(q, time.Second, models.NewTagOptions(), NewParseOptions())
   543  	require.NoError(t, err)
   544  	assert.NotPanics(t, func() { _, _, _ = p.DAG() })
   545  }
   546  
   547  var functionArgumentExpressionTests = []struct {
   548  	name string
   549  	q    string
   550  }{
   551  	{
   552  		"scalar argument",
   553  		"vector(((1)))",
   554  	},
   555  	{
   556  		"string argument",
   557  		`label_join(up, ("foo"), ((",")), ((("bar"))))`,
   558  	},
   559  	{
   560  		"vector argument",
   561  		"abs(((foo)))",
   562  	},
   563  	{
   564  		"matrix argument",
   565  		"stddev_over_time(((metric[1m])))",
   566  	},
   567  }
   568  
   569  func TestExpressionsInFunctionArgumentsDoNotError(t *testing.T) {
   570  	for _, tt := range functionArgumentExpressionTests {
   571  		t.Run(tt.name, func(t *testing.T) {
   572  			p, err := Parse(tt.q, time.Second, models.NewTagOptions(), NewParseOptions())
   573  			require.NoError(t, err)
   574  			_, _, err = p.DAG()
   575  			require.NoError(t, err)
   576  		})
   577  	}
   578  }
   579  
   580  var invalidFunctionArgumentsTests = []string{
   581  	"vector(())",
   582  	"vector((1)",
   583  	"vector(metric)",
   584  	`label_join(up, "f" + "oo", ",", "ba" + "r")`,
   585  	`label_join(up, 1, ",", 2)`,
   586  	`label_join("up", "foo", ",", "bar")`,
   587  	"abs(1)",
   588  	"abs(())",
   589  	"stddev_over_time(metric[1m]+1)",
   590  	"stddev_over_time(metric)",
   591  }
   592  
   593  func TestParseInvalidFunctionArgumentsErrors(t *testing.T) {
   594  	for _, q := range invalidFunctionArgumentsTests {
   595  		t.Run(q, func(t *testing.T) {
   596  			_, err := Parse(q, time.Second, models.NewTagOptions(), NewParseOptions())
   597  			require.Error(t, err)
   598  		})
   599  	}
   600  }
   601  
   602  func TestCustomParseOptions(t *testing.T) {
   603  	q := "query"
   604  	v := "foo"
   605  	called := 0
   606  	fn := func(query string) (pql.Expr, error) {
   607  		assert.Equal(t, q, query)
   608  		called++
   609  		return &pql.StringLiteral{Val: v}, nil
   610  	}
   611  
   612  	opts := NewParseOptions().SetParseFn(fn)
   613  	ex, err := Parse(q, time.Second, models.NewTagOptions(), opts)
   614  	require.NoError(t, err)
   615  	assert.Equal(t, 1, called)
   616  	parse, ok := ex.(*promParser)
   617  	require.True(t, ok)
   618  	assert.Equal(t, pql.ValueTypeString, parse.expr.Type())
   619  	str, ok := parse.expr.(*pql.StringLiteral)
   620  	require.True(t, ok)
   621  	assert.Equal(t, v, str.Val)
   622  }
   623  
   624  type customParam struct {
   625  	prefix string
   626  }
   627  
   628  func (c customParam) String() string {
   629  	return fmt.Sprintf("%s_custom", c.prefix)
   630  }
   631  
   632  func (c customParam) OpType() string {
   633  	return fmt.Sprintf("%s_customOpType", c.prefix)
   634  }
   635  
   636  func TestCustomSort(t *testing.T) {
   637  	tests := []struct {
   638  		q  string
   639  		ex string
   640  	}{
   641  		{"sort(up)", "sort_customOpType"},
   642  		{"clamp_max(up, 0.3)", "clamp_max_customOpType"},
   643  	}
   644  
   645  	fn := func(s string, _ []interface{}, _ []string,
   646  		_ bool, _ string, _ models.TagOptions) (parser.Params, bool, error) {
   647  		return customParam{s}, true, nil
   648  	}
   649  
   650  	opts := NewParseOptions().SetFunctionParseExpr(fn)
   651  	for _, tt := range tests {
   652  		p, err := Parse(tt.q, time.Second, models.NewTagOptions(), opts)
   653  		require.NoError(t, err)
   654  		transforms, edges, err := p.DAG()
   655  		require.NoError(t, err)
   656  		require.Len(t, transforms, 2)
   657  		assert.Equal(t, functions.FetchType, transforms[0].Op.OpType())
   658  		assert.Equal(t, parser.NodeID("0"), transforms[0].ID)
   659  		assert.Len(t, edges, 1)
   660  		assert.Equal(t, tt.ex, transforms[1].Op.OpType())
   661  		assert.Equal(t, parser.NodeID("1"), transforms[1].ID)
   662  	}
   663  }