Repository: tomarrell/lbadd (go)

Issue:
parser: parse expression cannot parse functions
**Describe the bug**
The parser can not follow the function production rule `function-name ( expr )`.

**To Reproduce**
Try to parse `SELECT AVG(price) AS average_price FROM items LEFT INNER JOIN prices`.

**Expected behavior**
`AVG` is clearly a literal, however, it must be stored in `function-name` instead of `literal-value`, because it is followed by an opening parenthesis.


Pull request:
parser: result column bug: can take expr of single and recursive type.
Fixes #151, is a part of #153.

Fixes bug in result column, can take single and recursive expr.

Added test for proof.

Patch:
diff --git a/go.mod b/go.mod
index 1315afa..b0391af 100644
--- a/go.mod
+++ b/go.mod
@@ -18,7 +18,7 @@ require (
 	golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
 	golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 // indirect
 	golang.org/x/text v0.3.2
-	golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8
+	golang.org/x/tools v0.0.0-20200521211927-2b542361a4fc
 	gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
 	gopkg.in/yaml.v2 v2.2.8 // indirect
 )
diff --git a/go.sum b/go.sum
index efc183a..8e11b78 100644
--- a/go.sum
+++ b/go.sum
@@ -190,8 +190,8 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
 golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74 h1:4cFkmztxtMslUX2SctSl+blCyXfpzhGOy9LhKAqSMA4=
 golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8 h1:BMFHd4OFnFtWX46Xj4DN6vvT1btiBxyq+s0orYBqcQY=
-golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200521211927-2b542361a4fc h1:6m2YO+AmBApbUOmhsghW+IfRyZOY4My4UYvQQrEpHfY=
+golang.org/x/tools v0.0.0-20200521211927-2b542361a4fc/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
diff --git a/internal/parser/simple_parser_rules.go b/internal/parser/simple_parser_rules.go
index ff58f4a..dc5216e 100644
--- a/internal/parser/simple_parser_rules.go
+++ b/internal/parser/simple_parser_rules.go
@@ -1285,6 +1285,23 @@ func (p *simpleParser) parseExprRecursive(expr *ast.Expr, r reporter) *ast.Expr
 	return nil
 }
 
