From 68e3669c1a9db6423d91b67f4d763f13f96d476f Mon Sep 17 00:00:00 2001 From: ysandler Date: Mon, 7 Apr 2025 19:03:29 -0500 Subject: [PATCH] feat: parse DISTINCT and ORDER BY --- q/select.go | 94 ++++++++++++++++++++++++++++++++++++++++-------- q/select_test.go | 50 +++++++++++++++++++++++++- 2 files changed, 128 insertions(+), 16 deletions(-) diff --git a/q/select.go b/q/select.go index b4cbb70..6309504 100644 --- a/q/select.go +++ b/q/select.go @@ -1,6 +1,7 @@ package q import ( + "fmt" "strings" "github.com/DataDog/go-sqllexer" @@ -11,7 +12,14 @@ type Select struct { Table string Columns []string Conditionals []Conditional + OrderBys []OrderBy IsWildcard bool + IsDistinct bool +} + +type OrderBy struct { + Key string + IsDescend bool } func (q *Select) GetFullSql() string { @@ -33,10 +41,11 @@ func (q *Select) GetFullSql() string { workingSqlSlice = append(workingSqlSlice, "FROM "+q.Table) + // TODO: need to account for `AND` and `OR`s and stuff for _, condition := range q.Conditionals { workingSqlSlice = append(workingSqlSlice, condition.Key) 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, " ") @@ -44,6 +53,22 @@ func (q *Select) GetFullSql() string { 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 { query := Select{} @@ -52,10 +77,14 @@ func ParseSelectStatement(sql string) Select { passedFROM := false passedTable := false passedWHERE := false + passedConditionals := false + passedOrderBy := false + lookBehindBuffer := [10]string{} // TODO: make this an array of tokens instead var workingConditional = Conditional{} var columns []string + var orderBys []OrderBy lexer := sqllexer.New(sql) for { token := lexer.Scan() @@ -70,54 +99,89 @@ func ParseSelectStatement(sql string) Select { 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 token.Type == sqllexer.WILDCARD { passedColumns = true columns = make([]string, 0) query.IsWildcard = true + continue } else if token.Type == sqllexer.IDENT { columns = append(columns, token.Value) continue } else if token.Type == sqllexer.PUNCTUATION || token.Type == sqllexer.SPACE { continue } else { - passedColumns = true // TODO: make sure that I should be doing this + passedColumns = true query.Columns = columns + continue } } if !passedFROM && strings.ToUpper(token.Value) == "FROM" { passedFROM = true + continue } 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 { passedTable = true query.Table = token.Value + continue } else if !passedTable { continue } - if !passedWHERE && strings.ToUpper(token.Value) == "WHERE" { + if !passedWHERE && token.Type == sqllexer.KEYWORD && strings.ToUpper(token.Value) == "WHERE" { passedWHERE = true - } else if !passedWHERE { continue } - 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 - } // TODO: add captire for data type + 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 + } // 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 } + + // 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 diff --git a/q/select_test.go b/q/select_test.go index ef91656..c999465 100644 --- a/q/select_test.go +++ b/q/select_test.go @@ -13,7 +13,7 @@ type ParsingTest struct { func TestParseSelectStatement(t *testing.T) { var testSqlStatements = []ParsingTest{ { - input: "SELECT * FROM users WHERE age >= 30", + input: "SELECT * FROM users WHERE age >= 30", expected: Select{ Table: "users", 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 { @@ -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) + } + } + } + }) } }