Repository: tomarrell/lbadd (go)

Issue:
parser: JoinClause.JoinClausePart must be slice
In the AST, according to https://www.sqlite.org/syntax/join-clause.html , JoinClause.JoinClausePart must be a slice.
With the current state, the AST doesn't allow to represent statements like `SELECT * FROM a JOIN b JOIN c`

Pull request:
Converts join-clause-part into a slice of join-clause-parts
Fixes #130 

Bug fixed, test added to verify the change.

Patch:
diff --git a/internal/parser/ast/statement.go b/internal/parser/ast/statement.go
index 3f3ab7c..68a1ae2 100644
--- a/internal/parser/ast/statement.go
+++ b/internal/parser/ast/statement.go
@@ -737,7 +737,7 @@ type (
 	// JoinClause as in the SQLite grammar.
 	JoinClause struct {
 		TableOrSubquery *TableOrSubquery
-		JoinClausePart  *JoinClausePart
+		JoinClausePart  []*JoinClausePart
 	}
 
 	// JoinClausePart as in the SQLite grammar.
diff --git a/internal/parser/simple_parser_rules.go b/internal/parser/simple_parser_rules.go
index 7b72728..86adae8 100644
--- a/internal/parser/simple_parser_rules.go
+++ b/internal/parser/simple_parser_rules.go
@@ -4438,7 +4438,7 @@ func (p *simpleParser) parseJoinClause(r reporter) (stmt *ast.JoinClause) {
 			next.Type() == token.KeywordLeft ||
 			next.Type() == token.KeywordInner ||
 			next.Type() == token.KeywordCross {
-			stmt.JoinClausePart = p.parseJoinClausePart(r)
+			stmt.JoinClausePart = append(stmt.JoinClausePart, p.parseJoinClausePart(r))
 		} else {
 			break
 		}


Test patch:
diff --git a/internal/parser/parser_test.go b/internal/parser/parser_test.go
index 232f881..71419f9 100644
--- a/internal/parser/parser_test.go
+++ b/internal/parser/parser_test.go
@@ -1774,12 +1774,14 @@ func TestSingleStatementParse(t *testing.T) {
 												TableOrSubquery: &ast.TableOrSubquery{
 													TableName: token.New(1, 32, 31, 8, token.Literal, "myTable1"),
 												},
-												JoinClausePart: &ast.JoinClausePart{
-													JoinOperator: &ast.JoinOperator{
-														Comma: token.New(1, 40, 39, 1, token.Delimiter, ","),
-													},
-													TableOrSubquery: &ast.TableOrSubquery{
-														TableName: token.New(1, 41, 40, 8, token.Literal, "myTable2"),
+												JoinClausePart: []*ast.JoinClausePart{
+													{
+														JoinOperator: &ast.JoinOperator{
+															Comma: token.New(1, 40, 39, 1, token.Delimiter, ","),
+														},
+														TableOrSubquery: &ast.TableOrSubquery{
+															TableName: token.New(1, 41, 40, 8, token.Literal, "myTable2"),
+														},
 													},
 												},
 											},
@@ -1799,8 +1801,8 @@ func TestSingleStatementParse(t *testing.T) {
 			},
 		},
 		{
-			"DELETE with basic with clause, select stmt with FROM with joinclause's ON and basic cte-table-name",
-			"WITH myTable AS (SELECT * FROM myTable1,myTable2 ON myExpr) DELETE FROM myTable",
+			"DELETE with basic with clause, select stmt with FROM with joinclause with multiple join-clause-parts, join constraint and basic cte-table-name",
+			"WITH myTable AS (SELECT * FROM myTable1,myTable2 JOIN myTable3) DELETE FROM myTable",
 			&ast.SQLStmt{
 				DeleteStmt: &ast.DeleteStmt{
 					WithClause: &ast.WithClause{
@@ -1826,17 +1828,81 @@ func TestSingleStatementParse(t *testing.T) {
 												TableOrSubquery: &ast.TableOrSubquery{
 													TableName: token.New(1, 32, 31, 8, token.Literal, "myTable1"),
 												},
-												JoinClausePart: &ast.JoinClausePart{
-													JoinOperator: &ast.JoinOperator{
-														Comma: token.New(1, 40, 39, 1, token.Delimiter, ","),
+												JoinClausePart: []*ast.JoinClausePart{
+													{
+														JoinOperator: &ast.JoinOperator{
+															Comma: token.New(1, 40, 39, 1, token.Delimiter, ","),
+														},
+														TableOrSubquery: &ast.TableOrSubquery{
+															TableName: token.New(1, 41, 40, 8, token.Literal, "myTable2"),
+														},
 													},
-													TableOrSubquery: &ast.TableOrSubquery{
-														TableName: token.New(1, 41, 40, 8, token.Literal, "myTable2"),
+													{
+														JoinOperator: &ast.JoinOperator{
+															Join: token.New(1, 50, 49, 4, token.KeywordJoin, "JOIN"),
+														},
+														TableOrSubquery: &ast.TableOrSubquery{
+															TableName: token.New(1, 55, 54, 8, token.Literal, "myTable3"),
+														},
 													},
-													JoinConstraint: &ast.JoinConstraint{
-														On: token.New(1, 50, 49, 2, token.KeywordOn, "ON"),
-														Expr: &ast.Expr{
-															LiteralValue: token.New(1, 53, 52, 6, token.Literal, "myExpr"),
+												},
+											},
+										},
+									},
+								},
+								RightParen: token.New(1, 63, 62, 1, token.Delimiter, ")"),
+							},
+						},
+					},
+					Delete: token.New(1, 65, 64, 6, token.KeywordDelete, "DELETE"),
+					From:   token.New(1, 72, 71, 4, token.KeywordFrom, "FROM"),
+					QualifiedTableName: &ast.QualifiedTableName{
+						TableName: token.New(1, 77, 76, 7, token.Literal, "myTable"),
+					},
+				},
+			},
+		},
+		{
+			"DELETE with basic with clause, select stmt with FROM with joinclause's ON and basic cte-table-name",
+			"WITH myTable AS (SELECT * FROM myTable1,myTable2 ON myExpr) DELETE FROM myTable",
+			&ast.SQLStmt{
+				DeleteStmt: &ast.DeleteStmt{
+					WithClause: &ast.WithClause{
+						With: token.New(1, 1, 0, 4, token.KeywordWith, "WITH"),
+						RecursiveCte: []*ast.RecursiveCte{
+							{
+								CteTableName: &ast.CteTableName{
+									TableName: token.New(1, 6, 5, 7, token.Literal, "myTable"),
+								},
+								As:        token.New(1, 14, 13, 2, token.KeywordAs, "AS"),
+								LeftParen: token.New(1, 17, 16, 1, token.Delimiter, "("),
+								SelectStmt: &ast.SelectStmt{
+									SelectCore: []*ast.SelectCore{
+										{
+											Select: token.New(1, 18, 17, 6, token.KeywordSelect, "SELECT"),
+											ResultColumn: []*ast.ResultColumn{
+												{
+													Asterisk: token.New(1, 25, 24, 1, token.BinaryOperator, "*"),
+												},
+											},
+											From: token.New(1, 27, 26, 4, token.KeywordFrom, "FROM"),
+											JoinClause: &ast.JoinClause{
+												TableOrSubquery: &ast.TableOrSubquery{
+													TableName: token.New(1, 32, 31, 8, token.Literal, "myTable1"),
+												},
+												JoinClausePart: []*ast.JoinClausePart{
+													{
+														JoinOperator: &ast.JoinOperator{
+															Comma: token.New(1, 40, 39, 1, token.Delimiter, ","),
+														},
+														TableOrSubquery: &ast.TableOrSubquery{
+															TableName: token.New(1, 41, 40, 8, token.Literal, "myTable2"),
+														},
+														JoinConstraint: &ast.JoinConstraint{
+															On: token.New(1, 50, 49, 2, token.KeywordOn, "ON"),
+															Expr: &ast.Expr{
+																LiteralValue: token.New(1, 53, 52, 6, token.Literal, "myExpr"),
+															},
 														},
 													},
 												},
@@ -1884,20 +1950,22 @@ func TestSingleStatementParse(t *testing.T) {
 												TableOrSubquery: &ast.TableOrSubquery{
 													TableName: token.New(1, 32, 31, 8, token.Literal, "myTable1"),
 												},
-												JoinClausePart: &ast.JoinClausePart{
-													JoinOperator: &ast.JoinOperator{
-														Comma: token.New(1, 40, 39, 1, token.Delimiter, ","),
-													},
-													TableOrSubquery: &ast.TableOrSubquery{
-														TableName: token.New(1, 41, 40, 8, token.Literal, "myTable2"),
-													},
-													JoinConstraint: &ast.JoinConstraint{
-														Using:     token.New(1, 50, 49, 5, token.KeywordUsing, "USING"),
-														LeftParen: token.New(1, 56, 55, 1, token.Delimiter, "("),
-														ColumnName: []token.Token{
-															token.New(1, 57, 56, 5, token.Literal, "myCol"),
+												JoinClausePart: []*ast.JoinClausePart{
+													{
+														JoinOperator: &ast.JoinOperator{
+															Comma: token.New(1, 40, 39, 1, token.Delimiter, ","),
+														},
+														TableOrSubquery: &ast.TableOrSubquery{
+															TableName: token.New(1, 41, 40, 8, token.Literal, "myTable2"),
+														},
+														JoinConstraint: &ast.JoinConstraint{
+															Using:     token.New(1, 50, 49, 5, token.KeywordUsing, "USING"),
+															LeftParen: token.New(1, 56, 55, 1, token.Delimiter, "("),
+															ColumnName: []token.Token{
+																token.New(1, 57, 56, 5, token.Literal, "myCol"),
+															},
+															RightParen: token.New(1, 62, 61, 1, token.Delimiter, ")"),
 														},
-														RightParen: token.New(1, 62, 61, 1, token.Delimiter, ")"),
 													},
 												},
 											},
@@ -1944,21 +2012,23 @@ func TestSingleStatementParse(t *testing.T) {
 												TableOrSubquery: &ast.TableOrSubquery{
 													TableName: token.New(1, 32, 31, 8, token.Literal, "myTable1"),
 												},
-												JoinClausePart: &ast.JoinClausePart{
-													JoinOperator: &ast.JoinOperator{
-														Comma: token.New(1, 40, 39, 1, token.Delimiter, ","),
-													},
-													TableOrSubquery: &ast.TableOrSubquery{
-														TableName: token.New(1, 41, 40, 8, token.Literal, "myTable2"),
-													},
-													JoinConstraint: &ast.JoinConstraint{
-														Using:     token.New(1, 50, 49, 5, token.KeywordUsing, "USING"),
-														LeftParen: token.New(1, 56, 55, 1, token.Delimiter, "("),
-														ColumnName: []token.Token{
-															token.New(1, 57, 56, 6, token.Literal, "myCol1"),
-															token.New(1, 64, 63, 6, token.Literal, "myCol2"),
+												JoinClausePart: []*ast.JoinClausePart{
+													{
+														JoinOperator: &ast.JoinOperator{
+															Comma: token.New(1, 40, 39, 1, token.Delimiter, ","),
+														},
+														TableOrSubquery: &ast.TableOrSubquery{
+															TableName: token.New(1, 41, 40, 8, token.Literal, "myTable2"),
+														},
+														JoinConstraint: &ast.JoinConstraint{
+															Using:     token.New(1, 50, 49, 5, token.KeywordUsing, "USING"),
+															LeftParen: token.New(1, 56, 55, 1, token.Delimiter, "("),
+															ColumnName: []token.Token{
+																token.New(1, 57, 56, 6, token.Literal, "myCol1"),
+																token.New(1, 64, 63, 6, token.Literal, "myCol2"),
+															},
+															RightParen: token.New(1, 70, 69, 1, token.Delimiter, ")"),
 														},
-														RightParen: token.New(1, 70, 69, 1, token.Delimiter, ")"),
 													},
 												},
 											},
@@ -2005,12 +2075,14 @@ func TestSingleStatementParse(t *testing.T) {
 												TableOrSubquery: &ast.TableOrSubquery{
 													TableName: token.New(1, 32, 31, 8, token.Literal, "myTable1"),
 												},
-												JoinClausePart: &ast.JoinClausePart{
-													JoinOperator: &ast.JoinOperator{
-														Join: token.New(1, 41, 40, 4, token.KeywordJoin, "JOIN"),
-													},
-													TableOrSubquery: &ast.TableOrSubquery{
-														TableName: token.New(1, 46, 45, 8, token.Literal, "myTable2"),
+												JoinClausePart: []*ast.JoinClausePart{
+													{
+														JoinOperator: &ast.JoinOperator{
+															Join: token.New(1, 41, 40, 4, token.KeywordJoin, "JOIN"),
+														},
+														TableOrSubquery: &ast.TableOrSubquery{
+															TableName: token.New(1, 46, 45, 8, token.Literal, "myTable2"),
+														},
 													},
 												},
 											},
@@ -2057,13 +2129,15 @@ func TestSingleStatementParse(t *testing.T) {
 												TableOrSubquery: &ast.TableOrSubquery{
 													TableName: token.New(1, 32, 31, 8, token.Literal, "myTable1"),
 												},
-												JoinClausePart: &ast.JoinClausePart{
-													JoinOperator: &ast.JoinOperator{
-														Natural: token.New(1, 41, 40, 7, token.KeywordNatural, "NATURAL"),
-														Join:    token.New(1, 49, 48, 4, token.KeywordJoin, "JOIN"),
-													},
-													TableOrSubquery: &ast.TableOrSubquery{
-														TableName: token.New(1, 54, 53, 8, token.Literal, "myTable2"),
+												JoinClausePart: []*ast.JoinClausePart{
+													{
+														JoinOperator: &ast.JoinOperator{
+															Natural: token.New(1, 41, 40, 7, token.KeywordNatural, "NATURAL"),
+															Join:    token.New(1, 49, 48, 4, token.KeywordJoin, "JOIN"),
+														},
+														TableOrSubquery: &ast.TableOrSubquery{
+															TableName: token.New(1, 54, 53, 8, token.Literal, "myTable2"),
+														},
 													},
 												},
 											},
@@ -2110,13 +2184,15 @@ func TestSingleStatementParse(t *testing.T) {
 												TableOrSubquery: &ast.TableOrSubquery{
 													TableName: token.New(1, 32, 31, 8, token.Literal, "myTable1"),
 												},
-												JoinClausePart: &ast.JoinClausePart{
-													JoinOperator: &ast.JoinOperator{
-														Left: token.New(1, 41, 40, 4, token.KeywordLeft, "LEFT"),
-														Join: token.New(1, 46, 45, 4, token.KeywordJoin, "JOIN"),
-													},
-													TableOrSubquery: &ast.TableOrSubquery{
-														TableName: token.New(1, 51, 50, 8, token.Literal, "myTable2"),
+												JoinClausePart: []*ast.JoinClausePart{
+													{
+														JoinOperator: &ast.JoinOperator{
+															Left: token.New(1, 41, 40, 4, token.KeywordLeft, "LEFT"),
+															Join: token.New(1, 46, 45, 4, token.KeywordJoin, "JOIN"),
+														},
+														TableOrSubquery: &ast.TableOrSubquery{
+															TableName: token.New(1, 51, 50, 8, token.Literal, "myTable2"),
+														},
 													},
 												},
 											},
@@ -2163,14 +2239,16 @@ func TestSingleStatementParse(t *testing.T) {
 												TableOrSubquery: &ast.TableOrSubquery{
 													TableName: token.New(1, 32, 31, 8, token.Literal, "myTable1"),
 												},
-												JoinClausePart: &ast.JoinClausePart{
-													JoinOperator: &ast.JoinOperator{
-														Left:  token.New(1, 41, 40, 4, token.KeywordLeft, "LEFT"),
-														Outer: token.New(1, 46, 45, 5, token.KeywordOuter, "OUTER"),
-														Join:  token.New(1, 52, 51, 4, token.KeywordJoin, "JOIN"),
-													},
-													TableOrSubquery: &ast.TableOrSubquery{
-														TableName: token.New(1, 57, 56, 8, token.Literal, "myTable2"),
+												JoinClausePart: []*ast.JoinClausePart{
+													{
+														JoinOperator: &ast.JoinOperator{
+															Left:  token.New(1, 41, 40, 4, token.KeywordLeft, "LEFT"),
+															Outer: token.New(1, 46, 45, 5, token.KeywordOuter, "OUTER"),
+															Join:  token.New(1, 52, 51, 4, token.KeywordJoin, "JOIN"),
+														},
+														TableOrSubquery: &ast.TableOrSubquery{
+															TableName: token.New(1, 57, 56, 8, token.Literal, "myTable2"),
+														},
 													},
 												},
 											},
@@ -2217,13 +2295,15 @@ func TestSingleStatementParse(t *testing.T) {
 												TableOrSubquery: &ast.TableOrSubquery{
 													TableName: token.New(1, 32, 31, 8, token.Literal, "myTable1"),
 												},
-												JoinClausePart: &ast.JoinClausePart{
-													JoinOperator: &ast.JoinOperator{
-														Inner: token.New(1, 41, 40, 5, token.KeywordInner, "INNER"),
-														Join:  token.New(1, 47, 46, 4, token.KeywordJoin, "JOIN"),
-													},
-													TableOrSubquery: &ast.TableOrSubquery{
-														TableName: token.New(1, 52, 51, 8, token.Literal, "myTable2"),
+												JoinClausePart: []*ast.JoinClausePart{
+													{
+														JoinOperator: &ast.JoinOperator{
+															Inner: token.New(1, 41, 40, 5, token.KeywordInner, "INNER"),
+															Join:  token.New(1, 47, 46, 4, token.KeywordJoin, "JOIN"),
+														},
+														TableOrSubquery: &ast.TableOrSubquery{
+															TableName: token.New(1, 52, 51, 8, token.Literal, "myTable2"),
+														},
 													},
 												},
 											},
@@ -2270,13 +2350,15 @@ func TestSingleStatementParse(t *testing.T) {
 												TableOrSubquery: &ast.TableOrSubquery{
 													TableName: token.New(1, 32, 31, 8, token.Literal, "myTable1"),
 												},
-												JoinClausePart: &ast.JoinClausePart{
-													JoinOperator: &ast.JoinOperator{
-														Cross: token.New(1, 41, 40, 5, token.KeywordCross, "CROSS"),
-														Join:  token.New(1, 47, 46, 4, token.KeywordJoin, "JOIN"),
-													},
-													TableOrSubquery: &ast.TableOrSubquery{
-														TableName: token.New(1, 52, 51, 8, token.Literal, "myTable2"),
+												JoinClausePart: []*ast.JoinClausePart{
+													{
+														JoinOperator: &ast.JoinOperator{
+															Cross: token.New(1, 41, 40, 5, token.KeywordCross, "CROSS"),
+															Join:  token.New(1, 47, 46, 4, token.KeywordJoin, "JOIN"),
+														},
+														TableOrSubquery: &ast.TableOrSubquery{
+															TableName: token.New(1, 52, 51, 8, token.Literal, "myTable2"),
+														},
 													},
 												},
 											},