Skip to content

Commit

Permalink
Merge branch 'hotfix-0.1.9' into stable
Browse files Browse the repository at this point in the history
  • Loading branch information
hgouchet committed Jan 10, 2017
2 parents 56c76d2 + e74cf01 commit a26db47
Show file tree
Hide file tree
Showing 6 changed files with 345 additions and 212 deletions.
47 changes: 39 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,44 @@

Only the first statement is supported by Adwords API, the others are proposed by the AWQL command line tool.

## Example
## Examples

```go
q := `SELECT CampaignName FROM CAMPAIGN_PERFORMACE_REPORT ORDER BY 1 LIMIT 5\G`
stmts, _ := NewParser(strings.NewReader(q)).Parse()
if stmt, ok := stmts[0].(SelectStmt); ok {
fmt.Println(stmt.OrderList()[0].ColumnName)
// Output: CampaignName
### Unknown single statement.

```go
q := `SELECT CampaignId, CampaignName FROM CAMPAIGN_PERFORMANCE_REPORT;`
stmt, _ := awql.NewParser(strings.NewReader(q)).ParseRow()
if stmt, ok := stmt.(awql.SelectStmt); ok {
fmt.Println(stmt.SourceName())
// Output: CAMPAIGN_PERFORMANCE_REPORT
}
```

### Select statement.

```go
q := `SELECT AdGroupName FROM ADGROUP_PERFORMANCE_REPORT;`
stmt, _ := awql.NewParser(strings.NewReader(q)).ParseSelect()
fmt.Printf("Gets the column named %v from %v.\n", stmt.Columns()[0].Name(), stmt.SourceName())
// Output: Gets the column named AdGroupName from ADGROUP_PERFORMANCE_REPORT.
```

### Multiple statements.

```go
q := `SELECT CampaignName FROM CAMPAIGN_PERFORMANCE_REPORT ORDER BY 1 LIMIT 5\GDESC ADGROUP_PERFORMANCE_REPORT AdGroupName;`
stmts, _ := awql.NewParser(strings.NewReader(q)).Parse()
for _, stmt := range stmts {
switch stmt.(type) {
case awql.SelectStmt:
fmt.Println(stmt.(awql.SelectStmt).OrderList()[0].Name())
case awql.DescribeStmt:
fmt.Println(stmt.(awql.DescribeStmt).SourceName())
fmt.Println(stmt.(awql.DescribeStmt).Columns()[0].Name())
}
}
```
// Output:
// CampaignName
// ADGROUP_PERFORMANCE_REPORT
// AdGroupName
```
40 changes: 23 additions & 17 deletions format.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,49 +6,54 @@ import "strconv"
// Indeed, aggregate functions, ORDER BY, GROUP BY and LIMIT are not supported for reports.
// Implements fmt.Stringer interface.
func (s SelectStatement) String() (q string) {
if len(s.Fields) == 0 || s.TableName == "" {
if len(s.Columns()) == 0 || s.SourceName() == "" {
return
}
// Concat selected fields.

// Concats selected fields.
q = "SELECT "
for i, c := range s.Fields {
for i, c := range s.Columns() {
if i > 0 {
q += ", "
}
q += c.ColumnName
q += c.Name()
}
// Data source
q += " FROM " + s.TableName
// Conditions
if len(s.Where) > 0 {

// Adds data source name.
q += " FROM " + s.SourceName()

// Conditions.
if len(s.ConditionList()) > 0 {
q += " WHERE "
for i, c := range s.Where {
for i, c := range s.ConditionList() {
if i > 0 {
q += " AND "
}
q += c.ColumnName + " " + c.Operator
if len(c.Value) > 1 {
q += c.Name() + " " + c.Operator()
val, lit := c.Value()
if len(val) > 1 {
q += " ["
for y, v := range c.Value {
for y, v := range val {
if y > 0 {
q += " ,"
}
if c.IsValueLiteral {
if lit {
q += " " + v
} else {
q += " " + strconv.Quote(v)
}
}
q += " ]"
} else if c.IsValueLiteral {
q += " " + c.Value[0]
} else if lit {
q += " " + val[0]
} else {
q += " " + strconv.Quote(c.Value[0])
q += " " + strconv.Quote(val[0])
}
}
}

// Range date
d := s.During
d := s.DuringList()
if ds := len(d); ds > 0 {
q += " DURING "
if ds == 2 {
Expand All @@ -58,5 +63,6 @@ func (s SelectStatement) String() (q string) {
q += d[0]
}
}

return
}
81 changes: 47 additions & 34 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,28 @@ type Parser struct {
}
}

// Error messages
// Error messages.
var (
ErrMsgBadStmt = "ParserError.UNKWOWN_STATEMENT"
ErrMsgMissingSrc = "ParserError.MISSING_SOURCE"
ErrMsgBadColumn = "ParserError.UNKNOWN_COLUMN (%s)"
ErrMsgBadMethod = "ParserError.INVALID_METHOD (%s)"
ErrMsgBadField = "ParserError.INVALID_FIELD (%s)"
ErrMsgBadFunc = "ParserError.INVALID_FUNCTION (%s)"
ErrMsgBadSrc = "ParserError.INVALID_SOURCE (%s)"
ErrMsgBadDuring = "ParserError.INVALID_DURING (%s)"
ErrMsgBadGroup = "ParserError.INVALID_GROUP_BY (%s)"
ErrMsgBadOrder = "ParserError.INVALID_ORDER_BY (%s)"
ErrMsgBadLimit = "ParserError.INVALID_LIMIT (%s)"
ErrMsgSyntax = "ParserError.SYNTAX_NEAR (%s)"
ErrMsgDuringSize = "unexpected number of date range"
ErrMsgDuringLitSize = "expected date range literal"
ErrMsgDuringDateSize = "expected no literal date"
ErrMsgBadStmt = "ParserError.UNKWOWN_STATEMENT"
ErrMsgMissingSrc = "ParserError.MISSING_SOURCE"
ErrMsgColumnsNotMatch = "ParserError.COLUMNS_NOT_MATCH"
ErrMsgBadColumn = "ParserError.UNKNOWN_COLUMN (%s)"
ErrMsgBadMethod = "ParserError.INVALID_METHOD (%s)"
ErrMsgBadField = "ParserError.INVALID_FIELD (%s)"
ErrMsgBadFunc = "ParserError.INVALID_FUNCTION (%s)"
ErrMsgBadSrc = "ParserError.INVALID_SOURCE (%s)"
ErrMsgBadDuring = "ParserError.INVALID_DURING (%s)"
ErrMsgBadGroup = "ParserError.INVALID_GROUP_BY (%s)"
ErrMsgBadOrder = "ParserError.INVALID_ORDER_BY (%s)"
ErrMsgBadLimit = "ParserError.INVALID_LIMIT (%s)"
ErrMsgSyntax = "ParserError.SYNTAX_NEAR (%s)"
)

// Internal error messages.
var (
IErrMsgDuringSize = "unexpected number of date range"
IErrMsgDuringLitSize = "expected date range literal"
IErrMsgDuringDateSize = "expected no literal date"
)

// NewParser returns a new instance of Parser.
Expand Down Expand Up @@ -115,7 +120,7 @@ func (p *Parser) ParseDescribe() (DescribeStmt, error) {

// Next we may see a column name.
if tk, literal := p.scanIgnoreWhitespace(); tk == IDENTIFIER {
field := Field{Column{ColumnName: literal}, "", false}
field := NewDynamicColumn(NewColumn(literal, ""), "", false)
stmt.Fields = append(stmt.Fields, field)
} else {
p.unscan()
Expand Down Expand Up @@ -165,7 +170,7 @@ func (p *Parser) ParseCreateView() (CreateViewStmt, error) {
if tk, literal := p.scanIgnoreWhitespace(); tk == RIGHT_PARENTHESIS {
break
} else if tk == IDENTIFIER {
stmt.Fields = append(stmt.Fields, Field{Column: Column{ColumnName: literal}})
stmt.Fields = append(stmt.Fields, NewDynamicColumn(NewColumn(literal, ""), "", false))
} else if tk == COMMA {
// If the next token is not an "COMMA" then break the loop.
continue
Expand All @@ -188,6 +193,13 @@ func (p *Parser) ParseCreateView() (CreateViewStmt, error) {
} else {
stmt.View = selectStmt.(*SelectStatement)
}

// Checks if the nomber of view's columns match with the source.
if vcs := len(stmt.Fields); vcs > 0 {
if vcs != len(stmt.View.Fields) {
return nil, errors.New(ErrMsgColumnsNotMatch)
}
}
return stmt, nil
}

Expand Down Expand Up @@ -268,13 +280,13 @@ func (p *Parser) ParseSelect() (SelectStmt, error) {
// Next we should loop over all our comma-delimited fields.
for {
// Read a field.
field := Field{}
field := &DynamicColumn{Column: &Column{}}
tk, literal := p.scanIgnoreWhitespace()
switch tk {
case ASTERISK:
field.ColumnName = literal
case DISTINCT:
if err := p.scanDistinct(&field); err != nil {
if err := p.scanDistinct(field); err != nil {
return nil, err
}
case IDENTIFIER:
Expand All @@ -300,7 +312,7 @@ func (p *Parser) ParseSelect() (SelectStmt, error) {
}
field.ColumnName = literal
case DISTINCT:
if err := p.scanDistinct(&field); err != nil {
if err := p.scanDistinct(field); err != nil {
return nil, err
}
case DIGIT:
Expand Down Expand Up @@ -365,7 +377,7 @@ func (p *Parser) ParseSelect() (SelectStmt, error) {
if tk, _ := p.scanIgnoreWhitespace(); tk == WHERE {
for {
// Parse each condition, begin by the column name.
cond := Condition{}
cond := &Where{Column: &Column{}}
if tk, literal := p.scanIgnoreWhitespace(); tk != IDENTIFIER {
return nil, fmt.Errorf(ErrMsgBadField, literal)
} else {
Expand All @@ -375,7 +387,7 @@ func (p *Parser) ParseSelect() (SelectStmt, error) {
if tk, literal := p.scanIgnoreWhitespace(); !isOperator(tk) {
return nil, fmt.Errorf(ErrMsgSyntax, literal)
} else {
cond.Operator = literal
cond.Sign = literal
}
// And the value of the condition.ValueLiteral | String | ValueLiteralList | StringList
tk, literal := p.scanIgnoreWhitespace()
Expand All @@ -384,10 +396,10 @@ func (p *Parser) ParseSelect() (SelectStmt, error) {
cond.IsValueLiteral = true
fallthrough
case STRING:
cond.Value = append(cond.Value, literal)
cond.ColumnValue = append(cond.ColumnValue, literal)
case LEFT_SQUARE_BRACKETS:
p.unscan()
if tk, cond.Value = p.scanValueList(); tk != VALUE_LITERAL_LIST && tk != STRING_LIST {
if tk, cond.ColumnValue = p.scanValueList(); tk != VALUE_LITERAL_LIST && tk != STRING_LIST {
return nil, fmt.Errorf(ErrMsgSyntax, literal)
} else if tk == VALUE_LITERAL_LIST {
cond.IsValueLiteral = true
Expand Down Expand Up @@ -430,11 +442,11 @@ func (p *Parser) ParseSelect() (SelectStmt, error) {
}
// Checks expected bounds.
if rangeSize := len(stmt.During); rangeSize > 2 {
return nil, fmt.Errorf(ErrMsgBadDuring, ErrMsgDuringSize)
return nil, fmt.Errorf(ErrMsgBadDuring, IErrMsgDuringSize)
} else if rangeSize == 1 && !dateLiteral {
return nil, fmt.Errorf(ErrMsgBadDuring, ErrMsgDuringLitSize)
return nil, fmt.Errorf(ErrMsgBadDuring, IErrMsgDuringLitSize)
} else if rangeSize == 2 && dateLiteral {
return nil, fmt.Errorf(ErrMsgBadDuring, ErrMsgDuringDateSize)
return nil, fmt.Errorf(ErrMsgBadDuring, IErrMsgDuringDateSize)
}
} else {
// No during clause.
Expand Down Expand Up @@ -481,11 +493,11 @@ func (p *Parser) ParseSelect() (SelectStmt, error) {
return nil, fmt.Errorf(ErrMsgBadOrder, literal)
}
// Check if the column exists as field.
orderBy := &Ordering{}
orderBy := &Order{}
if column, err := stmt.searchColumn(literal); err != nil {
return nil, err
} else {
orderBy.ColumnPosition = *column
orderBy.ColumnPosition = column
}
// Then, we may find a DESC or ASC keywords.
if tk, _ = p.scanIgnoreWhitespace(); tk == DESC {
Expand Down Expand Up @@ -552,8 +564,9 @@ func (s SelectStatement) searchColumn(expr string) (*ColumnPosition, error) {
}
// Otherwise fetch each column to find it by name or alias.
for i, field := range s.Fields {
field := field.(*DynamicColumn)
if field.ColumnName == expr || field.ColumnAlias == expr {
return &ColumnPosition{Column: field.Column, Position: (i + 1)}, nil
return NewColumnPosition(field.Column, (i + 1)), nil
}
}
return nil, fmt.Errorf(ErrMsgBadColumn, expr)
Expand All @@ -564,7 +577,7 @@ func (s DataStatement) searchColumnByPosition(pos int) (*ColumnPosition, error)
if pos < 1 || pos > len(s.Fields) {
return nil, fmt.Errorf(ErrMsgBadColumn, pos)
}
return &ColumnPosition{Column: s.Fields[(pos - 1)].Column, Position: pos}, nil
return NewColumnPosition(s.Fields[(pos-1)].(*DynamicColumn).Column, pos), nil
}

// scan returns the next token from the underlying scanner.
Expand All @@ -580,12 +593,12 @@ func (p *Parser) scan() (Token, string) {
}

// scanDistinct scans the next runes as column to use to group.
func (p *Parser) scanDistinct(field *Field) error {
func (p *Parser) scanDistinct(field *DynamicColumn) error {
tk, literal := p.scanIgnoreWhitespace()
if tk != IDENTIFIER {
return fmt.Errorf(ErrMsgBadField, literal)
}
field.Distinct = true
field.Unique = true
field.ColumnName = literal

return nil
Expand Down
Loading

0 comments on commit a26db47

Please sign in to comment.