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 }