1use snafu::{ensure, ResultExt};
16use sqlparser::keywords::Keyword;
17use sqlparser::tokenizer::Token;
18
19use crate::error::{self, InvalidSqlSnafu, InvalidTableNameSnafu, Result, UnexpectedTokenSnafu};
20use crate::parser::ParserContext;
21use crate::statements::statement::Statement;
22use crate::statements::truncate::TruncateTable;
23
24impl ParserContext<'_> {
26 pub(crate) fn parse_truncate(&mut self) -> Result<Statement> {
27 let _ = self.parser.next_token();
28 let _ = self.parser.parse_keyword(Keyword::TABLE);
29
30 let raw_table_ident =
31 self.parse_object_name()
32 .with_context(|_| error::UnexpectedSnafu {
33 expected: "a table name",
34 actual: self.peek_token_as_string(),
35 })?;
36 let table_ident = Self::canonicalize_object_name(raw_table_ident);
37
38 ensure!(
39 !table_ident.0.is_empty(),
40 InvalidTableNameSnafu {
41 name: table_ident.to_string()
42 }
43 );
44
45 let have_range = self.parser.parse_keywords(&[Keyword::FILE, Keyword::RANGE]);
46
47 if !have_range {
49 return Ok(Statement::TruncateTable(TruncateTable::new(table_ident)));
50 }
51
52 let mut time_ranges = vec![];
54 loop {
55 let _ = self
56 .parser
57 .expect_token(&sqlparser::tokenizer::Token::LParen)
58 .with_context(|_| error::UnexpectedSnafu {
59 expected: "a left parenthesis",
60 actual: self.peek_token_as_string(),
61 })?;
62
63 let start = self
65 .parser
66 .parse_value()
67 .map(|x| x.value)
68 .with_context(|e| error::UnexpectedSnafu {
69 expected: "a timestamp value",
70 actual: e.to_string(),
71 })?;
72
73 let _ = self
74 .parser
75 .expect_token(&sqlparser::tokenizer::Token::Comma)
76 .with_context(|_| error::UnexpectedSnafu {
77 expected: "a comma",
78 actual: self.peek_token_as_string(),
79 })?;
80
81 let end = self
82 .parser
83 .parse_value()
84 .map(|x| x.value)
85 .with_context(|_| error::UnexpectedSnafu {
86 expected: "a timestamp",
87 actual: self.peek_token_as_string(),
88 })?;
89
90 let _ = self
91 .parser
92 .expect_token(&sqlparser::tokenizer::Token::RParen)
93 .with_context(|_| error::UnexpectedSnafu {
94 expected: "a right parenthesis",
95 actual: self.peek_token_as_string(),
96 })?;
97
98 time_ranges.push((start, end));
99
100 let peek = self.parser.peek_token().token;
101
102 match peek {
103 sqlparser::tokenizer::Token::EOF | Token::SemiColon => {
104 if time_ranges.is_empty() {
105 return Err(InvalidSqlSnafu {
106 msg: "TRUNCATE TABLE RANGE must have at least one range".to_string(),
107 }
108 .build());
109 }
110 break;
111 }
112 Token::Comma => {
113 self.parser.next_token(); let next_peek = self.parser.peek_token().token; if matches!(next_peek, Token::EOF | Token::SemiColon) {
116 break; }
118 continue;
120 }
121 _ => UnexpectedTokenSnafu {
122 expected: "a comma or end of statement",
123 actual: self.peek_token_as_string(),
124 }
125 .fail()?,
126 }
127 }
128
129 Ok(Statement::TruncateTable(TruncateTable::new_with_ranges(
130 table_ident,
131 time_ranges,
132 )))
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use sqlparser::ast::{Ident, ObjectName};
139
140 use super::*;
141 use crate::dialect::GreptimeDbDialect;
142 use crate::parser::ParseOptions;
143
144 #[test]
145 pub fn test_parse_truncate_with_ranges() {
146 let sql = r#"TRUNCATE foo FILE RANGE (0, 20)"#;
147 let mut stmts =
148 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
149 .unwrap();
150 assert_eq!(
151 stmts.pop().unwrap(),
152 Statement::TruncateTable(TruncateTable::new_with_ranges(
153 ObjectName::from(vec![Ident::new("foo")]),
154 vec![(
155 sqlparser::ast::Value::Number("0".to_string(), false),
156 sqlparser::ast::Value::Number("20".to_string(), false)
157 )]
158 ))
159 );
160
161 let sql = r#"TRUNCATE TABLE foo FILE RANGE ("2000-01-01 00:00:00+00:00", "2000-01-01 00:00:00+00:00"), (2,33)"#;
162 let mut stmts =
163 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
164 .unwrap();
165 assert_eq!(
166 stmts.pop().unwrap(),
167 Statement::TruncateTable(TruncateTable::new_with_ranges(
168 ObjectName::from(vec![Ident::new("foo")]),
169 vec![
170 (
171 sqlparser::ast::Value::DoubleQuotedString(
172 "2000-01-01 00:00:00+00:00".to_string()
173 ),
174 sqlparser::ast::Value::DoubleQuotedString(
175 "2000-01-01 00:00:00+00:00".to_string()
176 )
177 ),
178 (
179 sqlparser::ast::Value::Number("2".to_string(), false),
180 sqlparser::ast::Value::Number("33".to_string(), false)
181 )
182 ]
183 ))
184 );
185
186 let sql = "TRUNCATE TABLE my_schema.foo FILE RANGE (1, 2), (3, 4),";
187 let mut stmts =
188 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
189 .unwrap();
190 assert_eq!(
191 stmts.pop().unwrap(),
192 Statement::TruncateTable(TruncateTable::new_with_ranges(
193 ObjectName::from(vec![Ident::new("my_schema"), Ident::new("foo")]),
194 vec![
195 (
196 sqlparser::ast::Value::Number("1".to_string(), false),
197 sqlparser::ast::Value::Number("2".to_string(), false)
198 ),
199 (
200 sqlparser::ast::Value::Number("3".to_string(), false),
201 sqlparser::ast::Value::Number("4".to_string(), false)
202 )
203 ]
204 ))
205 );
206
207 let sql = "TRUNCATE my_schema.foo FILE RANGE (1,2),";
208 let mut stmts =
209 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
210 .unwrap();
211 assert_eq!(
212 stmts.pop().unwrap(),
213 Statement::TruncateTable(TruncateTable::new_with_ranges(
214 ObjectName::from(vec![Ident::new("my_schema"), Ident::new("foo")]),
215 vec![(
216 sqlparser::ast::Value::Number("1".to_string(), false),
217 sqlparser::ast::Value::Number("2".to_string(), false)
218 )]
219 ))
220 );
221
222 let sql = "TRUNCATE TABLE my_catalog.my_schema.foo FILE RANGE (1,2);";
223 let mut stmts =
224 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
225 .unwrap();
226 assert_eq!(
227 stmts.pop().unwrap(),
228 Statement::TruncateTable(TruncateTable::new_with_ranges(
229 ObjectName::from(vec![
230 Ident::new("my_catalog"),
231 Ident::new("my_schema"),
232 Ident::new("foo")
233 ]),
234 vec![(
235 sqlparser::ast::Value::Number("1".to_string(), false),
236 sqlparser::ast::Value::Number("2".to_string(), false)
237 )]
238 ))
239 );
240
241 let sql = "TRUNCATE drop FILE RANGE (1,2)";
242 let mut stmts =
243 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
244 .unwrap();
245 assert_eq!(
246 stmts.pop().unwrap(),
247 Statement::TruncateTable(TruncateTable::new_with_ranges(
248 ObjectName::from(vec![Ident::new("drop")]),
249 vec![(
250 sqlparser::ast::Value::Number("1".to_string(), false),
251 sqlparser::ast::Value::Number("2".to_string(), false)
252 )]
253 ))
254 );
255
256 let sql = "TRUNCATE `drop`";
257 let mut stmts =
258 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
259 .unwrap();
260 assert_eq!(
261 stmts.pop().unwrap(),
262 Statement::TruncateTable(TruncateTable::new(ObjectName::from(vec![
263 Ident::with_quote('`', "drop"),
264 ])))
265 );
266
267 let sql = "TRUNCATE \"drop\" FILE RANGE (\"1\", \"2\")";
268 let mut stmts =
269 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
270 .unwrap();
271 assert_eq!(
272 stmts.pop().unwrap(),
273 Statement::TruncateTable(TruncateTable::new_with_ranges(
274 ObjectName::from(vec![Ident::with_quote('"', "drop")]),
275 vec![(
276 sqlparser::ast::Value::DoubleQuotedString("1".to_string()),
277 sqlparser::ast::Value::DoubleQuotedString("2".to_string())
278 )]
279 ))
280 );
281 }
282
283 #[test]
284 pub fn test_parse_truncate() {
285 let sql = "TRUNCATE foo";
286 let mut stmts =
287 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
288 .unwrap();
289 assert_eq!(
290 stmts.pop().unwrap(),
291 Statement::TruncateTable(TruncateTable::new(ObjectName::from(vec![Ident::new(
292 "foo"
293 )])))
294 );
295
296 let sql = "TRUNCATE TABLE foo";
297 let mut stmts =
298 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
299 .unwrap();
300 assert_eq!(
301 stmts.pop().unwrap(),
302 Statement::TruncateTable(TruncateTable::new(ObjectName::from(vec![Ident::new(
303 "foo"
304 )])))
305 );
306
307 let sql = "TRUNCATE TABLE my_schema.foo";
308 let mut stmts =
309 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
310 .unwrap();
311 assert_eq!(
312 stmts.pop().unwrap(),
313 Statement::TruncateTable(TruncateTable::new(ObjectName::from(vec![
314 Ident::new("my_schema"),
315 Ident::new("foo")
316 ])))
317 );
318
319 let sql = "TRUNCATE my_schema.foo";
320 let mut stmts =
321 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
322 .unwrap();
323 assert_eq!(
324 stmts.pop().unwrap(),
325 Statement::TruncateTable(TruncateTable::new(ObjectName::from(vec![
326 Ident::new("my_schema"),
327 Ident::new("foo")
328 ])))
329 );
330
331 let sql = "TRUNCATE TABLE my_catalog.my_schema.foo";
332 let mut stmts =
333 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
334 .unwrap();
335 assert_eq!(
336 stmts.pop().unwrap(),
337 Statement::TruncateTable(TruncateTable::new(ObjectName::from(vec![
338 Ident::new("my_catalog"),
339 Ident::new("my_schema"),
340 Ident::new("foo")
341 ])))
342 );
343
344 let sql = "TRUNCATE drop";
345 let mut stmts =
346 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
347 .unwrap();
348 assert_eq!(
349 stmts.pop().unwrap(),
350 Statement::TruncateTable(TruncateTable::new(ObjectName::from(vec![Ident::new(
351 "drop"
352 )])))
353 );
354
355 let sql = "TRUNCATE `drop`";
356 let mut stmts =
357 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
358 .unwrap();
359 assert_eq!(
360 stmts.pop().unwrap(),
361 Statement::TruncateTable(TruncateTable::new(ObjectName::from(vec![
362 Ident::with_quote('`', "drop"),
363 ])))
364 );
365
366 let sql = "TRUNCATE \"drop\"";
367 let mut stmts =
368 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
369 .unwrap();
370 assert_eq!(
371 stmts.pop().unwrap(),
372 Statement::TruncateTable(TruncateTable::new(ObjectName::from(vec![
373 Ident::with_quote('"', "drop"),
374 ])))
375 );
376 }
377
378 #[test]
379 pub fn test_parse_invalid_truncate() {
380 let sql = "TRUNCATE SCHEMA foo";
381 let result =
382 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
383 assert!(result.is_err(), "result is: {result:?}");
384
385 let sql = "TRUNCATE";
386 let result =
387 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
388 assert!(result.is_err(), "result is: {result:?}");
389
390 let sql = "TRUNCATE TABLE foo RANGE";
391 let result =
392 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
393 assert!(
394 result.is_err()
395 && format!("{result:?}").contains("SQL statement is not supported, keyword: RANGE"),
396 "result is: {result:?}"
397 );
398
399 let sql = "TRUNCATE TABLE foo FILE";
400 let result =
401 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
402 assert!(
403 result.is_err()
404 && format!("{result:?}").contains("SQL statement is not supported, keyword: FILE"),
405 "result is: {result:?}"
406 );
407
408 let sql = "TRUNCATE TABLE foo FILE RANGE";
409 let result =
410 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
411 assert!(
412 result.is_err() && format!("{result:?}").contains("expected: 'a left parenthesis'"),
413 "result is: {result:?}"
414 );
415
416 let sql = "TRUNCATE TABLE foo FILE RANGE (";
417 let result =
418 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
419 assert!(
420 result.is_err() && format!("{result:?}").contains("expected: 'a timestamp value'"),
421 "result is: {result:?}"
422 );
423
424 let sql = "TRUNCATE TABLE foo FILE RANGE ()";
425 let result =
426 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
427 assert!(
428 result.is_err() && format!("{result:?}").contains("expected: 'a timestamp value'"),
429 "result is: {result:?}"
430 );
431
432 let sql = "TRUNCATE TABLE foo FILE RANGE (1 2) (3 4)";
433 let result =
434 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
435 assert!(
436 result.is_err() && format!("{result:?}").contains("expected: 'a comma'"),
437 "result is: {result:?}"
438 );
439
440 let sql = "TRUNCATE TABLE foo FILE RANGE (,),(3,4)";
441 let result =
442 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
443 assert!(
444 result.is_err() && format!("{result:?}").contains("Expected: a value, found: ,"),
445 "result is: {result:?}"
446 );
447
448 let sql = "TRUNCATE TABLE foo FILE RANGE (1,2) (3,4)";
449 let result =
450 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
451 assert!(
452 result.is_err()
453 && format!("{result:?}")
454 .contains("expected: 'a comma or end of statement', found: ("),
455 "result is: {result:?}"
456 );
457
458 let sql = "TRUNCATE TABLE foo FILE RANGE (1,2),,,,,,,,,(3,4)";
459 let result =
460 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
461 assert!(
462 result.is_err()
463 && format!("{result:?}").contains("expected: 'a left parenthesis', found: ,"),
464 "result is: {result:?}"
465 );
466 }
467}