From f23417fa5a9fa22b6ed91acc63c959d5a698f7bf Mon Sep 17 00:00:00 2001 From: David Norton Date: Tue, 6 Jan 2015 16:30:27 -0500 Subject: [PATCH 1/9] influxql: add CREATE CONTINUOUS QUERY, : separator --- influxql/ast.go | 52 ++++++++++++++++-- influxql/parser.go | 110 +++++++++++++++++++++++++++++++-------- influxql/parser_test.go | 28 ++++++++-- influxql/scanner.go | 10 ++++ influxql/scanner_test.go | 3 ++ influxql/token.go | 7 +++ 6 files changed, 183 insertions(+), 27 deletions(-) diff --git a/influxql/ast.go b/influxql/ast.go index 5ce7cde859..ef2b4a0ea0 100644 --- a/influxql/ast.go +++ b/influxql/ast.go @@ -88,6 +88,7 @@ func (_ *ParenExpr) node() {} func (_ *Wildcard) node() {} func (_ SortFields) node() {} func (_ *SortField) node() {} +func (_ *Target) node() {} // Query represents a collection of ordered statements. type Query struct { @@ -411,6 +412,9 @@ type SelectStatement struct { // Expressions returned from the selection. Fields Fields + // Target (destination) for the result of the select. + Target *Target + // Expressions used for grouping the selection. Dimensions Dimensions @@ -433,6 +437,11 @@ func (s *SelectStatement) String() string { var buf bytes.Buffer _, _ = buf.WriteString("SELECT ") _, _ = buf.WriteString(s.Fields.String()) + + if s.Target != nil { + _, _ = buf.WriteString(" ") + _, _ = buf.WriteString(s.Target.String()) + } _, _ = buf.WriteString(" FROM ") _, _ = buf.WriteString(s.Source.String()) if s.Condition != nil { @@ -591,6 +600,38 @@ func MatchSource(src Source, name string) string { return "" } +// Target represents a target (destination) policy, measurment, and DB. +type Target struct { + // Retention policy to write into. + RetentionPolicy string + + // Measurement to write into. + Measurement string + + // Database to write into. + DB string +} + +// String returns a string representation of the Target. +func (t *Target) String() string { + var buf bytes.Buffer + _, _ = buf.WriteString("INTO ") + + if t.RetentionPolicy != "" { + _, _ = buf.WriteString(t.RetentionPolicy) + _, _ = buf.WriteString(".") + } + + _, _ = buf.WriteString(t.Measurement) + + if t.DB != "" { + _, _ = buf.WriteString(" ON ") + _, _ = buf.WriteString(t.DB) + } + + return buf.String() +} + // DeleteStatement represents a command for removing data from the database. type DeleteStatement struct { // Data source that values are removed from. @@ -661,14 +702,19 @@ func (s *ListContinuousQueriesStatement) String() string { return "LIST CONTINUO // CreateContinuousQueriesStatement represents a command for creating a continuous query. type CreateContinuousQueryStatement struct { - Name string + // Name of the continuous query to be created. + Name string + + // Name of the database to create the continuous query on. + DB string + + // Source of data (SELECT statement). Source *SelectStatement - Target string } // String returns a string representation of the statement. func (s *CreateContinuousQueryStatement) String() string { - return fmt.Sprintf("CREATE CONTINUOUS QUERY %s AS %s INTO %s", s.Name, s.Source.String(), s.Target) + return fmt.Sprintf("CREATE CONTINUOUS QUERY %s ON %s BEGIN %s END", s.Name, s.DB, s.Source.String()) } // DropContinuousQueriesStatement represents a command for removing a continuous query. diff --git a/influxql/parser.go b/influxql/parser.go index 3949003cfb..4ffa339021 100644 --- a/influxql/parser.go +++ b/influxql/parser.go @@ -65,7 +65,7 @@ func (p *Parser) ParseStatement() (Statement, error) { tok, pos, lit := p.scanIgnoreWhitespace() switch tok { case SELECT: - return p.parseSelectStatement() + return p.parseSelectStatement(targetNotRequired) case DELETE: return p.parseDeleteStatement() case LIST: @@ -442,7 +442,7 @@ func (p *Parser) parsePrivilege() (Privilege, error) { // parseSelectStatement parses a select string and returns a Statement AST object. // This function assumes the SELECT token has already been consumed. -func (p *Parser) parseSelectStatement() (*SelectStatement, error) { +func (p *Parser) parseSelectStatement(tr targetRequirement) (*SelectStatement, error) { stmt := &SelectStatement{} // Parse fields: "SELECT FIELD+". @@ -452,6 +452,14 @@ func (p *Parser) parseSelectStatement() (*SelectStatement, error) { } stmt.Fields = fields + // Parse target: "INTO" + target, err := p.parseTarget(tr) + if err != nil { + return nil, err + } else if target != nil { + stmt.Target = target + } + // Parse source. if tok, pos, lit := p.scanIgnoreWhitespace(); tok != FROM { return nil, newParseError(tokstr(tok, lit), []string{"FROM"}, pos) @@ -493,6 +501,63 @@ func (p *Parser) parseSelectStatement() (*SelectStatement, error) { return stmt, nil } +// targetRequirement specifies whether or not a target clause is required. +type targetRequirement int + +const ( + targetRequired targetRequirement = iota + targetNotRequired +) + +// parseTarget parses a string and returns a Target. +func (p *Parser) parseTarget(tr targetRequirement) (*Target, error) { + if tok, pos, lit := p.scanIgnoreWhitespace(); tok != INTO { + if tr == targetRequired { + return nil, newParseError(tokstr(tok, lit), []string{"INTO"}, pos) + } + p.unscan() + return nil, nil + } + + // Parse identifier. Could be policy or measurement name. + ident, err := p.parseIdentifier() + if err != nil { + return nil, err + } + + target := &Target{} + + tok, _, _ := p.scanIgnoreWhitespace() + if tok == COLON { + // Previous identifier was retention policy name. + target.RetentionPolicy = ident + + // Parse required measurement. + ident, err = p.parseIdentifier() + if err != nil { + return nil, err + } + } else { + p.unscan() + } + + target.Measurement = ident + + // Parse optional ON. + if tok, _, _ := p.scanIgnoreWhitespace(); tok != ON { + p.unscan() + return target, nil + } + + // Found an ON token so parse required identifier. + if ident, err = p.parseIdentifier(); err != nil { + return nil, err + } + target.DB = ident + + return target, nil +} + // parseDeleteStatement parses a delete string and returns a DeleteStatement. // This function assumes the DELETE token has already been consumed. func (p *Parser) parseDeleteStatement() (*DeleteStatement, error) { @@ -771,38 +836,41 @@ func (p *Parser) parseCreateContinuousQueryStatement() (*CreateContinuousQuerySt } // Read the id of the query to create. - tok, pos, lit := p.scanIgnoreWhitespace() - if tok != IDENT && tok != STRING { - return nil, newParseError(tokstr(tok, lit), []string{"identifier", "string"}, pos) + ident, err := p.parseIdentifier() + if err != nil { + return nil, err } - stmt.Name = lit + stmt.Name = ident - // Expect an "AS SELECT" keyword. - if tok, pos, lit := p.scanIgnoreWhitespace(); tok != AS { - return nil, newParseError(tokstr(tok, lit), []string{"AS"}, pos) + // Expect an "ON" keyword. + if tok, pos, lit := p.scanIgnoreWhitespace(); tok != ON { + return nil, newParseError(tokstr(tok, lit), []string{"ON"}, pos) } - if tok, pos, lit := p.scanIgnoreWhitespace(); tok != SELECT { - return nil, newParseError(tokstr(tok, lit), []string{"SELECT"}, pos) + + // Read the name of the database to create the query on. + if ident, err = p.parseIdentifier(); err != nil { + return nil, err + } + stmt.DB = ident + + // Expect a "BEGIN SELECT" tokens. + if err := p.parseTokens([]Token{BEGIN, SELECT}); err != nil { + return nil, err } // Read the select statement to be used as the source. - source, err := p.parseSelectStatement() + source, err := p.parseSelectStatement(targetRequired) if err != nil { return nil, err } stmt.Source = source - // Expect an INTO keyword. - if tok, pos, lit := p.scanIgnoreWhitespace(); tok != INTO { - return nil, newParseError(tokstr(tok, lit), []string{"INTO"}, pos) + // Expect a "END" keyword. + if tok, pos, lit := p.scanIgnoreWhitespace(); tok != END { + return nil, newParseError(tokstr(tok, lit), []string{"END"}, pos) } - // Read the target of the query. - tok, pos, lit = p.scanIgnoreWhitespace() - if tok != IDENT && tok != STRING { - return nil, newParseError(tokstr(tok, lit), []string{"identifier", "string"}, pos) - } - stmt.Target = lit + fmt.Println(stmt.String()) return stmt, nil } diff --git a/influxql/parser_test.go b/influxql/parser_test.go index fbdaeefec3..d9cc875512 100644 --- a/influxql/parser_test.go +++ b/influxql/parser_test.go @@ -277,16 +277,34 @@ func TestParser_ParseStatement(t *testing.T) { stmt: &influxql.ListContinuousQueriesStatement{}, }, - // CREATE CONTINUOUS QUERY statement + // CREATE CONTINUOUS QUERY ... INTO { - s: `CREATE CONTINUOUS QUERY myquery AS SELECT count() FROM myseries INTO foo`, + s: `CREATE CONTINUOUS QUERY myquery ON testdb BEGIN SELECT count() INTO measure1 FROM myseries END`, stmt: &influxql.CreateContinuousQueryStatement{ Name: "myquery", + DB: "testdb", Source: &influxql.SelectStatement{ Fields: influxql.Fields{&influxql.Field{Expr: &influxql.Call{Name: "count"}}}, + Target: &influxql.Target{Measurement: "measure1"}, + Source: &influxql.Measurement{Name: "myseries"}, + }, + }, + }, + + // CREATE CONTINUOUS QUERY ... INTO . + { + s: `CREATE CONTINUOUS QUERY myquery ON testdb BEGIN SELECT count() INTO "1h.policy1":cpu.load FROM myseries END`, + stmt: &influxql.CreateContinuousQueryStatement{ + Name: "myquery", + DB: "testdb", + Source: &influxql.SelectStatement{ + Fields: influxql.Fields{&influxql.Field{Expr: &influxql.Call{Name: "count"}}}, + Target: &influxql.Target{ + RetentionPolicy: "1h.policy1", + Measurement: "cpu.load", + }, Source: &influxql.Measurement{Name: "myseries"}, }, - Target: "foo", }, }, @@ -545,6 +563,10 @@ func TestParser_ParseStatement(t *testing.T) { t.Errorf("%d. %q: error mismatch:\n exp=%s\n got=%s\n\n", i, tt.s, tt.err, err) } else if tt.err == "" && !reflect.DeepEqual(tt.stmt, stmt) { t.Errorf("%d. %q\n\nstmt mismatch:\n\nexp=%#v\n\ngot=%#v\n\n", i, tt.s, tt.stmt, stmt) + exp := tt.stmt.(*influxql.CreateContinuousQueryStatement).Source.Target + got := stmt.(*influxql.CreateContinuousQueryStatement).Source.Target + t.Errorf("exp.String() = %#v\n", *exp) + t.Errorf("got.String() = %#v\n", *got) } } } diff --git a/influxql/scanner.go b/influxql/scanner.go index 64bdb65519..3894d7584c 100644 --- a/influxql/scanner.go +++ b/influxql/scanner.go @@ -42,6 +42,14 @@ func (s *Scanner) Scan() (tok Token, pos Pos, lit string) { return EOF, pos, "" case '"', '\'': return s.scanString() + // case '.': + // ch1, _ := s.r.read() + // s.r.unread() + // if isDigit(ch1) { + // s.r.unread() + // return s.scanNumber() + // } + // return DOT, pos, "" case '.', '+', '-': return s.scanNumber() case '*': @@ -72,6 +80,8 @@ func (s *Scanner) Scan() (tok Token, pos Pos, lit string) { return COMMA, pos, "" case ';': return SEMICOLON, pos, "" + case ':': + return COLON, pos, "" } return ILLEGAL, pos, string(ch0) diff --git a/influxql/scanner_test.go b/influxql/scanner_test.go index 5d65b72619..7be1cefc93 100644 --- a/influxql/scanner_test.go +++ b/influxql/scanner_test.go @@ -100,6 +100,7 @@ func TestScanner_Scan(t *testing.T) { {s: `ALTER`, tok: influxql.ALTER}, {s: `AS`, tok: influxql.AS}, {s: `ASC`, tok: influxql.ASC}, + {s: `BEGIN`, tok: influxql.BEGIN}, {s: `BY`, tok: influxql.BY}, {s: `CREATE`, tok: influxql.CREATE}, {s: `CONTINUOUS`, tok: influxql.CONTINUOUS}, @@ -107,8 +108,10 @@ func TestScanner_Scan(t *testing.T) { {s: `DEFAULT`, tok: influxql.DEFAULT}, {s: `DELETE`, tok: influxql.DELETE}, {s: `DESC`, tok: influxql.DESC}, + //{s: `DOT`, tok: influxql.DOT}, {s: `DROP`, tok: influxql.DROP}, {s: `DURATION`, tok: influxql.DURATION}, + {s: `END`, tok: influxql.END}, {s: `EXISTS`, tok: influxql.EXISTS}, {s: `EXPLAIN`, tok: influxql.EXPLAIN}, {s: `FIELD`, tok: influxql.FIELD}, diff --git a/influxql/token.go b/influxql/token.go index bed5b043f7..12a7039eea 100644 --- a/influxql/token.go +++ b/influxql/token.go @@ -47,6 +47,8 @@ const ( RPAREN // ) COMMA // , SEMICOLON // ; + COLON // : + //DOT // . keyword_beg // Keywords @@ -54,6 +56,7 @@ const ( ALTER AS ASC + BEGIN BY CREATE CONTINUOUS @@ -63,6 +66,7 @@ const ( DESC DROP DURATION + END EXISTS EXPLAIN FIELD @@ -132,11 +136,13 @@ var tokens = [...]string{ RPAREN: ")", COMMA: ",", SEMICOLON: ";", + COLON: ":", ALL: "ALL", ALTER: "ALTER", AS: "AS", ASC: "ASC", + BEGIN: "BEGIN", BY: "BY", CREATE: "CREATE", CONTINUOUS: "CONTINUOUS", @@ -146,6 +152,7 @@ var tokens = [...]string{ DESC: "DESC", DROP: "DROP", DURATION: "DURATION", + END: "END", EXISTS: "EXISTS", EXPLAIN: "EXPLAIN", FIELD: "FIELD", From 3b649d3e63b1a52fda58f2c809f6ca73940a984b Mon Sep 17 00:00:00 2001 From: David Norton Date: Tue, 6 Jan 2015 17:34:01 -0500 Subject: [PATCH 2/9] influxql: add CREATE CONTINUOUS QUERY (WIP) --- influxql/parser.go | 2 +- influxql/parser_test.go | 2 +- influxql/scanner.go | 20 +++++++++----------- influxql/scanner_test.go | 4 ++-- influxql/token.go | 5 ++--- 5 files changed, 15 insertions(+), 18 deletions(-) diff --git a/influxql/parser.go b/influxql/parser.go index 4ffa339021..8e908ca34f 100644 --- a/influxql/parser.go +++ b/influxql/parser.go @@ -528,7 +528,7 @@ func (p *Parser) parseTarget(tr targetRequirement) (*Target, error) { target := &Target{} tok, _, _ := p.scanIgnoreWhitespace() - if tok == COLON { + if tok == DOT { // Previous identifier was retention policy name. target.RetentionPolicy = ident diff --git a/influxql/parser_test.go b/influxql/parser_test.go index d9cc875512..11534bf449 100644 --- a/influxql/parser_test.go +++ b/influxql/parser_test.go @@ -293,7 +293,7 @@ func TestParser_ParseStatement(t *testing.T) { // CREATE CONTINUOUS QUERY ... INTO . { - s: `CREATE CONTINUOUS QUERY myquery ON testdb BEGIN SELECT count() INTO "1h.policy1":cpu.load FROM myseries END`, + s: `CREATE CONTINUOUS QUERY myquery ON testdb BEGIN SELECT count() INTO "1h.policy1"."cpu.load" FROM myseries END`, stmt: &influxql.CreateContinuousQueryStatement{ Name: "myquery", DB: "testdb", diff --git a/influxql/scanner.go b/influxql/scanner.go index 3894d7584c..0fca2a1dcb 100644 --- a/influxql/scanner.go +++ b/influxql/scanner.go @@ -42,15 +42,15 @@ func (s *Scanner) Scan() (tok Token, pos Pos, lit string) { return EOF, pos, "" case '"', '\'': return s.scanString() - // case '.': - // ch1, _ := s.r.read() - // s.r.unread() - // if isDigit(ch1) { - // s.r.unread() - // return s.scanNumber() - // } - // return DOT, pos, "" - case '.', '+', '-': + case '.': + ch1, _ := s.r.read() + s.r.unread() + if isDigit(ch1) { + s.r.unread() + return s.scanNumber() + } + return DOT, pos, "" + case '+', '-': return s.scanNumber() case '*': return MUL, pos, "" @@ -80,8 +80,6 @@ func (s *Scanner) Scan() (tok Token, pos Pos, lit string) { return COMMA, pos, "" case ';': return SEMICOLON, pos, "" - case ':': - return COLON, pos, "" } return ILLEGAL, pos, string(ch0) diff --git a/influxql/scanner_test.go b/influxql/scanner_test.go index 7be1cefc93..3807d039f5 100644 --- a/influxql/scanner_test.go +++ b/influxql/scanner_test.go @@ -54,6 +54,7 @@ func TestScanner_Scan(t *testing.T) { {s: `)`, tok: influxql.RPAREN}, {s: `,`, tok: influxql.COMMA}, {s: `;`, tok: influxql.SEMICOLON}, + {s: `.`, tok: influxql.DOT}, // Identifiers {s: `foo`, tok: influxql.IDENT, lit: `foo`}, @@ -79,7 +80,7 @@ func TestScanner_Scan(t *testing.T) { {s: `.23`, tok: influxql.NUMBER, lit: `.23`}, {s: `+.23`, tok: influxql.NUMBER, lit: `+.23`}, {s: `-.23`, tok: influxql.NUMBER, lit: `-.23`}, - {s: `.`, tok: influxql.ILLEGAL, lit: `.`}, + //{s: `.`, tok: influxql.ILLEGAL, lit: `.`}, {s: `-.`, tok: influxql.SUB, lit: ``}, {s: `+.`, tok: influxql.ADD, lit: ``}, {s: `10.3s`, tok: influxql.NUMBER, lit: `10.3`}, @@ -108,7 +109,6 @@ func TestScanner_Scan(t *testing.T) { {s: `DEFAULT`, tok: influxql.DEFAULT}, {s: `DELETE`, tok: influxql.DELETE}, {s: `DESC`, tok: influxql.DESC}, - //{s: `DOT`, tok: influxql.DOT}, {s: `DROP`, tok: influxql.DROP}, {s: `DURATION`, tok: influxql.DURATION}, {s: `END`, tok: influxql.END}, diff --git a/influxql/token.go b/influxql/token.go index 12a7039eea..c66ad43058 100644 --- a/influxql/token.go +++ b/influxql/token.go @@ -47,8 +47,7 @@ const ( RPAREN // ) COMMA // , SEMICOLON // ; - COLON // : - //DOT // . + DOT // . keyword_beg // Keywords @@ -136,7 +135,7 @@ var tokens = [...]string{ RPAREN: ")", COMMA: ",", SEMICOLON: ";", - COLON: ":", + DOT: ".", ALL: "ALL", ALTER: "ALTER", From 5ea393f5ac446bf6a218288188b6628bf79d81ef Mon Sep 17 00:00:00 2001 From: David Norton Date: Tue, 6 Jan 2015 19:56:04 -0500 Subject: [PATCH 3/9] influxql: fix scanner bug caused by double unread --- influxql/scanner.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/influxql/scanner.go b/influxql/scanner.go index 0fca2a1dcb..4b8e4d9d95 100644 --- a/influxql/scanner.go +++ b/influxql/scanner.go @@ -46,7 +46,6 @@ func (s *Scanner) Scan() (tok Token, pos Pos, lit string) { ch1, _ := s.r.read() s.r.unread() if isDigit(ch1) { - s.r.unread() return s.scanNumber() } return DOT, pos, "" @@ -241,7 +240,6 @@ func (s *Scanner) scanNumber() (tok Token, pos Pos, lit string) { } s.r.unread() } - return NUMBER, pos, buf.String() } From 0439c032a0baaa7cca90d48f2fe99cad36df8aea Mon Sep 17 00:00:00 2001 From: David Norton Date: Thu, 8 Jan 2015 17:05:17 -0500 Subject: [PATCH 4/9] influxql: add specification (WIP) --- influxql/INFLUXQL.md | 258 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 influxql/INFLUXQL.md diff --git a/influxql/INFLUXQL.md b/influxql/INFLUXQL.md new file mode 100644 index 0000000000..bc4da7b835 --- /dev/null +++ b/influxql/INFLUXQL.md @@ -0,0 +1,258 @@ +# The Influx Query Language Specification + +## Introduction + +This is a reference for the Influx Query Language ("InfluxQL"). + +InfluxQL is a SQL-like query language for interacting with InfluxDB. It was lovingly crafted to feel familiar to those coming from other +SQL or SQL-like environments while providing features specific to storing +and analyzing time series data. + +## Notation + +This specification uses the same notation used by Google's Go programming language, which can be found at http://golang.org. The syntax is specified in Extended Backus-Naur Form ("EBNF"): + +``` +Production = production_name "=" [ Expression ] "." . +Expression = Alternative { "|" Alternative } . +Alternative = Term { Term } . +Term = production_name | token [ "…" token ] | Group | Option | Repetition . +Group = "(" Expression ")" . +Option = "[" Expression "]" . +Repetition = "{" Expression "}" . +``` + +Notation operators in order of increasing precedence: + +``` +| alternation +() grouping +[] option (0 or 1 times) +{} repetition (0 to n times) +``` + +## Characters & Digits + +``` +newline = /* the Unicode code point U+000A */ . +unicode_char = /* an arbitrary Unicode code point except newline */ . +ascii_letter = "A" .. "Z" | "a" .. "z" . +decimal_digit = "0" .. "9" . +``` + +## Database name + +Database names are more limited than other identifiers because they appear in URLs. + +``` +db_name = ascii_letter { ascii_letter | decimal_digit | "_" | "-" } . +``` + +## Identifiers + +``` +identifier = unquoted_identifier | quoted_identifier . +unquoted_identifier = ascii_letter { ascii_letter | decimal_digit | "_" | "." } . +quoted_identifier = `"` unicode_char { unicode_char } `"` . +``` + +## Keywords + +``` +ALL ALTER AS ASC BEGIN +BY CREATE CONTINUOUS DATABASE DEFAULT +DELETE DESC DROP DURATION END +EXISTS EXPLAIN FIELD FROM GRANT +GROUP IF INNER INSERT INTO +KEYS LIMIT LIST MEASUREMENT MEASUREMENTS +ON ORDER PASSWORD POLICY PRIVILEGES +QUERIES QUERY READ REPLICATION RETENTION +EVOKE SELECT SERIES TAG TO +USER VALUES WHERE WITH WRITE +``` + +## Literals + +### Numbers + +``` +int_lit = decimal_lit . +decimal_lit = ( "1" .. "9" ) { decimal_digit } . +float_lit = decimals "." decimals . +decimals = decimal_digit { decimal_digit } . +``` + +### Strings + +``` +string_lit = '"' { unicode_char } '"' . +``` + +### Durations + +``` +duration_lit = decimals duration_unit . +duration_unit = "u" | "µ" | "s" | "h" | "d" | "w" | "ms" . +``` + +## Queries + +A query is composed of one or more statements separated by a semicolon. + +``` +query = statement { ; statement } . + +statement = alter_retention_policy_stmt | + create_continuous_query_stmt | + create_database_stmt | + create_retention_policy_stmt | + create_user_stmt | + delete_stmt | + drop_continuous_query_stmt | + drop_database_stmt | + drop_series_stmt | + drop_user_stmt | + grant_stmt | + list_continuous_queries_stmt | + list_field_key_stmt | + list_field_value_stmt | + list_measurements_stmt | + list_series_stmt | + list_tag_key_stmt | + list_tag_value_stmt | + revoke_stmt | + select_stmt . +``` + +## Statements + +### ALTER RETENTION POLICY + +``` +alter_retention_policy_stmt = "ALTER RETENTION POLICY" policy_name "ON" + db_name retention_policy_option + [ retention_policy_option ] + [ retention_policy_option ] . + +policy_name = identifier . + +retention_policy_option = retention_policy_duration | + retention_policy_replication | + "DEFAULT" . + +retention_policy_duration = "DURATION" duration_lit . +retention_policy_replication = "REPLICATION" int_lit +``` + +#### Examples: + +```sql +-- Set default retention policy for mydb to 1h.cpu. +ALTER RETENTION POLICY "1h.cpu" ON mydb DEFAULT; + +-- Change duration and replication factor. +ALTER RETENTION POLICY policy1 ON somedb DURATION 1h REPLICATION 4 +``` + +### CREATE CONTINUOUS QUERY + +``` +create_continuous_query_stmt = "CREATE CONTINUOUS QUERY" query_name "ON" db_name + "BEGIN" select_stmt "END" . + +query_name = identifier . +``` + +#### Examples: + +```sql +CREATE CONTINUOUS QUERY 10m_event_count +ON db_name +BEGIN + SELECT count(value) + INTO 10m.events + FROM events + GROUP BY time(10m) +END; + +-- this selects from the output of one continuous query and outputs to another series +CREATE CONTINUOUS QUERY 1h_event_count +ON db_name +BEGIN + SELECT sum(count) as count + INTO 1h.events + FROM events + GROUP BY time(1h) +END; +``` + +### CREATE DATABASE + +``` +create_database_stmt = "CREATE DATABASE" db_name +``` + +#### Example: + +```sql +CREATE DATABASE foo +``` + +### CREATE RETENTION POLICY + +``` +create_retention_policy_stmt = "CREATE RETENTION POLICY" policy_name "ON" + db_name retention_policy_duration + retention_policy_replication + [ "DEFAULT" ] . +``` + +#### Examples + +```sql +-- Create a retention policy. +CREATE RETENTION POLICY "10m.events" ON somedb DURATION 10m REPLICATION 2; + +-- Create a retention policy and set it as the default. +CREATE RETENTION POLICY "10m.events" ON somedb DURATION 10m REPLICATION 2 DEFAULT; +``` + +### CREATE USER + +``` +create_user_stmt = "CREATE USER" user_name "WITH PASSWORD" password . +user_name = identifier +password = identifier +``` + +#### Example: + +```sql +CREATE USER jdoe WITH PASSWORD "1337password"; +``` + +### DELETE + +``` +delete_stmt = "DELETE" from_clause where_clause . +``` + +#### Example: + +```sql +DELETE FROM +``` + +## Clauses (sadly, we haven't implemented `SANTA` yet) + +``` +from_clause = "FROM" measurements . + +where_clause = "WHERE" +``` + +## Other + +``` +measurements = +``` From 776e9f2ec254225686eb49303c117bb9761d9414 Mon Sep 17 00:00:00 2001 From: David Norton Date: Thu, 8 Jan 2015 20:54:55 -0500 Subject: [PATCH 5/9] influxql: add GRANT to INFLUXQL.md --- influxql/INFLUXQL.md | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/influxql/INFLUXQL.md b/influxql/INFLUXQL.md index bc4da7b835..a1766f8534 100644 --- a/influxql/INFLUXQL.md +++ b/influxql/INFLUXQL.md @@ -221,8 +221,6 @@ CREATE RETENTION POLICY "10m.events" ON somedb DURATION 10m REPLICATION 2 DEFAUL ``` create_user_stmt = "CREATE USER" user_name "WITH PASSWORD" password . -user_name = identifier -password = identifier ``` #### Example: @@ -243,16 +241,42 @@ delete_stmt = "DELETE" from_clause where_clause . DELETE FROM ``` +### GRANT + +``` +grant_stmt = "GRANT" privilege [ on_clause ] to_clause +``` + +#### Examples: + +```sql +-- grant cluster admin privileges +GRANT ALL TO jdoe; + +-- grant read access to a database +GRANT READ ON mydb TO jdoe; +``` + ## Clauses (sadly, we haven't implemented `SANTA` yet) ``` from_clause = "FROM" measurements . where_clause = "WHERE" + +on_clause = db_name . + +to_clause = user_name . ``` ## Other ``` measurements = + +user_name = identifier . + +password = identifier . + +privilege = "ALL" [ "PRIVILEGES" ] | "READ" | "WRITE" . ``` From 8da9494a46404bf21d04ec1cba99b6a760a516b7 Mon Sep 17 00:00:00 2001 From: David Norton Date: Fri, 9 Jan 2015 09:09:55 -0500 Subject: [PATCH 6/9] influxql: fix comment in parser.go --- influxql/parser.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/influxql/parser.go b/influxql/parser.go index 8e908ca34f..0c0fa33bd7 100644 --- a/influxql/parser.go +++ b/influxql/parser.go @@ -251,7 +251,7 @@ func (p *Parser) parseAlterRetentionPolicyStatement() (*AlterRetentionPolicyStat } stmt.DB = ident - // Loop through option tokens (DURATION, RETENTION, DEFAULT, etc.). + // Loop through option tokens (DURATION, REPLICATION, DEFAULT, etc.). maxNumOptions := 3 Loop: for i := 0; i < maxNumOptions; i++ { From 877f35ae57dc5f7a0cd56b0f6bdcc8eae5aff41e Mon Sep 17 00:00:00 2001 From: David Norton Date: Fri, 9 Jan 2015 09:25:00 -0500 Subject: [PATCH 7/9] influxql: sort node lists in ast.go --- influxql/ast.go | 102 ++++++++++++++++++++++++------------------------ 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/influxql/ast.go b/influxql/ast.go index ef2b4a0ea0..5e5d28c11b 100644 --- a/influxql/ast.go +++ b/influxql/ast.go @@ -47,48 +47,48 @@ type Node interface { func (_ *Query) node() {} 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 (_ *ListContinuousQueriesStatement) node() {} -func (_ *DropSeriesStatement) node() {} -func (_ *DropContinuousQueryStatement) node() {} -func (_ *DropDatabaseStatement) node() {} -func (_ *DropUserStatement) node() {} +func (_ *AlterRetentionPolicyStatement) node() {} func (_ *CreateContinuousQueryStatement) node() {} func (_ *CreateDatabaseStatement) node() {} -func (_ *CreateUserStatement) node() {} func (_ *CreateRetentionPolicyStatement) node() {} +func (_ *CreateUserStatement) node() {} +func (_ *DeleteStatement) node() {} +func (_ *DropContinuousQueryStatement) node() {} +func (_ *DropDatabaseStatement) node() {} +func (_ *DropSeriesStatement) node() {} +func (_ *DropUserStatement) node() {} func (_ *GrantStatement) node() {} +func (_ *ListContinuousQueriesStatement) node() {} +func (_ *ListFieldKeysStatement) node() {} +func (_ *ListFieldValuesStatement) node() {} +func (_ *ListMeasurementsStatement) node() {} +func (_ *ListSeriesStatement) node() {} +func (_ *ListTagKeysStatement) node() {} +func (_ *ListTagValuesStatement) node() {} func (_ *RevokeStatement) node() {} -func (_ *AlterRetentionPolicyStatement) node() {} +func (_ *SelectStatement) node() {} -func (_ Fields) node() {} -func (_ *Field) node() {} -func (_ Dimensions) node() {} +func (_ *BinaryExpr) node() {} +func (_ *BooleanLiteral) node() {} +func (_ *Call) node() {} func (_ *Dimension) node() {} +func (_ Dimensions) node() {} +func (_ *DurationLiteral) node() {} +func (_ *Field) node() {} +func (_ Fields) node() {} +func (_ *Join) node() {} func (_ *Measurement) node() {} func (_ Measurements) node() {} -func (_ *Join) node() {} func (_ *Merge) node() {} -func (_ *VarRef) node() {} -func (_ *Call) node() {} func (_ *NumberLiteral) node() {} -func (_ *StringLiteral) node() {} -func (_ *BooleanLiteral) node() {} -func (_ *TimeLiteral) node() {} -func (_ *DurationLiteral) node() {} -func (_ *BinaryExpr) node() {} func (_ *ParenExpr) node() {} -func (_ *Wildcard) node() {} -func (_ SortFields) node() {} func (_ *SortField) node() {} +func (_ SortFields) node() {} +func (_ *StringLiteral) node() {} func (_ *Target) node() {} +func (_ *TimeLiteral) node() {} +func (_ *VarRef) node() {} +func (_ *Wildcard) node() {} // Query represents a collection of ordered statements. type Query struct { @@ -116,26 +116,26 @@ type Statement interface { stmt() } -func (_ *SelectStatement) stmt() {} -func (_ *DeleteStatement) stmt() {} -func (_ *ListSeriesStatement) stmt() {} -func (_ *DropSeriesStatement) stmt() {} -func (_ *ListContinuousQueriesStatement) stmt() {} +func (_ *AlterRetentionPolicyStatement) stmt() {} func (_ *CreateContinuousQueryStatement) stmt() {} +func (_ *CreateDatabaseStatement) stmt() {} +func (_ *CreateRetentionPolicyStatement) stmt() {} +func (_ *CreateUserStatement) stmt() {} +func (_ *DeleteStatement) stmt() {} func (_ *DropContinuousQueryStatement) stmt() {} -func (_ *ListMeasurementsStatement) stmt() {} -func (_ *ListTagKeysStatement) stmt() {} -func (_ *ListTagValuesStatement) stmt() {} +func (_ *DropDatabaseStatement) stmt() {} +func (_ *DropSeriesStatement) stmt() {} +func (_ *DropUserStatement) stmt() {} +func (_ *GrantStatement) stmt() {} +func (_ *ListContinuousQueriesStatement) stmt() {} func (_ *ListFieldKeysStatement) stmt() {} func (_ *ListFieldValuesStatement) stmt() {} -func (_ *CreateDatabaseStatement) stmt() {} -func (_ *CreateUserStatement) stmt() {} -func (_ *GrantStatement) stmt() {} +func (_ *ListMeasurementsStatement) stmt() {} +func (_ *ListSeriesStatement) stmt() {} +func (_ *ListTagKeysStatement) stmt() {} +func (_ *ListTagValuesStatement) stmt() {} func (_ *RevokeStatement) stmt() {} -func (_ *CreateRetentionPolicyStatement) stmt() {} -func (_ *DropDatabaseStatement) stmt() {} -func (_ *DropUserStatement) stmt() {} -func (_ *AlterRetentionPolicyStatement) stmt() {} +func (_ *SelectStatement) stmt() {} // Expr represents an expression that can be evaluated to a value. type Expr interface { @@ -143,15 +143,15 @@ type Expr interface { expr() } -func (_ *VarRef) expr() {} -func (_ *Call) expr() {} -func (_ *NumberLiteral) expr() {} -func (_ *StringLiteral) expr() {} -func (_ *BooleanLiteral) expr() {} -func (_ *TimeLiteral) expr() {} -func (_ *DurationLiteral) expr() {} func (_ *BinaryExpr) expr() {} +func (_ *BooleanLiteral) expr() {} +func (_ *Call) expr() {} +func (_ *DurationLiteral) expr() {} +func (_ *NumberLiteral) expr() {} func (_ *ParenExpr) expr() {} +func (_ *StringLiteral) expr() {} +func (_ *TimeLiteral) expr() {} +func (_ *VarRef) expr() {} func (_ *Wildcard) expr() {} // Source represents a source of data for a statement. @@ -160,8 +160,8 @@ type Source interface { source() } -func (_ *Measurement) source() {} func (_ *Join) source() {} +func (_ *Measurement) source() {} func (_ *Merge) source() {} // SortField represens a field to sort results by. From 05e2bff6c4275af3359cc7630f973a228d914426 Mon Sep 17 00:00:00 2001 From: David Norton Date: Fri, 9 Jan 2015 10:47:57 -0500 Subject: [PATCH 8/9] influxql: add LIST DATABASES statement --- influxql/INFLUXQL.md | 3 ++- influxql/ast.go | 8 ++++++++ influxql/parser.go | 11 +++++++++-- influxql/parser_test.go | 6 ++++++ influxql/scanner_test.go | 1 + influxql/token.go | 2 ++ 6 files changed, 28 insertions(+), 3 deletions(-) diff --git a/influxql/INFLUXQL.md b/influxql/INFLUXQL.md index a1766f8534..bffc37fe19 100644 --- a/influxql/INFLUXQL.md +++ b/influxql/INFLUXQL.md @@ -103,7 +103,7 @@ A query is composed of one or more statements separated by a semicolon. query = statement { ; statement } . statement = alter_retention_policy_stmt | - create_continuous_query_stmt | + create_continuous_query_stmt | create_database_stmt | create_retention_policy_stmt | create_user_stmt | @@ -114,6 +114,7 @@ statement = alter_retention_policy_stmt | drop_user_stmt | grant_stmt | list_continuous_queries_stmt | + list_databases_stmt | list_field_key_stmt | list_field_value_stmt | list_measurements_stmt | diff --git a/influxql/ast.go b/influxql/ast.go index 5e5d28c11b..54ccf01d9e 100644 --- a/influxql/ast.go +++ b/influxql/ast.go @@ -59,6 +59,7 @@ func (_ *DropSeriesStatement) node() {} func (_ *DropUserStatement) node() {} func (_ *GrantStatement) node() {} func (_ *ListContinuousQueriesStatement) node() {} +func (_ *ListDatabasesStatement) node() {} func (_ *ListFieldKeysStatement) node() {} func (_ *ListFieldValuesStatement) node() {} func (_ *ListMeasurementsStatement) node() {} @@ -128,6 +129,7 @@ func (_ *DropSeriesStatement) stmt() {} func (_ *DropUserStatement) stmt() {} func (_ *GrantStatement) stmt() {} func (_ *ListContinuousQueriesStatement) stmt() {} +func (_ *ListDatabasesStatement) stmt() {} func (_ *ListFieldKeysStatement) stmt() {} func (_ *ListFieldValuesStatement) stmt() {} func (_ *ListMeasurementsStatement) stmt() {} @@ -700,6 +702,12 @@ type ListContinuousQueriesStatement struct{} // String returns a string representation of the list continuous queries statement. func (s *ListContinuousQueriesStatement) String() string { return "LIST CONTINUOUS QUERIES" } +// ListDatabasesStatement represents a command for listing all databases in the cluster. +type ListDatabasesStatement struct{} + +// String returns a string representation of the list databases command. +func (s *ListDatabasesStatement) String() string { return "LIST DATABASES" } + // CreateContinuousQueriesStatement represents a command for creating a continuous query. type CreateContinuousQueryStatement struct { // Name of the continuous query to be created. diff --git a/influxql/parser.go b/influxql/parser.go index 0c0fa33bd7..724fee69a8 100644 --- a/influxql/parser.go +++ b/influxql/parser.go @@ -93,6 +93,8 @@ func (p *Parser) parseListStatement() (Statement, error) { return p.parseListSeriesStatement() } else if tok == CONTINUOUS { return p.parseListContinuousQueriesStatement() + } else if tok == DATABASES { + return p.parseListDatabasesStatement() } else if tok == MEASUREMENTS { return p.parseListMeasurementsStatement() } else if tok == TAG { @@ -825,6 +827,13 @@ func (p *Parser) parseListContinuousQueriesStatement() (*ListContinuousQueriesSt return stmt, nil } +// parseListDatabasesStatement parses a string and returns a ListDatabasesStatement. +// This function assumes the "LIST DATABASE" tokens have already been consumed. +func (p *Parser) parseListDatabasesStatement() (*ListDatabasesStatement, error) { + stmt := &ListDatabasesStatement{} + return stmt, nil +} + // parseCreateContinuousQueriesStatement parses a string and returns a CreateContinuousQueryStatement. // This function assumes the "CREATE CONTINUOUS" tokens have already been consumed. func (p *Parser) parseCreateContinuousQueryStatement() (*CreateContinuousQueryStatement, error) { @@ -870,8 +879,6 @@ func (p *Parser) parseCreateContinuousQueryStatement() (*CreateContinuousQuerySt return nil, newParseError(tokstr(tok, lit), []string{"END"}, pos) } - fmt.Println(stmt.String()) - return stmt, nil } diff --git a/influxql/parser_test.go b/influxql/parser_test.go index 11534bf449..12339d9ad4 100644 --- a/influxql/parser_test.go +++ b/influxql/parser_test.go @@ -147,6 +147,12 @@ func TestParser_ParseStatement(t *testing.T) { }, }, + // LIST DATABASES + { + s: `LIST DATABASES`, + stmt: &influxql.ListDatabasesStatement{}, + }, + // LIST SERIES statement { s: `LIST SERIES`, diff --git a/influxql/scanner_test.go b/influxql/scanner_test.go index 3807d039f5..580aaa5bb8 100644 --- a/influxql/scanner_test.go +++ b/influxql/scanner_test.go @@ -106,6 +106,7 @@ func TestScanner_Scan(t *testing.T) { {s: `CREATE`, tok: influxql.CREATE}, {s: `CONTINUOUS`, tok: influxql.CONTINUOUS}, {s: `DATABASE`, tok: influxql.DATABASE}, + {s: `DATABASES`, tok: influxql.DATABASES}, {s: `DEFAULT`, tok: influxql.DEFAULT}, {s: `DELETE`, tok: influxql.DELETE}, {s: `DESC`, tok: influxql.DESC}, diff --git a/influxql/token.go b/influxql/token.go index c66ad43058..b07f7e8912 100644 --- a/influxql/token.go +++ b/influxql/token.go @@ -60,6 +60,7 @@ const ( CREATE CONTINUOUS DATABASE + DATABASES DEFAULT DELETE DESC @@ -146,6 +147,7 @@ var tokens = [...]string{ CREATE: "CREATE", CONTINUOUS: "CONTINUOUS", DATABASE: "DATABASE", + DATABASES: "DATABASES", DEFAULT: "DEFAULT", DELETE: "DELETE", DESC: "DESC", From ccab32f33e97b1419f205e576b6502a694275995 Mon Sep 17 00:00:00 2001 From: David Norton Date: Fri, 9 Jan 2015 17:50:33 -0500 Subject: [PATCH 9/9] influxql: add WITH to CREATE USER & cleanup --- influxql/INFLUXQL.md | 18 +++++++++++++----- influxql/ast.go | 30 +++++++++++++++++++++--------- influxql/parser.go | 36 ++++++++++++++++++++++++------------ influxql/parser_test.go | 32 +++++++++++++++++++++++--------- 4 files changed, 81 insertions(+), 35 deletions(-) diff --git a/influxql/INFLUXQL.md b/influxql/INFLUXQL.md index bffc37fe19..d4d5db9e8b 100644 --- a/influxql/INFLUXQL.md +++ b/influxql/INFLUXQL.md @@ -221,13 +221,19 @@ CREATE RETENTION POLICY "10m.events" ON somedb DURATION 10m REPLICATION 2 DEFAUL ### CREATE USER ``` -create_user_stmt = "CREATE USER" user_name "WITH PASSWORD" password . +create_user_stmt = "CREATE USER" user_name "WITH PASSWORD" password + [ "WITH ALL PRIVILEGES" ] . ``` -#### Example: +#### Examples: ```sql +-- Create a normal database user. CREATE USER jdoe WITH PASSWORD "1337password"; + +-- Create a cluster admin. +-- Note: Unlike the GRANT statement, the "PRIVILEGES" keyword is required here. +CREATE USER jdoe WITH PASSWORD "1337password" WITH ALL PRIVILEGES; ``` ### DELETE @@ -239,7 +245,7 @@ delete_stmt = "DELETE" from_clause where_clause . #### Example: ```sql -DELETE FROM +DELETE FROM cpu WHERE region = 'uswest' ``` ### GRANT @@ -258,12 +264,12 @@ GRANT ALL TO jdoe; GRANT READ ON mydb TO jdoe; ``` -## Clauses (sadly, we haven't implemented `SANTA` yet) +## Clauses ``` from_clause = "FROM" measurements . -where_clause = "WHERE" +where_clause = "WHERE" expr . on_clause = db_name . @@ -273,6 +279,8 @@ to_clause = user_name . ## Other ``` +expr = + measurements = user_name = identifier . diff --git a/influxql/ast.go b/influxql/ast.go index 54ccf01d9e..92bbbd421f 100644 --- a/influxql/ast.go +++ b/influxql/ast.go @@ -231,6 +231,9 @@ type CreateUserStatement struct { // User's password Password string + + // User's privilege level. + Privilege *Privilege } // String returns a string representation of the create user statement. @@ -240,6 +243,12 @@ func (s *CreateUserStatement) String() string { _, _ = buf.WriteString(s.Name) _, _ = buf.WriteString(" WITH PASSWORD ") _, _ = buf.WriteString(s.Password) + + if s.Privilege != nil { + _, _ = buf.WriteString(" WITH ") + _, _ = buf.WriteString(s.Privilege.String()) + } + return buf.String() } @@ -266,6 +275,9 @@ const ( AllPrivileges ) +// NewPrivilege returns an initialized *Privilege. +func NewPrivilege(p Privilege) *Privilege { return &p } + // String returns a string representation of a Privilege. func (p Privilege) String() string { switch p { @@ -337,7 +349,7 @@ type CreateRetentionPolicyStatement struct { Name string // Name of database this policy belongs to. - DB string + Database string // Duration data written to this policy will be retained. Duration time.Duration @@ -355,7 +367,7 @@ func (s *CreateRetentionPolicyStatement) String() string { _, _ = buf.WriteString("CREATE RETENTION POLICY ") _, _ = buf.WriteString(s.Name) _, _ = buf.WriteString(" ON ") - _, _ = buf.WriteString(s.DB) + _, _ = buf.WriteString(s.Database) _, _ = buf.WriteString(" DURATION ") _, _ = buf.WriteString(FormatDuration(s.Duration)) _, _ = buf.WriteString(" REPLICATION ") @@ -372,7 +384,7 @@ type AlterRetentionPolicyStatement struct { Name string // Name of the database this policy belongs to. - DB string + Database string // Duration data written to this policy will be retained. Duration *time.Duration @@ -390,7 +402,7 @@ func (s *AlterRetentionPolicyStatement) String() string { _, _ = buf.WriteString("ALTER RETENTION POLICY ") _, _ = buf.WriteString(s.Name) _, _ = buf.WriteString(" ON ") - _, _ = buf.WriteString(s.DB) + _, _ = buf.WriteString(s.Database) if s.Duration != nil { _, _ = buf.WriteString(" DURATION ") @@ -611,7 +623,7 @@ type Target struct { Measurement string // Database to write into. - DB string + Database string } // String returns a string representation of the Target. @@ -626,9 +638,9 @@ func (t *Target) String() string { _, _ = buf.WriteString(t.Measurement) - if t.DB != "" { + if t.Database != "" { _, _ = buf.WriteString(" ON ") - _, _ = buf.WriteString(t.DB) + _, _ = buf.WriteString(t.Database) } return buf.String() @@ -714,7 +726,7 @@ type CreateContinuousQueryStatement struct { Name string // Name of the database to create the continuous query on. - DB string + Database string // Source of data (SELECT statement). Source *SelectStatement @@ -722,7 +734,7 @@ type CreateContinuousQueryStatement struct { // String returns a string representation of the statement. func (s *CreateContinuousQueryStatement) String() string { - return fmt.Sprintf("CREATE CONTINUOUS QUERY %s ON %s BEGIN %s END", s.Name, s.DB, s.Source.String()) + return fmt.Sprintf("CREATE CONTINUOUS QUERY %s ON %s BEGIN %s END", s.Name, s.Database, s.Source.String()) } // DropContinuousQueriesStatement represents a command for removing a continuous query. diff --git a/influxql/parser.go b/influxql/parser.go index 724fee69a8..5adfac1852 100644 --- a/influxql/parser.go +++ b/influxql/parser.go @@ -192,7 +192,7 @@ func (p *Parser) parseCreateRetentionPolicyStatement() (*CreateRetentionPolicySt if err != nil { return nil, err } - stmt.DB = ident + stmt.Database = ident // Parse required DURATION token. tok, pos, lit := p.scanIgnoreWhitespace() @@ -251,7 +251,7 @@ func (p *Parser) parseAlterRetentionPolicyStatement() (*AlterRetentionPolicyStat if err != nil { return nil, err } - stmt.DB = ident + stmt.Database = ident // Loop through option tokens (DURATION, REPLICATION, DEFAULT, etc.). maxNumOptions := 3 @@ -555,7 +555,7 @@ func (p *Parser) parseTarget(tr targetRequirement) (*Target, error) { if ident, err = p.parseIdentifier(); err != nil { return nil, err } - target.DB = ident + target.Database = ident return target, nil } @@ -860,7 +860,7 @@ func (p *Parser) parseCreateContinuousQueryStatement() (*CreateContinuousQuerySt if ident, err = p.parseIdentifier(); err != nil { return nil, err } - stmt.DB = ident + stmt.Database = ident // Expect a "BEGIN SELECT" tokens. if err := p.parseTokens([]Token{BEGIN, SELECT}); err != nil { @@ -918,11 +918,11 @@ func (p *Parser) parseCreateUserStatement() (*CreateUserStatement, error) { stmt := &CreateUserStatement{} // Parse name of the user to be created. - tok, pos, lit := p.scanIgnoreWhitespace() - if tok != IDENT && tok != STRING { - return nil, newParseError(tokstr(tok, lit), []string{"identifier", "string"}, pos) + ident, err := p.parseIdentifier() + if err != nil { + return nil, err } - stmt.Name = lit + stmt.Name = ident // Consume "WITH PASSWORD" tokens if err := p.parseTokens([]Token{WITH, PASSWORD}); err != nil { @@ -930,11 +930,23 @@ func (p *Parser) parseCreateUserStatement() (*CreateUserStatement, error) { } // Parse new user's password - tok, pos, lit = p.scanIgnoreWhitespace() - if tok != IDENT && tok != STRING { - return nil, newParseError(tokstr(tok, lit), []string{"identifier", "string"}, pos) + if ident, err = p.parseIdentifier(); err != nil { + return nil, err } - stmt.Password = lit + stmt.Password = ident + + // Check for option WITH clause. + if tok, _, _ := p.scanIgnoreWhitespace(); tok != WITH { + p.unscan() + return stmt, nil + } + + // We only allow granting of "ALL PRIVILEGES" during CREATE USER. + // All other privileges must be granted using a GRANT statement. + if err := p.parseTokens([]Token{ALL, PRIVILEGES}); err != nil { + return nil, err + } + stmt.Privilege = NewPrivilege(AllPrivileges) return stmt, nil } diff --git a/influxql/parser_test.go b/influxql/parser_test.go index 12339d9ad4..57d411fc7c 100644 --- a/influxql/parser_test.go +++ b/influxql/parser_test.go @@ -287,8 +287,8 @@ func TestParser_ParseStatement(t *testing.T) { { s: `CREATE CONTINUOUS QUERY myquery ON testdb BEGIN SELECT count() INTO measure1 FROM myseries END`, stmt: &influxql.CreateContinuousQueryStatement{ - Name: "myquery", - DB: "testdb", + Name: "myquery", + Database: "testdb", Source: &influxql.SelectStatement{ Fields: influxql.Fields{&influxql.Field{Expr: &influxql.Call{Name: "count"}}}, Target: &influxql.Target{Measurement: "measure1"}, @@ -301,8 +301,8 @@ func TestParser_ParseStatement(t *testing.T) { { s: `CREATE CONTINUOUS QUERY myquery ON testdb BEGIN SELECT count() INTO "1h.policy1"."cpu.load" FROM myseries END`, stmt: &influxql.CreateContinuousQueryStatement{ - Name: "myquery", - DB: "testdb", + Name: "myquery", + Database: "testdb", Source: &influxql.SelectStatement{ Fields: influxql.Fields{&influxql.Field{Expr: &influxql.Call{Name: "count"}}}, Target: &influxql.Target{ @@ -331,6 +331,16 @@ func TestParser_ParseStatement(t *testing.T) { }, }, + // CREATE USER ... WITH ALL PRIVILEGES + { + s: `CREATE USER testuser WITH PASSWORD pwd1337 WITH ALL PRIVILEGES`, + stmt: &influxql.CreateUserStatement{ + Name: "testuser", + Password: "pwd1337", + Privilege: influxql.NewPrivilege(influxql.AllPrivileges), + }, + }, + // DROP CONTINUOUS QUERY statement { s: `DROP CONTINUOUS QUERY myquery`, @@ -452,7 +462,7 @@ func TestParser_ParseStatement(t *testing.T) { s: `CREATE RETENTION POLICY policy1 ON testdb DURATION 1h REPLICATION 2`, stmt: &influxql.CreateRetentionPolicyStatement{ Name: "policy1", - DB: "testdb", + Database: "testdb", Duration: time.Hour, Replication: 2, }, @@ -463,7 +473,7 @@ func TestParser_ParseStatement(t *testing.T) { s: `CREATE RETENTION POLICY policy1 ON testdb DURATION 2m REPLICATION 4 DEFAULT`, stmt: &influxql.CreateRetentionPolicyStatement{ Name: "policy1", - DB: "testdb", + Database: "testdb", Duration: 2 * time.Minute, Replication: 4, Default: true, @@ -530,6 +540,10 @@ func TestParser_ParseStatement(t *testing.T) { {s: `DROP DATABASE`, err: `found EOF, expected identifier at line 1, char 15`}, {s: `DROP USER`, err: `found EOF, expected identifier at line 1, char 11`}, {s: `CREATE USER testuser`, err: `found EOF, expected WITH at line 1, char 22`}, + {s: `CREATE USER testuser WITH`, err: `found EOF, expected PASSWORD at line 1, char 27`}, + {s: `CREATE USER testuser WITH PASSWORD`, err: `found EOF, expected identifier at line 1, char 36`}, + {s: `CREATE USER testuser WITH PASSWORD "pwd" WITH`, err: `found EOF, expected ALL at line 1, char 47`}, + {s: `CREATE USER testuser WITH PASSWORD "pwd" WITH ALL`, err: `found EOF, expected PRIVILEGES at line 1, char 51`}, {s: `GRANT`, err: `found EOF, expected READ, WRITE, ALL [PRIVILEGES] at line 1, char 7`}, {s: `GRANT BOGUS`, err: `found BOGUS, expected READ, WRITE, ALL [PRIVILEGES] at line 1, char 7`}, {s: `GRANT READ`, err: `found EOF, expected ON at line 1, char 12`}, @@ -861,9 +875,9 @@ func errstring(err error) string { // newAlterRetentionPolicyStatement creates an initialized AlterRetentionPolicyStatement. func newAlterRetentionPolicyStatement(name string, DB string, d time.Duration, replication int, dfault bool) *influxql.AlterRetentionPolicyStatement { stmt := &influxql.AlterRetentionPolicyStatement{ - Name: name, - DB: DB, - Default: dfault, + Name: name, + Database: DB, + Default: dfault, } if d > -1 {