Skip to content

Commit 70917a5

Browse files
togami2864alamb
andauthored
feat: SELECT * REPLACE <Expr> AS <Identifier> for bigquery (#798)
* chore: add test for wildcard replace * feat: define opt_replace for wildcard replace * fix: modify replace option ast * fix: add test cases * chore: fmt * redefine ast * feat: parse select replace items * ci * Update src/ast/query.rs --------- Co-authored-by: Andrew Lamb <[email protected]>
1 parent 0c0d088 commit 70917a5

File tree

4 files changed

+150
-2
lines changed

4 files changed

+150
-2
lines changed

src/ast/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,9 @@ pub use self::operator::{BinaryOperator, UnaryOperator};
3636
pub use self::query::{
3737
Cte, ExceptSelectItem, ExcludeSelectItem, Fetch, IdentWithAlias, Join, JoinConstraint,
3838
JoinOperator, LateralView, LockClause, LockType, NonBlock, Offset, OffsetRows, OrderByExpr,
39-
Query, RenameSelectItem, Select, SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier,
40-
Table, TableAlias, TableFactor, TableWithJoins, Top, Values, WildcardAdditionalOptions, With,
39+
Query, RenameSelectItem, ReplaceSelectElement, ReplaceSelectItem, Select, SelectInto,
40+
SelectItem, SetExpr, SetOperator, SetQuantifier, Table, TableAlias, TableFactor,
41+
TableWithJoins, Top, Values, WildcardAdditionalOptions, With,
4142
};
4243
pub use self::value::{
4344
escape_quoted_string, DateTimeField, DollarQuotedString, TrimWhereField, Value,

src/ast/query.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,9 @@ pub struct WildcardAdditionalOptions {
394394
pub opt_except: Option<ExceptSelectItem>,
395395
/// `[RENAME ...]`.
396396
pub opt_rename: Option<RenameSelectItem>,
397+
/// `[REPLACE]`
398+
/// BigQuery syntax: <https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#select_replace>
399+
pub opt_replace: Option<ReplaceSelectItem>,
397400
}
398401

399402
impl fmt::Display for WildcardAdditionalOptions {
@@ -407,6 +410,9 @@ impl fmt::Display for WildcardAdditionalOptions {
407410
if let Some(rename) = &self.opt_rename {
408411
write!(f, " {rename}")?;
409412
}
413+
if let Some(replace) = &self.opt_replace {
414+
write!(f, " {replace}")?;
415+
}
410416
Ok(())
411417
}
412418
}
@@ -526,6 +532,51 @@ impl fmt::Display for ExceptSelectItem {
526532
}
527533
}
528534

535+
/// Bigquery `REPLACE` information.
536+
///
537+
/// # Syntax
538+
/// ```plaintext
539+
/// REPLACE (<new_expr> [AS] <col_name>)
540+
/// REPLACE (<col_name> [AS] <col_alias>, <col_name> [AS] <col_alias>, ...)
541+
/// ```
542+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
543+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
544+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
545+
pub struct ReplaceSelectItem {
546+
pub items: Vec<Box<ReplaceSelectElement>>,
547+
}
548+
549+
impl fmt::Display for ReplaceSelectItem {
550+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
551+
write!(f, "REPLACE")?;
552+
write!(f, " ({})", display_comma_separated(&self.items))?;
553+
Ok(())
554+
}
555+
}
556+
557+
/// # Syntax
558+
/// ```plaintext
559+
/// <expr> [AS] <column_name>
560+
/// ```
561+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
562+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
563+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
564+
pub struct ReplaceSelectElement {
565+
pub expr: Expr,
566+
pub colum_name: Ident,
567+
pub as_keyword: bool,
568+
}
569+
570+
impl fmt::Display for ReplaceSelectElement {
571+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
572+
if self.as_keyword {
573+
write!(f, "{} AS {}", self.expr, self.colum_name)
574+
} else {
575+
write!(f, "{} {}", self.expr, self.colum_name)
576+
}
577+
}
578+
}
579+
529580
impl fmt::Display for SelectItem {
530581
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
531582
match &self {

src/parser.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6164,10 +6164,17 @@ impl<'a> Parser<'a> {
61646164
None
61656165
};
61666166

6167+
let opt_replace = if dialect_of!(self is GenericDialect | BigQueryDialect) {
6168+
self.parse_optional_select_item_replace()?
6169+
} else {
6170+
None
6171+
};
6172+
61676173
Ok(WildcardAdditionalOptions {
61686174
opt_exclude,
61696175
opt_except,
61706176
opt_rename,
6177+
opt_replace,
61716178
})
61726179
}
61736180

@@ -6241,6 +6248,38 @@ impl<'a> Parser<'a> {
62416248
Ok(opt_rename)
62426249
}
62436250

6251+
/// Parse a [`Replace`](ReplaceSelectItem) information for wildcard select items.
6252+
pub fn parse_optional_select_item_replace(
6253+
&mut self,
6254+
) -> Result<Option<ReplaceSelectItem>, ParserError> {
6255+
let opt_replace = if self.parse_keyword(Keyword::REPLACE) {
6256+
if self.consume_token(&Token::LParen) {
6257+
let items = self.parse_comma_separated(|parser| {
6258+
Ok(Box::new(parser.parse_replace_elements()?))
6259+
})?;
6260+
self.expect_token(&Token::RParen)?;
6261+
Some(ReplaceSelectItem { items })
6262+
} else {
6263+
let tok = self.next_token();
6264+
return self.expected("( after REPLACE but", tok);
6265+
}
6266+
} else {
6267+
None
6268+
};
6269+
6270+
Ok(opt_replace)
6271+
}
6272+
pub fn parse_replace_elements(&mut self) -> Result<ReplaceSelectElement, ParserError> {
6273+
let expr = self.parse_expr()?;
6274+
let as_keyword = self.parse_keyword(Keyword::AS);
6275+
let ident = self.parse_identifier()?;
6276+
Ok(ReplaceSelectElement {
6277+
expr,
6278+
colum_name: ident,
6279+
as_keyword,
6280+
})
6281+
}
6282+
62446283
/// Parse an expression, optionally followed by ASC or DESC (used in ORDER BY)
62456284
pub fn parse_order_by_expr(&mut self) -> Result<OrderByExpr, ParserError> {
62466285
let expr = self.parse_expr()?;

tests/sqlparser_bigquery.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ use sqlparser::ast::*;
1717
use sqlparser::dialect::{BigQueryDialect, GenericDialect};
1818
use test_utils::*;
1919

20+
#[cfg(feature = "bigdecimal")]
21+
use bigdecimal::*;
22+
#[cfg(feature = "bigdecimal")]
23+
use std::str::FromStr;
24+
2025
#[test]
2126
fn parse_literal_string() {
2227
let sql = r#"SELECT 'single', "double""#;
@@ -313,6 +318,58 @@ fn test_select_wildcard_with_except() {
313318
);
314319
}
315320

321+
#[test]
322+
fn test_select_wildcard_with_replace() {
323+
let select = bigquery_and_generic()
324+
.verified_only_select(r#"SELECT * REPLACE ('widget' AS item_name) FROM orders"#);
325+
let expected = SelectItem::Wildcard(WildcardAdditionalOptions {
326+
opt_replace: Some(ReplaceSelectItem {
327+
items: vec![Box::new(ReplaceSelectElement {
328+
expr: Expr::Value(Value::SingleQuotedString("widget".to_owned())),
329+
colum_name: Ident::new("item_name"),
330+
as_keyword: true,
331+
})],
332+
}),
333+
..Default::default()
334+
});
335+
assert_eq!(expected, select.projection[0]);
336+
337+
let select = bigquery_and_generic().verified_only_select(
338+
r#"SELECT * REPLACE (quantity / 2 AS quantity, 3 AS order_id) FROM orders"#,
339+
);
340+
let expected = SelectItem::Wildcard(WildcardAdditionalOptions {
341+
opt_replace: Some(ReplaceSelectItem {
342+
items: vec![
343+
Box::new(ReplaceSelectElement {
344+
expr: Expr::BinaryOp {
345+
left: Box::new(Expr::Identifier(Ident::new("quantity"))),
346+
op: BinaryOperator::Divide,
347+
#[cfg(not(feature = "bigdecimal"))]
348+
right: Box::new(Expr::Value(Value::Number("2".to_string(), false))),
349+
#[cfg(feature = "bigdecimal")]
350+
right: Box::new(Expr::Value(Value::Number(
351+
BigDecimal::from_str("2").unwrap(),
352+
false,
353+
))),
354+
},
355+
colum_name: Ident::new("quantity"),
356+
as_keyword: true,
357+
}),
358+
Box::new(ReplaceSelectElement {
359+
#[cfg(not(feature = "bigdecimal"))]
360+
expr: Expr::Value(Value::Number("3".to_string(), false)),
361+
#[cfg(feature = "bigdecimal")]
362+
expr: Expr::Value(Value::Number(BigDecimal::from_str("3").unwrap(), false)),
363+
colum_name: Ident::new("order_id"),
364+
as_keyword: true,
365+
}),
366+
],
367+
}),
368+
..Default::default()
369+
});
370+
assert_eq!(expected, select.projection[0]);
371+
}
372+
316373
fn bigquery() -> TestedDialects {
317374
TestedDialects {
318375
dialects: vec![Box::new(BigQueryDialect {})],

0 commit comments

Comments
 (0)