github.com/influxdata/influxdb/v2@v2.7.6/influxql/query/statement_rewriter.go (about)

     1  package query
     2  
     3  import (
     4  	"errors"
     5  	"regexp"
     6  
     7  	"github.com/influxdata/influxql"
     8  )
     9  
    10  var matchAllRegex = regexp.MustCompile(`.+`)
    11  
    12  // RewriteStatement rewrites stmt into a new statement, if applicable.
    13  func RewriteStatement(stmt influxql.Statement) (influxql.Statement, error) {
    14  	switch stmt := stmt.(type) {
    15  	case *influxql.ShowFieldKeysStatement:
    16  		return rewriteShowFieldKeysStatement(stmt)
    17  	case *influxql.ShowFieldKeyCardinalityStatement:
    18  		return rewriteShowFieldKeyCardinalityStatement(stmt)
    19  	case *influxql.ShowMeasurementsStatement:
    20  		return rewriteShowMeasurementsStatement(stmt)
    21  	case *influxql.ShowMeasurementCardinalityStatement:
    22  		return rewriteShowMeasurementCardinalityStatement(stmt)
    23  	case *influxql.ShowSeriesStatement:
    24  		return rewriteShowSeriesStatement(stmt)
    25  	case *influxql.ShowSeriesCardinalityStatement:
    26  		return rewriteShowSeriesCardinalityStatement(stmt)
    27  	case *influxql.ShowTagKeysStatement:
    28  		return rewriteShowTagKeysStatement(stmt)
    29  	case *influxql.ShowTagKeyCardinalityStatement:
    30  		return rewriteShowTagKeyCardinalityStatement(stmt)
    31  	case *influxql.ShowTagValuesStatement:
    32  		return rewriteShowTagValuesStatement(stmt)
    33  	case *influxql.ShowTagValuesCardinalityStatement:
    34  		return rewriteShowTagValuesCardinalityStatement(stmt)
    35  	default:
    36  		return stmt, nil
    37  	}
    38  }
    39  
    40  func rewriteShowFieldKeysStatement(stmt *influxql.ShowFieldKeysStatement) (influxql.Statement, error) {
    41  	return &influxql.SelectStatement{
    42  		Fields: influxql.Fields([]*influxql.Field{
    43  			{Expr: &influxql.VarRef{Val: "fieldKey"}},
    44  			{Expr: &influxql.VarRef{Val: "fieldType"}},
    45  		}),
    46  		Sources:    rewriteSources(stmt.Sources, "_fieldKeys", stmt.Database),
    47  		Condition:  rewriteSourcesCondition(stmt.Sources, nil),
    48  		Offset:     stmt.Offset,
    49  		Limit:      stmt.Limit,
    50  		SortFields: stmt.SortFields,
    51  		OmitTime:   true,
    52  		Dedupe:     true,
    53  		IsRawQuery: true,
    54  	}, nil
    55  }
    56  
    57  func rewriteShowFieldKeyCardinalityStatement(stmt *influxql.ShowFieldKeyCardinalityStatement) (influxql.Statement, error) {
    58  	// Check for time in WHERE clause (not supported).
    59  	if influxql.HasTimeExpr(stmt.Condition) {
    60  		return nil, errors.New("SHOW FIELD KEY CARDINALITY doesn't support time in WHERE clause")
    61  	}
    62  
    63  	// Use all field keys, if zero.
    64  	if len(stmt.Sources) == 0 {
    65  		stmt.Sources = influxql.Sources{
    66  			&influxql.Measurement{Regex: &influxql.RegexLiteral{Val: matchAllRegex}},
    67  		}
    68  	}
    69  
    70  	return &influxql.SelectStatement{
    71  		Fields: []*influxql.Field{
    72  			{
    73  				Expr: &influxql.Call{
    74  					Name: "count",
    75  					Args: []influxql.Expr{
    76  						&influxql.Call{
    77  							Name: "distinct",
    78  							Args: []influxql.Expr{&influxql.VarRef{Val: "fieldKey"}},
    79  						},
    80  					},
    81  				},
    82  				Alias: "count",
    83  			},
    84  		},
    85  		Condition:  stmt.Condition,
    86  		Dimensions: stmt.Dimensions,
    87  		Offset:     stmt.Offset,
    88  		Limit:      stmt.Limit,
    89  		OmitTime:   true,
    90  		Sources: influxql.Sources{
    91  			&influxql.SubQuery{
    92  				Statement: &influxql.SelectStatement{
    93  					Fields: []*influxql.Field{
    94  						{Expr: &influxql.VarRef{Val: "fieldKey"}},
    95  						{Expr: &influxql.VarRef{Val: "fieldType"}},
    96  					},
    97  					Sources:    rewriteSources(stmt.Sources, "_fieldKeys", stmt.Database),
    98  					Condition:  rewriteSourcesCondition(stmt.Sources, nil),
    99  					OmitTime:   true,
   100  					Dedupe:     true,
   101  					IsRawQuery: true,
   102  				},
   103  			},
   104  		},
   105  	}, nil
   106  }
   107  
   108  func rewriteShowMeasurementsStatement(stmt *influxql.ShowMeasurementsStatement) (influxql.Statement, error) {
   109  	var sources influxql.Sources
   110  	if stmt.Source != nil {
   111  		sources = influxql.Sources{stmt.Source}
   112  	}
   113  
   114  	// Currently time based SHOW MEASUREMENT queries can't be supported because
   115  	// it's not possible to appropriate set operations such as a negated regex
   116  	// using the query engine.
   117  	if influxql.HasTimeExpr(stmt.Condition) {
   118  		return nil, errors.New("SHOW MEASUREMENTS doesn't support time in WHERE clause")
   119  	}
   120  
   121  	// rewrite condition to push a source measurement into a "_name" tag.
   122  	stmt.Condition = rewriteSourcesCondition(sources, stmt.Condition)
   123  	return stmt, nil
   124  }
   125  
   126  func rewriteShowMeasurementCardinalityStatement(stmt *influxql.ShowMeasurementCardinalityStatement) (influxql.Statement, error) {
   127  	// TODO(edd): currently we only support cardinality estimation for certain
   128  	// types of query. As the estimation coverage is expanded, this condition
   129  	// will become less strict.
   130  	if !stmt.Exact && stmt.Sources == nil && stmt.Condition == nil && stmt.Dimensions == nil && stmt.Limit == 0 && stmt.Offset == 0 {
   131  		return stmt, nil
   132  	}
   133  
   134  	// Check for time in WHERE clause (not supported).
   135  	if influxql.HasTimeExpr(stmt.Condition) {
   136  		return nil, errors.New("SHOW MEASUREMENT EXACT CARDINALITY doesn't support time in WHERE clause")
   137  	}
   138  
   139  	// Use all measurements, if zero.
   140  	if len(stmt.Sources) == 0 {
   141  		stmt.Sources = influxql.Sources{
   142  			&influxql.Measurement{Regex: &influxql.RegexLiteral{Val: matchAllRegex}},
   143  		}
   144  	}
   145  
   146  	return &influxql.SelectStatement{
   147  		Fields: []*influxql.Field{
   148  			{
   149  				Expr: &influxql.Call{
   150  					Name: "count",
   151  					Args: []influxql.Expr{
   152  						&influxql.Call{
   153  							Name: "distinct",
   154  							Args: []influxql.Expr{&influxql.VarRef{Val: "_name"}},
   155  						},
   156  					},
   157  				},
   158  				Alias: "count",
   159  			},
   160  		},
   161  		Sources:    rewriteSources2(stmt.Sources, stmt.Database),
   162  		Condition:  stmt.Condition,
   163  		Dimensions: stmt.Dimensions,
   164  		Offset:     stmt.Offset,
   165  		Limit:      stmt.Limit,
   166  		OmitTime:   true,
   167  		StripName:  true,
   168  	}, nil
   169  }
   170  
   171  func rewriteShowSeriesStatement(stmt *influxql.ShowSeriesStatement) (influxql.Statement, error) {
   172  	s := &influxql.SelectStatement{
   173  		Condition:  stmt.Condition,
   174  		Offset:     stmt.Offset,
   175  		Limit:      stmt.Limit,
   176  		SortFields: stmt.SortFields,
   177  		OmitTime:   true,
   178  		StripName:  true,
   179  		Dedupe:     true,
   180  		IsRawQuery: true,
   181  	}
   182  	// Check if we can exclusively use the index.
   183  	if !influxql.HasTimeExpr(stmt.Condition) {
   184  		s.Fields = []*influxql.Field{{Expr: &influxql.VarRef{Val: "key"}}}
   185  		s.Sources = rewriteSources(stmt.Sources, "_series", stmt.Database)
   186  		s.Condition = rewriteSourcesCondition(s.Sources, s.Condition)
   187  		return s, nil
   188  	}
   189  
   190  	// The query is bounded by time then it will have to query TSM data rather
   191  	// than utilising the index via system iterators.
   192  	s.Fields = []*influxql.Field{
   193  		{Expr: &influxql.VarRef{Val: "_seriesKey"}, Alias: "key"},
   194  	}
   195  	s.Sources = rewriteSources2(stmt.Sources, stmt.Database)
   196  	return s, nil
   197  }
   198  
   199  func rewriteShowSeriesCardinalityStatement(stmt *influxql.ShowSeriesCardinalityStatement) (influxql.Statement, error) {
   200  	// TODO(edd): currently we only support cardinality estimation for certain
   201  	// types of query. As the estimation coverage is expanded, this condition
   202  	// will become less strict.
   203  	if !stmt.Exact && stmt.Sources == nil && stmt.Condition == nil && stmt.Dimensions == nil && stmt.Limit == 0 && stmt.Offset == 0 {
   204  		return stmt, nil
   205  	}
   206  
   207  	// Check for time in WHERE clause (not supported).
   208  	if influxql.HasTimeExpr(stmt.Condition) {
   209  		return nil, errors.New("SHOW SERIES EXACT CARDINALITY doesn't support time in WHERE clause")
   210  	}
   211  
   212  	// Use all measurements, if zero.
   213  	if len(stmt.Sources) == 0 {
   214  		stmt.Sources = influxql.Sources{
   215  			&influxql.Measurement{Regex: &influxql.RegexLiteral{Val: matchAllRegex}},
   216  		}
   217  	}
   218  
   219  	return &influxql.SelectStatement{
   220  		Fields: []*influxql.Field{
   221  			{
   222  				Expr: &influxql.Call{
   223  					Name: "count",
   224  					Args: []influxql.Expr{&influxql.Call{
   225  						Name: "distinct",
   226  						Args: []influxql.Expr{&influxql.VarRef{Val: "_seriesKey"}},
   227  					}},
   228  				},
   229  				Alias: "count",
   230  			},
   231  		},
   232  		Sources:    rewriteSources2(stmt.Sources, stmt.Database),
   233  		Condition:  stmt.Condition,
   234  		Dimensions: stmt.Dimensions,
   235  		Offset:     stmt.Offset,
   236  		Limit:      stmt.Limit,
   237  		OmitTime:   true,
   238  	}, nil
   239  }
   240  
   241  func rewriteShowTagValuesStatement(stmt *influxql.ShowTagValuesStatement) (influxql.Statement, error) {
   242  	var expr influxql.Expr
   243  	if list, ok := stmt.TagKeyExpr.(*influxql.ListLiteral); ok {
   244  		for _, tagKey := range list.Vals {
   245  			tagExpr := &influxql.BinaryExpr{
   246  				Op:  influxql.EQ,
   247  				LHS: &influxql.VarRef{Val: "_tagKey"},
   248  				RHS: &influxql.StringLiteral{Val: tagKey},
   249  			}
   250  
   251  			if expr != nil {
   252  				expr = &influxql.BinaryExpr{
   253  					Op:  influxql.OR,
   254  					LHS: expr,
   255  					RHS: tagExpr,
   256  				}
   257  			} else {
   258  				expr = tagExpr
   259  			}
   260  		}
   261  	} else {
   262  		expr = &influxql.BinaryExpr{
   263  			Op:  stmt.Op,
   264  			LHS: &influxql.VarRef{Val: "_tagKey"},
   265  			RHS: stmt.TagKeyExpr,
   266  		}
   267  	}
   268  
   269  	// Set condition or "AND" together.
   270  	condition := stmt.Condition
   271  	if condition == nil {
   272  		condition = expr
   273  	} else {
   274  		condition = &influxql.BinaryExpr{
   275  			Op:  influxql.AND,
   276  			LHS: &influxql.ParenExpr{Expr: condition},
   277  			RHS: &influxql.ParenExpr{Expr: expr},
   278  		}
   279  	}
   280  	condition = rewriteSourcesCondition(stmt.Sources, condition)
   281  
   282  	return &influxql.ShowTagValuesStatement{
   283  		Database:   stmt.Database,
   284  		Op:         stmt.Op,
   285  		TagKeyExpr: stmt.TagKeyExpr,
   286  		Condition:  condition,
   287  		SortFields: stmt.SortFields,
   288  		Limit:      stmt.Limit,
   289  		Offset:     stmt.Offset,
   290  	}, nil
   291  }
   292  
   293  func rewriteShowTagValuesCardinalityStatement(stmt *influxql.ShowTagValuesCardinalityStatement) (influxql.Statement, error) {
   294  	// Use all measurements, if zero.
   295  	if len(stmt.Sources) == 0 {
   296  		stmt.Sources = influxql.Sources{
   297  			&influxql.Measurement{Regex: &influxql.RegexLiteral{Val: matchAllRegex}},
   298  		}
   299  	}
   300  
   301  	var expr influxql.Expr
   302  	if list, ok := stmt.TagKeyExpr.(*influxql.ListLiteral); ok {
   303  		for _, tagKey := range list.Vals {
   304  			tagExpr := &influxql.BinaryExpr{
   305  				Op:  influxql.EQ,
   306  				LHS: &influxql.VarRef{Val: "_tagKey"},
   307  				RHS: &influxql.StringLiteral{Val: tagKey},
   308  			}
   309  
   310  			if expr != nil {
   311  				expr = &influxql.BinaryExpr{
   312  					Op:  influxql.OR,
   313  					LHS: expr,
   314  					RHS: tagExpr,
   315  				}
   316  			} else {
   317  				expr = tagExpr
   318  			}
   319  		}
   320  	} else {
   321  		expr = &influxql.BinaryExpr{
   322  			Op:  stmt.Op,
   323  			LHS: &influxql.VarRef{Val: "_tagKey"},
   324  			RHS: stmt.TagKeyExpr,
   325  		}
   326  	}
   327  
   328  	// Set condition or "AND" together.
   329  	condition := stmt.Condition
   330  	if condition == nil {
   331  		condition = expr
   332  	} else {
   333  		condition = &influxql.BinaryExpr{
   334  			Op:  influxql.AND,
   335  			LHS: &influxql.ParenExpr{Expr: condition},
   336  			RHS: &influxql.ParenExpr{Expr: expr},
   337  		}
   338  	}
   339  
   340  	return &influxql.SelectStatement{
   341  		Fields: []*influxql.Field{
   342  			{
   343  				Expr: &influxql.Call{
   344  					Name: "count",
   345  					Args: []influxql.Expr{
   346  						&influxql.Call{
   347  							Name: "distinct",
   348  							Args: []influxql.Expr{&influxql.VarRef{Val: "_tagValue"}},
   349  						},
   350  					},
   351  				},
   352  				Alias: "count",
   353  			},
   354  		},
   355  		Sources:    rewriteSources2(stmt.Sources, stmt.Database),
   356  		Condition:  condition,
   357  		Dimensions: stmt.Dimensions,
   358  		Offset:     stmt.Offset,
   359  		Limit:      stmt.Limit,
   360  		OmitTime:   true,
   361  	}, nil
   362  }
   363  
   364  func rewriteShowTagKeysStatement(stmt *influxql.ShowTagKeysStatement) (influxql.Statement, error) {
   365  	return &influxql.ShowTagKeysStatement{
   366  		Database:   stmt.Database,
   367  		Condition:  rewriteSourcesCondition(stmt.Sources, stmt.Condition),
   368  		SortFields: stmt.SortFields,
   369  		Limit:      stmt.Limit,
   370  		Offset:     stmt.Offset,
   371  		SLimit:     stmt.SLimit,
   372  		SOffset:    stmt.SOffset,
   373  	}, nil
   374  }
   375  
   376  func rewriteShowTagKeyCardinalityStatement(stmt *influxql.ShowTagKeyCardinalityStatement) (influxql.Statement, error) {
   377  	// Check for time in WHERE clause (not supported).
   378  	if influxql.HasTimeExpr(stmt.Condition) {
   379  		return nil, errors.New("SHOW TAG KEY EXACT CARDINALITY doesn't support time in WHERE clause")
   380  	}
   381  
   382  	// Use all measurements, if zero.
   383  	if len(stmt.Sources) == 0 {
   384  		stmt.Sources = influxql.Sources{
   385  			&influxql.Measurement{Regex: &influxql.RegexLiteral{Val: matchAllRegex}},
   386  		}
   387  	}
   388  
   389  	return &influxql.SelectStatement{
   390  		Fields: []*influxql.Field{
   391  			{
   392  				Expr: &influxql.Call{
   393  					Name: "count",
   394  					Args: []influxql.Expr{
   395  						&influxql.Call{
   396  							Name: "distinct",
   397  							Args: []influxql.Expr{&influxql.VarRef{Val: "_tagKey"}},
   398  						},
   399  					},
   400  				},
   401  				Alias: "count",
   402  			},
   403  		},
   404  		Sources:    rewriteSources2(stmt.Sources, stmt.Database),
   405  		Condition:  stmt.Condition,
   406  		Dimensions: stmt.Dimensions,
   407  		Offset:     stmt.Offset,
   408  		Limit:      stmt.Limit,
   409  		OmitTime:   true,
   410  	}, nil
   411  }
   412  
   413  // rewriteSources rewrites sources to include the provided system iterator.
   414  //
   415  // rewriteSources also sets the default database where necessary.
   416  func rewriteSources(sources influxql.Sources, systemIterator, defaultDatabase string) influxql.Sources {
   417  	newSources := influxql.Sources{}
   418  	for _, src := range sources {
   419  		if src == nil {
   420  			continue
   421  		}
   422  		mm := src.(*influxql.Measurement)
   423  		database := mm.Database
   424  		if database == "" {
   425  			database = defaultDatabase
   426  		}
   427  
   428  		newM := mm.Clone()
   429  		newM.SystemIterator, newM.Database = systemIterator, database
   430  		newSources = append(newSources, newM)
   431  	}
   432  
   433  	if len(newSources) <= 0 {
   434  		return append(newSources, &influxql.Measurement{
   435  			Database:       defaultDatabase,
   436  			SystemIterator: systemIterator,
   437  		})
   438  	}
   439  	return newSources
   440  }
   441  
   442  // rewriteSourcesCondition rewrites sources into `name` expressions.
   443  // Merges with cond and returns a new condition.
   444  func rewriteSourcesCondition(sources influxql.Sources, cond influxql.Expr) influxql.Expr {
   445  	if len(sources) == 0 {
   446  		return cond
   447  	}
   448  
   449  	// Generate an OR'd set of filters on source name.
   450  	var scond influxql.Expr
   451  	for _, source := range sources {
   452  		mm := source.(*influxql.Measurement)
   453  
   454  		// Generate a filtering expression on the measurement name.
   455  		var expr influxql.Expr
   456  		if mm.Regex != nil {
   457  			expr = &influxql.BinaryExpr{
   458  				Op:  influxql.EQREGEX,
   459  				LHS: &influxql.VarRef{Val: "_name"},
   460  				RHS: &influxql.RegexLiteral{Val: mm.Regex.Val},
   461  			}
   462  		} else if mm.Name != "" {
   463  			expr = &influxql.BinaryExpr{
   464  				Op:  influxql.EQ,
   465  				LHS: &influxql.VarRef{Val: "_name"},
   466  				RHS: &influxql.StringLiteral{Val: mm.Name},
   467  			}
   468  		}
   469  
   470  		if scond == nil {
   471  			scond = expr
   472  		} else {
   473  			scond = &influxql.BinaryExpr{
   474  				Op:  influxql.OR,
   475  				LHS: scond,
   476  				RHS: expr,
   477  			}
   478  		}
   479  	}
   480  
   481  	// This is the case where the original query has a WHERE on a tag, and also
   482  	// is requesting from a specific source.
   483  	if cond != nil && scond != nil {
   484  		return &influxql.BinaryExpr{
   485  			Op:  influxql.AND,
   486  			LHS: &influxql.ParenExpr{Expr: scond},
   487  			RHS: &influxql.ParenExpr{Expr: cond},
   488  		}
   489  	} else if cond != nil {
   490  		// This is the case where the original query has a WHERE on a tag but
   491  		// is not requesting from a specific source.
   492  		return cond
   493  	}
   494  	return scond
   495  }
   496  
   497  func rewriteSources2(sources influxql.Sources, database string) influxql.Sources {
   498  	if len(sources) == 0 {
   499  		sources = influxql.Sources{&influxql.Measurement{Regex: &influxql.RegexLiteral{Val: matchAllRegex}}}
   500  	}
   501  	for _, source := range sources {
   502  		switch source := source.(type) {
   503  		case *influxql.Measurement:
   504  			if source.Database == "" {
   505  				source.Database = database
   506  			}
   507  		}
   508  	}
   509  	return sources
   510  }