Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions e2e/expected_outputs/1_select_with_where_expected_output
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,10 @@ Data Inserted
| one | two | three | four |
+-----+-----+-------+------+
+-----+-----+-------+------+
+-----------+------+-------+------+
| one | two | three | four |
+-----------+------+-------+------+
| 'hello' | 1 | 11 | 'q' |
| 'goodbye' | 1 | 22 | 'w' |
| 'byebye' | NULL | 33 | 'e' |
+-----------+------+-------+------+
1 change: 1 addition & 0 deletions e2e/test_files/1_select_with_where_test
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ SELECT * FROM tbl WHERE one NOT 'goodbye' AND two EQUAL NULL;
SELECT * FROM tbl WHERE one IN ('goodbye', 'byebye');
SELECT * FROM tbl WHERE one NOTIN ('goodbye', 'byebye');
SELECT * FROM tbl WHERE FALSE;
SELECT * FROM tbl WHERE 'colName1 EQUAL;' EQUAL 'colName1 EQUAL;';
186 changes: 86 additions & 100 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -707,126 +707,118 @@ func (parser *Parser) parseUpdateCommand() (ast.Command, error) {
// - ast.ConditionExpression
// - ast.ContainExpression
func (parser *Parser) getExpression() (bool, ast.Expression, error) {
booleanExpressionExists, booleanExpression := parser.getBooleanExpression()

conditionalExpressionExists, conditionalExpression, err := parser.getConditionalExpression()
if err != nil {
return false, nil, err
}
if parser.currentToken.Type == token.IDENT ||
parser.currentToken.Type == token.LITERAL ||
parser.currentToken.Type == token.NULL ||
parser.currentToken.Type == token.APOSTROPHE ||
parser.currentToken.Type == token.TRUE ||
parser.currentToken.Type == token.FALSE {

containExpressionExists, containExpression, err := parser.getContainExpression()
if err != nil {
return false, nil, err
}
leftSide, isAnonymitifier, err := parser.getExpressionLeftSideValue()
if err != nil {
return false, nil, err
}

operationExpressionExists, operationExpression, err := parser.getOperationExpression(booleanExpressionExists, conditionalExpressionExists, containExpressionExists, booleanExpression, conditionalExpression, containExpression)
if err != nil {
return false, nil, err
}
isValidExpression := false
var expression ast.Expression

if parser.currentToken.Type == token.EQUAL || parser.currentToken.Type == token.NOT {
isValidExpression, expression, err = parser.getConditionalExpression(leftSide, isAnonymitifier)
} else if parser.currentToken.Type == token.IN || parser.currentToken.Type == token.NOTIN {
isValidExpression, expression, err = parser.getContainExpression(leftSide, isAnonymitifier)
} else if leftSide.Type == token.TRUE || leftSide.Type == token.FALSE {
expression = &ast.BooleanExpression{Boolean: leftSide}
isValidExpression = true
err = nil
}

if operationExpressionExists {
return true, operationExpression, err
}
if err != nil {
return false, nil, err
}

if conditionalExpressionExists {
return true, conditionalExpression, err
}
if (parser.currentToken.Type == token.AND || parser.currentToken.Type == token.OR) && isValidExpression {
isValidExpression, expression, err = parser.getOperationExpression(expression)
}

if containExpressionExists {
return true, containExpression, err
}
if err != nil {
return false, nil, err
}

if booleanExpressionExists {
return true, booleanExpression, err
if isValidExpression {
return true, expression, nil
}
}

return false, nil, err
return false, nil, nil
}

// getOperationExpression - Return ast.OperationExpression created from tokens and validate the syntax
func (parser *Parser) getOperationExpression(booleanExpressionExists bool, conditionalExpressionExists bool, containExpressionExists bool, booleanExpression *ast.BooleanExpression, conditionalExpression *ast.ConditionExpression, containExpression *ast.ContainExpression) (bool, *ast.OperationExpression, error) {
operationExpression := &ast.OperationExpression{}

if (booleanExpressionExists || conditionalExpressionExists || containExpressionExists) && (parser.currentToken.Type == token.OR || parser.currentToken.Type == token.AND) {
if booleanExpressionExists {
operationExpression.Left = booleanExpression
}
func (parser *Parser) getExpressionLeftSideValue() (token.Token, bool, error) {
var leftSide token.Token
isAnonymitifier := false
startedWithApostrophe := parser.skipIfCurrentTokenIsApostrophe()

if conditionalExpressionExists {
operationExpression.Left = conditionalExpression
if startedWithApostrophe {
isAnonymitifier = true
value := ""
for parser.currentToken.Type != token.EOF && parser.currentToken.Type != token.APOSTROPHE {
value += parser.currentToken.Literal
parser.nextToken()
}

if containExpressionExists {
operationExpression.Left = containExpression
}
leftSide = token.Token{Type: token.IDENT, Literal: value}

operationExpression.Operation = parser.currentToken
parser.nextToken()
finishedWithApostrophe := parser.skipIfCurrentTokenIsApostrophe()

expressionIsValid, expression, err := parser.getExpression()
err := validateApostropheWrapping(startedWithApostrophe, finishedWithApostrophe, leftSide)

if err != nil {
return false, nil, err
}
if !expressionIsValid {
return false, nil, &LogicalExpressionParsingError{afterToken: &operationExpression.Operation.Literal}
return token.Token{}, isAnonymitifier, err
}

operationExpression.Right = expression

return true, operationExpression, nil
} else {
leftSide = parser.currentToken
parser.nextToken()
}

return false, operationExpression, nil
return leftSide, isAnonymitifier, nil
}

// getBooleanExpression - Return ast.BooleanExpression created from tokens and validate the syntax
func (parser *Parser) getBooleanExpression() (bool, *ast.BooleanExpression) {
booleanExpression := &ast.BooleanExpression{}
isValid := false
// getOperationExpression - Return ast.OperationExpression created from tokens and validate the syntax
func (parser *Parser) getOperationExpression(expression ast.Expression) (bool, *ast.OperationExpression, error) {
operationExpression := &ast.OperationExpression{}
operationExpression.Left = expression

if parser.currentToken.Type == token.TRUE || parser.currentToken.Type == token.FALSE {
booleanExpression.Boolean = parser.currentToken
parser.nextToken()
isValid = true
}
operationExpression.Operation = parser.currentToken
parser.nextToken()

return isValid, booleanExpression
}
expressionIsValid, expression, err := parser.getExpression()

// getConditionalExpression - Return ast.ConditionExpression created from tokens and validate the syntax
func (parser *Parser) getConditionalExpression() (bool, *ast.ConditionExpression, error) {
conditionalExpression := &ast.ConditionExpression{}

err := validateToken(parser.peekToken.Type, []token.Type{token.EQUAL, token.NOT})
if err != nil {
return false, nil, nil
return false, nil, err
}
conditionalExpression.Condition = parser.peekToken

if parser.currentToken.Type == token.IDENT || parser.currentToken.Type == token.LITERAL || parser.currentToken.Type == token.NULL || parser.currentToken.Type == token.APOSTROPHE {
startedWithApostrophe := parser.skipIfCurrentTokenIsApostrophe()
if !expressionIsValid {
return false, nil, &LogicalExpressionParsingError{afterToken: &operationExpression.Operation.Literal}
}

if !startedWithApostrophe && parser.currentToken.Type == token.IDENT {
conditionalExpression.Left = ast.Identifier{Token: parser.currentToken}
} else {
conditionalExpression.Left = ast.Anonymitifier{Token: parser.currentToken}
}
parser.nextToken()
operationExpression.Right = expression

finishedWithApostrophe := parser.skipIfCurrentTokenIsApostrophe()
err := validateApostropheWrapping(startedWithApostrophe, finishedWithApostrophe, conditionalExpression.Left.GetToken())
if err != nil {
return false, nil, err
}
return true, operationExpression, nil
}

// getConditionalExpression - Return ast.ConditionExpression created from tokens and validate the syntax
func (parser *Parser) getConditionalExpression(leftSide token.Token, isAnonymitifier bool) (bool, *ast.ConditionExpression, error) {
conditionalExpression := &ast.ConditionExpression{Condition: parser.currentToken}

if isAnonymitifier {
conditionalExpression.Left = ast.Anonymitifier{Token: leftSide}
} else {
return false, conditionalExpression, nil
conditionalExpression.Left = ast.Identifier{Token: leftSide}
}

// skip EQUAL or NOT
parser.nextToken()

if parser.currentToken.Type == token.IDENT || parser.currentToken.Type == token.LITERAL || parser.currentToken.Type == token.NULL || parser.currentToken.Type == token.APOSTROPHE {
if parser.currentToken.Type == token.IDENT || parser.currentToken.Type == token.LITERAL ||
parser.currentToken.Type == token.NULL || parser.currentToken.Type == token.APOSTROPHE {
startedWithApostrophe := parser.skipIfCurrentTokenIsApostrophe()

if !startedWithApostrophe && parser.currentToken.Type == token.IDENT {
Expand All @@ -837,7 +829,7 @@ func (parser *Parser) getConditionalExpression() (bool, *ast.ConditionExpression
parser.nextToken()

finishedWithApostrophe := parser.skipIfCurrentTokenIsApostrophe()
err = validateApostropheWrapping(startedWithApostrophe, finishedWithApostrophe, conditionalExpression.Right.GetToken())
err := validateApostropheWrapping(startedWithApostrophe, finishedWithApostrophe, conditionalExpression.Right.GetToken())
if err != nil {
return false, nil, err
}
Expand All @@ -849,31 +841,25 @@ func (parser *Parser) getConditionalExpression() (bool, *ast.ConditionExpression
}

// getContainExpression - Return ast.ContainExpression created from tokens and validate the syntax
func (parser *Parser) getContainExpression() (bool, *ast.ContainExpression, error) {
func (parser *Parser) getContainExpression(leftSide token.Token, isAnonymitifier bool) (bool, *ast.ContainExpression, error) {
containExpression := &ast.ContainExpression{}

err := validateToken(parser.peekToken.Type, []token.Type{token.IN, token.NOTIN})
if err != nil {
return false, nil, nil
if isAnonymitifier {
return false, nil, &SyntaxError{expecting: []string{token.IDENT}, got: "'" + leftSide.Literal + "'"}
}
if parser.peekToken.Type == token.IN {

containExpression.Left = ast.Identifier{Token: leftSide}

if parser.currentToken.Type == token.IN {
containExpression.Contains = true
} else {
containExpression.Contains = false
}

err = validateToken(parser.currentToken.Type, []token.Type{token.IDENT})
if err != nil {
return false, nil, nil
}
containExpression.Left = ast.Identifier{Token: parser.currentToken}

parser.nextToken()

// skip IN or NOTIN
parser.nextToken()

err = validateTokenAndSkip(parser, []token.Type{token.LPAREN})
err := validateTokenAndSkip(parser, []token.Type{token.LPAREN})
if err != nil {
return false, nil, err
}
Expand Down
9 changes: 6 additions & 3 deletions parser/parser_error_handling_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ func TestParseWhereCommandErrorHandling(t *testing.T) {
selectCommandPrefix := "SELECT * FROM tbl "
noPredecessorError := NoPredecessorParserError{command: token.WHERE}
noColName := LogicalExpressionParsingError{}
noLeftAphostrophe := LogicalExpressionParsingError{}
noOperatorInsideWhereStatementException := LogicalExpressionParsingError{}
valueIsMissing := SyntaxError{expecting: []string{token.APOSTROPHE, token.IDENT, token.LITERAL, token.NULL}, got: token.SEMICOLON}
tokenAnd := token.AND
Expand All @@ -138,10 +139,12 @@ func TestParseWhereCommandErrorHandling(t *testing.T) {
noLeftParGotSemicolon := SyntaxError{expecting: []string{token.LPAREN}, got: ";"}
noLeftParGotNumber := SyntaxError{expecting: []string{token.LPAREN}, got: token.LITERAL}
noComma := SyntaxError{expecting: []string{token.COMMA, token.RPAREN}, got: token.LITERAL}
anonymitifierInContains := SyntaxError{expecting: []string{token.IDENT}, got: "'one'"}
noInKeywordException := LogicalExpressionParsingError{}
noLeftApostropheGoodbye := NoApostropheOnLeftParserError{ident: "goodbye"}
noLeftApostropheFive := NoApostropheOnLeftParserError{ident: "5"}
noRightApostropheGoodbye := NoApostropheOnRightParserError{ident: "goodbye"}
noRightApostropheGoodbyeBigger := NoApostropheOnRightParserError{ident: "goodbye EQUAL two"}
noRightApostropheFive := NoApostropheOnRightParserError{ident: "5"}

tests := []errorHandlingTestSuite{
Expand All @@ -157,12 +160,12 @@ func TestParseWhereCommandErrorHandling(t *testing.T) {
{selectCommandPrefix + "WHERE one IN (5 6);", noComma.Error()},
{selectCommandPrefix + "WHERE one IN ('5", noRightApostropheFive.Error()},
{selectCommandPrefix + "WHERE one IN (5');", noLeftApostropheFive.Error()},
{selectCommandPrefix + "WHERE 'one' IN (5);", anonymitifierInContains.Error()},
{selectCommandPrefix + "WHERE one (5, 6);", noInKeywordException.Error()},
{selectCommandPrefix + "WHERE one EQUAL goodbye';", noLeftApostropheGoodbye.Error()},
{selectCommandPrefix + "WHERE one EQUAL 'goodbye", noRightApostropheGoodbye.Error()},
// TODO: Add after fix apostrophe on left side of condition
//{selectCommandPrefix + "WHERE 'goodbye EQUAL two", noRightApostropheGoodbye.Error()},
//{selectCommandPrefix + "WHERE goodbye' EQUAL two", noLeftApostropheGoodbye.Error()},
{selectCommandPrefix + "WHERE 'goodbye EQUAL two", noRightApostropheGoodbyeBigger.Error()},
{selectCommandPrefix + "WHERE goodbye' EQUAL two", noLeftAphostrophe.Error()},
}

runParserErrorHandlingSuite(t, tests)
Expand Down
25 changes: 17 additions & 8 deletions parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -716,20 +716,20 @@ func TestParseUpdateCommand(t *testing.T) {
}{
{
input: "UPDATE tbl SET colName TO 5;", expectedTableName: "tbl", expectedChanges: map[token.Token]ast.Anonymitifier{
{Type: token.IDENT, Literal: "colName"}: {Token: token.Token{Type: token.LITERAL, Literal: "5"}},
},
{Type: token.IDENT, Literal: "colName"}: {Token: token.Token{Type: token.LITERAL, Literal: "5"}},
},
},
{
input: "UPDATE tbl1 SET colName1 TO 'hi hello', colName2 TO 5;", expectedTableName: "tbl1", expectedChanges: map[token.Token]ast.Anonymitifier{
{Type: token.IDENT, Literal: "colName1"}: {Token: token.Token{Type: token.IDENT, Literal: "hi hello"}},
{Type: token.IDENT, Literal: "colName2"}: {Token: token.Token{Type: token.LITERAL, Literal: "5"}},
},
{Type: token.IDENT, Literal: "colName1"}: {Token: token.Token{Type: token.IDENT, Literal: "hi hello"}},
{Type: token.IDENT, Literal: "colName2"}: {Token: token.Token{Type: token.LITERAL, Literal: "5"}},
},
},
{
input: "UPDATE tbl1 SET colName1 TO NULL, colName2 TO 'NULL';", expectedTableName: "tbl1", expectedChanges: map[token.Token]ast.Anonymitifier{
{Type: token.IDENT, Literal: "colName1"}: {Token: token.Token{Type: token.NULL, Literal: "NULL"}},
{Type: token.IDENT, Literal: "colName2"}: {Token: token.Token{Type: token.LITERAL, Literal: "NULL"}},
},
{Type: token.IDENT, Literal: "colName1"}: {Token: token.Token{Type: token.NULL, Literal: "NULL"}},
{Type: token.IDENT, Literal: "colName2"}: {Token: token.Token{Type: token.LITERAL, Literal: "NULL"}},
},
},
}

Expand Down Expand Up @@ -834,6 +834,11 @@ func TestParseLogicOperatorsInCommand(t *testing.T) {
Boolean: token.Token{Type: token.TRUE, Literal: "TRUE"},
}

fourthExpression := ast.ConditionExpression{
Left: ast.Anonymitifier{Token: token.Token{Type: token.IDENT, Literal: "colName1 EQUAL;"}},
Right: ast.Anonymitifier{Token: token.Token{Type: token.IDENT, Literal: "colName1 EQUAL;"}},
Condition: token.Token{Type: token.EQUAL, Literal: "EQUAL"}}

tests := []struct {
input string
expectedExpression ast.Expression
Expand All @@ -850,6 +855,10 @@ func TestParseLogicOperatorsInCommand(t *testing.T) {
input: "SELECT * FROM TBL WHERE TRUE;",
expectedExpression: thirdExpression,
},
{
input: "SELECT * FROM TBL WHERE 'colName1 EQUAL;' EQUAL 'colName1 EQUAL;';",
expectedExpression: fourthExpression,
},
}

for testIndex, tt := range tests {
Expand Down
Loading