Skip to content

Commit 938ee01

Browse files
committed
recursive select calls are parsed with bad trailing_commas parameter
1 parent fad2ddd commit 938ee01

File tree

5 files changed

+310
-12
lines changed

5 files changed

+310
-12
lines changed

src/parser/mod.rs

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3532,16 +3532,11 @@ impl<'a> Parser<'a> {
35323532
// e.g. `SELECT 1, 2, FROM t`
35333533
// https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#trailing_commas
35343534
// https://docs.snowflake.com/en/release-notes/2024/8_11#select-supports-trailing-commas
3535-
//
3536-
// This pattern could be captured better with RAII type semantics, but it's quite a bit of
3537-
// code to add for just one case, so we'll just do it manually here.
3538-
let old_value = self.options.trailing_commas;
3539-
self.options.trailing_commas |= self.dialect.supports_projection_trailing_commas();
35403535

3541-
let ret = self.parse_comma_separated(|p| p.parse_select_item());
3542-
self.options.trailing_commas = old_value;
3536+
let trailing_commas =
3537+
self.options.trailing_commas | self.dialect.supports_projection_trailing_commas();
35433538

3544-
ret
3539+
self.parse_comma_separated_with_trailing_commas(|p| p.parse_select_item(), trailing_commas)
35453540
}
35463541

