Merge pull request #1300 from influxdb/cqQL

InfluxQL changes for continuous queries
pull/1307/head
dgnorton 2015-01-09 18:59:38 -05:00
commit 3065a0358f
7 changed files with 607 additions and 103 deletions

291
influxql/INFLUXQL.md Normal file
View File

@ -0,0 +1,291 @@
# 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_databases_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
[ "WITH ALL PRIVILEGES" ] .
```
#### 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
```
delete_stmt = "DELETE" from_clause where_clause .
```
#### Example:
```sql
DELETE FROM cpu WHERE region = 'uswest'
```
### 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
```
from_clause = "FROM" measurements .
where_clause = "WHERE" expr .
on_clause = db_name .
to_clause = user_name .
```
## Other
```
expr =
measurements =
user_name = identifier .
password = identifier .
privilege = "ALL" [ "PRIVILEGES" ] | "READ" | "WRITE" .
```

View File

@ -47,47 +47,49 @@ 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 (_ *ListDatabasesStatement) 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 {
@ -115,26 +117,27 @@ 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 (_ *ListDatabasesStatement) 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 {
@ -142,15 +145,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.
@ -159,8 +162,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.
@ -228,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.
@ -237,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()
}
@ -263,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 {
@ -334,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
@ -352,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 ")
@ -369,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
@ -387,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 ")
@ -411,6 +426,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 +451,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 +614,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.
Database 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.Database != "" {
_, _ = buf.WriteString(" ON ")
_, _ = buf.WriteString(t.Database)
}
return buf.String()
}
// DeleteStatement represents a command for removing data from the database.
type DeleteStatement struct {
// Data source that values are removed from.
@ -659,16 +714,27 @@ 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 string
// Name of the continuous query to be created.
Name string
// Name of the database to create the continuous query on.
Database 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.Database, s.Source.String())
}
// DropContinuousQueriesStatement represents a command for removing a continuous query.

View File

@ -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:
@ -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 {
@ -190,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()
@ -249,9 +251,9 @@ func (p *Parser) parseAlterRetentionPolicyStatement() (*AlterRetentionPolicyStat
if err != nil {
return nil, err
}
stmt.DB = ident
stmt.Database = 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++ {
@ -442,7 +444,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 +454,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 +503,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 == DOT {
// 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.Database = 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) {
@ -760,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) {
@ -771,39 +845,40 @@ 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.Database = 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
return stmt, nil
}
@ -843,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 {
@ -855,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
}

View File

@ -147,6 +147,12 @@ func TestParser_ParseStatement(t *testing.T) {
},
},
// LIST DATABASES
{
s: `LIST DATABASES`,
stmt: &influxql.ListDatabasesStatement{},
},
// LIST SERIES statement
{
s: `LIST SERIES`,
@ -277,16 +283,34 @@ func TestParser_ParseStatement(t *testing.T) {
stmt: &influxql.ListContinuousQueriesStatement{},
},
// CREATE CONTINUOUS QUERY statement
// CREATE CONTINUOUS QUERY ... INTO <measurement>
{
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",
Name: "myquery",
Database: "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 <retention-policy>.<measurement>
{
s: `CREATE CONTINUOUS QUERY myquery ON testdb BEGIN SELECT count() INTO "1h.policy1"."cpu.load" FROM myseries END`,
stmt: &influxql.CreateContinuousQueryStatement{
Name: "myquery",
Database: "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",
},
},
@ -307,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`,
@ -428,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,
},
@ -439,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,
@ -506,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`},
@ -545,6 +583,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)
}
}
}
@ -833,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 {

View File

@ -42,7 +42,14 @@ func (s *Scanner) Scan() (tok Token, pos Pos, lit string) {
return EOF, pos, ""
case '"', '\'':
return s.scanString()
case '.', '+', '-':
case '.':
ch1, _ := s.r.read()
s.r.unread()
if isDigit(ch1) {
return s.scanNumber()
}
return DOT, pos, ""
case '+', '-':
return s.scanNumber()
case '*':
return MUL, pos, ""
@ -233,7 +240,6 @@ func (s *Scanner) scanNumber() (tok Token, pos Pos, lit string) {
}
s.r.unread()
}
return NUMBER, pos, buf.String()
}

View File

@ -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`},
@ -100,15 +101,18 @@ 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},
{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},
{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},

View File

@ -47,6 +47,7 @@ const (
RPAREN // )
COMMA // ,
SEMICOLON // ;
DOT // .
keyword_beg
// Keywords
@ -54,15 +55,18 @@ const (
ALTER
AS
ASC
BEGIN
BY
CREATE
CONTINUOUS
DATABASE
DATABASES
DEFAULT
DELETE
DESC
DROP
DURATION
END
EXISTS
EXPLAIN
FIELD
@ -132,20 +136,24 @@ var tokens = [...]string{
RPAREN: ")",
COMMA: ",",
SEMICOLON: ";",
DOT: ".",
ALL: "ALL",
ALTER: "ALTER",
AS: "AS",
ASC: "ASC",
BEGIN: "BEGIN",
BY: "BY",
CREATE: "CREATE",
CONTINUOUS: "CONTINUOUS",
DATABASE: "DATABASE",
DATABASES: "DATABASES",
DEFAULT: "DEFAULT",
DELETE: "DELETE",
DESC: "DESC",
DROP: "DROP",
DURATION: "DURATION",
END: "END",
EXISTS: "EXISTS",
EXPLAIN: "EXPLAIN",
FIELD: "FIELD",