feat: parse DISTINCT and ORDER BY

This commit is contained in:
Yehoshua Sandler 2025-04-07 19:03:29 -05:00
parent a0a6b8822e
commit 68e3669c1a
2 changed files with 128 additions and 16 deletions

View File

@ -1,6 +1,7 @@
package q package q
import ( import (
"fmt"
"strings" "strings"
"github.com/DataDog/go-sqllexer" "github.com/DataDog/go-sqllexer"
@ -11,7 +12,14 @@ type Select struct {
Table string Table string
Columns []string Columns []string
Conditionals []Conditional Conditionals []Conditional
OrderBys []OrderBy
IsWildcard bool IsWildcard bool
IsDistinct bool
}
type OrderBy struct {
Key string
IsDescend bool
} }
func (q *Select) GetFullSql() string { func (q *Select) GetFullSql() string {
@ -33,10 +41,11 @@ func (q *Select) GetFullSql() string {
workingSqlSlice = append(workingSqlSlice, "FROM "+q.Table) workingSqlSlice = append(workingSqlSlice, "FROM "+q.Table)
// TODO: need to account for `AND` and `OR`s and stuff
for _, condition := range q.Conditionals { for _, condition := range q.Conditionals {
workingSqlSlice = append(workingSqlSlice, condition.Key) workingSqlSlice = append(workingSqlSlice, condition.Key)
workingSqlSlice = append(workingSqlSlice, condition.Operator) workingSqlSlice = append(workingSqlSlice, condition.Operator)
workingSqlSlice = append(workingSqlSlice, condition.Value) // TODO: need to account for `AND` and `OR`s and stuff workingSqlSlice = append(workingSqlSlice, condition.Value)
} }
fullSql := strings.Join(workingSqlSlice, " ") fullSql := strings.Join(workingSqlSlice, " ")
@ -44,6 +53,22 @@ func (q *Select) GetFullSql() string {
return fullSql return fullSql
} }
func mutateSelectFromKeyword(query *Select, keyword string) {
switch strings.ToUpper(keyword) {
case "DISTINCT":
query.IsDistinct = true
}
}
// TODO: make this an array of tokens instead
func unshiftBuffer(buf *[10]string, value string) {
for i := 9; i >= 1; i-- {
buf[i] = buf[i-1]
}
buf[0] = value
}
func ParseSelectStatement(sql string) Select { func ParseSelectStatement(sql string) Select {
query := Select{} query := Select{}
@ -52,10 +77,14 @@ func ParseSelectStatement(sql string) Select {
passedFROM := false passedFROM := false
passedTable := false passedTable := false
passedWHERE := false passedWHERE := false
passedConditionals := false
passedOrderBy := false
lookBehindBuffer := [10]string{} // TODO: make this an array of tokens instead
var workingConditional = Conditional{} var workingConditional = Conditional{}
var columns []string var columns []string
var orderBys []OrderBy
lexer := sqllexer.New(sql) lexer := sqllexer.New(sql)
for { for {
token := lexer.Scan() token := lexer.Scan()
@ -70,54 +99,89 @@ func ParseSelectStatement(sql string) Select {
continue continue
} }
// For any keywords that are before the columns or wildcard
if passedSELECT && len(columns) == 0 && !passedColumns {
if token.Type == sqllexer.KEYWORD {
mutateSelectFromKeyword(&query, token.Value)
continue
}
}
if !passedColumns { if !passedColumns {
if token.Type == sqllexer.WILDCARD { if token.Type == sqllexer.WILDCARD {
passedColumns = true passedColumns = true
columns = make([]string, 0) columns = make([]string, 0)
query.IsWildcard = true query.IsWildcard = true
continue
} else if token.Type == sqllexer.IDENT { } else if token.Type == sqllexer.IDENT {
columns = append(columns, token.Value) columns = append(columns, token.Value)
continue continue
} else if token.Type == sqllexer.PUNCTUATION || token.Type == sqllexer.SPACE { } else if token.Type == sqllexer.PUNCTUATION || token.Type == sqllexer.SPACE {
continue continue
} else { } else {
passedColumns = true // TODO: make sure that I should be doing this passedColumns = true
query.Columns = columns query.Columns = columns
continue
} }
} }
if !passedFROM && strings.ToUpper(token.Value) == "FROM" { if !passedFROM && strings.ToUpper(token.Value) == "FROM" {
passedFROM = true passedFROM = true
continue
} else if !passedFROM { } else if !passedFROM {
continue // TODO: make sure to check for other keywords that are allowed // continue // TODO: make sure to check for other keywords that are allowed
} }
if !passedTable && token.Type == sqllexer.IDENT { if !passedTable && token.Type == sqllexer.IDENT {
passedTable = true passedTable = true
query.Table = token.Value query.Table = token.Value
continue
} else if !passedTable { } else if !passedTable {
continue continue
} }
if !passedWHERE && strings.ToUpper(token.Value) == "WHERE" { if !passedWHERE && token.Type == sqllexer.KEYWORD && strings.ToUpper(token.Value) == "WHERE" {
passedWHERE = true passedWHERE = true
} else if !passedWHERE {
continue continue
} }
if token.Type == sqllexer.IDENT { if passedWHERE && !passedConditionals {
workingConditional.Key = token.Value if token.Type == sqllexer.IDENT {
} else if token.Type == sqllexer.OPERATOR { workingConditional.Key = token.Value
workingConditional.Operator = token.Value } else if token.Type == sqllexer.OPERATOR {
} else if token.Type == sqllexer.BOOLEAN || token.Type == sqllexer.NULL || token.Type == sqllexer.STRING || token.Type == sqllexer.NUMBER { workingConditional.Operator = token.Value
workingConditional.Value = token.Value } else if token.Type == sqllexer.BOOLEAN || token.Type == sqllexer.NULL || token.Type == sqllexer.STRING || token.Type == sqllexer.NUMBER {
} // TODO: add captire for data type workingConditional.Value = token.Value
} // TODO: add captire for data type
if workingConditional.Key != "" && workingConditional.Operator != "" && workingConditional.Value != "" {
query.Conditionals = append(query.Conditionals, workingConditional)
workingConditional = Conditional{}
passedConditionals = true
}
if workingConditional.Key != "" && workingConditional.Operator != "" && workingConditional.Value != "" {
query.Conditionals = append(query.Conditionals, workingConditional)
workingConditional = Conditional{}
continue continue
} }
// Checking For ORDER BY
if !passedOrderBy && token.Type == sqllexer.KEYWORD {
unshiftBuffer(&lookBehindBuffer, token.Value)
if strings.ToUpper(lookBehindBuffer[1]) == "ORDER" && strings.ToUpper(lookBehindBuffer[0]) == "BY" {
passedOrderBy = true
}
fmt.Printf("VALUE: %s %s \n", lookBehindBuffer[1], lookBehindBuffer[0])
}
if passedOrderBy && token.Type == sqllexer.IDENT {
orderBys = append(orderBys, OrderBy{
Key: token.Value, // TODO: need to be able to get the ASC or DESC keyword too
})
query.OrderBys = orderBys
}
} }
return query return query

View File

@ -13,7 +13,7 @@ type ParsingTest struct {
func TestParseSelectStatement(t *testing.T) { func TestParseSelectStatement(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{
Table: "users", Table: "users",
IsWildcard: true, IsWildcard: true,
@ -26,6 +26,41 @@ func TestParseSelectStatement(t *testing.T) {
}, },
}, },
}, },
{
input: "SELECT CustomerName, City FROM Customers",
expected: Select{
Table: "Customers",
IsWildcard: false,
Columns: []string{
"CustomerName",
"City",
},
},
},
{
input: "SELECT DISTINCT Country FROM Nations;",
expected: Select{
Table: "Nations",
Columns: []string{
"Country",
},
},
},
{
input: "SELECT * FROM Orders ORDER BY StreetNumber, CountryCode;",
expected: Select{
Table: "Orders",
IsWildcard: true,
OrderBys: []OrderBy{
{
Key: "StreetNumber",
},
{
Key: "CountryCode",
},
},
},
},
} }
for _, sql := range testSqlStatements { for _, sql := range testSqlStatements {
@ -73,6 +108,19 @@ func TestParseSelectStatement(t *testing.T) {
} }
} }
if len(answer.OrderBys) != len(expected.OrderBys) {
t.Errorf("got %d number of orderBys for Select.OrderBys, expected %d", len(answer.OrderBys), len(expected.OrderBys))
} else {
for i, expectedOrderBy := range expected.OrderBys {
if expectedOrderBy.Key != answer.OrderBys[i].Key {
t.Errorf("got %s for Select.OrderBys[%d].Key, expected %s", answer.OrderBys[i].Key, i, expectedOrderBy.Key)
}
if expectedOrderBy.IsDescend != answer.OrderBys[i].IsDescend {
t.Errorf("got %#v for Select.OrderBys[%d].IsDescend, expected %#v", answer.OrderBys[i].IsDescend, i, expectedOrderBy.IsDescend)
}
}
}
}) })
} }
} }