Skip to content

Commit 83cb734

Browse files
authored
Support Snowflake/BigQuery TRIM. (#975)
1 parent 5263da6 commit 83cb734

File tree

5 files changed

+95
-0
lines changed

5 files changed

+95
-0
lines changed

src/ast/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -496,12 +496,14 @@ pub enum Expr {
496496
/// ```sql
497497
/// TRIM([BOTH | LEADING | TRAILING] [<expr> FROM] <expr>)
498498
/// TRIM(<expr>)
499+
/// TRIM(<expr>, [, characters]) -- only Snowflake or Bigquery
499500
/// ```
500501
Trim {
501502
expr: Box<Expr>,
502503
// ([BOTH | LEADING | TRAILING]
503504
trim_where: Option<TrimWhereField>,
504505
trim_what: Option<Box<Expr>>,
506+
trim_characters: Option<Vec<Expr>>,
505507
},
506508
/// ```sql
507509
/// OVERLAY(<expr> PLACING <expr> FROM <expr>[ FOR <expr> ]
@@ -895,6 +897,7 @@ impl fmt::Display for Expr {
895897
expr,
896898
trim_where,
897899
trim_what,
900+
trim_characters,
898901
} => {
899902
write!(f, "TRIM(")?;
900903
if let Some(ident) = trim_where {
@@ -905,6 +908,9 @@ impl fmt::Display for Expr {
905908
} else {
906909
write!(f, "{expr}")?;
907910
}
911+
if let Some(characters) = trim_characters {
912+
write!(f, ", {}", display_comma_separated(characters))?;
913+
}
908914

909915
write!(f, ")")
910916
}

src/parser/mod.rs

+14
Original file line numberDiff line numberDiff line change
@@ -1315,6 +1315,7 @@ impl<'a> Parser<'a> {
13151315
/// ```sql
13161316
/// TRIM ([WHERE] ['text' FROM] 'text')
13171317
/// TRIM ('text')
1318+
/// TRIM(<expr>, [, characters]) -- only Snowflake or BigQuery
13181319
/// ```
13191320
pub fn parse_trim_expr(&mut self) -> Result<Expr, ParserError> {
13201321
self.expect_token(&Token::LParen)?;
@@ -1336,13 +1337,26 @@ impl<'a> Parser<'a> {
13361337
expr: Box::new(expr),
13371338
trim_where,
13381339
trim_what: Some(trim_what),
1340+
trim_characters: None,
1341+
})
1342+
} else if self.consume_token(&Token::Comma)
1343+
&& dialect_of!(self is SnowflakeDialect | BigQueryDialect | GenericDialect)
1344+
{
1345+
let characters = self.parse_comma_separated(Parser::parse_expr)?;
1346+
self.expect_token(&Token::RParen)?;
1347+
Ok(Expr::Trim {
1348+
expr: Box::new(expr),
1349+
trim_where: None,
1350+
trim_what: None,
1351+
trim_characters: Some(characters),
13391352
})
13401353
} else {
13411354
self.expect_token(&Token::RParen)?;
13421355
Ok(Expr::Trim {
13431356
expr: Box::new(expr),
13441357
trim_where,
13451358
trim_what: None,
1359+
trim_characters: None,
13461360
})
13471361
}
13481362
}

tests/sqlparser_bigquery.rs

+26
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use std::ops::Deref;
1717

1818
use sqlparser::ast::*;
1919
use sqlparser::dialect::{BigQueryDialect, GenericDialect};
20+
use sqlparser::parser::ParserError;
2021
use test_utils::*;
2122

2223
#[test]
@@ -549,3 +550,28 @@ fn parse_map_access_offset() {
549550
bigquery().verified_only_select(sql);
550551
}
551552
}
553+
554+
#[test]
555+
fn test_bigquery_trim() {
556+
let real_sql = r#"SELECT customer_id, TRIM(item_price_id, '"', "a") AS item_price_id FROM models_staging.subscriptions"#;
557+
assert_eq!(bigquery().verified_stmt(real_sql).to_string(), real_sql);
558+
559+
let sql_only_select = "SELECT TRIM('xyz', 'a')";
560+
let select = bigquery().verified_only_select(sql_only_select);
561+
assert_eq!(
562+
&Expr::Trim {
563+
expr: Box::new(Expr::Value(Value::SingleQuotedString("xyz".to_owned()))),
564+
trim_where: None,
565+
trim_what: None,
566+
trim_characters: Some(vec![Expr::Value(Value::SingleQuotedString("a".to_owned()))]),
567+
},
568+
expr_from_projection(only(&select.projection))
569+
);
570+
571+
// missing comma separation
572+
let error_sql = "SELECT TRIM('xyz' 'a')";
573+
assert_eq!(
574+
ParserError::ParserError("Expected ), found: 'a'".to_owned()),
575+
bigquery().parse_sql_statements(error_sql).unwrap_err()
576+
);
577+
}

tests/sqlparser_common.rs

+24
Original file line numberDiff line numberDiff line change
@@ -5225,6 +5225,30 @@ fn parse_trim() {
52255225
ParserError::ParserError("Expected ), found: 'xyz'".to_owned()),
52265226
parse_sql_statements("SELECT TRIM(FOO 'xyz' FROM 'xyzfooxyz')").unwrap_err()
52275227
);
5228+
5229+
//keep Snowflake/BigQuery TRIM syntax failing
5230+
let all_expected_snowflake = TestedDialects {
5231+
dialects: vec![
5232+
//Box::new(GenericDialect {}),
5233+
Box::new(PostgreSqlDialect {}),
5234+
Box::new(MsSqlDialect {}),
5235+
Box::new(AnsiDialect {}),
5236+
//Box::new(SnowflakeDialect {}),
5237+
Box::new(HiveDialect {}),
5238+
Box::new(RedshiftSqlDialect {}),
5239+
Box::new(MySqlDialect {}),
5240+
//Box::new(BigQueryDialect {}),
5241+
Box::new(SQLiteDialect {}),
5242+
Box::new(DuckDbDialect {}),
5243+
],
5244+
options: None,
5245+
};
5246+
assert_eq!(
5247+
ParserError::ParserError("Expected ), found: 'a'".to_owned()),
5248+
all_expected_snowflake
5249+
.parse_sql_statements("SELECT TRIM('xyz', 'a')")
5250+
.unwrap_err()
5251+
);
52285252
}
52295253