35473542
pub fn parse_actions_list(&mut self) -> Result<Vec<ParsedAction>, ParserError> {
@@ -3568,11 +3563,12 @@ impl<'a> Parser<'a> {
35683563
}
35693564

35703565
/// Parse the comma of a comma-separated syntax element.
3566+
/// Allows for control over trailing commas
35713567
/// Returns true if there is a next element
3572-
fn is_parse_comma_separated_end(&mut self) -> bool {
3568+
fn is_parse_comma_separated_end_with_trailing_commas(&mut self, trailing_commas: bool) -> bool {
35733569
if !self.consume_token(&Token::Comma) {
35743570
true
3575-
} else if self.options.trailing_commas {
3571+
} else if trailing_commas {
35763572
let token = self.peek_token().token;
35773573
match token {
35783574
Token::Word(ref kw)
@@ -3590,15 +3586,34 @@ impl<'a> Parser<'a> {
35903586
}
35913587
}
35923588

3589+
/// Parse the comma of a comma-separated syntax element.
3590+
/// Returns true if there is a next element
3591+
fn is_parse_comma_separated_end(&mut self) -> bool {
3592+
self.is_parse_comma_separated_end_with_trailing_commas(self.options.trailing_commas)
3593+
}
3594+
35933595
/// Parse a comma-separated list of 1+ items accepted by `F`
3594-
pub fn parse_comma_separated<T, F>(&mut self, mut f: F) -> Result<Vec<T>, ParserError>
3596+
pub fn parse_comma_separated<T, F>(&mut self, f: F) -> Result<Vec<T>, ParserError>
3597+
where
3598+
F: FnMut(&mut Parser<'a>) -> Result<T, ParserError>,
3599+
{
3600+
self.parse_comma_separated_with_trailing_commas(f, self.options.trailing_commas)
3601+
}
3602+
3603+
/// Parse a comma-separated list of 1+ items accepted by `F`
3604+
/// Allows for control over trailing commas
3605+
fn parse_comma_separated_with_trailing_commas<T, F>(
3606+
&mut self,
3607+
mut f: F,
3608+
trailing_commas: bool,
3609+
) -> Result<Vec<T>, ParserError>
35953610
where
35963611
F: FnMut(&mut Parser<'a>) -> Result<T, ParserError>,
35973612
{
35983613
let mut values = vec![];
35993614
loop {
36003615
values.push(f(self)?);
3601-
if self.is_parse_comma_separated_end() {
3616+
if self.is_parse_comma_separated_end_with_trailing_commas(trailing_commas) {
36023617
break;
36033618
}
36043619
}

test.sql

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
WITH EventData AS (
2+
SELECT
3+
TICKET_ID,
4+
(SELECT events.value:assignee_id::STRING
5+
FROM VIA.ZENDESK_PARTNERSHIPS.TICKET_EVENTS T,
6+
LATERAL FLATTEN(input => t.Child_events) AS events
7+
WHERE events.value:event_type::STRING LIKE 'AAA'
8+
AND events.value:assignee_id::STRING IN (
9+
'AAA', 'AAA', 'AAA', 'AAA',
10+
'AAA', 'AAA', 'AAA', 'AAA',
11+
'AAA', 'AAA', 'AAA', 'AAA',
12+
'AAA', 'AAA', 'AAA', 'AAA',
13+
'AAA'
14+
)
15+
AND T.TICKET_ID = t.TICKET_ID
16+
ORDER BY t.created_at DESC
17+
LIMIT 1
18+
) AS Tier2_ticket_owner,
19+
CASE
20+
WHEN MIN(CASE
21+
WHEN events.value:event_type::STRING LIKE 'AAA'
22+
AND events.value:group_id::STRING LIKE 'AAA'
23+
THEN created_at
24+
END) IS NULL
25+
THEN NULL
26+
ELSE COALESCE(MIN(CASE
27+
WHEN events.value:event_type::STRING LIKE 'AAA'
28+
AND events.value:group_id::STRING LIKE 'AAA'
29+
THEN created_at
30+
END), TIMESTAMP 'AAA')
31+
END AS Earliest_Change_to_TSO_Group,
32+
MAX(CASE
33+
WHEN events.value:event_type::STRING LIKE 'AAA'
34+
AND events.value:group_id::STRING LIKE 'AAA'
35+
THEN created_at END) AS Latest_Change_to_PSO_Group,
36+
MIN(CASE
37+
WHEN events.value:added_tags::STRING LIKE 'AAA'
38+
THEN created_at
39+
END) AS In_Progress_Timestamp,
40+
MIN(CASE
41+
WHEN events.value:added_tags::STRING LIKE 'AAA'
42+
THEN created_at
43+
END) AS Pending_Approval_Timestamp,
44+
MIN(CASE
45+
WHEN events.value:added_tags::STRING LIKE 'AAA'
46+
THEN created_at
47+
END) AS Pending_Deploy_Timestamp,
48+
MAX(CASE
49+
WHEN events.value:added_tags::STRING LIKE 'AAA'
50+
THEN created_at
51+
END) AS Pending_Tier3_Product_Timestamp,
52+
MAX(CASE
53+
WHEN events.value:added_tags::STRING LIKE 'AAA'
54+
THEN created_at
55+
END) AS Pending_Tier3_Dev_Timestamp,
56+
MAX(CASE
57+
WHEN events.value:added_tags::STRING LIKE 'AAA'
58+
THEN created_at
59+
END) AS Pending_Feature_Flag_Timestamp,
60+
MAX(CASE
61+
WHEN events.value:added_tags::STRING LIKE 'AAA'
62+
THEN created_at
63+
END) AS Pending_PSO_Closure_Timestamp,
64+
MIN(CASE
65+
WHEN events.value:added_tags::STRING LIKE 'AAA'
66+
THEN created_at
67+
END) AS Pending_PSO_More_Info_Timestamp,
68+
MAX(CASE
69+
WHEN events.value:added_tags::STRING LIKE 'AAA'
70+
THEN created_at
71+
END) AS Not_Possible_Timestamp,
72+
CASE
73+
WHEN In_Progress_Timestamp IS NULL
74+
AND Pending_Approval_Timestamp IS NULL
75+
AND Pending_Deploy_Timestamp IS NULL
76+
AND Pending_PSO_More_Info_Timestamp IS NULL
77+
THEN NULL
78+
ELSE LEAST(
79+
COALESCE(In_Progress_Timestamp, TIMESTAMP 'AAA'),
80+
COALESCE(Pending_Approval_Timestamp, TIMESTAMP 'AAA'),
81+
COALESCE(Pending_Deploy_Timestamp, TIMESTAMP 'AAA'),
82+
COALESCE(Pending_PSO_More_Info_Timestamp, TIMESTAMP 'AAA')
83+
84+
)
85+
END AS TSO_start_time,
86+
CASE
87+
WHEN Pending_Tier3_Product_Timestamp IS NULL
88+
AND Pending_Tier3_Dev_Timestamp IS NULL
89+
AND Pending_Feature_Flag_Timestamp IS NULL
90+
AND Pending_PSO_Closure_Timestamp IS NULL
91+
AND Not_Possible_Timestamp IS NULL
92+
THEN NULL
93+
ELSE GREATEST(
94+
COALESCE(Pending_Tier3_Product_Timestamp, TIMESTAMP 'AAA'),
95+
COALESCE(Pending_Tier3_Dev_Timestamp, TIMESTAMP 'AAA'),
96+
COALESCE(Pending_Feature_Flag_Timestamp, TIMESTAMP 'AAA'),
97+
COALESCE(Pending_PSO_Closure_Timestamp, TIMESTAMP 'AAA'),
98+
COALESCE(Not_Possible_Timestamp, TIMESTAMP 'AAA')
99+
100+
)
101+
END AS TSO_finish_time,
102+
CASE
103+
WHEN TSO_finish_time IS NOT NULL
104+
AND TSO_start_time IS NOT NULL
105+
AND DATEDIFF(minutes, TSO_start_time, TSO_finish_time) >= 0
106+
THEN DATEDIFF(day, TSO_start_time, TSO_finish_time)
107+
108+
WHEN TSO_finish_time IS NOT NULL
109+
AND TSO_start_time IS NOT NULL
110+
AND DATEDIFF(minutes, TSO_start_time, TSO_finish_time) < 0
111+
THEN DATEDIFF(day, TSO_start_time, CURRENT_TIMESTAMP())
112+
113+
WHEN TSO_finish_time IS NULL
114+
AND TSO_start_time IS NOT NULL
115+
THEN DATEDIFF(day, TSO_start_time, CURRENT_TIMESTAMP())
116+
117+
WHEN (TSO_finish_time IS NULL AND TSO_start_time IS NULL)
118+
THEN DATEDIFF(day, Earliest_Change_to_TSO_Group, CURRENT_TIMESTAMP())
119+
120+
WHEN TSO_finish_time IS NULL
121+
AND Latest_Change_to_PSO_Group IS NOT NULL
122+
AND Earliest_Change_to_TSO_Group IS NOT NULL
123+
AND DATEDIFF(minutes, Earliest_Change_to_TSO_Group, Latest_Change_to_PSO_Group) >= 0
124+
THEN DATEDIFF(day, Earliest_Change_to_TSO_Group, Latest_Change_to_PSO_Group)
125+
126+
WHEN TSO_finish_time IS NULL
127+
AND Latest_Change_to_PSO_Group IS NOT NULL
128+
AND Earliest_Change_to_TSO_Group IS NOT NULL
129+
AND DATEDIFF(minutes, Earliest_Change_to_TSO_Group, Latest_Change_to_PSO_Group) < 0
130+
THEN DATEDIFF(day, Earliest_Change_to_TSO_Group, CURRENT_TIMESTAMP())
131+
132+
WHEN TSO_start_time IS NULL AND TSO_finish_time IS NOT NULL
133+
AND DATEDIFF(minutes, Earliest_Change_to_TSO_Group, TSO_finish_time) >= 0
134+
THEN DATEDIFF(day, Earliest_Change_to_TSO_Group, TSO_finish_time)
135+
136+
WHEN TSO_start_time IS NULL AND TSO_finish_time IS NOT NULL
137+
AND DATEDIFF(minutes, Earliest_Change_to_TSO_Group, TSO_finish_time) < 0
138+
THEN DATEDIFF(day, Earliest_Change_to_TSO_Group, CURRENT_TIMESTAMP())
139+
140+
WHEN TSO_finish_time IS NULL
141+
AND Latest_Change_to_PSO_Group IS NOT NULL
142+
AND Earliest_Change_to_TSO_Group IS NOT NULL
143+
AND Pending_PSO_More_Info_Timestamp IS NOT NULL
144+
THEN DATEDIFF(day, COALESCE(TSO_start_time, Earliest_Change_to_TSO_Group), CURRENT_TIMESTAMP())
145+
146+
147+
WHEN TSO_finish_time IS NULL
148+
AND Latest_Change_to_PSO_Group IS NOT NULL
149+
AND Earliest_Change_to_TSO_Group IS NOT NULL
150+
AND DATEDIFF(minutes, Earliest_Change_to_TSO_Group, Latest_Change_to_PSO_Group) < 0
151+
THEN DATEDIFF(day, COALESCE(Latest_Change_to_PSO_Group, Earliest_Change_to_TSO_Group), CURRENT_TIMESTAMP())
152+
ELSE NULL
153+
END AS TSO_time_Status,
154+
FROM
155+
VIA.ZENDESK_PARTNERSHIPS.TICKET_EVENTS,
156+
LATERAL FLATTEN(input => Child_events) AS events
157+
WHERE
158+
(events.value:added_tags::STRING LIKE 'AAA' OR
159+
events.value:added_tags::STRING LIKE 'AAA' OR
160+
events.value:added_tags::STRING LIKE 'AAA' OR
161+
events.value:added_tags::STRING LIKE 'AAA' OR
162+
events.value:added_tags::STRING LIKE 'AAA' OR
163+
events.value:added_tags::STRING LIKE 'AAA' OR
164+
events.value:added_tags::STRING LIKE 'AAA' OR
165+
events.value:added_tags::STRING LIKE 'AAA' OR
166+
events.value:added_tags::STRING LIKE 'AAA' OR
167+
events.value:event_type::STRING LIKE 'AAA'
168+
AND (events.value:group_id::STRING LIKE 'AAA' OR events.value:group_id::STRING LIKE 'AAA' OR
169+
events.value:group_id::STRING LIKE 'AAA')) OR
170+
(events.value:event_type::STRING LIKE 'AAA'
171+
AND events.value:assignee_id::STRING IN (
172+
4763338146451,
173+
8737893951123,
174+
9246986348435,
175+
27502412386323,
176+
6403976924691,
177+
14124695442323,
178+
7901630909715,
179+
398450669673,
180+
14124726117779,
181+
5597884196755,
182+
1916060427385,
183+
28950546428307,
184+
6750540975379,
185+
5466485443475,
186+
1511724071422,
187+
4763344069139,
188+
395464397734)) OR
189+
(events.value:event_type::STRING LIKE 'AAA'
190+
AND events.value:group_id::STRING LIKE 'AAA')
191+
GROUP BY
192+
TICKET_ID
193+
)
194+
SELECT
195+
t.*,
196+
e.*,
197+
CASE
198+
WHEN t.tags::STRING LIKE 'AAA' THEN 'AAA'
199+
WHEN t.tags::STRING LIKE 'AAA' THEN 'AAA'
200+
ELSE NULL
201+
END AS Pod_Value,
202+
CASE
203+
WHEN t.tags::STRING LIKE 'AAA' THEN 'AAA'
204+
WHEN t.tags::STRING LIKE 'AAA' THEN 'AAA'
205+
WHEN t.tags::STRING LIKE 'AAA' THEN 'AAA'
206+
WHEN t.tags::STRING LIKE 'AAA' THEN 'AAA'
207+
WHEN t.tags::STRING LIKE 'AAA' THEN 'AAA'
208+
WHEN t.tags::STRING LIKE 'AAA' THEN 'AAA'
209+
WHEN t.tags::STRING LIKE 'AAA' THEN 'AAA'
210+
WHEN t.tags::STRING LIKE 'AAA' THEN 'AAA'
211+
WHEN t.tags::STRING LIKE 'AAA' THEN 'AAA'
212+
ELSE 'AAA'
213+
END AS TSO_Status,
214+
u.NAME,
215+
CASE
216+
WHEN TSO_STATUS IN ('AAA', 'AAA', 'AAA', 'AAA', 'AAA')
217+
THEN 'AAA'
218+
ELSE 'AAA'
219+
END AS Ticket_Close_status
220+
FROM
221+
VIA.ZENDESK_PARTNERSHIPS.TICKETS t
222+
LEFT JOIN
223+
EventData e ON t.ID = e.TICKET_ID
224+
LEFT JOIN
225+
VIA.ZENDESK_PARTNERSHIPS.USERS u ON u.ID = e.Tier2_ticket_owner
226+
WHERE
227+
GROUP_ID = 34443886197651;
228+
229+

test2.sql

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
-- select (select LATERAL FLATTEN(input => t.Child_events) AS events from b)
2+
3+
-- Not parsed
4+
-- select LATERAL FLATTEN(input => t.Child_events) from b
5+
6+
-- Not parsed
7+
SELECT
8+
(SELECT events.value:assignee_id::STRING
9+
FROM VIA.ZENDESK_PARTNERSHIPS.TICKET_EVENTS T,
10+
LATERAL FLATTEN(input => t.Child_events) AS events
11+
WHERE events.value:event_type::STRING LIKE 'AAA'
12+
13+
)
14+
15+
16+
-- Parsed
17+
SELECT events.value:assignee_id::STRING
18+
FROM VIA.ZENDESK_PARTNERSHIPS.TICKET_EVENTS T,
19+
LATERAL FLATTEN(input => t.Child_events) AS events
20+
WHERE events.value:event_type::STRING LIKE 'AAA'
21+
22+
-- WITH EventData AS (
23+
-- SELECT
24+
-- (SELECT events.value:assignee_id::STRING
25+
-- FROM VIA.ZENDESK_PARTNERSHIPS.TICKET_EVENTS T,
26+
-- LATERAL FLATTEN(input => t.Child_events) AS events
27+
-- WHERE events.value:event_type::STRING LIKE 'AAA'
28+
29+
-- )
30+
-- )
31+
-- SELECT
32+
-- *
33+
-- FROM
34+
-- EventData
35+
36+

test3.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
SET search_path TO myschema,public;

tests/sqlparser_snowflake.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2846,3 +2846,20 @@ fn test_parse_show_columns_sql() {
28462846
snowflake().verified_stmt("SHOW COLUMNS IN TABLE abc");
28472847
snowflake().verified_stmt("SHOW COLUMNS LIKE '%xyz%' IN TABLE abc");
28482848
}
2849+
2850+
#[test]
2851+
fn test_projection_with_nested_trailing_commas() {
2852+
let sql = "SELECT a, FROM b, LATERAL FLATTEN(input => events)";
2853+
let _ = snowflake().parse_sql_statements(sql).unwrap();
2854+
2855+
//Single nesting
2856+
let sql = "SELECT (SELECT a, FROM b, LATERAL FLATTEN(input => events))";
2857+
let _ = snowflake().parse_sql_statements(sql).unwrap();
2858+
2859+
//Double nesting
2860+
let sql = "SELECT (SELECT (SELECT a, FROM b, LATERAL FLATTEN(input => events)))";
2861+
let _ = snowflake().parse_sql_statements(sql).unwrap();
2862+
2863+
let sql = "SELECT a, b, FROM c, (SELECT d, e, FROM f, LATERAL FLATTEN(input => events))";
2864+
let _ = snowflake().parse_sql_statements(sql).unwrap();
2865+
}

0 commit comments

Comments
 (0)