Skip to content

Commit e693098

Browse files
authored
Fix JOIN conditions in correlated JOINs (#257)
* Fix Cond in Join * Update testdata
1 parent d1d0213 commit e693098

11 files changed

+1197
-10
lines changed

ast/ast.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -1163,7 +1163,11 @@ type Join struct {
11631163
Method JoinMethod
11641164
Hint *Hint // optional
11651165
Left, Right TableExpr
1166-
Cond JoinCondition // nil when Op is CrossJoin, otherwise it must be set.
1166+
1167+
// nil when Op is CrossJoin
1168+
// optional when Right is PathTableExpr or Unnest
1169+
// otherwise it must be set.
1170+
Cond JoinCondition
11671171
}
11681172

11691173
// On is ON condition of JOIN expression.

parser.go

+14-9
Original file line numberDiff line numberDiff line change
@@ -953,18 +953,16 @@ func (p *Parser) parseTableExpr(toplevel bool) ast.TableExpr {
953953
hint := p.tryParseHint()
954954
right := p.parseSimpleTableExpr()
955955

956-
if op == ast.CrossJoin || op == ast.CommaJoin {
957-
join = &ast.Join{
958-
Op: op,
959-
Method: method,
960-
Hint: hint,
961-
Left: join,
962-
Right: right,
956+
var cond ast.JoinCondition
957+
if op != ast.CrossJoin && op != ast.CommaJoin {
958+
switch right.(type) {
959+
case *ast.PathTableExpr, *ast.Unnest:
960+
cond = p.tryParseJoinCondition()
961+
default:
962+
cond = p.parseJoinCondition()
963963
}
964-
continue
965964
}
966965

967-
cond := p.parseJoinCondition()
968966
join = &ast.Join{
969967
Op: op,
970968
Method: method,
@@ -1155,6 +1153,13 @@ func (p *Parser) parseJoinCondition() ast.JoinCondition {
11551153
panic(p.errorfAtToken(&p.Token, "expected token: ON, USING, but: %s", p.Token.Kind))
11561154
}
11571155

1156+
func (p *Parser) tryParseJoinCondition() ast.JoinCondition {
1157+
if p.Token.Kind != "ON" && p.Token.Kind != "USING" {
1158+
return nil
1159+
}
1160+
return p.parseJoinCondition()
1161+
}
1162+
11581163
func (p *Parser) parseOn() *ast.On {
11591164
pos := p.expect("ON").Pos
11601165
expr := p.parseExpr()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
-- https://cloud.google.com/spanner/docs/reference/standard-sql/query-syntax#correlated_join
2+
SELECT A.name, item
3+
FROM
4+
UNNEST(
5+
[
6+
STRUCT(
7+
'first' AS name,
8+
[1, 2, 3, 4] AS items),
9+
STRUCT(
10+
'second' AS name,
11+
[] AS items)]) AS A
12+
INNER JOIN
13+
A.items AS item
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-- https://cloud.google.com/spanner/docs/reference/standard-sql/query-syntax#correlated_join
2+
SELECT *
3+
FROM
4+
Roster
5+
JOIN
6+
UNNEST(
7+
ARRAY(
8+
SELECT AS STRUCT *
9+
FROM PlayerStats
10+
WHERE PlayerStats.OpponentID = Roster.SchoolID
11+
)) AS PlayerMatches
12+
ON PlayerMatches.LastName = 'Buchanan'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
-- https://cloud.google.com/spanner/docs/reference/standard-sql/query-syntax#correlated_join
2+
SELECT A.name, item, ARRAY_LENGTH(A.items) item_count_for_name
3+
FROM
4+
UNNEST(
5+
[
6+
STRUCT(
7+
'first' AS name,
8+
[1, 2, 3, 4] AS items),
9+
STRUCT(
10+
'second' AS name,
11+
[] AS items)]) AS A
12+
LEFT JOIN
13+
A.items AS item
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
--- select_from_inner_join_path_table_expr.sql
2+
-- https://cloud.google.com/spanner/docs/reference/standard-sql/query-syntax#correlated_join
3+
SELECT A.name, item
4+
FROM
5+
UNNEST(
6+
[
7+
STRUCT(
8+
'first' AS name,
9+
[1, 2, 3, 4] AS items),
10+
STRUCT(
11+
'second' AS name,
12+
[] AS items)]) AS A
13+
INNER JOIN
14+
A.items AS item
15+
16+
--- AST
17+
&ast.QueryStatement{
18+
Query: &ast.Select{
19+
Select: 93,
20+
Results: []ast.SelectItem{
21+
&ast.ExprSelectItem{
22+
Expr: &ast.Path{
23+
Idents: []*ast.Ident{
24+
&ast.Ident{
25+
NamePos: 100,
26+
NameEnd: 101,
27+
Name: "A",
28+
},
29+
&ast.Ident{
30+
NamePos: 102,
31+
NameEnd: 106,
32+
Name: "name",
33+
},
34+
},
35+
},
36+
},
37+
&ast.ExprSelectItem{
38+
Expr: &ast.Ident{
39+
NamePos: 108,
40+
NameEnd: 112,
41+
Name: "item",
42+
},
43+
},
44+
},
45+
From: &ast.From{
46+
From: 113,
47+
Source: &ast.Join{
48+
Op: "INNER JOIN",
49+
Left: &ast.Unnest{
50+
Unnest: 120,
51+
Rparen: 268,
52+
Expr: &ast.ArrayLiteral{
53+
Array: -1,
54+
Lbrack: 132,
55+
Rbrack: 267,
56+
Values: []ast.Expr{
57+
&ast.TypelessStructLiteral{
58+
Struct: 140,
59+
Rparen: 202,
60+
Values: []ast.TypelessStructLiteralArg{
61+
&ast.Alias{
62+
Expr: &ast.StringLiteral{
63+
ValuePos: 156,
64+
ValueEnd: 163,
65+
Value: "first",
66+
},
67+
As: &ast.AsAlias{
68+
As: 164,
69+
Alias: &ast.Ident{
70+
NamePos: 167,
71+
NameEnd: 171,
72+
Name: "name",
73+
},
74+
},
75+
},
76+
&ast.Alias{
77+
Expr: &ast.ArrayLiteral{
78+
Array: -1,
79+
Lbrack: 181,
80+
Rbrack: 192,
81+
Values: []ast.Expr{
82+
&ast.IntLiteral{
83+
ValuePos: 182,
84+
ValueEnd: 183,
85+
Base: 10,
86+
Value: "1",
87+
},
88+
&ast.IntLiteral{
89+
ValuePos: 185,
90+
ValueEnd: 186,
91+
Base: 10,
92+
Value: "2",
93+
},
94+
&ast.IntLiteral{
95+
ValuePos: 188,
96+
ValueEnd: 189,
97+
Base: 10,
98+
Value: "3",
99+
},
100+
&ast.IntLiteral{
101+
ValuePos: 191,
102+
ValueEnd: 192,
103+
Base: 10,
104+
Value: "4",
105+
},
106+
},
107+
},
108+
As: &ast.AsAlias{
109+
As: 194,
110+
Alias: &ast.Ident{
111+
NamePos: 197,
112+
NameEnd: 202,
113+
Name: "items",
114+
},
115+
},
116+
},
117+
},
118+
},
119+
&ast.TypelessStructLiteral{
120+
Struct: 211,
121+
Rparen: 266,
122+
Values: []ast.TypelessStructLiteralArg{
123+
&ast.Alias{
124+
Expr: &ast.StringLiteral{
125+
ValuePos: 229,
126+
ValueEnd: 237,
127+
Value: "second",
128+
},
129+
As: &ast.AsAlias{
130+
As: 238,
131+
Alias: &ast.Ident{
132+
NamePos: 241,
133+
NameEnd: 245,
134+
Name: "name",
135+
},
136+
},
137+
},
138+
&ast.Alias{
139+
Expr: &ast.ArrayLiteral{
140+
Array: -1,
141+
Lbrack: 255,
142+
Rbrack: 256,
143+
},
144+
As: &ast.AsAlias{
145+
As: 258,
146+
Alias: &ast.Ident{
147+
NamePos: 261,
148+
NameEnd: 266,
149+
Name: "items",
150+
},
151+
},
152+
},
153+
},
154+
},
155+
},
156+
},
157+
As: &ast.AsAlias{
158+
As: 270,
159+
Alias: &ast.Ident{
160+
NamePos: 273,
161+
NameEnd: 274,
162+
Name: "A",
163+
},
164+
},
165+
},
166+
Right: &ast.PathTableExpr{
167+
Path: &ast.Path{
168+
Idents: []*ast.Ident{
169+
&ast.Ident{
170+
NamePos: 292,
171+
NameEnd: 293,
172+
Name: "A",
173+
},
174+
&ast.Ident{
175+
NamePos: 294,
176+
NameEnd: 299,
177+
Name: "items",
178+
},
179+
},
180+
},
181+
As: &ast.AsAlias{
182+
As: 300,
183+
Alias: &ast.Ident{
184+
NamePos: 303,
185+
NameEnd: 307,
186+
Name: "item",
187+
},
188+
},
189+
},
190+
},
191+
},
192+
},
193+
}
194+
195+
--- SQL
196+
SELECT A.name, item FROM UNNEST([STRUCT("first" AS name, [1, 2, 3, 4] AS items), STRUCT("second" AS name, [] AS items)]) AS A INNER JOIN A.items AS item

0 commit comments

Comments
 (0)