refact: moved select stuff to seperate file
This commit is contained in:
parent
325f660a04
commit
50bfba4c09
4
main.go
4
main.go
@ -75,12 +75,12 @@ func HandlePostQuery(w http.ResponseWriter, r *http.Request) {
|
|||||||
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
|
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
var query q.Select
|
var query q.Query
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer cancel()
|
defer cancel()
|
||||||
query = q.ParseSelectStatement(request.SQL)
|
query, err = q.Parse(request.SQL)
|
||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|||||||
2
q/dto.go
2
q/dto.go
@ -150,7 +150,7 @@ type Select struct {
|
|||||||
IsDistinct bool `json:"is_distinct"`
|
IsDistinct bool `json:"is_distinct"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func MarshalSelect(selectStatement Select) ([]byte, error) {
|
func MarshalSelect(selectStatement Query) ([]byte, error) {
|
||||||
jsonData, err := json.MarshalIndent(selectStatement, "", " ")
|
jsonData, err := json.MarshalIndent(selectStatement, "", " ")
|
||||||
return jsonData, err
|
return jsonData, err
|
||||||
}
|
}
|
||||||
|
|||||||
375
q/parse.go
375
q/parse.go
@ -133,362 +133,53 @@ func findQueryType(p *parser) (string, error) {
|
|||||||
return firstCommand.Value, nil
|
return firstCommand.Value, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseSelectStatement(p *parser) error {
|
func GetFullStringFromColumn(column Column) string {
|
||||||
selectQuery := p.query.(*Select)
|
var workingSlice string
|
||||||
|
|
||||||
distinctErr := parseDistinct(p)
|
if column.AggregateFunction > 0 {
|
||||||
if distinctErr != nil {
|
workingSlice = fmt.Sprintf(
|
||||||
return distinctErr
|
"%s(%s)",
|
||||||
|
AggregateFunctionTypeString(column.AggregateFunction),
|
||||||
|
column.Name,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
workingSlice = column.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
foundWildcard, _ := p.findToken(func(t Token) bool {
|
if column.Alias != "" {
|
||||||
return t.Type == sqllexer.WILDCARD
|
workingSlice += fmt.Sprintf(" AS %s", column.Alias)
|
||||||
})
|
|
||||||
|
|
||||||
selectQuery.IsWildcard = foundWildcard != nil
|
|
||||||
|
|
||||||
if !selectQuery.IsWildcard {
|
|
||||||
err := parseSelectColumns(p)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err.Error())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tableErr := parseSelectTable(p)
|
return workingSlice
|
||||||
if tableErr != nil {
|
|
||||||
return tableErr
|
|
||||||
}
|
|
||||||
|
|
||||||
conditionalsErr := parseSelectConditionals(p)
|
|
||||||
if conditionalsErr != nil {
|
|
||||||
return conditionalsErr
|
|
||||||
}
|
|
||||||
|
|
||||||
ordersByErr := parseOrderBys(p)
|
|
||||||
if ordersByErr != nil {
|
|
||||||
return ordersByErr
|
|
||||||
}
|
|
||||||
|
|
||||||
parseJoins(p)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseDistinct(p *parser) error {
|
func (q *Select) GetFullSql() string {
|
||||||
selectQuery := p.query.(*Select)
|
var workingSqlSlice []string
|
||||||
|
|
||||||
foundDistinctKeyword, _ := p.findToken(func(t Token) bool {
|
workingSqlSlice = append(workingSqlSlice, "SELECT")
|
||||||
return t.Type == sqllexer.KEYWORD && strings.ToUpper(t.Value) == "DISTINCT"
|
|
||||||
})
|
|
||||||
|
|
||||||
if foundDistinctKeyword != nil {
|
if q.IsWildcard {
|
||||||
selectQuery.IsDistinct = true
|
workingSqlSlice = append(workingSqlSlice, "*")
|
||||||
}
|
} else {
|
||||||
|
for i, column := range q.Columns {
|
||||||
return nil
|
if i < (len(q.Columns) - 1) {
|
||||||
}
|
workingSqlSlice = append(workingSqlSlice, GetFullStringFromColumn(column)+",")
|
||||||
|
|
||||||
func parseSelectColumns(p *parser) error {
|
|
||||||
selectQuery := p.query.(*Select)
|
|
||||||
|
|
||||||
_, selectCommandIndex := p.findToken(func(t Token) bool {
|
|
||||||
return t.Type == sqllexer.COMMAND && strings.ToUpper(t.Value) == "SELECT"
|
|
||||||
})
|
|
||||||
|
|
||||||
_, fromKeywordIndex := p.findToken(func(t Token) bool {
|
|
||||||
return t.Type == sqllexer.KEYWORD && strings.ToUpper(t.Value) == "FROM"
|
|
||||||
})
|
|
||||||
|
|
||||||
if selectCommandIndex < 0 || fromKeywordIndex < 0 {
|
|
||||||
return fmt.Errorf("Could not find range between SELECT and FROM")
|
|
||||||
}
|
|
||||||
|
|
||||||
lookBehindBuffer := [10]Token{}
|
|
||||||
var workingColumn Column
|
|
||||||
columns := make([]Column, 0)
|
|
||||||
|
|
||||||
startRange := selectCommandIndex + 1
|
|
||||||
endRange := fromKeywordIndex - 1
|
|
||||||
|
|
||||||
for i := startRange; i <= endRange; i++ {
|
|
||||||
token := p.tokens[i]
|
|
||||||
|
|
||||||
if token.Type == sqllexer.FUNCTION {
|
|
||||||
unshiftBuffer(&lookBehindBuffer, token)
|
|
||||||
workingColumn.AggregateFunction = AggregateFunctionTypeByName(token.Value)
|
|
||||||
continue
|
|
||||||
} else if token.Type == sqllexer.PUNCTUATION && token.Value == "," {
|
|
||||||
columns = append(columns, workingColumn)
|
|
||||||
workingColumn = Column{}
|
|
||||||
continue
|
|
||||||
} else if token.Type == sqllexer.IDENT {
|
|
||||||
unshiftBuffer(&lookBehindBuffer, token)
|
|
||||||
|
|
||||||
if lookBehindBuffer[1].Type == sqllexer.ALIAS_INDICATOR {
|
|
||||||
workingColumn.Alias = token.Value
|
|
||||||
} else {
|
} else {
|
||||||
workingColumn.Name = token.Value
|
workingSqlSlice = append(workingSqlSlice, GetFullStringFromColumn(column))
|
||||||
}
|
|
||||||
continue
|
|
||||||
} else if token.Type == sqllexer.ALIAS_INDICATOR {
|
|
||||||
unshiftBuffer(&lookBehindBuffer, token)
|
|
||||||
continue
|
|
||||||
} else if i == endRange {
|
|
||||||
if workingColumn.Name != "" {
|
|
||||||
columns = append(columns, workingColumn)
|
|
||||||
workingColumn = Column{}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectQuery.Columns = columns
|
workingSqlSlice = append(workingSqlSlice, "FROM "+q.Table.Name) // TODO: figure out what to do from alias
|
||||||
|
|
||||||
return nil
|
for _, condition := range q.Conditionals {
|
||||||
}
|
workingSqlSlice = append(workingSqlSlice, condition.Key)
|
||||||
|
workingSqlSlice = append(workingSqlSlice, condition.Operator)
|
||||||
func parseSelectTable(p *parser) error {
|
workingSqlSlice = append(workingSqlSlice, condition.Value)
|
||||||
selectQuery := p.query.(*Select)
|
}
|
||||||
|
|
||||||
_, fromKeywordIndex := p.findToken(func(t Token) bool {
|
fullSql := strings.Join(workingSqlSlice, " ")
|
||||||
return t.Type == sqllexer.KEYWORD && strings.ToUpper(t.Value) == "FROM"
|
|
||||||
})
|
return fullSql
|
||||||
|
|
||||||
if fromKeywordIndex < 0 {
|
|
||||||
return fmt.Errorf("Could not FROM keyword to look for table name")
|
|
||||||
}
|
|
||||||
|
|
||||||
var foundTable Table
|
|
||||||
|
|
||||||
for i := fromKeywordIndex + 1; i < len(p.tokens); i++ {
|
|
||||||
t := &p.tokens[i]
|
|
||||||
if foundTable.Name == "" && t.Type == sqllexer.IDENT {
|
|
||||||
foundTable.Name = p.tokens[i].Value
|
|
||||||
continue
|
|
||||||
} else if t.Type == sqllexer.IDENT {
|
|
||||||
foundTable.Alias = p.tokens[i].Value
|
|
||||||
break
|
|
||||||
} else if t.Type == sqllexer.SPACE || t.Type == sqllexer.ALIAS_INDICATOR {
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if foundTable.Name == "" {
|
|
||||||
return fmt.Errorf("Could not find table name")
|
|
||||||
}
|
|
||||||
|
|
||||||
selectQuery.Table = foundTable
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseSelectConditionals(p *parser) error {
|
|
||||||
selectQuery := p.query.(*Select)
|
|
||||||
|
|
||||||
_, whereKeywordIndex := p.findToken(func(t Token) bool {
|
|
||||||
return t.Type == sqllexer.KEYWORD && strings.ToUpper(t.Value) == "WHERE"
|
|
||||||
})
|
|
||||||
|
|
||||||
if whereKeywordIndex < 0 {
|
|
||||||
return nil // fmt.Errorf("Could not find WHERE to look for conditionals")
|
|
||||||
}
|
|
||||||
|
|
||||||
var workingConditional Conditional
|
|
||||||
for i := whereKeywordIndex + 1; i < len(p.tokens); i++ {
|
|
||||||
t := &p.tokens[i]
|
|
||||||
|
|
||||||
if t.Type == sqllexer.KEYWORD && strings.ToUpper(t.Value) != "AND" && strings.ToUpper(t.Value) != "OR" && strings.ToUpper(t.Value) != "NOT" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.Type == sqllexer.IDENT {
|
|
||||||
workingConditional.Key = t.Value
|
|
||||||
} else if t.Type == sqllexer.OPERATOR {
|
|
||||||
workingConditional.Operator = t.Value
|
|
||||||
} else if t.Type == sqllexer.BOOLEAN || t.Type == sqllexer.NULL || t.Type == sqllexer.STRING || t.Type == sqllexer.NUMBER {
|
|
||||||
workingConditional.Value = t.Value
|
|
||||||
} else if t.Type == sqllexer.KEYWORD {
|
|
||||||
if strings.ToUpper(t.Value) == "AND" || strings.ToUpper(t.Value) == "OR" {
|
|
||||||
workingConditional.Extension = strings.ToUpper(t.Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if workingConditional.Key != "" && workingConditional.Operator != "" && workingConditional.Value != "" {
|
|
||||||
selectQuery.Conditionals = append(selectQuery.Conditionals, workingConditional)
|
|
||||||
workingConditional = Conditional{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseOrderBys(p *parser) error {
|
|
||||||
selectQuery := p.query.(*Select)
|
|
||||||
|
|
||||||
_, byKeywordIndex := p.findToken(func(t Token) bool {
|
|
||||||
return t.Type == sqllexer.KEYWORD && strings.ToUpper(t.Value) == "BY"
|
|
||||||
})
|
|
||||||
|
|
||||||
if byKeywordIndex < 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var orderBys []OrderBy
|
|
||||||
|
|
||||||
var workingOrderBy OrderBy
|
|
||||||
for i := byKeywordIndex + 1; i < len(p.tokens); i++ {
|
|
||||||
t := &p.tokens[i]
|
|
||||||
if t.Type == sqllexer.SPACE {
|
|
||||||
continue
|
|
||||||
} else if t.Type == sqllexer.IDENT && workingOrderBy.Key == "" {
|
|
||||||
workingOrderBy.Key = t.Value
|
|
||||||
continue
|
|
||||||
} else if t.Type == sqllexer.IDENT && workingOrderBy.Key != "" {
|
|
||||||
orderBys = append(orderBys, workingOrderBy)
|
|
||||||
workingOrderBy.Key = t.Value
|
|
||||||
continue
|
|
||||||
} else if t.Type == sqllexer.KEYWORD {
|
|
||||||
if t.Value == "DESC" {
|
|
||||||
workingOrderBy.IsDescend = true
|
|
||||||
} else if t.Value != "ASC" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else if t.Type == sqllexer.PUNCTUATION {
|
|
||||||
orderBys = append(orderBys, workingOrderBy)
|
|
||||||
workingOrderBy = OrderBy{}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if workingOrderBy.Key != "" {
|
|
||||||
orderBys = append(orderBys, workingOrderBy)
|
|
||||||
}
|
|
||||||
|
|
||||||
selectQuery.OrderBys = orderBys
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseJoins(p *parser) error {
|
|
||||||
selectQuery := p.query.(*Select)
|
|
||||||
|
|
||||||
foundJoinKeywords := p.findAllTokens(func(t Token) bool {
|
|
||||||
return t.Type == sqllexer.COMMAND && strings.ToUpper(t.Value) == "JOIN"
|
|
||||||
})
|
|
||||||
|
|
||||||
if len(foundJoinKeywords) <= 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type FoundJoinSubslices struct {
|
|
||||||
Tokens []Token
|
|
||||||
StartingIndexInGreaterStatement int
|
|
||||||
}
|
|
||||||
|
|
||||||
var joinTokenRanges []FoundJoinSubslices
|
|
||||||
|
|
||||||
for i, foundJoin := range foundJoinKeywords {
|
|
||||||
|
|
||||||
startRangeIndex := foundJoin.Index
|
|
||||||
var endRangeIndex int
|
|
||||||
|
|
||||||
if i == (len(foundJoinKeywords) - 1) {
|
|
||||||
endRangeIndex = len(p.tokens) - 1
|
|
||||||
} else {
|
|
||||||
endRangeIndex = foundJoinKeywords[i+1].Index
|
|
||||||
}
|
|
||||||
|
|
||||||
joinTokenRanges = append(joinTokenRanges, FoundJoinSubslices{
|
|
||||||
Tokens: p.tokens[startRangeIndex:endRangeIndex],
|
|
||||||
StartingIndexInGreaterStatement: startRangeIndex,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, joinRange := range joinTokenRanges {
|
|
||||||
var workingJoin Join
|
|
||||||
workingJoin.MainTable = selectQuery.Table
|
|
||||||
|
|
||||||
// check for the join type by looking backwards in the greater statement
|
|
||||||
joinTypeSearchIndex := joinRange.StartingIndexInGreaterStatement - 1
|
|
||||||
for ; joinTypeSearchIndex >= 0; joinTypeSearchIndex-- {
|
|
||||||
if p.tokens[joinTypeSearchIndex].Type == sqllexer.KEYWORD {
|
|
||||||
switch strings.ToUpper(p.tokens[joinTypeSearchIndex].Value) {
|
|
||||||
case "LEFT":
|
|
||||||
workingJoin.Type = LEFT
|
|
||||||
break
|
|
||||||
case "RIGHT":
|
|
||||||
workingJoin.Type = RIGHT
|
|
||||||
break
|
|
||||||
case "FULL":
|
|
||||||
workingJoin.Type = FULL
|
|
||||||
break
|
|
||||||
case "SELF":
|
|
||||||
workingJoin.Type = SELF
|
|
||||||
break
|
|
||||||
case "INNER":
|
|
||||||
workingJoin.Type = INNER
|
|
||||||
default:
|
|
||||||
workingJoin.Type = INNER
|
|
||||||
}
|
|
||||||
break // Stop after finding first keyword
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find joined table name
|
|
||||||
for i := 1; i < len(joinRange.Tokens); i++ {
|
|
||||||
if joinRange.Tokens[i].Type == sqllexer.IDENT {
|
|
||||||
workingJoin.JoiningTable.Name = joinRange.Tokens[i].Value
|
|
||||||
break // Stop after finding first IDENT
|
|
||||||
// TODO: make sure you dont have to check for aliases
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//var ons []Conditional
|
|
||||||
var workingOn Conditional
|
|
||||||
|
|
||||||
_, foundOnTokenIndex := FindTokenInArray(joinRange.Tokens, func(t Token) bool {
|
|
||||||
return t.Type == sqllexer.KEYWORD && strings.ToUpper(t.Value) == "ON"
|
|
||||||
})
|
|
||||||
|
|
||||||
if foundOnTokenIndex < 0 {
|
|
||||||
selectQuery.Joins = append(selectQuery.Joins, workingJoin)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := foundOnTokenIndex + 1; i < len(joinRange.Tokens); i++ {
|
|
||||||
t := &joinRange.Tokens[i]
|
|
||||||
|
|
||||||
if t.Type == sqllexer.KEYWORD && strings.ToUpper(t.Value) != "AND" && strings.ToUpper(t.Value) != "OR" && strings.ToUpper(t.Value) != "NOT" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.Type == sqllexer.IDENT {
|
|
||||||
if workingOn.Key == "" {
|
|
||||||
workingOn.Key = t.Value
|
|
||||||
} else {
|
|
||||||
workingOn.Value = t.Value
|
|
||||||
}
|
|
||||||
} else if t.Type == sqllexer.OPERATOR {
|
|
||||||
workingOn.Operator = t.Value
|
|
||||||
} else if t.Type == sqllexer.BOOLEAN || t.Type == sqllexer.NULL || t.Type == sqllexer.STRING || t.Type == sqllexer.NUMBER {
|
|
||||||
workingOn.Value = t.Value
|
|
||||||
} else if t.Type == sqllexer.KEYWORD {
|
|
||||||
if strings.ToUpper(t.Value) == "AND" || strings.ToUpper(t.Value) == "OR" {
|
|
||||||
workingOn.Extension = strings.ToUpper(t.Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if workingOn.Key != "" && workingOn.Operator != "" && workingOn.Value != "" {
|
|
||||||
workingJoin.Ons = append(workingJoin.Ons, workingOn)
|
|
||||||
workingOn = Conditional{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selectQuery.Joins = append(selectQuery.Joins, workingJoin)
|
|
||||||
workingJoin = Join{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
344
q/parse_test.go
344
q/parse_test.go
@ -1,344 +0,0 @@
|
|||||||
package q
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ParsingTest struct {
|
|
||||||
input string
|
|
||||||
expected Query
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseSelectStatement_StateMachine(t *testing.T) {
|
|
||||||
var testSqlStatements = []ParsingTest{
|
|
||||||
{
|
|
||||||
input: "SELECT * FROM users WHERE age >= 30",
|
|
||||||
expected: &Select{
|
|
||||||
Type: SELECT,
|
|
||||||
Table: Table{Name: "users"},
|
|
||||||
IsWildcard: true,
|
|
||||||
Conditionals: []Conditional{
|
|
||||||
{
|
|
||||||
Key: "age",
|
|
||||||
Operator: ">=",
|
|
||||||
Value: "30",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "SELECT CustomerName, City FROM Customers",
|
|
||||||
expected: &Select{
|
|
||||||
Type: SELECT,
|
|
||||||
Table: Table{Name: "Customers"},
|
|
||||||
IsWildcard: false,
|
|
||||||
Columns: []Column{
|
|
||||||
{
|
|
||||||
Name: "CustomerName",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "City",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "SELECT DISTINCT username, email FROM users",
|
|
||||||
expected: &Select{
|
|
||||||
Type: SELECT,
|
|
||||||
IsDistinct: true,
|
|
||||||
Table: Table{Name: "users"},
|
|
||||||
IsWildcard: false,
|
|
||||||
Columns: []Column{
|
|
||||||
{
|
|
||||||
Name: "username",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "email",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "SELECT CustomerName AS Customer, City AS town FROM Customers AS People",
|
|
||||||
expected: &Select{
|
|
||||||
Type: SELECT,
|
|
||||||
Table: Table{Name: "Customers", Alias: "People"},
|
|
||||||
IsWildcard: false,
|
|
||||||
Columns: []Column{
|
|
||||||
{
|
|
||||||
Name: "CustomerName",
|
|
||||||
Alias: "Customer",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "City",
|
|
||||||
Alias: "town",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "SELECT * FROM Orders ORDER BY StreetNumber, CountryCode;",
|
|
||||||
expected: &Select{
|
|
||||||
Type: SELECT,
|
|
||||||
Table: Table{Name: "Orders"},
|
|
||||||
IsWildcard: true,
|
|
||||||
OrderBys: []OrderBy{
|
|
||||||
{
|
|
||||||
Key: "StreetNumber",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "CountryCode",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "SELECT * FROM ZipCodes ORDER BY Code ASC, StateName DESC",
|
|
||||||
expected: &Select{
|
|
||||||
Type: SELECT,
|
|
||||||
Table: Table{Name: "ZipCodes"},
|
|
||||||
IsWildcard: true,
|
|
||||||
OrderBys: []OrderBy{
|
|
||||||
{
|
|
||||||
Key: "Code",
|
|
||||||
IsDescend: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "StateName",
|
|
||||||
IsDescend: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "SELECT id, streetNumber AS streetNum, streetName, city, state FROM Addresses WHERE state = 'AL' AND zip > 9000 OR zip <= 12000 ORDER BY zip DESC, streetNumber",
|
|
||||||
expected: &Select{
|
|
||||||
Type: SELECT,
|
|
||||||
Table: Table{Name: "Addresses"},
|
|
||||||
IsWildcard: false,
|
|
||||||
Columns: []Column{
|
|
||||||
{
|
|
||||||
Name: "id",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "streetNumber",
|
|
||||||
Alias: "streetNum",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "streetName",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "city",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "state",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Conditionals: []Conditional{
|
|
||||||
{
|
|
||||||
Key: "state",
|
|
||||||
Operator: "=",
|
|
||||||
Value: "'AL'",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "zip",
|
|
||||||
Operator: ">",
|
|
||||||
Value: "9000",
|
|
||||||
Extension: "AND",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "zip",
|
|
||||||
Operator: "<=",
|
|
||||||
Value: "12000",
|
|
||||||
Extension: "OR",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
OrderBys: []OrderBy{
|
|
||||||
{
|
|
||||||
Key: "zip",
|
|
||||||
IsDescend: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "streetNumber",
|
|
||||||
IsDescend: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "SELECT ProductID, ProductName, CategoryName FROM Products INNER JOIN Categories ON Products.CategoryID = Categories.CategoryID AND Products.SupplierID = Categories.SupplierID LEFT JOIN Stores ON Products.StoreID = Stores.ID ;",
|
|
||||||
expected: &Select{
|
|
||||||
Type: SELECT,
|
|
||||||
Table: Table{Name: "Products"},
|
|
||||||
Columns: []Column{
|
|
||||||
{Name: "ProductID"},
|
|
||||||
{Name: "ProductName"},
|
|
||||||
{Name: "CategoryName"},
|
|
||||||
},
|
|
||||||
Joins: []Join{
|
|
||||||
{
|
|
||||||
Type: INNER,
|
|
||||||
MainTable: Table{
|
|
||||||
Name: "Products",
|
|
||||||
},
|
|
||||||
JoiningTable: Table{
|
|
||||||
Name: "Categories",
|
|
||||||
},
|
|
||||||
Ons: []Conditional{
|
|
||||||
{
|
|
||||||
Key: "Products.CategoryID",
|
|
||||||
Operator: "=",
|
|
||||||
Value: "Categories.CategoryID",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "Products.SupplierID",
|
|
||||||
Operator: "=",
|
|
||||||
Value: "Categories.SupplierID",
|
|
||||||
Extension: "AND",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: LEFT,
|
|
||||||
MainTable: Table{
|
|
||||||
Name: "Products",
|
|
||||||
},
|
|
||||||
JoiningTable: Table{
|
|
||||||
Name: "Stores",
|
|
||||||
},
|
|
||||||
Ons: []Conditional{
|
|
||||||
{
|
|
||||||
Key: "Products.StoreID",
|
|
||||||
Operator: "=",
|
|
||||||
Value: "Stores.ID",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, sql := range testSqlStatements {
|
|
||||||
testName := fmt.Sprintf("%s", sql.input)
|
|
||||||
expected := sql.expected.(*Select)
|
|
||||||
|
|
||||||
t.Run(testName, func(t *testing.T) {
|
|
||||||
answer, err := Parse(sql.input)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
answerAsSelect := answer.(*Select)
|
|
||||||
|
|
||||||
if answerAsSelect.Type != expected.Type {
|
|
||||||
t.Errorf("got %d for Select.Type, expected %d", answerAsSelect.Type, expected.Type)
|
|
||||||
}
|
|
||||||
if answerAsSelect.IsWildcard != expected.IsWildcard {
|
|
||||||
t.Errorf("got %#v for Select.IsWildcard, expected %#v", answerAsSelect.IsWildcard, expected.IsWildcard)
|
|
||||||
}
|
|
||||||
if answerAsSelect.Table.Name != expected.Table.Name {
|
|
||||||
t.Errorf("got %s for Select.Table.Name, expected %s", answerAsSelect.Table.Name, expected.Table.Name)
|
|
||||||
}
|
|
||||||
if answerAsSelect.Table.Alias != expected.Table.Alias {
|
|
||||||
t.Errorf("got %s for Select.Table.Alias, expected %s", answerAsSelect.Table.Alias, expected.Table.Alias)
|
|
||||||
}
|
|
||||||
if answerAsSelect.IsDistinct != expected.IsDistinct {
|
|
||||||
t.Errorf("got %v for Select.IsDistinct, expected %v", answerAsSelect.IsDistinct, expected.IsDistinct)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(answerAsSelect.Columns) != len(expected.Columns) {
|
|
||||||
t.Errorf("got %d number of columns for Select.Columns, expected %d", len(answerAsSelect.Columns), len(expected.Columns))
|
|
||||||
} else {
|
|
||||||
for i, expectedColumn := range expected.Columns {
|
|
||||||
if expectedColumn.Name != answerAsSelect.Columns[i].Name {
|
|
||||||
t.Errorf("got %s for Select.Column[%d].Name, expected %s", answerAsSelect.Columns[i].Name, i, expectedColumn.Name)
|
|
||||||
}
|
|
||||||
if expectedColumn.Alias != answerAsSelect.Columns[i].Alias {
|
|
||||||
t.Errorf("got %s for Select.Column[%d].Alias, expected %s", answerAsSelect.Columns[i].Alias, i, expectedColumn.Alias)
|
|
||||||
}
|
|
||||||
if expectedColumn.AggregateFunction != answerAsSelect.Columns[i].AggregateFunction {
|
|
||||||
t.Errorf("got %d for Select.Column[%d].AggregateFunction, expected %d", answerAsSelect.Columns[i].AggregateFunction, i, expectedColumn.AggregateFunction)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(answerAsSelect.Conditionals) != len(expected.Conditionals) {
|
|
||||||
t.Errorf("got %d number of conditionals for Select.Conditionals, expected %d", len(answerAsSelect.Conditionals), len(expected.Conditionals))
|
|
||||||
} else {
|
|
||||||
for i, expectedCondition := range expected.Conditionals {
|
|
||||||
if expectedCondition.Key != answerAsSelect.Conditionals[i].Key {
|
|
||||||
t.Errorf("got %s for Select.Conditionals[%d].Key, expected %s", answerAsSelect.Conditionals[i].Key, i, expectedCondition.Key)
|
|
||||||
}
|
|
||||||
if expectedCondition.Operator != answerAsSelect.Conditionals[i].Operator {
|
|
||||||
t.Errorf("got %s for Select.Conditionals[%d].Operator, expected %s", answerAsSelect.Conditionals[i].Operator, i, expectedCondition.Operator)
|
|
||||||
}
|
|
||||||
if expectedCondition.Value != answerAsSelect.Conditionals[i].Value {
|
|
||||||
t.Errorf("got %s for Select.Conditionals[%d].Value, expected %s", answerAsSelect.Conditionals[i].Value, i, expectedCondition.Value)
|
|
||||||
}
|
|
||||||
if expectedCondition.Extension != answerAsSelect.Conditionals[i].Extension {
|
|
||||||
t.Errorf("got %s for Select.Conditionals[%d].Extension, expected %s", answerAsSelect.Conditionals[i].Extension, i, expectedCondition.Extension)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(answerAsSelect.OrderBys) != len(expected.OrderBys) {
|
|
||||||
t.Errorf("got %d number of orderBys for Select.OrderBys, expected %d", len(answerAsSelect.OrderBys), len(expected.OrderBys))
|
|
||||||
} else {
|
|
||||||
for i, expectedOrderBy := range expected.OrderBys {
|
|
||||||
if expectedOrderBy.Key != answerAsSelect.OrderBys[i].Key {
|
|
||||||
t.Errorf("got %s for Select.OrderBys[%d].Key, expected %s", answerAsSelect.OrderBys[i].Key, i, expectedOrderBy.Key)
|
|
||||||
}
|
|
||||||
if expectedOrderBy.IsDescend != answerAsSelect.OrderBys[i].IsDescend {
|
|
||||||
t.Errorf("got %#v for Select.OrderBys[%d].IsDescend, expected %#v", answerAsSelect.OrderBys[i].IsDescend, i, expectedOrderBy.IsDescend)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(answerAsSelect.Joins) != len(expected.Joins) {
|
|
||||||
t.Errorf("got %d number of joins for Select.Joinss, expected %d", len(answerAsSelect.Joins), len(expected.Joins))
|
|
||||||
} else {
|
|
||||||
for i, expectedJoin := range expected.Joins {
|
|
||||||
if answerAsSelect.Joins[i].Type != expectedJoin.Type {
|
|
||||||
t.Errorf("got %d for Select.Joins[%d].Type, expected %d", answerAsSelect.Joins[i].Type, i, expectedJoin.Type)
|
|
||||||
}
|
|
||||||
if answerAsSelect.Joins[i].MainTable.Name != expectedJoin.MainTable.Name {
|
|
||||||
t.Errorf("got %s for Select.Joins[%d].MainTable.Name, expected %s", answerAsSelect.Joins[i].MainTable.Name, i, expectedJoin.MainTable.Name)
|
|
||||||
}
|
|
||||||
if answerAsSelect.Joins[i].MainTable.Alias != expectedJoin.MainTable.Alias {
|
|
||||||
t.Errorf("got %s for Select.Joins[%d].MainTable.Alias, expected %s", answerAsSelect.Joins[i].MainTable.Alias, i, expectedJoin.MainTable.Alias)
|
|
||||||
}
|
|
||||||
if answerAsSelect.Joins[i].JoiningTable.Name != expectedJoin.JoiningTable.Name {
|
|
||||||
t.Errorf("got %s for Select.Joins[%d].JoiningTable.Name, expected %s", answerAsSelect.Joins[i].JoiningTable.Name, i, expectedJoin.JoiningTable.Name)
|
|
||||||
}
|
|
||||||
if answerAsSelect.Joins[i].JoiningTable.Alias != expectedJoin.JoiningTable.Alias {
|
|
||||||
t.Errorf("got %s for Select.Joins[%d].JoiningTable.Alias, expected %s", answerAsSelect.Joins[i].JoiningTable.Alias, i, expectedJoin.JoiningTable.Alias)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(answerAsSelect.Joins[i].Ons) != len(expectedJoin.Ons) {
|
|
||||||
t.Errorf("got %d number of ons for Select.Joins.Ons, expected %d", len(answerAsSelect.Joins[i].Ons), len(expectedJoin.Ons))
|
|
||||||
} else {
|
|
||||||
for on_i, expectedCondition := range expected.Joins[i].Ons {
|
|
||||||
if expectedCondition.Key != answerAsSelect.Joins[i].Ons[on_i].Key {
|
|
||||||
t.Errorf("got %s for Select.Conditionals[%d].Key, expected %s", answerAsSelect.Joins[i].Ons[on_i].Key, on_i, expectedCondition.Key)
|
|
||||||
}
|
|
||||||
if expectedCondition.Operator != answerAsSelect.Joins[i].Ons[on_i].Operator {
|
|
||||||
t.Errorf("got %s for Select.Conditionals[%d].Operator, expected %s", answerAsSelect.Joins[i].Ons[on_i].Operator, on_i, expectedCondition.Operator)
|
|
||||||
}
|
|
||||||
if expectedCondition.Value != answerAsSelect.Joins[i].Ons[on_i].Value {
|
|
||||||
t.Errorf("got %s for Select.Conditionals[%d].Value, expected %s", answerAsSelect.Joins[i].Ons[on_i].Value, on_i, expectedCondition.Value)
|
|
||||||
}
|
|
||||||
if expectedCondition.Extension != answerAsSelect.Joins[i].Ons[on_i].Extension {
|
|
||||||
t.Errorf("got %s for Select.Conditionals[%d].Extension, expected %s", answerAsSelect.Joins[i].Ons[on_i].Extension, on_i, expectedCondition.Extension)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
520
q/select.go
520
q/select.go
@ -7,244 +7,362 @@ import (
|
|||||||
"github.com/DataDog/go-sqllexer"
|
"github.com/DataDog/go-sqllexer"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetFullStringFromColumn(column Column) string {
|
func parseSelectStatement(p *parser) error {
|
||||||
var workingSlice string
|
selectQuery := p.query.(*Select)
|
||||||
|
|
||||||
if column.AggregateFunction > 0 {
|
distinctErr := parseDistinct(p)
|
||||||
workingSlice = fmt.Sprintf(
|
if distinctErr != nil {
|
||||||
"%s(%s)",
|
return distinctErr
|
||||||
AggregateFunctionTypeString(column.AggregateFunction),
|
|
||||||
column.Name,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
workingSlice = column.Name
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if column.Alias != "" {
|
foundWildcard, _ := p.findToken(func(t Token) bool {
|
||||||
workingSlice += fmt.Sprintf(" AS %s", column.Alias)
|
return t.Type == sqllexer.WILDCARD
|
||||||
}
|
})
|
||||||
|
|
||||||
return workingSlice
|
selectQuery.IsWildcard = foundWildcard != nil
|
||||||
|
|
||||||
}
|
if !selectQuery.IsWildcard {
|
||||||
|
err := parseSelectColumns(p)
|
||||||
func (q *Select) GetFullSql() string {
|
if err != nil {
|
||||||
var workingSqlSlice []string
|
fmt.Println(err.Error())
|
||||||
|
return err
|
||||||
workingSqlSlice = append(workingSqlSlice, "SELECT")
|
|
||||||
|
|
||||||
if q.IsWildcard {
|
|
||||||
workingSqlSlice = append(workingSqlSlice, "*")
|
|
||||||
} else {
|
|
||||||
for i, column := range q.Columns {
|
|
||||||
if i < (len(q.Columns) - 1) {
|
|
||||||
workingSqlSlice = append(workingSqlSlice, GetFullStringFromColumn(column)+",")
|
|
||||||
} else {
|
|
||||||
workingSqlSlice = append(workingSqlSlice, GetFullStringFromColumn(column))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
workingSqlSlice = append(workingSqlSlice, "FROM "+q.Table.Name) // TODO: figure out what to do from alias
|
tableErr := parseSelectTable(p)
|
||||||
|
if tableErr != nil {
|
||||||
for _, condition := range q.Conditionals {
|
return tableErr
|
||||||
workingSqlSlice = append(workingSqlSlice, condition.Key)
|
|
||||||
workingSqlSlice = append(workingSqlSlice, condition.Operator)
|
|
||||||
workingSqlSlice = append(workingSqlSlice, condition.Value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fullSql := strings.Join(workingSqlSlice, " ")
|
conditionalsErr := parseSelectConditionals(p)
|
||||||
|
if conditionalsErr != nil {
|
||||||
|
return conditionalsErr
|
||||||
|
}
|
||||||
|
|
||||||
return fullSql
|
ordersByErr := parseOrderBys(p)
|
||||||
|
if ordersByErr != nil {
|
||||||
|
return ordersByErr
|
||||||
|
}
|
||||||
|
|
||||||
|
parseJoins(p)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseSelectStatement(sql string) Select {
|
func parseDistinct(p *parser) error {
|
||||||
query := Select{}
|
selectQuery := p.query.(*Select)
|
||||||
|
|
||||||
passedSELECT := false
|
foundDistinctKeyword, _ := p.findToken(func(t Token) bool {
|
||||||
passedColumns := false
|
return t.Type == sqllexer.KEYWORD && strings.ToUpper(t.Value) == "DISTINCT"
|
||||||
passedFROM := false
|
})
|
||||||
passedTable := false
|
|
||||||
passedWHERE := false
|
|
||||||
passedConditionals := false
|
|
||||||
passedOrderByKeywords := false
|
|
||||||
passesOrderByColumns := false
|
|
||||||
|
|
||||||
lookBehindBuffer := [10]sqllexer.Token{}
|
if foundDistinctKeyword != nil {
|
||||||
var workingConditional = Conditional{}
|
selectQuery.IsDistinct = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSelectColumns(p *parser) error {
|
||||||
|
selectQuery := p.query.(*Select)
|
||||||
|
|
||||||
|
_, selectCommandIndex := p.findToken(func(t Token) bool {
|
||||||
|
return t.Type == sqllexer.COMMAND && strings.ToUpper(t.Value) == "SELECT"
|
||||||
|
})
|
||||||
|
|
||||||
|
_, fromKeywordIndex := p.findToken(func(t Token) bool {
|
||||||
|
return t.Type == sqllexer.KEYWORD && strings.ToUpper(t.Value) == "FROM"
|
||||||
|
})
|
||||||
|
|
||||||
|
if selectCommandIndex < 0 || fromKeywordIndex < 0 {
|
||||||
|
return fmt.Errorf("Could not find range between SELECT and FROM")
|
||||||
|
}
|
||||||
|
|
||||||
|
lookBehindBuffer := [10]Token{}
|
||||||
var workingColumn Column
|
var workingColumn Column
|
||||||
|
columns := make([]Column, 0)
|
||||||
|
|
||||||
var columns []Column
|
startRange := selectCommandIndex + 1
|
||||||
var orderBys []OrderBy
|
endRange := fromKeywordIndex - 1
|
||||||
lexer := sqllexer.New(sql)
|
|
||||||
for {
|
|
||||||
token := lexer.Scan()
|
|
||||||
|
|
||||||
if !passedSELECT && strings.ToUpper(token.Value) != "SELECT" {
|
for i := startRange; i <= endRange; i++ {
|
||||||
break
|
token := p.tokens[i]
|
||||||
} else if !passedSELECT {
|
|
||||||
passedSELECT = true
|
if token.Type == sqllexer.FUNCTION {
|
||||||
|
unshiftBuffer(&lookBehindBuffer, token)
|
||||||
|
workingColumn.AggregateFunction = AggregateFunctionTypeByName(token.Value)
|
||||||
continue
|
continue
|
||||||
}
|
} else if token.Type == sqllexer.PUNCTUATION && token.Value == "," {
|
||||||
|
columns = append(columns, workingColumn)
|
||||||
|
workingColumn = Column{}
|
||||||
|
continue
|
||||||
|
} else if token.Type == sqllexer.IDENT {
|
||||||
|
unshiftBuffer(&lookBehindBuffer, token)
|
||||||
|
|
||||||
// For any keywords that are before the columns or wildcard
|
if lookBehindBuffer[1].Type == sqllexer.ALIAS_INDICATOR {
|
||||||
if passedSELECT && len(columns) == 0 && !passedColumns {
|
workingColumn.Alias = token.Value
|
||||||
if token.Type == sqllexer.KEYWORD {
|
|
||||||
switch strings.ToUpper(token.Value) {
|
|
||||||
case "DISTINCT":
|
|
||||||
query.IsDistinct = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !passedColumns {
|
|
||||||
if token.Type == sqllexer.WILDCARD {
|
|
||||||
passedColumns = true
|
|
||||||
columns = make([]Column, 0)
|
|
||||||
query.IsWildcard = true
|
|
||||||
continue
|
|
||||||
} else if token.Type == sqllexer.FUNCTION {
|
|
||||||
unshiftBuffer(&lookBehindBuffer, *token)
|
|
||||||
workingColumn.AggregateFunction = AggregateFunctionTypeByName(token.Value)
|
|
||||||
continue
|
|
||||||
} else if token.Type == sqllexer.PUNCTUATION {
|
|
||||||
if token.Value == "," {
|
|
||||||
columns = append(columns, workingColumn)
|
|
||||||
workingColumn = Column{}
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
} else if token.Type == sqllexer.IDENT {
|
|
||||||
unshiftBuffer(&lookBehindBuffer, *token)
|
|
||||||
|
|
||||||
if lookBehindBuffer[0].Type == sqllexer.ALIAS_INDICATOR {
|
|
||||||
workingColumn.Alias = token.Value
|
|
||||||
} else {
|
|
||||||
workingColumn.Name = token.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
} else if token.Type == sqllexer.ALIAS_INDICATOR {
|
|
||||||
unshiftBuffer(&lookBehindBuffer, *token)
|
|
||||||
continue
|
|
||||||
} else if token.Type == sqllexer.SPACE {
|
|
||||||
continue
|
|
||||||
} else {
|
} else {
|
||||||
if workingColumn.Name != "" {
|
workingColumn.Name = token.Value
|
||||||
columns = append(columns, workingColumn)
|
}
|
||||||
workingColumn = Column{}
|
continue
|
||||||
|
} else if token.Type == sqllexer.ALIAS_INDICATOR {
|
||||||
|
unshiftBuffer(&lookBehindBuffer, token)
|
||||||
|
continue
|
||||||
|
} else if i == endRange {
|
||||||
|
if workingColumn.Name != "" {
|
||||||
|
columns = append(columns, workingColumn)
|
||||||
|
workingColumn = Column{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectQuery.Columns = columns
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSelectTable(p *parser) error {
|
||||||
|
selectQuery := p.query.(*Select)
|
||||||
|
|
||||||
|
_, fromKeywordIndex := p.findToken(func(t Token) bool {
|
||||||
|
return t.Type == sqllexer.KEYWORD && strings.ToUpper(t.Value) == "FROM"
|
||||||
|
})
|
||||||
|
|
||||||
|
if fromKeywordIndex < 0 {
|
||||||
|
return fmt.Errorf("Could not FROM keyword to look for table name")
|
||||||
|
}
|
||||||
|
|
||||||
|
var foundTable Table
|
||||||
|
|
||||||
|
for i := fromKeywordIndex + 1; i < len(p.tokens); i++ {
|
||||||
|
t := &p.tokens[i]
|
||||||
|
if foundTable.Name == "" && t.Type == sqllexer.IDENT {
|
||||||
|
foundTable.Name = p.tokens[i].Value
|
||||||
|
continue
|
||||||
|
} else if t.Type == sqllexer.IDENT {
|
||||||
|
foundTable.Alias = p.tokens[i].Value
|
||||||
|
break
|
||||||
|
} else if t.Type == sqllexer.SPACE || t.Type == sqllexer.ALIAS_INDICATOR {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if foundTable.Name == "" {
|
||||||
|
return fmt.Errorf("Could not find table name")
|
||||||
|
}
|
||||||
|
|
||||||
|
selectQuery.Table = foundTable
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSelectConditionals(p *parser) error {
|
||||||
|
selectQuery := p.query.(*Select)
|
||||||
|
|
||||||
|
_, whereKeywordIndex := p.findToken(func(t Token) bool {
|
||||||
|
return t.Type == sqllexer.KEYWORD && strings.ToUpper(t.Value) == "WHERE"
|
||||||
|
})
|
||||||
|
|
||||||
|
if whereKeywordIndex < 0 {
|
||||||
|
return nil // fmt.Errorf("Could not find WHERE to look for conditionals")
|
||||||
|
}
|
||||||
|
|
||||||
|
var workingConditional Conditional
|
||||||
|
for i := whereKeywordIndex + 1; i < len(p.tokens); i++ {
|
||||||
|
t := &p.tokens[i]
|
||||||
|
|
||||||
|
if t.Type == sqllexer.KEYWORD && strings.ToUpper(t.Value) != "AND" && strings.ToUpper(t.Value) != "OR" && strings.ToUpper(t.Value) != "NOT" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Type == sqllexer.IDENT {
|
||||||
|
workingConditional.Key = t.Value
|
||||||
|
} else if t.Type == sqllexer.OPERATOR {
|
||||||
|
workingConditional.Operator = t.Value
|
||||||
|
} else if t.Type == sqllexer.BOOLEAN || t.Type == sqllexer.NULL || t.Type == sqllexer.STRING || t.Type == sqllexer.NUMBER {
|
||||||
|
workingConditional.Value = t.Value
|
||||||
|
} else if t.Type == sqllexer.KEYWORD {
|
||||||
|
if strings.ToUpper(t.Value) == "AND" || strings.ToUpper(t.Value) == "OR" {
|
||||||
|
workingConditional.Extension = strings.ToUpper(t.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if workingConditional.Key != "" && workingConditional.Operator != "" && workingConditional.Value != "" {
|
||||||
|
selectQuery.Conditionals = append(selectQuery.Conditionals, workingConditional)
|
||||||
|
workingConditional = Conditional{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseOrderBys(p *parser) error {
|
||||||
|
selectQuery := p.query.(*Select)
|
||||||
|
|
||||||
|
_, byKeywordIndex := p.findToken(func(t Token) bool {
|
||||||
|
return t.Type == sqllexer.KEYWORD && strings.ToUpper(t.Value) == "BY"
|
||||||
|
})
|
||||||
|
|
||||||
|
if byKeywordIndex < 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var orderBys []OrderBy
|
||||||
|
|
||||||
|
var workingOrderBy OrderBy
|
||||||
|
for i := byKeywordIndex + 1; i < len(p.tokens); i++ {
|
||||||
|
t := &p.tokens[i]
|
||||||
|
if t.Type == sqllexer.SPACE {
|
||||||
|
continue
|
||||||
|
} else if t.Type == sqllexer.IDENT && workingOrderBy.Key == "" {
|
||||||
|
workingOrderBy.Key = t.Value
|
||||||
|
continue
|
||||||
|
} else if t.Type == sqllexer.IDENT && workingOrderBy.Key != "" {
|
||||||
|
orderBys = append(orderBys, workingOrderBy)
|
||||||
|
workingOrderBy.Key = t.Value
|
||||||
|
continue
|
||||||
|
} else if t.Type == sqllexer.KEYWORD {
|
||||||
|
if t.Value == "DESC" {
|
||||||
|
workingOrderBy.IsDescend = true
|
||||||
|
} else if t.Value != "ASC" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else if t.Type == sqllexer.PUNCTUATION {
|
||||||
|
orderBys = append(orderBys, workingOrderBy)
|
||||||
|
workingOrderBy = OrderBy{}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if workingOrderBy.Key != "" {
|
||||||
|
orderBys = append(orderBys, workingOrderBy)
|
||||||
|
}
|
||||||
|
|
||||||
|
selectQuery.OrderBys = orderBys
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseJoins(p *parser) error {
|
||||||
|
selectQuery := p.query.(*Select)
|
||||||
|
|
||||||
|
foundJoinKeywords := p.findAllTokens(func(t Token) bool {
|
||||||
|
return t.Type == sqllexer.COMMAND && strings.ToUpper(t.Value) == "JOIN"
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(foundJoinKeywords) <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type FoundJoinSubslices struct {
|
||||||
|
Tokens []Token
|
||||||
|
StartingIndexInGreaterStatement int
|
||||||
|
}
|
||||||
|
|
||||||
|
var joinTokenRanges []FoundJoinSubslices
|
||||||
|
|
||||||
|
for i, foundJoin := range foundJoinKeywords {
|
||||||
|
|
||||||
|
startRangeIndex := foundJoin.Index
|
||||||
|
var endRangeIndex int
|
||||||
|
|
||||||
|
if i == (len(foundJoinKeywords) - 1) {
|
||||||
|
endRangeIndex = len(p.tokens) - 1
|
||||||
|
} else {
|
||||||
|
endRangeIndex = foundJoinKeywords[i+1].Index
|
||||||
|
}
|
||||||
|
|
||||||
|
joinTokenRanges = append(joinTokenRanges, FoundJoinSubslices{
|
||||||
|
Tokens: p.tokens[startRangeIndex:endRangeIndex],
|
||||||
|
StartingIndexInGreaterStatement: startRangeIndex,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, joinRange := range joinTokenRanges {
|
||||||
|
var workingJoin Join
|
||||||
|
workingJoin.MainTable = selectQuery.Table
|
||||||
|
|
||||||
|
// check for the join type by looking backwards in the greater statement
|
||||||
|
joinTypeSearchIndex := joinRange.StartingIndexInGreaterStatement - 1
|
||||||
|
for ; joinTypeSearchIndex >= 0; joinTypeSearchIndex-- {
|
||||||
|
if p.tokens[joinTypeSearchIndex].Type == sqllexer.KEYWORD {
|
||||||
|
switch strings.ToUpper(p.tokens[joinTypeSearchIndex].Value) {
|
||||||
|
case "LEFT":
|
||||||
|
workingJoin.Type = LEFT
|
||||||
|
break
|
||||||
|
case "RIGHT":
|
||||||
|
workingJoin.Type = RIGHT
|
||||||
|
break
|
||||||
|
case "FULL":
|
||||||
|
workingJoin.Type = FULL
|
||||||
|
break
|
||||||
|
case "SELF":
|
||||||
|
workingJoin.Type = SELF
|
||||||
|
break
|
||||||
|
case "INNER":
|
||||||
|
workingJoin.Type = INNER
|
||||||
|
default:
|
||||||
|
workingJoin.Type = INNER
|
||||||
}
|
}
|
||||||
|
break // Stop after finding first keyword
|
||||||
passedColumns = true
|
|
||||||
query.Columns = columns
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make sure to check for other keywords that are allowed
|
// Find joined table name
|
||||||
if !passedFROM && strings.ToUpper(token.Value) == "FROM" {
|
for i := 1; i < len(joinRange.Tokens); i++ {
|
||||||
passedFROM = true
|
if joinRange.Tokens[i].Type == sqllexer.IDENT {
|
||||||
continue
|
workingJoin.JoiningTable.Name = joinRange.Tokens[i].Value
|
||||||
}
|
break // Stop after finding first IDENT
|
||||||
|
// TODO: make sure you dont have to check for aliases
|
||||||
if !passedTable && token.Type == sqllexer.IDENT {
|
|
||||||
passedTable = true
|
|
||||||
query.Table.Name = token.Value
|
|
||||||
continue
|
|
||||||
} else if !passedTable {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !passedWHERE && token.Type == sqllexer.KEYWORD && strings.ToUpper(token.Value) == "WHERE" {
|
|
||||||
passedWHERE = true
|
|
||||||
continue
|
|
||||||
} else if !passedWHERE && token.Type == sqllexer.KEYWORD && strings.ToUpper(token.Value) != "WHERE" {
|
|
||||||
passedWHERE = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if passedWHERE && !passedConditionals {
|
|
||||||
if token.Type == sqllexer.KEYWORD && strings.ToUpper(token.Value) != "AND" && strings.ToUpper(token.Value) != "OR" && strings.ToUpper(token.Value) != "NOT" {
|
|
||||||
passedConditionals = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if passedWHERE && !passedConditionals {
|
|
||||||
if token.Type == sqllexer.IDENT {
|
|
||||||
workingConditional.Key = token.Value
|
|
||||||
} else if token.Type == sqllexer.OPERATOR {
|
|
||||||
workingConditional.Operator = token.Value
|
|
||||||
} else if token.Type == sqllexer.BOOLEAN || token.Type == sqllexer.NULL || token.Type == sqllexer.STRING || token.Type == sqllexer.NUMBER {
|
|
||||||
workingConditional.Value = token.Value
|
|
||||||
} else if token.Type == sqllexer.KEYWORD {
|
|
||||||
if strings.ToUpper(token.Value) == "AND" || strings.ToUpper(token.Value) == "OR" {
|
|
||||||
workingConditional.Extension = strings.ToUpper(token.Value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if workingConditional.Key != "" && workingConditional.Operator != "" && workingConditional.Value != "" {
|
}
|
||||||
query.Conditionals = append(query.Conditionals, workingConditional)
|
|
||||||
workingConditional = Conditional{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if IsTokenEndOfStatement(token) {
|
//var ons []Conditional
|
||||||
|
var workingOn Conditional
|
||||||
|
|
||||||
|
_, foundOnTokenIndex := FindTokenInArray(joinRange.Tokens, func(t Token) bool {
|
||||||
|
return t.Type == sqllexer.KEYWORD && strings.ToUpper(t.Value) == "ON"
|
||||||
|
})
|
||||||
|
|
||||||
|
if foundOnTokenIndex < 0 {
|
||||||
|
selectQuery.Joins = append(selectQuery.Joins, workingJoin)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := foundOnTokenIndex + 1; i < len(joinRange.Tokens); i++ {
|
||||||
|
t := &joinRange.Tokens[i]
|
||||||
|
|
||||||
|
if t.Type == sqllexer.KEYWORD && strings.ToUpper(t.Value) != "AND" && strings.ToUpper(t.Value) != "OR" && strings.ToUpper(t.Value) != "NOT" {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
continue
|
if t.Type == sqllexer.IDENT {
|
||||||
}
|
if workingOn.Key == "" {
|
||||||
|
workingOn.Key = t.Value
|
||||||
// Checking For ORDER BY
|
} else {
|
||||||
if passedConditionals && !passedOrderByKeywords && token.Type == sqllexer.KEYWORD {
|
workingOn.Value = t.Value
|
||||||
unshiftBuffer(&lookBehindBuffer, *token)
|
|
||||||
|
|
||||||
if strings.ToUpper(lookBehindBuffer[1].Value) == "ORDER" && strings.ToUpper(lookBehindBuffer[0].Value) == "BY" {
|
|
||||||
passedOrderByKeywords = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if passedOrderByKeywords && !passesOrderByColumns {
|
|
||||||
|
|
||||||
if token.Type == sqllexer.IDENT || token.Type == sqllexer.KEYWORD {
|
|
||||||
unshiftBuffer(&lookBehindBuffer, *token)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if token.Type == sqllexer.PUNCTUATION || token.Type == sqllexer.EOF {
|
|
||||||
|
|
||||||
var orderByColumnName string
|
|
||||||
var directionKeyword string
|
|
||||||
var isDescend bool = false
|
|
||||||
|
|
||||||
if lookBehindBuffer[0].Type == sqllexer.KEYWORD {
|
|
||||||
orderByColumnName = lookBehindBuffer[1].Value
|
|
||||||
directionKeyword = lookBehindBuffer[0].Value
|
|
||||||
} else if lookBehindBuffer[0].Type == sqllexer.IDENT {
|
|
||||||
orderByColumnName = lookBehindBuffer[0].Value
|
|
||||||
}
|
}
|
||||||
|
} else if t.Type == sqllexer.OPERATOR {
|
||||||
if strings.ToUpper(directionKeyword) == "DESC" {
|
workingOn.Operator = t.Value
|
||||||
isDescend = true
|
} else if t.Type == sqllexer.BOOLEAN || t.Type == sqllexer.NULL || t.Type == sqllexer.STRING || t.Type == sqllexer.NUMBER {
|
||||||
}
|
workingOn.Value = t.Value
|
||||||
|
} else if t.Type == sqllexer.KEYWORD {
|
||||||
orderBys = append(orderBys, OrderBy{
|
if strings.ToUpper(t.Value) == "AND" || strings.ToUpper(t.Value) == "OR" {
|
||||||
Key: orderByColumnName,
|
workingOn.Extension = strings.ToUpper(t.Value)
|
||||||
IsDescend: isDescend,
|
|
||||||
})
|
|
||||||
|
|
||||||
if IsTokenEndOfStatement(token) {
|
|
||||||
query.OrderBys = orderBys
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if workingOn.Key != "" && workingOn.Operator != "" && workingOn.Value != "" {
|
||||||
|
workingJoin.Ons = append(workingJoin.Ons, workingOn)
|
||||||
|
workingOn = Conditional{}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
query.OrderBys = orderBys
|
selectQuery.Joins = append(selectQuery.Joins, workingJoin)
|
||||||
|
workingJoin = Join{}
|
||||||
if IsTokenEndOfStatement(token) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return query
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
242
q/select_test.go
242
q/select_test.go
@ -5,11 +5,17 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseSelectStatement(t *testing.T) {
|
type ParsingTest struct {
|
||||||
|
input string
|
||||||
|
expected Query
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseSelectStatement_StateMachine(t *testing.T) {
|
||||||
var testSqlStatements = []ParsingTest{
|
var testSqlStatements = []ParsingTest{
|
||||||
{
|
{
|
||||||
input: "SELECT * FROM users WHERE age >= 30",
|
input: "SELECT * FROM users WHERE age >= 30",
|
||||||
expected: &Select{
|
expected: &Select{
|
||||||
|
Type: SELECT,
|
||||||
Table: Table{Name: "users"},
|
Table: Table{Name: "users"},
|
||||||
IsWildcard: true,
|
IsWildcard: true,
|
||||||
Conditionals: []Conditional{
|
Conditionals: []Conditional{
|
||||||
@ -24,6 +30,7 @@ func TestParseSelectStatement(t *testing.T) {
|
|||||||
{
|
{
|
||||||
input: "SELECT CustomerName, City FROM Customers",
|
input: "SELECT CustomerName, City FROM Customers",
|
||||||
expected: &Select{
|
expected: &Select{
|
||||||
|
Type: SELECT,
|
||||||
Table: Table{Name: "Customers"},
|
Table: Table{Name: "Customers"},
|
||||||
IsWildcard: false,
|
IsWildcard: false,
|
||||||
Columns: []Column{
|
Columns: []Column{
|
||||||
@ -37,12 +44,36 @@ func TestParseSelectStatement(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "SELECT DISTINCT Country FROM Nations;",
|
input: "SELECT DISTINCT username, email FROM users",
|
||||||
expected: &Select{
|
expected: &Select{
|
||||||
Table: Table{Name: "Nations"},
|
Type: SELECT,
|
||||||
|
IsDistinct: true,
|
||||||
|
Table: Table{Name: "users"},
|
||||||
|
IsWildcard: false,
|
||||||
Columns: []Column{
|
Columns: []Column{
|
||||||
{
|
{
|
||||||
Name: "Country",
|
Name: "username",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "email",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "SELECT CustomerName AS Customer, City AS town FROM Customers AS People",
|
||||||
|
expected: &Select{
|
||||||
|
Type: SELECT,
|
||||||
|
Table: Table{Name: "Customers", Alias: "People"},
|
||||||
|
IsWildcard: false,
|
||||||
|
Columns: []Column{
|
||||||
|
{
|
||||||
|
Name: "CustomerName",
|
||||||
|
Alias: "Customer",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "City",
|
||||||
|
Alias: "town",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -50,6 +81,7 @@ func TestParseSelectStatement(t *testing.T) {
|
|||||||
{
|
{
|
||||||
input: "SELECT * FROM Orders ORDER BY StreetNumber, CountryCode;",
|
input: "SELECT * FROM Orders ORDER BY StreetNumber, CountryCode;",
|
||||||
expected: &Select{
|
expected: &Select{
|
||||||
|
Type: SELECT,
|
||||||
Table: Table{Name: "Orders"},
|
Table: Table{Name: "Orders"},
|
||||||
IsWildcard: true,
|
IsWildcard: true,
|
||||||
OrderBys: []OrderBy{
|
OrderBys: []OrderBy{
|
||||||
@ -65,6 +97,7 @@ func TestParseSelectStatement(t *testing.T) {
|
|||||||
{
|
{
|
||||||
input: "SELECT * FROM ZipCodes ORDER BY Code ASC, StateName DESC",
|
input: "SELECT * FROM ZipCodes ORDER BY Code ASC, StateName DESC",
|
||||||
expected: &Select{
|
expected: &Select{
|
||||||
|
Type: SELECT,
|
||||||
Table: Table{Name: "ZipCodes"},
|
Table: Table{Name: "ZipCodes"},
|
||||||
IsWildcard: true,
|
IsWildcard: true,
|
||||||
OrderBys: []OrderBy{
|
OrderBys: []OrderBy{
|
||||||
@ -80,8 +113,9 @@ func TestParseSelectStatement(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "SELECT id, streetNumber, streetName, city, state FROM Addresses WHERE state = 'AL' AND zip > 9000 OR zip <= 12000 ORDER BY zip DESC, streetNumber",
|
input: "SELECT id, streetNumber AS streetNum, streetName, city, state FROM Addresses WHERE state = 'AL' AND zip > 9000 OR zip <= 12000 ORDER BY zip DESC, streetNumber",
|
||||||
expected: &Select{
|
expected: &Select{
|
||||||
|
Type: SELECT,
|
||||||
Table: Table{Name: "Addresses"},
|
Table: Table{Name: "Addresses"},
|
||||||
IsWildcard: false,
|
IsWildcard: false,
|
||||||
Columns: []Column{
|
Columns: []Column{
|
||||||
@ -89,7 +123,8 @@ func TestParseSelectStatement(t *testing.T) {
|
|||||||
Name: "id",
|
Name: "id",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "streetNumber",
|
Name: "streetNumber",
|
||||||
|
Alias: "streetNum",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "streetName",
|
Name: "streetName",
|
||||||
@ -132,32 +167,58 @@ func TestParseSelectStatement(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// input: "SELECT ProductID, ProductName, CategoryName FROM Products INNER JOIN Categories ON Products.CategoryID = Categories.CategoryID; ",
|
input: "SELECT ProductID, ProductName, CategoryName FROM Products INNER JOIN Categories ON Products.CategoryID = Categories.CategoryID AND Products.SupplierID = Categories.SupplierID LEFT JOIN Stores ON Products.StoreID = Stores.ID ;",
|
||||||
// expected: Select{
|
expected: &Select{
|
||||||
// Table: "Products",
|
Type: SELECT,
|
||||||
// Columns: []Column{
|
Table: Table{Name: "Products"},
|
||||||
// {Name: "ProductID"},
|
Columns: []Column{
|
||||||
// {Name: "ProductName"},
|
{Name: "ProductID"},
|
||||||
// {Name: "CategoryName"},
|
{Name: "ProductName"},
|
||||||
// },
|
{Name: "CategoryName"},
|
||||||
// Joins: []Join{
|
},
|
||||||
// {
|
Joins: []Join{
|
||||||
// Type: INNER,
|
{
|
||||||
// Table: Table{
|
Type: INNER,
|
||||||
// Name: "Categories",
|
MainTable: Table{
|
||||||
// },
|
Name: "Products",
|
||||||
// Ons: []Conditional{
|
},
|
||||||
// {
|
JoiningTable: Table{
|
||||||
// Key: "Products.CategoryID",
|
Name: "Categories",
|
||||||
// Operator: "=",
|
},
|
||||||
// Value: "Categories.CategoryID",
|
Ons: []Conditional{
|
||||||
// },
|
{
|
||||||
// },
|
Key: "Products.CategoryID",
|
||||||
// },
|
Operator: "=",
|
||||||
// },
|
Value: "Categories.CategoryID",
|
||||||
// },
|
},
|
||||||
// },
|
{
|
||||||
|
Key: "Products.SupplierID",
|
||||||
|
Operator: "=",
|
||||||
|
Value: "Categories.SupplierID",
|
||||||
|
Extension: "AND",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: LEFT,
|
||||||
|
MainTable: Table{
|
||||||
|
Name: "Products",
|
||||||
|
},
|
||||||
|
JoiningTable: Table{
|
||||||
|
Name: "Stores",
|
||||||
|
},
|
||||||
|
Ons: []Conditional{
|
||||||
|
{
|
||||||
|
Key: "Products.StoreID",
|
||||||
|
Operator: "=",
|
||||||
|
Value: "Stores.ID",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, sql := range testSqlStatements {
|
for _, sql := range testSqlStatements {
|
||||||
@ -165,74 +226,119 @@ func TestParseSelectStatement(t *testing.T) {
|
|||||||
expected := sql.expected.(*Select)
|
expected := sql.expected.(*Select)
|
||||||
|
|
||||||
t.Run(testName, func(t *testing.T) {
|
t.Run(testName, func(t *testing.T) {
|
||||||
answer := ParseSelectStatement(sql.input)
|
answer, err := Parse(sql.input)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if answer.Table.Alias != expected.Table.Alias {
|
answerAsSelect := answer.(*Select)
|
||||||
t.Errorf("got %s for Select.Table.Alias, expected %s", answer.Table.Alias, expected.Table.Alias)
|
|
||||||
|
if answerAsSelect.Type != expected.Type {
|
||||||
|
t.Errorf("got %d for Select.Type, expected %d", answerAsSelect.Type, expected.Type)
|
||||||
}
|
}
|
||||||
if answer.Table.Name != expected.Table.Name {
|
if answerAsSelect.IsWildcard != expected.IsWildcard {
|
||||||
t.Errorf("got %s for Select.Table.Name, expected %s", answer.Table.Name, expected.Table.Name)
|
t.Errorf("got %#v for Select.IsWildcard, expected %#v", answerAsSelect.IsWildcard, expected.IsWildcard)
|
||||||
}
|
}
|
||||||
if answer.IsWildcard != expected.IsWildcard {
|
if answerAsSelect.Table.Name != expected.Table.Name {
|
||||||
t.Errorf("got %#v for Select.IsWildcard, expected %#v", answer.IsWildcard, expected.IsWildcard)
|
t.Errorf("got %s for Select.Table.Name, expected %s", answerAsSelect.Table.Name, expected.Table.Name)
|
||||||
}
|
}
|
||||||
if len(answer.Columns) != len(expected.Columns) {
|
if answerAsSelect.Table.Alias != expected.Table.Alias {
|
||||||
t.Errorf("got %d number of columns for Select.Columns, expected %d", len(answer.Columns), len(expected.Columns))
|
t.Errorf("got %s for Select.Table.Alias, expected %s", answerAsSelect.Table.Alias, expected.Table.Alias)
|
||||||
|
}
|
||||||
|
if answerAsSelect.IsDistinct != expected.IsDistinct {
|
||||||
|
t.Errorf("got %v for Select.IsDistinct, expected %v", answerAsSelect.IsDistinct, expected.IsDistinct)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(answerAsSelect.Columns) != len(expected.Columns) {
|
||||||
|
t.Errorf("got %d number of columns for Select.Columns, expected %d", len(answerAsSelect.Columns), len(expected.Columns))
|
||||||
} else {
|
} else {
|
||||||
for i, expectedColumn := range expected.Columns {
|
for i, expectedColumn := range expected.Columns {
|
||||||
if expectedColumn.Name != answer.Columns[i].Name {
|
if expectedColumn.Name != answerAsSelect.Columns[i].Name {
|
||||||
t.Errorf("got %s for Select.Column[%d].Name, expected %s", answer.Columns[i].Name, i, expectedColumn.Name)
|
t.Errorf("got %s for Select.Column[%d].Name, expected %s", answerAsSelect.Columns[i].Name, i, expectedColumn.Name)
|
||||||
}
|
}
|
||||||
if expectedColumn.Alias != answer.Columns[i].Alias {
|
if expectedColumn.Alias != answerAsSelect.Columns[i].Alias {
|
||||||
t.Errorf("got %s for Select.Column[%ORDER].Alias, expected %s", answer.Columns[i].Alias, i, expectedColumn.Alias)
|
t.Errorf("got %s for Select.Column[%d].Alias, expected %s", answerAsSelect.Columns[i].Alias, i, expectedColumn.Alias)
|
||||||
}
|
}
|
||||||
if expectedColumn.AggregateFunction != answer.Columns[i].AggregateFunction {
|
if expectedColumn.AggregateFunction != answerAsSelect.Columns[i].AggregateFunction {
|
||||||
t.Errorf("got %d for Select.Column[%d].AggregateFunction, expected %d", answer.Columns[i].AggregateFunction, i, expectedColumn.AggregateFunction)
|
t.Errorf("got %d for Select.Column[%d].AggregateFunction, expected %d", answerAsSelect.Columns[i].AggregateFunction, i, expectedColumn.AggregateFunction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(answer.Conditionals) != len(expected.Conditionals) {
|
if len(answerAsSelect.Conditionals) != len(expected.Conditionals) {
|
||||||
t.Errorf("got %d number of conditionals for Select.Conditionals, expected %d", len(answer.Conditionals), len(expected.Conditionals))
|
t.Errorf("got %d number of conditionals for Select.Conditionals, expected %d", len(answerAsSelect.Conditionals), len(expected.Conditionals))
|
||||||
} else {
|
} else {
|
||||||
for i, expectedCondition := range expected.Conditionals {
|
for i, expectedCondition := range expected.Conditionals {
|
||||||
if expectedCondition.Key != answer.Conditionals[i].Key {
|
if expectedCondition.Key != answerAsSelect.Conditionals[i].Key {
|
||||||
t.Errorf("got %s for Select.Conditionals[%d].Key, expected %s", answer.Conditionals[i].Key, i, expectedCondition.Key)
|
t.Errorf("got %s for Select.Conditionals[%d].Key, expected %s", answerAsSelect.Conditionals[i].Key, i, expectedCondition.Key)
|
||||||
}
|
}
|
||||||
if expectedCondition.Operator != answer.Conditionals[i].Operator {
|
if expectedCondition.Operator != answerAsSelect.Conditionals[i].Operator {
|
||||||
t.Errorf("got %s for Select.Conditionals[%d].Operator, expected %s", answer.Conditionals[i].Operator, i, expectedCondition.Operator)
|
t.Errorf("got %s for Select.Conditionals[%d].Operator, expected %s", answerAsSelect.Conditionals[i].Operator, i, expectedCondition.Operator)
|
||||||
}
|
}
|
||||||
if expectedCondition.Value != answer.Conditionals[i].Value {
|
if expectedCondition.Value != answerAsSelect.Conditionals[i].Value {
|
||||||
t.Errorf("got %s for Select.Conditionals[%d].Value, expected %s", answer.Conditionals[i].Value, i, expectedCondition.Value)
|
t.Errorf("got %s for Select.Conditionals[%d].Value, expected %s", answerAsSelect.Conditionals[i].Value, i, expectedCondition.Value)
|
||||||
}
|
}
|
||||||
if expectedCondition.Extension != answer.Conditionals[i].Extension {
|
if expectedCondition.Extension != answerAsSelect.Conditionals[i].Extension {
|
||||||
t.Errorf("got %s for Select.Conditionals[%d].Extension, expected %s", answer.Conditionals[i].Extension, i, expectedCondition.Extension)
|
t.Errorf("got %s for Select.Conditionals[%d].Extension, expected %s", answerAsSelect.Conditionals[i].Extension, i, expectedCondition.Extension)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(answer.OrderBys) != len(expected.OrderBys) {
|
if len(answerAsSelect.OrderBys) != len(expected.OrderBys) {
|
||||||
t.Errorf("got %d number of orderBys for Select.OrderBys, expected %d", len(answer.OrderBys), len(expected.OrderBys))
|
t.Errorf("got %d number of orderBys for Select.OrderBys, expected %d", len(answerAsSelect.OrderBys), len(expected.OrderBys))
|
||||||
} else {
|
} else {
|
||||||
for i, expectedOrderBy := range expected.OrderBys {
|
for i, expectedOrderBy := range expected.OrderBys {
|
||||||
if expectedOrderBy.Key != answer.OrderBys[i].Key {
|
if expectedOrderBy.Key != answerAsSelect.OrderBys[i].Key {
|
||||||
t.Errorf("got %s for Select.OrderBys[%d].Key, expected %s", answer.OrderBys[i].Key, i, expectedOrderBy.Key)
|
t.Errorf("got %s for Select.OrderBys[%d].Key, expected %s", answerAsSelect.OrderBys[i].Key, i, expectedOrderBy.Key)
|
||||||
}
|
}
|
||||||
if expectedOrderBy.IsDescend != answer.OrderBys[i].IsDescend {
|
if expectedOrderBy.IsDescend != answerAsSelect.OrderBys[i].IsDescend {
|
||||||
t.Errorf("got %#v for Select.OrderBys[%d].IsDescend, expected %#v", answer.OrderBys[i].IsDescend, i, expectedOrderBy.IsDescend)
|
t.Errorf("got %#v for Select.OrderBys[%d].IsDescend, expected %#v", answerAsSelect.OrderBys[i].IsDescend, i, expectedOrderBy.IsDescend)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(answer.Joins) != len(expected.Joins) {
|
if len(answerAsSelect.Joins) != len(expected.Joins) {
|
||||||
t.Errorf("got %d number of joins for Select.Joinss, expected %d", len(answer.Joins), len(expected.Joins))
|
t.Errorf("got %d number of joins for Select.Joinss, expected %d", len(answerAsSelect.Joins), len(expected.Joins))
|
||||||
} else {
|
} else {
|
||||||
for i, expectedJoin := range expected.Joins {
|
for i, expectedJoin := range expected.Joins {
|
||||||
t.Errorf("got %d for Select.Joins[%d].Type, expected %d", answer.Joins[i].Type, i, expectedJoin.Type)
|
if answerAsSelect.Joins[i].Type != expectedJoin.Type {
|
||||||
|
t.Errorf("got %d for Select.Joins[%d].Type, expected %d", answerAsSelect.Joins[i].Type, i, expectedJoin.Type)
|
||||||
|
}
|
||||||
|
if answerAsSelect.Joins[i].MainTable.Name != expectedJoin.MainTable.Name {
|
||||||
|
t.Errorf("got %s for Select.Joins[%d].MainTable.Name, expected %s", answerAsSelect.Joins[i].MainTable.Name, i, expectedJoin.MainTable.Name)
|
||||||
|
}
|
||||||
|
if answerAsSelect.Joins[i].MainTable.Alias != expectedJoin.MainTable.Alias {
|
||||||
|
t.Errorf("got %s for Select.Joins[%d].MainTable.Alias, expected %s", answerAsSelect.Joins[i].MainTable.Alias, i, expectedJoin.MainTable.Alias)
|
||||||
|
}
|
||||||
|
if answerAsSelect.Joins[i].JoiningTable.Name != expectedJoin.JoiningTable.Name {
|
||||||
|
t.Errorf("got %s for Select.Joins[%d].JoiningTable.Name, expected %s", answerAsSelect.Joins[i].JoiningTable.Name, i, expectedJoin.JoiningTable.Name)
|
||||||
|
}
|
||||||
|
if answerAsSelect.Joins[i].JoiningTable.Alias != expectedJoin.JoiningTable.Alias {
|
||||||
|
t.Errorf("got %s for Select.Joins[%d].JoiningTable.Alias, expected %s", answerAsSelect.Joins[i].JoiningTable.Alias, i, expectedJoin.JoiningTable.Alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(answerAsSelect.Joins[i].Ons) != len(expectedJoin.Ons) {
|
||||||
|
t.Errorf("got %d number of ons for Select.Joins.Ons, expected %d", len(answerAsSelect.Joins[i].Ons), len(expectedJoin.Ons))
|
||||||
|
} else {
|
||||||
|
for on_i, expectedCondition := range expected.Joins[i].Ons {
|
||||||
|
if expectedCondition.Key != answerAsSelect.Joins[i].Ons[on_i].Key {
|
||||||
|
t.Errorf("got %s for Select.Conditionals[%d].Key, expected %s", answerAsSelect.Joins[i].Ons[on_i].Key, on_i, expectedCondition.Key)
|
||||||
|
}
|
||||||
|
if expectedCondition.Operator != answerAsSelect.Joins[i].Ons[on_i].Operator {
|
||||||
|
t.Errorf("got %s for Select.Conditionals[%d].Operator, expected %s", answerAsSelect.Joins[i].Ons[on_i].Operator, on_i, expectedCondition.Operator)
|
||||||
|
}
|
||||||
|
if expectedCondition.Value != answerAsSelect.Joins[i].Ons[on_i].Value {
|
||||||
|
t.Errorf("got %s for Select.Conditionals[%d].Value, expected %s", answerAsSelect.Joins[i].Ons[on_i].Value, on_i, expectedCondition.Value)
|
||||||
|
}
|
||||||
|
if expectedCondition.Extension != answerAsSelect.Joins[i].Ons[on_i].Extension {
|
||||||
|
t.Errorf("got %s for Select.Conditionals[%d].Extension, expected %s", answerAsSelect.Joins[i].Ons[on_i].Extension, on_i, expectedCondition.Extension)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user