feat: parse joins

This commit is contained in:
Yehoshua Sandler 2025-05-12 16:51:58 -05:00
parent bb9c71c49e
commit 2f7d996485
2 changed files with 125 additions and 12 deletions

View File

@ -7,6 +7,16 @@ import (
"github.com/DataDog/go-sqllexer"
)
// Find first token in array that matches condition
func FindTokenInArray(tokens []Token, condition func(Token) bool) (*Token, int) {
for i, element := range tokens {
if condition(element) {
return &element, i
}
}
return nil, -1
}
type Token = sqllexer.Token
type parser struct {
@ -352,10 +362,16 @@ func parseJoins(p *parser) error {
return nil
}
var joinTokenRanges []Token
type FoundJoinSubslices struct {
Tokens []Token
StartingIndexInGreaterStatement int
}
for i := 0; i < len(foundJoinKeywords); i++ {
startRangeIndex := foundJoinKeywords[i].Index
var joinTokenRanges []FoundJoinSubslices
for i, foundJoin := range foundJoinKeywords {
startRangeIndex := foundJoin.Index
var endRangeIndex int
if i == (len(foundJoinKeywords) - 1) {
@ -364,7 +380,101 @@ func parseJoins(p *parser) error {
endRangeIndex = foundJoinKeywords[i+1].Index
}
joinTokenRanges = append(joinTokenRanges, p.tokens[startRangeIndex:endRangeIndex]...)
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"
})
fmt.Printf("Found ON at '%d'\n", foundOnTokenIndex)
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
}
fmt.Printf("Currently at '%d', reading '%s'\n", i, t.Value)
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)
fmt.Println("Adding ON to workingJoin")
fmt.Println(workingOn)
workingOn = Conditional{}
}
}
selectQuery.Joins = append(selectQuery.Joins, workingJoin)
workingJoin = Join{}
}
return nil

View File

@ -164,6 +164,9 @@ func TestParseSelectStatement_StateMachine(t *testing.T) {
{
Type: INNER,
MainTable: Table{
Name: "Products",
},
JoiningTable: Table{
Name: "Categories",
},
Ons: []Conditional{
@ -277,17 +280,17 @@ func TestParseSelectStatement_StateMachine(t *testing.T) {
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.Conditionals[on_i].Key {
t.Errorf("got %s for Select.Conditionals[%d].Key, expected %s", answerAsSelect.Conditionals[on_i].Key, on_i, expectedCondition.Key)
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.Conditionals[on_i].Operator {
t.Errorf("got %s for Select.Conditionals[%d].Operator, expected %s", answerAsSelect.Conditionals[on_i].Operator, on_i, expectedCondition.Operator)
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.Conditionals[on_i].Value {
t.Errorf("got %s for Select.Conditionals[%d].Value, expected %s", answerAsSelect.Conditionals[on_i].Value, on_i, expectedCondition.Value)
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.Conditionals[on_i].Extension {
t.Errorf("got %s for Select.Conditionals[%d].Extension, expected %s", answerAsSelect.Conditionals[on_i].Extension, on_i, expectedCondition.Extension)
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)
}
}