Skip to content

Commit b35f750

Browse files
iffyioayman-sigma
authored andcommitted
BigQuery: Add support for select expr star (apache#1680)
1 parent b4a0c67 commit b35f750

10 files changed

+183
-30
lines changed

src/ast/mod.rs

+7-6
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,13 @@ pub use self::query::{
6868
NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset, OffsetRows, OpenJsonTableColumn,
6969
OrderBy, OrderByExpr, PivotValueSource, ProjectionSelect, Query, RenameSelectItem,
7070
RepetitionQuantifier, ReplaceSelectElement, ReplaceSelectItem, RowsPerMatch, Select,
71-
SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier, Setting, SymbolDefinition, Table,
72-
TableAlias, TableAliasColumnDef, TableFactor, TableFunctionArgs, TableIndexHintForClause,
73-
TableIndexHintType, TableIndexHints, TableIndexType, TableSample, TableSampleBucket,
74-
TableSampleKind, TableSampleMethod, TableSampleModifier, TableSampleQuantity, TableSampleSeed,
75-
TableSampleSeedModifier, TableSampleUnit, TableVersion, TableWithJoins, Top, TopQuantity,
76-
UpdateTableFromKind, ValueTableMode, Values, WildcardAdditionalOptions, With, WithFill,
71+
SelectInto, SelectItem, SelectItemQualifiedWildcardKind, SetExpr, SetOperator, SetQuantifier,
72+
Setting, SymbolDefinition, Table, TableAlias, TableAliasColumnDef, TableFactor,
73+
TableFunctionArgs, TableIndexHintForClause, TableIndexHintType, TableIndexHints,
74+
TableIndexType, TableSample, TableSampleBucket, TableSampleKind, TableSampleMethod,
75+
TableSampleModifier, TableSampleQuantity, TableSampleSeed, TableSampleSeedModifier,
76+
TableSampleUnit, TableVersion, TableWithJoins, Top, TopQuantity, UpdateTableFromKind,
77+
ValueTableMode, Values, WildcardAdditionalOptions, With, WithFill,
7778
};
7879

7980
pub use self::trigger::{

src/ast/query.rs

+30-4
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,20 @@ impl fmt::Display for Cte {
586586
}
587587
}
588588

589+
/// Represents an expression behind a wildcard expansion in a projection.
590+
/// `SELECT T.* FROM T;
591+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
592+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
593+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
594+
pub enum SelectItemQualifiedWildcardKind {
595+
/// Expression is an object name.
596+
/// e.g. `alias.*` or even `schema.table.*`
597+
ObjectName(ObjectName),
598+
/// Select star on an arbitrary expression.
599+
/// e.g. `STRUCT<STRING>('foo').*`
600+
Expr(Expr),
601+
}
602+
589603
/// One item of the comma-separated list following `SELECT`
590604
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
591605
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -595,12 +609,24 @@ pub enum SelectItem {
595609
UnnamedExpr(Expr),
596610
/// An expression, followed by `[ AS ] alias`
597611
ExprWithAlias { expr: Expr, alias: Ident },
598-
/// `alias.*` or even `schema.table.*`
599-
QualifiedWildcard(ObjectName, WildcardAdditionalOptions),
612+
/// An expression, followed by a wildcard expansion.
613+
/// e.g. `alias.*`, `STRUCT<STRING>('foo').*`
614+
QualifiedWildcard(SelectItemQualifiedWildcardKind, WildcardAdditionalOptions),
600615
/// An unqualified `*`
601616
Wildcard(WildcardAdditionalOptions),
602617
}
603618

619+
impl fmt::Display for SelectItemQualifiedWildcardKind {
620+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
621+
match &self {
622+
SelectItemQualifiedWildcardKind::ObjectName(object_name) => {
623+
write!(f, "{object_name}.*")
624+
}
625+
SelectItemQualifiedWildcardKind::Expr(expr) => write!(f, "{expr}.*"),
626+
}
627+
}
628+
}
629+
604630
/// Single aliased identifier
605631
///
606632
/// # Syntax
@@ -867,8 +893,8 @@ impl fmt::Display for SelectItem {
867893
match &self {
868894
SelectItem::UnnamedExpr(expr) => write!(f, "{expr}"),
869895
SelectItem::ExprWithAlias { expr, alias } => write!(f, "{expr} AS {alias}"),
870-
SelectItem::QualifiedWildcard(prefix, additional_options) => {
871-
write!(f, "{prefix}.*")?;
896+
SelectItem::QualifiedWildcard(kind, additional_options) => {
897+
write!(f, "{kind}")?;
872898
write!(f, "{additional_options}")?;
873899
Ok(())
874900
}

src/ast/spans.rs

+13-5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
// specific language governing permissions and limitations
1616
// under the License.
1717

18+
use crate::ast::query::SelectItemQualifiedWildcardKind;
1819
use core::iter;
1920

2021
use crate::tokenizer::Span;
@@ -1628,16 +1629,23 @@ impl Spanned for JsonPathElem {
16281629
}
16291630
}
16301631

1632+
impl Spanned for SelectItemQualifiedWildcardKind {
1633+
fn span(&self) -> Span {
1634+
match self {
1635+
SelectItemQualifiedWildcardKind::ObjectName(object_name) => object_name.span(),
1636+
SelectItemQualifiedWildcardKind::Expr(expr) => expr.span(),
1637+
}
1638+
}
1639+
}
1640+
16311641
impl Spanned for SelectItem {
16321642
fn span(&self) -> Span {
16331643
match self {
16341644
SelectItem::UnnamedExpr(expr) => expr.span(),
16351645
SelectItem::ExprWithAlias { expr, alias } => expr.span().union(&alias.span),
1636-
SelectItem::QualifiedWildcard(object_name, wildcard_additional_options) => union_spans(
1637-
object_name
1638-
.0
1639-
.iter()
1640-
.map(|i| i.span())
1646+
SelectItem::QualifiedWildcard(kind, wildcard_additional_options) => union_spans(
1647+
[kind.span()]
1648+
.into_iter()
16411649
.chain(iter::once(wildcard_additional_options.span())),
16421650
),
16431651
SelectItem::Wildcard(wildcard_additional_options) => wildcard_additional_options.span(),

src/dialect/bigquery.rs

+5
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ impl Dialect for BigQueryDialect {
8383
true
8484
}
8585

86+
/// See <https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#select_expression_star>
87+
fn supports_select_expr_star(&self) -> bool {
88+
true
89+
}
90+
8691
// See <https://cloud.google.com/bigquery/docs/access-historical-data>
8792
fn supports_timestamp_versioning(&self) -> bool {
8893
true

src/dialect/mod.rs

+11
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,17 @@ pub trait Dialect: Debug + Any {
447447
false
448448
}
449449

450+
/// Return true if the dialect supports wildcard expansion on
451+
/// arbitrary expressions in projections.
452+
///
453+
/// Example:
454+
/// ```sql
455+
/// SELECT STRUCT<STRING>('foo').* FROM T
456+
/// ```
457+
fn supports_select_expr_star(&self) -> bool {
458+
false
459+
}
460+
450461
/// Does the dialect support MySQL-style `'user'@'host'` grantee syntax?
451462
fn supports_user_host_grantee(&self) -> bool {
452463
false

src/parser/mod.rs

+23-7
Original file line numberDiff line numberDiff line change
@@ -1528,10 +1528,17 @@ impl<'a> Parser<'a> {
15281528
// function array_agg traverses this control flow
15291529
if dialect_of!(self is PostgreSqlDialect) {
15301530
ending_wildcard = Some(next_token);
1531-
break;
15321531
} else {
1533-
return self.expected("an identifier after '.'", next_token);
1532+
// Put back the consumed .* tokens before exiting.
1533+
// If this expression is being parsed in the
1534+
// context of a projection, then this could imply
1535+
// a wildcard expansion. For example:
1536+
// `SELECT STRUCT('foo').* FROM T`
1537+
self.prev_token(); // *
1538+
self.prev_token(); // .
15341539
}
1540+
1541+
break;
15351542
}
15361543
Token::SingleQuotedString(s) => {
15371544
let expr = Expr::Identifier(Ident::with_quote('\'', s));
@@ -1568,18 +1575,18 @@ impl<'a> Parser<'a> {
15681575
} else {
15691576
self.parse_function(ObjectName::from(id_parts))
15701577
}
1578+
} else if chain.is_empty() {
1579+
Ok(root)
15711580
} else {
15721581
if Self::is_all_ident(&root, &chain) {
15731582
return Ok(Expr::CompoundIdentifier(Self::exprs_to_idents(
15741583
root, chain,
15751584
)?));
15761585
}
1577-
if chain.is_empty() {
1578-
return Ok(root);
1579-
}
1586+
15801587
Ok(Expr::CompoundFieldAccess {
15811588
root: Box::new(root),
1582-
access_chain: chain.clone(),
1589+
access_chain: chain,
15831590
})
15841591
}
15851592
}
@@ -12945,7 +12952,7 @@ impl<'a> Parser<'a> {
1294512952
pub fn parse_select_item(&mut self) -> Result<SelectItem, ParserError> {
1294612953
match self.parse_wildcard_expr()? {
1294712954
Expr::QualifiedWildcard(prefix, token) => Ok(SelectItem::QualifiedWildcard(
12948-
prefix,
12955+
SelectItemQualifiedWildcardKind::ObjectName(prefix),
1294912956
self.parse_wildcard_additional_options(token.0)?,
1295012957
)),
1295112958
Expr::Wildcard(token) => Ok(SelectItem::Wildcard(
@@ -12975,6 +12982,15 @@ impl<'a> Parser<'a> {
1297512982
alias,
1297612983
})
1297712984
}
12985+
expr if self.dialect.supports_select_expr_star()
12986+
&& self.consume_tokens(&[Token::Period, Token::Mul]) =>
12987+
{
12988+
let wildcard_token = self.get_previous_token().clone();
12989+
Ok(SelectItem::QualifiedWildcard(
12990+
SelectItemQualifiedWildcardKind::Expr(expr),
12991+
self.parse_wildcard_additional_options(wildcard_token)?,
12992+
))
12993+
}
1297812994
expr => self
1297912995
.maybe_parse_select_item_alias()
1298012996
.map(|alias| match alias {

tests/sqlparser_bigquery.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -1540,9 +1540,6 @@ fn parse_hyphenated_table_identifiers() {
15401540
]))
15411541
})
15421542
);
1543-
1544-
let error_sql = "select foo-bar.* from foo-bar";
1545-
assert!(bigquery().parse_sql_statements(error_sql).is_err());
15461543
}
15471544

15481545
#[test]
@@ -2204,6 +2201,14 @@ fn parse_extract_weekday() {
22042201
);
22052202
}
22062203

2204+
#[test]
2205+
fn bigquery_select_expr_star() {
2206+
bigquery()
2207+
.verified_only_select("SELECT STRUCT<STRING>((SELECT foo FROM T WHERE true)).* FROM T");
2208+
bigquery().verified_only_select("SELECT [STRUCT<STRING>('foo')][0].* EXCEPT (foo) FROM T");
2209+
bigquery().verified_only_select("SELECT myfunc()[0].* FROM T");
2210+
}
2211+
22072212
#[test]
22082213
fn test_select_as_struct() {
22092214
bigquery().verified_only_select("SELECT * FROM (SELECT AS VALUE STRUCT(123 AS a, false AS b))");

tests/sqlparser_common.rs

+83-2
Original file line numberDiff line numberDiff line change
@@ -1002,7 +1002,7 @@ fn parse_select_wildcard() {
10021002
let select = verified_only_select(sql);
10031003
assert_eq!(
10041004
&SelectItem::QualifiedWildcard(
1005-
ObjectName::from(vec![Ident::new("foo")]),
1005+
SelectItemQualifiedWildcardKind::ObjectName(ObjectName::from(vec![Ident::new("foo")])),
10061006
WildcardAdditionalOptions::default()
10071007
),
10081008
only(&select.projection)
@@ -1012,7 +1012,10 @@ fn parse_select_wildcard() {
10121012
let select = verified_only_select(sql);
10131013
assert_eq!(
10141014
&SelectItem::QualifiedWildcard(
1015-
ObjectName::from(vec![Ident::new("myschema"), Ident::new("mytable"),]),
1015+
SelectItemQualifiedWildcardKind::ObjectName(ObjectName::from(vec![
1016+
Ident::new("myschema"),
1017+
Ident::new("mytable"),
1018+
])),
10161019
WildcardAdditionalOptions::default(),
10171020
),
10181021
only(&select.projection)
@@ -1057,6 +1060,84 @@ fn parse_column_aliases() {
10571060
one_statement_parses_to("SELECT a.col + 1 newname FROM foo AS a", sql);
10581061
}
10591062

1063+
#[test]
1064+
fn parse_select_expr_star() {
1065+
let dialects = all_dialects_where(|d| d.supports_select_expr_star());
1066+
1067+
// Identifier wildcard expansion.
1068+
let select = dialects.verified_only_select("SELECT foo.bar.* FROM T");
1069+
let SelectItem::QualifiedWildcard(SelectItemQualifiedWildcardKind::ObjectName(object_name), _) =
1070+
only(&select.projection)
1071+
else {
1072+
unreachable!(
1073+
"expected wildcard select item: got {:?}",
1074+
&select.projection[0]
1075+
)
1076+
};
1077+
assert_eq!(
1078+
&ObjectName::from(
1079+
["foo", "bar"]
1080+
.into_iter()
1081+
.map(Ident::new)
1082+
.collect::<Vec<_>>()
1083+
),
1084+
object_name
1085+
);
1086+
1087+
// Arbitrary compound expression with wildcard expansion.
1088+
let select = dialects.verified_only_select("SELECT foo - bar.* FROM T");
1089+
let SelectItem::QualifiedWildcard(
1090+
SelectItemQualifiedWildcardKind::Expr(Expr::BinaryOp { left, op, right }),
1091+
_,
1092+
) = only(&select.projection)
1093+
else {
1094+
unreachable!(
1095+
"expected wildcard select item: got {:?}",
1096+
&select.projection[0]
1097+
)
1098+
};
1099+
let (Expr::Identifier(left), BinaryOperator::Minus, Expr::Identifier(right)) =
1100+
(left.as_ref(), op, right.as_ref())
1101+
else {
1102+
unreachable!("expected binary op expr: got {:?}", &select.projection[0])
1103+
};
1104+
assert_eq!(&Ident::new("foo"), left);
1105+
assert_eq!(&Ident::new("bar"), right);
1106+
1107+
// Arbitrary expression wildcard expansion.
1108+
let select = dialects.verified_only_select("SELECT myfunc().foo.* FROM T");
1109+
let SelectItem::QualifiedWildcard(
1110+
SelectItemQualifiedWildcardKind::Expr(Expr::CompoundFieldAccess { root, access_chain }),
1111+
_,
1112+
) = only(&select.projection)
1113+
else {
1114+
unreachable!("expected wildcard expr: got {:?}", &select.projection[0])
1115+
};
1116+
assert!(matches!(root.as_ref(), Expr::Function(_)));
1117+
assert_eq!(1, access_chain.len());
1118+
assert!(matches!(
1119+
&access_chain[0],
1120+
AccessExpr::Dot(Expr::Identifier(_))
1121+
));
1122+
1123+
dialects.one_statement_parses_to(
1124+
"SELECT 2. * 3 FROM T",
1125+
#[cfg(feature = "bigdecimal")]
1126+
"SELECT 2 * 3 FROM T",
1127+
#[cfg(not(feature = "bigdecimal"))]
1128+
"SELECT 2. * 3 FROM T",
1129+
);
1130+
dialects.verified_only_select("SELECT myfunc().* FROM T");
1131+
dialects.verified_only_select("SELECT myfunc().* EXCEPT (foo) FROM T");
1132+
1133+
// Invalid
1134+
let res = dialects.parse_sql_statements("SELECT foo.*.* FROM T");
1135+
assert_eq!(
1136+
ParserError::ParserError("Expected: end of statement, found: .".to_string()),
1137+
res.unwrap_err()
1138+
);
1139+
}
1140+
10601141
#[test]
10611142
fn test_eof_after_as() {
10621143
let res = parse_sql_statements("SELECT foo AS");

tests/sqlparser_duckdb.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ fn test_select_wildcard_with_exclude() {
160160
let select =
161161
duckdb().verified_only_select("SELECT name.* EXCLUDE department_id FROM employee_table");
162162
let expected = SelectItem::QualifiedWildcard(
163-
ObjectName::from(vec![Ident::new("name")]),
163+
SelectItemQualifiedWildcardKind::ObjectName(ObjectName::from(vec![Ident::new("name")])),
164164
WildcardAdditionalOptions {
165165
opt_exclude: Some(ExcludeSelectItem::Single(Ident::new("department_id"))),
166166
..Default::default()

tests/sqlparser_snowflake.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1368,7 +1368,7 @@ fn test_select_wildcard_with_exclude() {
13681368
let select = snowflake_and_generic()
13691369
.verified_only_select("SELECT name.* EXCLUDE department_id FROM employee_table");
13701370
let expected = SelectItem::QualifiedWildcard(
1371-
ObjectName::from(vec![Ident::new("name")]),
1371+
SelectItemQualifiedWildcardKind::ObjectName(ObjectName::from(vec![Ident::new("name")])),
13721372
WildcardAdditionalOptions {
13731373
opt_exclude: Some(ExcludeSelectItem::Single(Ident::new("department_id"))),
13741374
..Default::default()
@@ -1405,7 +1405,7 @@ fn test_select_wildcard_with_rename() {
14051405
"SELECT name.* RENAME (department_id AS new_dep, employee_id AS new_emp) FROM employee_table",
14061406
);
14071407
let expected = SelectItem::QualifiedWildcard(
1408-
ObjectName::from(vec![Ident::new("name")]),
1408+
SelectItemQualifiedWildcardKind::ObjectName(ObjectName::from(vec![Ident::new("name")])),
14091409
WildcardAdditionalOptions {
14101410
opt_rename: Some(RenameSelectItem::Multiple(vec![
14111411
IdentWithAlias {

0 commit comments

Comments
 (0)