diff --git a/influxql/ast.go b/influxql/ast.go index 467d030595..9bb5731aaf 100644 --- a/influxql/ast.go +++ b/influxql/ast.go @@ -50,6 +50,11 @@ func (_ Statements) node() {} func (_ *SelectStatement) node() {} func (_ *DeleteStatement) node() {} func (_ *ListSeriesStatement) node() {} +func (_ *ListMeasurementsStatement) node() {} +func (_ *ListTagKeysStatement) node() {} +func (_ *ListTagValuesStatement) node() {} +func (_ *ListFieldKeysStatement) node() {} +func (_ *ListFieldValuesStatement) node() {} func (_ *DropSeriesStatement) node() {} func (_ *ListContinuousQueriesStatement) node() {} func (_ *CreateContinuousQueryStatement) node() {} @@ -108,6 +113,11 @@ func (_ *DropSeriesStatement) stmt() {} func (_ *ListContinuousQueriesStatement) stmt() {} func (_ *CreateContinuousQueryStatement) stmt() {} func (_ *DropContinuousQueryStatement) stmt() {} +func (_ *ListMeasurementsStatement) stmt() {} +func (_ *ListTagKeysStatement) stmt() {} +func (_ *ListTagValuesStatement) stmt() {} +func (_ *ListFieldKeysStatement) stmt() {} +func (_ *ListFieldValuesStatement) stmt() {} // Expr represents an expression that can be evaluated to a value. type Expr interface { @@ -203,13 +213,13 @@ func (s *SelectStatement) String() string { _, _ = buf.WriteString(" GROUP BY ") _, _ = buf.WriteString(s.Dimensions.String()) } - if s.Limit > 0 { - _, _ = fmt.Fprintf(&buf, " LIMIT %d", s.Limit) - } if len(s.SortFields) > 0 { _, _ = buf.WriteString(" ORDER BY ") _, _ = buf.WriteString(s.SortFields.String()) } + if s.Limit > 0 { + _, _ = fmt.Fprintf(&buf, " LIMIT %d", s.Limit) + } return buf.String() } @@ -376,16 +386,33 @@ type ListSeriesStatement struct{ // An expression evaluated on a series name or tag. Condition Expr + // Fields to sort results by + SortFields SortFields + // Maximum number of rows to be returned. // Unlimited if zero. Limit int - - // Fields to sort results by - SortFields SortFields } // String returns a string representation of the list series statement. -func (s *ListSeriesStatement) String() string { return "LIST SERIES" } +func (s *ListSeriesStatement) String() string { + var buf bytes.Buffer + _, _ = buf.WriteString("LIST SERIES") + + if s.Condition != nil { + _, _ = buf.WriteString(" WHERE ") + _, _ = buf.WriteString(s.Condition.String()) + } + if len(s.SortFields) > 0 { + _, _ = buf.WriteString(" ORDER BY ") + _, _ = buf.WriteString(s.SortFields.String()) + } + if s.Limit > 0 { + _, _ = buf.WriteString(" LIMIT ") + _, _ = buf.WriteString(strconv.Itoa(s.Limit)) + } + return buf.String() +} // DropSeriesStatement represents a command for removing a series from the database. type DropSeriesStatement struct { @@ -423,6 +450,198 @@ func (s *DropContinuousQueryStatement) String() string { return fmt.Sprintf("DROP CONTINUOUS QUERY %s", s.Name) } +// ListMeasurementsStatement represents a command for listing measurements. +type ListMeasurementsStatement struct { + // An expression evaluated on data point. + Condition Expr + + // Fields to sort results by + SortFields SortFields + + // Maximum number of rows to be returned. + // Unlimited if zero. + Limit int +} + +// String returns a string representation of the statement. +func (s *ListMeasurementsStatement) String() string { + var buf bytes.Buffer + _, _ = buf.WriteString("LIST MEASUREMENTS") + + if s.Condition != nil { + _, _ = buf.WriteString(" WHERE ") + _, _ = buf.WriteString(s.Condition.String()) + } + if len(s.SortFields) > 0 { + _, _ = buf.WriteString(" ORDER BY ") + _, _ = buf.WriteString(s.SortFields.String()) + } + if s.Limit > 0 { + _, _ = buf.WriteString(" LIMIT ") + _, _ = buf.WriteString(strconv.Itoa(s.Limit)) + } + return buf.String() +} + +// ListTagKeysStatement represents a command for listing tag keys. +type ListTagKeysStatement struct { + // Data source that fields are extracted from. + Source Source + + // An expression evaluated on data point. + Condition Expr + + // Fields to sort results by + SortFields SortFields + + // Maximum number of rows to be returned. + // Unlimited if zero. + Limit int +} + +// String returns a string representation of the statement. +func (s *ListTagKeysStatement) String() string { + var buf bytes.Buffer + _, _ = buf.WriteString("LIST TAG KEYS") + + if s.Source != nil { + _, _ = buf.WriteString(" FROM ") + _, _ = buf.WriteString(s.Source.String()) + } + if s.Condition != nil { + _, _ = buf.WriteString(" WHERE ") + _, _ = buf.WriteString(s.Condition.String()) + } + if len(s.SortFields) > 0 { + _, _ = buf.WriteString(" ORDER BY ") + _, _ = buf.WriteString(s.SortFields.String()) + } + if s.Limit > 0 { + _, _ = buf.WriteString(" LIMIT ") + _, _ = buf.WriteString(strconv.Itoa(s.Limit)) + } + return buf.String() +} + +// ListTagValuesStatement represents a command for listing tag values. +type ListTagValuesStatement struct { + // Data source that fields are extracted from. + Source Source + + // An expression evaluated on data point. + Condition Expr + + // Fields to sort results by + SortFields SortFields + + // Maximum number of rows to be returned. + // Unlimited if zero. + Limit int +} + +// String returns a string representation of the statement. +func (s *ListTagValuesStatement) String() string { + var buf bytes.Buffer + _, _ = buf.WriteString("LIST TAG VALUES") + + if s.Source != nil { + _, _ = buf.WriteString(" FROM ") + _, _ = buf.WriteString(s.Source.String()) + } + if s.Condition != nil { + _, _ = buf.WriteString(" WHERE ") + _, _ = buf.WriteString(s.Condition.String()) + } + if len(s.SortFields) > 0 { + _, _ = buf.WriteString(" ORDER BY ") + _, _ = buf.WriteString(s.SortFields.String()) + } + if s.Limit > 0 { + _, _ = buf.WriteString(" LIMIT ") + _, _ = buf.WriteString(strconv.Itoa(s.Limit)) + } + return buf.String() +} + +// ListFieldKeyStatement represents a command for listing field keys. +type ListFieldKeysStatement struct { + // Data source that fields are extracted from. + Source Source + + // An expression evaluated on data point. + Condition Expr + + // Fields to sort results by + SortFields SortFields + + // Maximum number of rows to be returned. + // Unlimited if zero. + Limit int +} + +// String returns a string representation of the statement. +func (s *ListFieldKeysStatement) String() string { + var buf bytes.Buffer + _, _ = buf.WriteString("LIST FIELD KEYS") + + if s.Source != nil { + _, _ = buf.WriteString(" FROM ") + _, _ = buf.WriteString(s.Source.String()) + } + if s.Condition != nil { + _, _ = buf.WriteString(" WHERE ") + _, _ = buf.WriteString(s.Condition.String()) + } + if len(s.SortFields) > 0 { + _, _ = buf.WriteString(" ORDER BY ") + _, _ = buf.WriteString(s.SortFields.String()) + } + if s.Limit > 0 { + _, _ = buf.WriteString(" LIMIT ") + _, _ = buf.WriteString(strconv.Itoa(s.Limit)) + } + return buf.String() +} + +// ListFieldValuesStatement represents a command for listing field values. +type ListFieldValuesStatement struct { + // Data source that fields are extracted from. + Source Source + + // An expression evaluated on data point. + Condition Expr + + // Fields to sort results by + SortFields SortFields + + // Maximum number of rows to be returned. + // Unlimited if zero. + Limit int +} + +// String returns a string representation of the statement. +func (s *ListFieldValuesStatement) String() string { + var buf bytes.Buffer + _, _ = buf.WriteString("LIST FIELD VALUES") + + if s.Source != nil { + _, _ = buf.WriteString(" FROM ") + _, _ = buf.WriteString(s.Source.String()) + } + if s.Condition != nil { + _, _ = buf.WriteString(" WHERE ") + _, _ = buf.WriteString(s.Condition.String()) + } + if len(s.SortFields) > 0 { + _, _ = buf.WriteString(" ORDER BY ") + _, _ = buf.WriteString(s.SortFields.String()) + } + if s.Limit > 0 { + _, _ = buf.WriteString(" LIMIT ") + _, _ = buf.WriteString(strconv.Itoa(s.Limit)) + } + return buf.String() +} // Fields represents a list of fields. type Fields []*Field diff --git a/influxql/parser.go b/influxql/parser.go index 35fd28dada..d9bab724b6 100644 --- a/influxql/parser.go +++ b/influxql/parser.go @@ -64,6 +64,24 @@ func (p *Parser) ParseStatement() (Statement, error) { return p.parseListSeriesStatement() } else if tok == CONTINUOUS { return p.parseListContinuousQueriesStatement() + } else if tok == MEASUREMENTS { + return p.parseListMeasurementsStatement() + } else if tok == TAG { + if tok, pos, lit := p.scanIgnoreWhitespace(); tok == KEYS { + return p.parseListTagKeysStatement() + } else if tok == VALUES { + return p.parseListTagValuesStatement() + } else { + return nil, newParseError(tokstr(tok, lit), []string{"KEYS", "VALUES"}, pos) + } + } else if tok == FIELD { + if tok, pos, lit := p.scanIgnoreWhitespace(); tok == KEYS { + return p.parseListFieldKeysStatement() + } else if tok == VALUES { + return p.parseListFieldValuesStatement() + } else { + return nil, newParseError(tokstr(tok, lit), []string{"KEYS", "VALUES"}, pos) + } } else { return nil, newParseError(tokstr(tok, lit), []string{"SERIES", "CONTINUOUS"}, pos) } @@ -211,6 +229,236 @@ func (p *Parser) parseListSeriesStatement() (*ListSeriesStatement, error) { return stmt, nil } +// parseListMeasurementsStatement parses a string and returns a ListSeriesStatement. +// This function assumes the "LIST MEASUREMENTS" tokens have already been consumed. +func (p *Parser) parseListMeasurementsStatement() (*ListMeasurementsStatement, error) { + stmt := &ListMeasurementsStatement{} + + // Parse condition: "WHERE EXPR". + condition, err := p.parseCondition() + if err != nil { + return nil, err + } + stmt.Condition = condition + + // Parse sort: "ORDER BY FIELD+". + if tok, _, _ := p.scanIgnoreWhitespace(); tok == ORDER { + if tok, pos, lit := p.scanIgnoreWhitespace(); tok != BY { + return nil, newParseError(tokstr(tok, lit), []string{"BY"}, pos) + } + + sortFields, err := p.parseSortFields() + if err != nil { + return nil, err + } + + stmt.SortFields = sortFields + } else { + p.unscan() + } + + // Parse limit: "LIMIT INT". + limit, err := p.parseLimit() + if err != nil { + return nil, err + } + stmt.Limit = limit + + return stmt, nil +} + +// parseListTagKeysStatement parses a string and returns a ListSeriesStatement. +// This function assumes the "LIST TAG KEYS" tokens have already been consumed. +func (p *Parser) parseListTagKeysStatement() (*ListTagKeysStatement, error) { + stmt := &ListTagKeysStatement{} + + // Parse source. + if tok, pos, lit := p.scanIgnoreWhitespace(); tok != FROM { + return nil, newParseError(tokstr(tok, lit), []string{"FROM"}, pos) + } + source, err := p.parseSource() + if err != nil { + return nil, err + } + stmt.Source = source + + // Parse condition: "WHERE EXPR". + condition, err := p.parseCondition() + if err != nil { + return nil, err + } + stmt.Condition = condition + + // Parse sort: "ORDER BY FIELD+". + if tok, _, _ := p.scanIgnoreWhitespace(); tok == ORDER { + if tok, pos, lit := p.scanIgnoreWhitespace(); tok != BY { + return nil, newParseError(tokstr(tok, lit), []string{"BY"}, pos) + } + + sortFields, err := p.parseSortFields() + if err != nil { + return nil, err + } + + stmt.SortFields = sortFields + } else { + p.unscan() + } + + // Parse limit: "LIMIT INT". + limit, err := p.parseLimit() + if err != nil { + return nil, err + } + stmt.Limit = limit + + return stmt, nil +} + +// parseListTagValuesStatement parses a string and returns a ListSeriesStatement. +// This function assumes the "LIST TAG VALUES" tokens have already been consumed. +func (p *Parser) parseListTagValuesStatement() (*ListTagValuesStatement, error) { + stmt := &ListTagValuesStatement{} + + // Parse source. + if tok, pos, lit := p.scanIgnoreWhitespace(); tok != FROM { + return nil, newParseError(tokstr(tok, lit), []string{"FROM"}, pos) + } + source, err := p.parseSource() + if err != nil { + return nil, err + } + stmt.Source = source + + // Parse condition: "WHERE EXPR". + condition, err := p.parseCondition() + if err != nil { + return nil, err + } + stmt.Condition = condition + + // Parse sort: "ORDER BY FIELD+". + if tok, _, _ := p.scanIgnoreWhitespace(); tok == ORDER { + if tok, pos, lit := p.scanIgnoreWhitespace(); tok != BY { + return nil, newParseError(tokstr(tok, lit), []string{"BY"}, pos) + } + + sortFields, err := p.parseSortFields() + if err != nil { + return nil, err + } + + stmt.SortFields = sortFields + } else { + p.unscan() + } + + // Parse limit: "LIMIT INT". + limit, err := p.parseLimit() + if err != nil { + return nil, err + } + stmt.Limit = limit + + return stmt, nil +} + +// parseListFieldKeysStatement parses a string and returns a ListSeriesStatement. +// This function assumes the "LIST FIELD KEYS" tokens have already been consumed. +func (p *Parser) parseListFieldKeysStatement() (*ListFieldKeysStatement, error) { + stmt := &ListFieldKeysStatement{} + + // Parse source. + if tok, pos, lit := p.scanIgnoreWhitespace(); tok != FROM { + return nil, newParseError(tokstr(tok, lit), []string{"FROM"}, pos) + } + source, err := p.parseSource() + if err != nil { + return nil, err + } + stmt.Source = source + + // Parse condition: "WHERE EXPR". + condition, err := p.parseCondition() + if err != nil { + return nil, err + } + stmt.Condition = condition + + // Parse sort: "ORDER BY FIELD+". + if tok, _, _ := p.scanIgnoreWhitespace(); tok == ORDER { + if tok, pos, lit := p.scanIgnoreWhitespace(); tok != BY { + return nil, newParseError(tokstr(tok, lit), []string{"BY"}, pos) + } + + sortFields, err := p.parseSortFields() + if err != nil { + return nil, err + } + + stmt.SortFields = sortFields + } else { + p.unscan() + } + + // Parse limit: "LIMIT INT". + limit, err := p.parseLimit() + if err != nil { + return nil, err + } + stmt.Limit = limit + + return stmt, nil +} + +// parseListFieldValuesStatement parses a string and returns a ListSeriesStatement. +// This function assumes the "LIST FIELD VALUES" tokens have already been consumed. +func (p *Parser) parseListFieldValuesStatement() (*ListFieldValuesStatement, error) { + stmt := &ListFieldValuesStatement{} + + // Parse source. + if tok, pos, lit := p.scanIgnoreWhitespace(); tok != FROM { + return nil, newParseError(tokstr(tok, lit), []string{"FROM"}, pos) + } + source, err := p.parseSource() + if err != nil { + return nil, err + } + stmt.Source = source + + // Parse condition: "WHERE EXPR". + condition, err := p.parseCondition() + if err != nil { + return nil, err + } + stmt.Condition = condition + + // Parse sort: "ORDER BY FIELD+". + if tok, _, _ := p.scanIgnoreWhitespace(); tok == ORDER { + if tok, pos, lit := p.scanIgnoreWhitespace(); tok != BY { + return nil, newParseError(tokstr(tok, lit), []string{"BY"}, pos) + } + + sortFields, err := p.parseSortFields() + if err != nil { + return nil, err + } + + stmt.SortFields = sortFields + } else { + p.unscan() + } + + // Parse limit: "LIMIT INT". + limit, err := p.parseLimit() + if err != nil { + return nil, err + } + stmt.Limit = limit + + return stmt, nil +} + // parseDropSeriesStatement parses a string and returns a DropSeriesStatement. // This function assumes the "DROP SERIES" tokens have already been consumed. func (p *Parser) parseDropSeriesStatement() (*DropSeriesStatement, error) { diff --git a/influxql/parser_test.go b/influxql/parser_test.go index 5e38a70768..34f0866ce8 100644 --- a/influxql/parser_test.go +++ b/influxql/parser_test.go @@ -172,6 +172,100 @@ func TestParser_ParseStatement(t *testing.T) { }, }, + // LIST MEASUREMENTS WHERE with ORDER BY and LIMIT + { + s: `LIST MEASUREMENTS WHERE region = 'uswest' ORDER BY ASC, field1, field2 DESC LIMIT 10`, + stmt: &influxql.ListMeasurementsStatement{ + Condition: &influxql.BinaryExpr{ + Op: influxql.EQ, + LHS: &influxql.VarRef{Val: "region"}, + RHS: &influxql.StringLiteral{Val: "uswest"}, + }, + SortFields: influxql.SortFields{ + &influxql.SortField{Ascending: true,}, + &influxql.SortField{Name: "field1",}, + &influxql.SortField{Name: "field2",}, + }, + Limit: 10, + }, + }, + + // LIST TAG KEYS + { + s: `LIST TAG KEYS FROM src WHERE region = 'uswest' ORDER BY ASC, field1, field2 DESC LIMIT 10`, + stmt: &influxql.ListTagKeysStatement{ + Source: &influxql.Series{Name: "src"}, + Condition: &influxql.BinaryExpr{ + Op: influxql.EQ, + LHS: &influxql.VarRef{Val: "region"}, + RHS: &influxql.StringLiteral{Val: "uswest"}, + }, + SortFields: influxql.SortFields{ + &influxql.SortField{Ascending: true,}, + &influxql.SortField{Name: "field1",}, + &influxql.SortField{Name: "field2",}, + }, + Limit: 10, + }, + }, + + // LIST TAG VALUES + { + s: `LIST TAG VALUES FROM src WHERE region = 'uswest' ORDER BY ASC, field1, field2 DESC LIMIT 10`, + stmt: &influxql.ListTagValuesStatement{ + Source: &influxql.Series{Name: "src"}, + Condition: &influxql.BinaryExpr{ + Op: influxql.EQ, + LHS: &influxql.VarRef{Val: "region"}, + RHS: &influxql.StringLiteral{Val: "uswest"}, + }, + SortFields: influxql.SortFields{ + &influxql.SortField{Ascending: true,}, + &influxql.SortField{Name: "field1",}, + &influxql.SortField{Name: "field2",}, + }, + Limit: 10, + }, + }, + + // LIST FIELD KEYS + { + s: `LIST FIELD KEYS FROM src WHERE region = 'uswest' ORDER BY ASC, field1, field2 DESC LIMIT 10`, + stmt: &influxql.ListFieldKeysStatement{ + Source: &influxql.Series{Name: "src"}, + Condition: &influxql.BinaryExpr{ + Op: influxql.EQ, + LHS: &influxql.VarRef{Val: "region"}, + RHS: &influxql.StringLiteral{Val: "uswest"}, + }, + SortFields: influxql.SortFields{ + &influxql.SortField{Ascending: true,}, + &influxql.SortField{Name: "field1",}, + &influxql.SortField{Name: "field2",}, + }, + Limit: 10, + }, + }, + + // LIST FIELD VALUES + { + s: `LIST FIELD VALUES FROM src WHERE region = 'uswest' ORDER BY ASC, field1, field2 DESC LIMIT 10`, + stmt: &influxql.ListFieldValuesStatement{ + Source: &influxql.Series{Name: "src"}, + Condition: &influxql.BinaryExpr{ + Op: influxql.EQ, + LHS: &influxql.VarRef{Val: "region"}, + RHS: &influxql.StringLiteral{Val: "uswest"}, + }, + SortFields: influxql.SortFields{ + &influxql.SortField{Ascending: true,}, + &influxql.SortField{Name: "field1",}, + &influxql.SortField{Name: "field2",}, + }, + Limit: 10, + }, + }, + // DROP SERIES statement { s: `DROP SERIES myseries`,