diff --git a/coordinator/statement_executor.go b/coordinator/statement_executor.go index 139562adeb..95d7d44b7c 100644 --- a/coordinator/statement_executor.go +++ b/coordinator/statement_executor.go @@ -174,6 +174,8 @@ func (e *StatementExecutor) ExecuteStatement(stmt influxql.Statement, ctx query. rows, err = e.executeShowGrantsForUserStatement(stmt) case *influxql.ShowMeasurementsStatement: return e.executeShowMeasurementsStatement(stmt, &ctx) + case *influxql.ShowMeasurementCardinalityStatement: + rows, err = e.executeShowMeasurementCardinalityStatement(stmt) case *influxql.ShowRetentionPoliciesStatement: rows, err = e.executeShowRetentionPoliciesStatement(stmt) case *influxql.ShowSeriesCardinalityStatement: @@ -781,6 +783,18 @@ func (e *StatementExecutor) executeShowMeasurementsStatement(q *influxql.ShowMea }) } +func (e *StatementExecutor) executeShowMeasurementCardinalityStatement(stmt *influxql.ShowMeasurementCardinalityStatement) (models.Rows, error) { + n, err := e.TSDBStore.MeasurementsCardinality(stmt.Database) + if err != nil { + return nil, err + } + + return []*models.Row{&models.Row{ + Columns: []string{"cardinality"}, + Values: [][]interface{}{{n}}, + }}, nil +} + func (e *StatementExecutor) executeShowRetentionPoliciesStatement(q *influxql.ShowRetentionPoliciesStatement) (models.Rows, error) { if q.Database == "" { return nil, ErrDatabaseNameRequired @@ -1165,6 +1179,10 @@ func (e *StatementExecutor) NormalizeStatement(stmt influxql.Statement, defaultD if node.Database == "" { node.Database = defaultDatabase } + case *influxql.ShowMeasurementCardinalityStatement: + if node.Database == "" { + node.Database = defaultDatabase + } case *influxql.Measurement: switch stmt.(type) { case *influxql.DropSeriesStatement, *influxql.DeleteSeriesStatement: @@ -1237,6 +1255,7 @@ type TSDBStore interface { TagValues(auth query.Authorizer, database string, cond influxql.Expr) ([]tsdb.TagValues, error) SeriesCardinality(database string) (int64, error) + MeasurementsCardinality(database string) (int64, error) } var _ TSDBStore = LocalTSDBStore{} diff --git a/coordinator/statement_executor_test.go b/coordinator/statement_executor_test.go index f357e0ed31..4ec31a222f 100644 --- a/coordinator/statement_executor_test.go +++ b/coordinator/statement_executor_test.go @@ -319,13 +319,14 @@ type TSDBStore struct { RestoreShardFn func(id uint64, r io.Reader) error BackupShardFn func(id uint64, since time.Time, w io.Writer) error - DeleteDatabaseFn func(name string) error - DeleteMeasurementFn func(database, name string) error - DeleteRetentionPolicyFn func(database, name string) error - DeleteShardFn func(id uint64) error - DeleteSeriesFn func(database string, sources []influxql.Source, condition influxql.Expr) error - ShardGroupFn func(ids []uint64) tsdb.ShardGroup - SeriesCardinalityFn func(database string) (int64, error) + DeleteDatabaseFn func(name string) error + DeleteMeasurementFn func(database, name string) error + DeleteRetentionPolicyFn func(database, name string) error + DeleteShardFn func(id uint64) error + DeleteSeriesFn func(database string, sources []influxql.Source, condition influxql.Expr) error + ShardGroupFn func(ids []uint64) tsdb.ShardGroup + MeasurementsCardinalityFn func(database string) (int64, error) + SeriesCardinalityFn func(database string) (int64, error) } func (s *TSDBStore) CreateShard(database, policy string, shardID uint64, enabled bool) error { @@ -379,6 +380,10 @@ func (s *TSDBStore) MeasurementNames(database string, cond influxql.Expr) ([][]b return nil, nil } +func (s *TSDBStore) MeasurementsCardinality(database string) (int64, error) { + return s.MeasurementsCardinalityFn(database) +} + func (s *TSDBStore) TagValues(_ query.Authorizer, database string, cond influxql.Expr) ([]tsdb.TagValues, error) { return nil, nil } diff --git a/influxql/ast.go b/influxql/ast.go index 7ef28d5c5d..8d29b7309d 100644 --- a/influxql/ast.go +++ b/influxql/ast.go @@ -2320,6 +2320,7 @@ func (s *DropContinuousQueryStatement) DefaultDatabase() string { // ShowMeasurementCardinalityStatement represents a command for listing measurement cardinality. type ShowMeasurementCardinalityStatement struct { + Exact bool // If false then cardinality estimation will be used. Database string Sources Sources Condition Expr @@ -2330,7 +2331,18 @@ type ShowMeasurementCardinalityStatement struct { // String returns a string representation of the statement. func (s *ShowMeasurementCardinalityStatement) String() string { var buf bytes.Buffer - _, _ = buf.WriteString("SHOW MEASUREMENT CARDINALITY") + _, _ = buf.WriteString("SHOW MEASUREMENT") + + if !s.Exact { + _, _ = buf.WriteString(" CARDINALITY") + if s.Database != "" { + _, _ = buf.WriteString(" ON ") + _, _ = buf.WriteString(QuoteIdent(s.Database)) + } + return buf.String() + } + + _, _ = buf.WriteString(" EXACT CARDINALITY") if s.Database != "" { _, _ = buf.WriteString(" ON ") @@ -2360,6 +2372,9 @@ func (s *ShowMeasurementCardinalityStatement) String() string { // RequiredPrivileges returns the privilege required to execute a ShowMeasurementCardinalityStatement. func (s *ShowMeasurementCardinalityStatement) RequiredPrivileges() (ExecutionPrivileges, error) { + if !s.Exact { + return ExecutionPrivileges{{Admin: false, Name: s.Database, Privilege: ReadPrivilege}}, nil + } return s.Sources.RequiredPrivileges() } diff --git a/influxql/parse_tree.go b/influxql/parse_tree.go index b7769b6e0b..002e0f707c 100644 --- a/influxql/parse_tree.go +++ b/influxql/parse_tree.go @@ -127,8 +127,11 @@ func init() { show.Group(GRANTS).Handle(FOR, func(p *Parser) (Statement, error) { return p.parseGrantsForUserStatement() }) + show.Group(MEASUREMENT).Handle(EXACT, func(p *Parser) (Statement, error) { + return p.parseShowMeasurementCardinalityStatement(true) + }) show.Group(MEASUREMENT).Handle(CARDINALITY, func(p *Parser) (Statement, error) { - return p.parseShowMeasurementCardinalityStatement() + return p.parseShowMeasurementCardinalityStatement(false) }) show.Handle(MEASUREMENTS, func(p *Parser) (Statement, error) { return p.parseShowMeasurementsStatement() diff --git a/influxql/parser.go b/influxql/parser.go index 6fa5ef8498..21bde8bd48 100644 --- a/influxql/parser.go +++ b/influxql/parser.go @@ -1008,9 +1008,17 @@ func (p *Parser) parseShowSeriesCardinalityStatement(exact bool) (Statement, err return stmt, nil } -// This function assumes the "SHOW MEASUREMENT CARDINALITY" tokens have already been consumed. -func (p *Parser) parseShowMeasurementCardinalityStatement() (Statement, error) { - stmt := &ShowMeasurementCardinalityStatement{} +// This function assumes the "SHOW MEASUREMENT EXACT" or "SHOW MEASUREMENT CARDINALITY" +// tokens have already been consumed. +func (p *Parser) parseShowMeasurementCardinalityStatement(exact bool) (Statement, error) { + stmt := &ShowMeasurementCardinalityStatement{Exact: exact} + + if stmt.Exact { + // Parse remaining CARDINALITY token + if tok, pos, lit := p.ScanIgnoreWhitespace(); tok != CARDINALITY { + return nil, newParseError(tokstr(tok, lit), []string{"CARDINALITY"}, pos) + } + } // Parse optional ON clause. var err error @@ -1022,6 +1030,11 @@ func (p *Parser) parseShowMeasurementCardinalityStatement() (Statement, error) { p.Unscan() } + // Estimation command doesn't support any further versions of the command. + if !stmt.Exact { + return stmt, nil + } + // Parse optional FROM. if tok, _, _ := p.ScanIgnoreWhitespace(); tok == FROM { if stmt.Sources, err = p.parseSources(false); err != nil { diff --git a/influxql/parser_test.go b/influxql/parser_test.go index 66fe085719..43b42cfdca 100644 --- a/influxql/parser_test.go +++ b/influxql/parser_test.go @@ -1690,26 +1690,46 @@ func TestParser_ParseStatement(t *testing.T) { stmt: &influxql.ShowMeasurementCardinalityStatement{}, }, - // SHOW MEASUREMENT CARDINALITY FROM cpu - { - s: `SHOW MEASUREMENT CARDINALITY FROM cpu`, - stmt: &influxql.ShowMeasurementCardinalityStatement{ - Sources: []influxql.Source{&influxql.Measurement{Name: "cpu"}}, - }, - }, - - // SHOW MEASUREMENT CARDINALITY ON db0 + // SHOW MEASUREMENT CARDINALITY ON db0 statement { s: `SHOW MEASUREMENT CARDINALITY ON db0`, stmt: &influxql.ShowMeasurementCardinalityStatement{ + Exact: false, Database: "db0", }, }, - // SHOW MEASUREMENT CARDINALITY FROM // + // SHOW MEASUREMENT EXACT CARDINALITY statement { - s: `SHOW MEASUREMENT CARDINALITY FROM /[cg]pu/`, + s: `SHOW MEASUREMENT EXACT CARDINALITY`, stmt: &influxql.ShowMeasurementCardinalityStatement{ + Exact: true, + }, + }, + + // SHOW MEASUREMENT EXACT CARDINALITY FROM cpu + { + s: `SHOW MEASUREMENT EXACT CARDINALITY FROM cpu`, + stmt: &influxql.ShowMeasurementCardinalityStatement{ + Exact: true, + Sources: []influxql.Source{&influxql.Measurement{Name: "cpu"}}, + }, + }, + + // SHOW MEASUREMENT EXACT CARDINALITY ON db0 + { + s: `SHOW MEASUREMENT EXACT CARDINALITY ON db0`, + stmt: &influxql.ShowMeasurementCardinalityStatement{ + Exact: true, + Database: "db0", + }, + }, + + // SHOW MEASUREMENT EXACT CARDINALITY FROM // + { + s: `SHOW MEASUREMENT EXACT CARDINALITY FROM /[cg]pu/`, + stmt: &influxql.ShowMeasurementCardinalityStatement{ + Exact: true, Sources: []influxql.Source{ &influxql.Measurement{ Regex: &influxql.RegexLiteral{Val: regexp.MustCompile(`[cg]pu`)}, @@ -1718,22 +1738,25 @@ func TestParser_ParseStatement(t *testing.T) { }, }, - // SHOW MEASUREMENT CARDINALITY with OFFSET 0 + // SHOW MEASUREMENT EXACT CARDINALITY with OFFSET 0 { - s: `SHOW MEASUREMENT CARDINALITY OFFSET 0`, - stmt: &influxql.ShowMeasurementCardinalityStatement{Offset: 0}, - }, - - // SHOW MEASUREMENT CARDINALITY with LIMIT 2 OFFSET 0 - { - s: `SHOW MEASUREMENT CARDINALITY LIMIT 2 OFFSET 0`, - stmt: &influxql.ShowMeasurementCardinalityStatement{Offset: 0, Limit: 2}, - }, - - // SHOW MEASUREMENT CARDINALITY WHERE with ORDER BY and LIMIT - { - s: `SHOW MEASUREMENT CARDINALITY WHERE region = 'order by desc' LIMIT 10`, + s: `SHOW MEASUREMENT EXACT CARDINALITY OFFSET 0`, stmt: &influxql.ShowMeasurementCardinalityStatement{ + Exact: true, Offset: 0}, + }, + + // SHOW MEASUREMENT EXACT CARDINALITY with LIMIT 2 OFFSET 0 + { + s: `SHOW MEASUREMENT EXACT CARDINALITY LIMIT 2 OFFSET 0`, + stmt: &influxql.ShowMeasurementCardinalityStatement{ + Exact: true, Offset: 0, Limit: 2}, + }, + + // SHOW MEASUREMENT EXACT CARDINALITY WHERE with ORDER BY and LIMIT + { + s: `SHOW MEASUREMENT EXACT CARDINALITY WHERE region = 'order by desc' LIMIT 10`, + stmt: &influxql.ShowMeasurementCardinalityStatement{ + Exact: true, Condition: &influxql.BinaryExpr{ Op: influxql.EQ, LHS: &influxql.VarRef{Val: "region"}, diff --git a/influxql/statement_rewriter.go b/influxql/statement_rewriter.go index 0a61e16204..dccf649dbf 100644 --- a/influxql/statement_rewriter.go +++ b/influxql/statement_rewriter.go @@ -111,9 +111,13 @@ func rewriteShowMeasurementsStatement(stmt *ShowMeasurementsStatement) (Statemen } func rewriteShowMeasurementCardinalityStatement(stmt *ShowMeasurementCardinalityStatement) (Statement, error) { + if !stmt.Exact { // Use cardinality estimation and don't rewrite. + return stmt, nil + } + // Check for time in WHERE clause (not supported). if HasTimeExpr(stmt.Condition) { - return nil, errors.New("SHOW MEASUREMENT CARDINALITY doesn't support time in WHERE clause") + return nil, errors.New("SHOW MEASUREMENT EXACT CARDINALITY doesn't support time in WHERE clause") } // Use all measurements, if zero. diff --git a/tests/server_test.go b/tests/server_test.go index a6562ba176..db903799d3 100644 --- a/tests/server_test.go +++ b/tests/server_test.go @@ -7462,43 +7462,54 @@ func TestServer_Query_ShowMeasurementCardinality(t *testing.T) { &Query{ name: `show measurement cardinality`, command: "SHOW MEASUREMENT CARDINALITY", + exp: `{"results":[{"statement_id":0,"series":[{"columns":["cardinality"],"values":[[3]]}]}]}`, + params: url.Values{"db": []string{"db0"}}, + }, + &Query{ + name: `show measurement cardinality on db0`, + command: "SHOW MEASUREMENT CARDINALITY ON db0", + exp: `{"results":[{"statement_id":0,"series":[{"columns":["cardinality"],"values":[[3]]}]}]}`, + }, + &Query{ + name: `show measurement exact cardinality`, + command: "SHOW MEASUREMENT EXACT CARDINALITY", exp: `{"results":[{"statement_id":0,"series":[{"columns":["count"],"values":[[3]]}]}]}`, params: url.Values{"db": []string{"db0"}}, }, &Query{ - name: `show measurement cardinality using FROM`, - command: "SHOW MEASUREMENT CARDINALITY FROM cpu", + name: `show measurement exact cardinality using FROM`, + command: "SHOW MEASUREMENT EXACT CARDINALITY FROM cpu", exp: `{"results":[{"statement_id":0,"series":[{"columns":["count"],"values":[[1]]}]}]}`, params: url.Values{"db": []string{"db0"}}, }, &Query{ - name: `show measurement cardinality using FROM and regex`, - command: "SHOW MEASUREMENT CARDINALITY FROM /[cg]pu/", + name: `show measurement exact cardinality using FROM and regex`, + command: "SHOW MEASUREMENT EXACT CARDINALITY FROM /[cg]pu/", exp: `{"results":[{"statement_id":0,"series":[{"columns":["count"],"values":[[2]]}]}]}`, params: url.Values{"db": []string{"db0"}}, }, &Query{ - name: `show measurement cardinality using FROM and regex - no matches`, - command: "SHOW MEASUREMENT CARDINALITY FROM /.*zzzzz.*/", + name: `show measurement exact cardinality using FROM and regex - no matches`, + command: "SHOW MEASUREMENT EXACT CARDINALITY FROM /.*zzzzz.*/", exp: `{"results":[{"statement_id":0}]}`, params: url.Values{"db": []string{"db0"}}, }, &Query{ - name: `show measurement cardinality where tag matches regular expression`, - command: "SHOW MEASUREMENT CARDINALITY WHERE region =~ /ca.*/", + name: `show measurement exact cardinality where tag matches regular expression`, + command: "SHOW MEASUREMENT EXACT CARDINALITY WHERE region =~ /ca.*/", exp: `{"results":[{"statement_id":0,"series":[{"columns":["count"],"values":[[2]]}]}]}`, params: url.Values{"db": []string{"db0"}}, }, &Query{ - name: `show measurement cardinality where tag does not match a regular expression`, - command: "SHOW MEASUREMENT CARDINALITY WHERE region !~ /ca.*/", + name: `show measurement exact cardinality where tag does not match a regular expression`, + command: "SHOW MEASUREMENT EXACT CARDINALITY WHERE region !~ /ca.*/", exp: `{"results":[{"statement_id":0,"series":[{"columns":["count"],"values":[[2]]}]}]}`, params: url.Values{"db": []string{"db0"}}, }, &Query{ - name: `show measurement cardinality with time in WHERE clauses errors`, - command: `SHOW MEASUREMENT CARDINALITY WHERE time > now() - 1h`, - exp: `{"results":[{"statement_id":0,"error":"SHOW MEASUREMENT CARDINALITY doesn't support time in WHERE clause"}]}`, + name: `show measurement exact cardinality with time in WHERE clauses errors`, + command: `SHOW MEASUREMENT EXACT CARDINALITY WHERE time > now() - 1h`, + exp: `{"results":[{"statement_id":0,"error":"SHOW MEASUREMENT EXACT CARDINALITY doesn't support time in WHERE clause"}]}`, params: url.Values{"db": []string{"db0"}}, }, }...)