+// parseExprBeginWithLiteral parses possible expressions that begin with a literal.
+// A nil is returned if it turns out not to be an expression.
+func (p *simpleParser) parseExprBeginWithLiteral(literal token.Token, r reporter) (expr *ast.Expr) {
+	next, ok := p.optionalLookahead(r)
+	if !ok || next.Type() == token.EOF || next.Type() == token.StatementSeparator {
+		return nil
+	}
+	if next.Value() == "." {
+		return p.parseExpr2(literal, nil, nil, r)
+	} else if next.Type() == token.Delimiter && next.Value() == "(" {
+		return p.parseExpr5(literal, r)
+	} else {
+		returnExpr := p.parseExprRecursive(&ast.Expr{LiteralValue: literal}, r)
+		return returnExpr
+	}
+}
+
 // parseExpr2 parses S' -> (schema.table.column clause) S'.
 func (p *simpleParser) parseExpr2(schemaOrTableName, period, tableOrColName token.Token, r reporter) (expr *ast.Expr) {
 	expr = &ast.Expr{}
@@ -3731,11 +3748,15 @@ func (p *simpleParser) parseResultColumn(r reporter) (stmt *ast.ResultColumn) {
 				stmt.Period = period
 			}
 		} else {
-			recExpr := p.parseExprRecursive(&ast.Expr{LiteralValue: tableNameOrAsteriskOrExpr}, r)
-			if recExpr != nil {
+			// Conditions for recursive expressions or single expressions.
+			if recExpr := p.parseExprRecursive(&ast.Expr{LiteralValue: tableNameOrAsteriskOrExpr}, r); recExpr != nil {
 				stmt.Expr = recExpr
 			} else {
-				stmt.Expr = &ast.Expr{LiteralValue: tableNameOrAsteriskOrExpr}
+				if singleExpr := p.parseExprBeginWithLiteral(tableNameOrAsteriskOrExpr, r); singleExpr != nil {
+					stmt.Expr = singleExpr
+				} else {
+					stmt.Expr = &ast.Expr{LiteralValue: tableNameOrAsteriskOrExpr}
+				}
 			}
 		}
 		next, ok := p.optionalLookahead(r)
@@ -4545,7 +4566,8 @@ func (p *simpleParser) parseJoinOperator(r reporter) (stmt *ast.JoinOperator) {
 	if !ok {
 		return
 	}
-	if next.Type() == token.KeywordLeft {
+	switch next.Type() {
+	case token.KeywordLeft:
 		stmt.Left = next
 		p.consumeToken()
 		next, ok = p.lookahead(r)
@@ -4556,22 +4578,10 @@ func (p *simpleParser) parseJoinOperator(r reporter) (stmt *ast.JoinOperator) {
 			stmt.Outer = next
 			p.consumeToken()
 		}
-	}
-
-	next, ok = p.lookahead(r)
-	if !ok {
-		return
-	}
-	if next.Type() == token.KeywordInner {
+	case token.KeywordInner:
 		stmt.Inner = next
 		p.consumeToken()
-	}
-
-	next, ok = p.lookahead(r)
-	if !ok {
-		return
-	}
-	if next.Type() == token.KeywordCross {
+	case token.KeywordCross:
 		stmt.Cross = next
 		p.consumeToken()
 	}


Test patch:
diff --git a/internal/network/tcp_conn_test.go b/internal/network/tcp_conn_test.go
index 7ad6ae2..19a74b5 100644
--- a/internal/network/tcp_conn_test.go
+++ b/internal/network/tcp_conn_test.go
@@ -21,7 +21,7 @@ func TestTCPConnSendReceive(t *testing.T) {
 
 	payload := []byte("Hello, World!")
 	recv := make([]byte, len(payload))
-	wg := &sync.WaitGroup{}
+	var wg sync.WaitGroup
 	wg.Add(1)
 	go func() {
 		var err error
@@ -47,6 +47,8 @@ func TestDialTCP(t *testing.T) {
 	lis, err := net.Listen("tcp", ":0")
 	assert.NoError(err)
 
+	var wg sync.WaitGroup
+	wg.Add(1)
 	var srvConnID string
 	go func() {
 		conn, err := lis.Accept()
@@ -56,6 +58,8 @@ func TestDialTCP(t *testing.T) {
 		srvConnID = tcpConn.ID().String()
 		assert.NoError(tcpConn.Send(ctx, tcpConn.ID().Bytes()))
 		assert.NoError(tcpConn.Send(ctx, payload))
+
+		wg.Done()
 	}()
 
 	port := lis.Addr().(*net.TCPAddr).Port
@@ -68,6 +72,8 @@ func TestDialTCP(t *testing.T) {
 	recv, err := conn.Receive(ctx)
 	assert.NoError(err)
 	assert.Equal(payload, recv)
+
+	wg.Wait()
 }
 
 func TestTCPConnWriteContext(t *testing.T) {
diff --git a/internal/parser/parser_test.go b/internal/parser/parser_test.go
index 49de30a..799ae2a 100644
--- a/internal/parser/parser_test.go
+++ b/internal/parser/parser_test.go
@@ -9645,6 +9645,53 @@ func TestSingleStatementParse(t *testing.T) {
 				},
 			},
 		},
+		{
+			"SELECT stmt with result column with single expr - function name",
+			"SELECT AVG(price) AS average_price FROM items LEFT OUTER JOIN prices",
+			&ast.SQLStmt{
+				SelectStmt: &ast.SelectStmt{
+					SelectCore: []*ast.SelectCore{
+						{
+							Select: token.New(1, 1, 0, 6, token.KeywordSelect, "SELECT"),
+							ResultColumn: []*ast.ResultColumn{
+								{
+									Expr: &ast.Expr{
+										FunctionName: token.New(1, 8, 7, 3, token.Literal, "AVG"),
+										LeftParen:    token.New(1, 11, 10, 1, token.Delimiter, "("),
+										Expr: []*ast.Expr{
+											{
+												LiteralValue: token.New(1, 12, 11, 5, token.Literal, "price"),
+											},
+										},
+										RightParen: token.New(1, 17, 16, 1, token.Delimiter, ")"),
+									},
+									As:          token.New(1, 19, 18, 2, token.KeywordAs, "AS"),
+									ColumnAlias: token.New(1, 22, 21, 13, token.Literal, "average_price"),
+								},
+							},
+							From: token.New(1, 36, 35, 4, token.KeywordFrom, "FROM"),
+							JoinClause: &ast.JoinClause{
+								TableOrSubquery: &ast.TableOrSubquery{
+									TableName: token.New(1, 41, 40, 5, token.Literal, "items"),
+								},
+								JoinClausePart: []*ast.JoinClausePart{
+									{
+										JoinOperator: &ast.JoinOperator{
+											Left:  token.New(1, 47, 46, 4, token.KeywordLeft, "LEFT"),
+											Outer: token.New(1, 52, 51, 5, token.KeywordOuter, "OUTER"),
+											Join:  token.New(1, 58, 57, 4, token.KeywordJoin, "JOIN"),
+										},
+										TableOrSubquery: &ast.TableOrSubquery{
+											TableName: token.New(1, 63, 62, 6, token.Literal, "prices"),
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
 	}
 
 	for _, input := range inputs {