diff --git a/e2e/expected_outputs/1_select_with_where_expected_output b/e2e/expected_outputs/1_select_with_where_expected_output index ba58bca..3a89f6a 100644 --- a/e2e/expected_outputs/1_select_with_where_expected_output +++ b/e2e/expected_outputs/1_select_with_where_expected_output @@ -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' | ++-----------+------+-------+------+ diff --git a/e2e/test_files/1_select_with_where_test b/e2e/test_files/1_select_with_where_test index cd8b6ce..00156cb 100644 --- a/e2e/test_files/1_select_with_where_test +++ b/e2e/test_files/1_select_with_where_test @@ -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;'; diff --git a/parser/parser.go b/parser/parser.go index ce5e5d4..3cc7e0e 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -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 { @@ -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 } @@ -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 } diff --git a/parser/parser_error_handling_test.go b/parser/parser_error_handling_test.go index cfd17c0..a823169 100644 --- a/parser/parser_error_handling_test.go +++ b/parser/parser_error_handling_test.go @@ -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 @@ -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{ @@ -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) diff --git a/parser/parser_test.go b/parser/parser_test.go index c887986..0823da4 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -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"}}, + }, }, } @@ -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 @@ -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 {