52305254
#[test]

tests/sqlparser_snowflake.rs

+25
Original file line numberDiff line numberDiff line change
@@ -1039,3 +1039,28 @@ fn test_snowflake_stage_object_names() {
10391039
}
10401040
}
10411041
}
1042+
1043+
#[test]
1044+
fn test_snowflake_trim() {
1045+
let real_sql = r#"SELECT customer_id, TRIM(sub_items.value:item_price_id, '"', "a") AS item_price_id FROM models_staging.subscriptions"#;
1046+
assert_eq!(snowflake().verified_stmt(real_sql).to_string(), real_sql);
1047+
1048+
let sql_only_select = "SELECT TRIM('xyz', 'a')";
1049+
let select = snowflake().verified_only_select(sql_only_select);
1050+
assert_eq!(
1051+
&Expr::Trim {
1052+
expr: Box::new(Expr::Value(Value::SingleQuotedString("xyz".to_owned()))),
1053+
trim_where: None,
1054+
trim_what: None,
1055+
trim_characters: Some(vec![Expr::Value(Value::SingleQuotedString("a".to_owned()))]),
1056+
},
1057+
expr_from_projection(only(&select.projection))
1058+
);
1059+
1060+
// missing comma separation
1061+
let error_sql = "SELECT TRIM('xyz' 'a')";
1062+
assert_eq!(
1063+
ParserError::ParserError("Expected ), found: 'a'".to_owned()),
1064+
snowflake().parse_sql_statements(error_sql).unwrap_err()
1065+
);
1066+
}

0 commit comments

Comments
 (0)