Skip to content

Commit 9517f5d

Browse files
committed
BigQuery: Add support for select expr star
Adds support for wildcard expression on arbitrary expressions in select lists. Example: ```sql SELECT l.LOCATION[offset(0)].* FROM l ``` https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#select_expression_star
1 parent 4f71542 commit 9517f5d

File tree

10 files changed

+177
-30
lines changed

10 files changed

+177
-30
lines changed

src/ast/mod.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,12 @@ 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, TableSample,
73-
TableSampleBucket, TableSampleKind, TableSampleMethod, TableSampleModifier,
74-
TableSampleQuantity, TableSampleSeed, TableSampleSeedModifier, TableSampleUnit, TableVersion,
75-
TableWithJoins, Top, TopQuantity, UpdateTableFromKind, ValueTableMode, Values,
76-
WildcardAdditionalOptions, With, WithFill,
71+
SelectInto, SelectItem, SelectItemQualifiedWildcardKind, SetExpr, SetOperator, SetQuantifier,
72+
Setting, SymbolDefinition, Table, TableAlias, TableAliasColumnDef, TableFactor,
73+
TableFunctionArgs, TableSample, TableSampleBucket, TableSampleKind, TableSampleMethod,
74+
TableSampleModifier, TableSampleQuantity, TableSampleSeed, TableSampleSeedModifier,
75+
TableSampleUnit, TableVersion, TableWithJoins, Top, TopQuantity, UpdateTableFromKind,
76+
ValueTableMode, Values, WildcardAdditionalOptions, With, WithFill,
7777
};
7878

7979
pub use self::trigger::{

src/ast/query.rs

Lines changed: 30 additions & 4 deletions
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

Lines changed: 13 additions & 5 deletions
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;
@@ -1615,16 +1616,23 @@ impl Spanned for JsonPathElem {
16151616
}
16161617
}
16171618

1619+
impl Spanned for SelectItemQualifiedWildcardKind {
1620+
fn span(&self) -> Span {
1621+
match self {
1622+
SelectItemQualifiedWildcardKind::ObjectName(object_name) => object_name.span(),
1623+
SelectItemQualifiedWildcardKind::Expr(expr) => expr.span(),
1624+
}
1625+
}
1626+
}
1627+
16181628
impl Spanned for SelectItem {
16191629
fn span(&self) -> Span {
16201630
match self {
16211631
SelectItem::UnnamedExpr(expr) => expr.span(),
16221632
SelectItem::ExprWithAlias { expr, alias } => expr.span().union(&alias.span),
1623-
SelectItem::QualifiedWildcard(object_name, wildcard_additional_options) => union_spans(
1624-
object_name
1625-
.0
1626-
.iter()
1627-
.map(|i| i.span)
1633+
SelectItem::QualifiedWildcard(kind, wildcard_additional_options) => union_spans(
1634+
[kind.span()]
1635+
.into_iter()
16281636
.chain(iter::once(wildcard_additional_options.span())),
16291637
),
16301638
SelectItem::Wildcard(wildcard_additional_options) => wildcard_additional_options.span(),

src/dialect/bigquery.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ impl Dialect for BigQueryDialect {
7878
true
7979
}
8080

81+
/// See <https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#select_expression_star>
82+
fn supports_select_expr_star(&self) -> bool {
83+
true
84+
}
85+
8186
// See <https://cloud.google.com/bigquery/docs/access-historical-data>
8287
fn supports_timestamp_versioning(&self) -> bool {
8388
true

src/dialect/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,17 @@ pub trait Dialect: Debug + Any {
440440
false
441441
}
442442

443+
/// Return true if the dialect supports wildcard expansion on
444+
/// arbitrary expressions in projections.
445+
///
446+
/// Example:
447+
/// ```sql
448+
/// SELECT STRUCT<STRING>('foo').* FROM T
449+
/// ```
450+
fn supports_select_expr_star(&self) -> bool {
451+
false
452+
}
453+
443454
/// Does the dialect support MySQL-style `'user'@'host'` grantee syntax?
444455
fn supports_user_host_grantee(&self) -> bool {
445456
false

src/parser/mod.rs

Lines changed: 23 additions & 7 deletions
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(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
}
@@ -12834,7 +12841,7 @@ impl<'a> Parser<'a> {
1283412841
pub fn parse_select_item(&mut self) -> Result<SelectItem, ParserError> {
1283512842
match self.parse_wildcard_expr()? {
1283612843
Expr::QualifiedWildcard(prefix, token) => Ok(SelectItem::QualifiedWildcard(
12837-
prefix,
12844+
SelectItemQualifiedWildcardKind::ObjectName(prefix),
1283812845
self.parse_wildcard_additional_options(token.0)?,
1283912846
)),
1284012847
Expr::Wildcard(token) => Ok(SelectItem::Wildcard(
@@ -12864,6 +12871,15 @@ impl<'a> Parser<'a> {
1286412871
alias,
1286512872
})
1286612873
}
12874+
expr if self.dialect.supports_select_expr_star()
12875+
&& self.consume_tokens(&[Token::Period, Token::Mul]) =>
12876+
{
12877+
let wildcard_token = self.get_previous_token().clone();
12878+
Ok(SelectItem::QualifiedWildcard(
12879+
SelectItemQualifiedWildcardKind::Expr(expr),
12880+
self.parse_wildcard_additional_options(wildcard_token)?,
12881+
))
12882+
}
1286712883
expr => self
1286812884
.maybe_parse_select_item_alias()
1286912885
.map(|alias| match alias {

tests/sqlparser_bigquery.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1537,9 +1537,6 @@ fn parse_hyphenated_table_identifiers() {
15371537
]))
15381538
})
15391539
);
1540-
1541-
let error_sql = "select foo-bar.* from foo-bar";
1542-
assert!(bigquery().parse_sql_statements(error_sql).is_err());
15431540
}
15441541

15451542
#[test]
@@ -2198,6 +2195,14 @@ fn parse_extract_weekday() {
21982195
);
21992196
}
22002197

2198+
#[test]
2199+
fn bigquery_select_expr_star() {
2200+
bigquery()
2201+
.verified_only_select("SELECT STRUCT<STRING>((SELECT foo FROM T WHERE true)).* FROM T");
2202+
bigquery().verified_only_select("SELECT [STRUCT<STRING>('foo')][0].* EXCEPT (foo) FROM T");
2203+
bigquery().verified_only_select("SELECT myfunc()[0].* FROM T");
2204+
}
2205+
22012206
#[test]
22022207
fn test_select_as_struct() {
22032208
bigquery().verified_only_select("SELECT * FROM (SELECT AS VALUE STRUCT(123 AS a, false AS b))");

tests/sqlparser_common.rs

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -995,7 +995,7 @@ fn parse_select_wildcard() {
995995
let select = verified_only_select(sql);
996996
assert_eq!(
997997
&SelectItem::QualifiedWildcard(
998-
ObjectName(vec![Ident::new("foo")]),
998+
SelectItemQualifiedWildcardKind::ObjectName(ObjectName(vec![Ident::new("foo")])),
999999
WildcardAdditionalOptions::default()
10001000
),
10011001
only(&select.projection)
@@ -1005,7 +1005,10 @@ fn parse_select_wildcard() {
10051005
let select = verified_only_select(sql);
10061006
assert_eq!(
10071007
&SelectItem::QualifiedWildcard(
1008-
ObjectName(vec![Ident::new("myschema"), Ident::new("mytable"),]),
1008+
SelectItemQualifiedWildcardKind::ObjectName(ObjectName(vec![
1009+
Ident::new("myschema"),
1010+
Ident::new("mytable"),
1011+
])),
10091012
WildcardAdditionalOptions::default(),
10101013
),
10111014
only(&select.projection)
@@ -1050,6 +1053,79 @@ fn parse_column_aliases() {
10501053
one_statement_parses_to("SELECT a.col + 1 newname FROM foo AS a", sql);
10511054
}
10521055

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

tests/sqlparser_duckdb.rs

Lines changed: 1 addition & 1 deletion
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(vec![Ident::new("name")]),
163+
SelectItemQualifiedWildcardKind::ObjectName(ObjectName(vec![Ident::new("name")])),
164164
WildcardAdditionalOptions {
165165
opt_exclude: Some(ExcludeSelectItem::Single(Ident::new("department_id"))),
166166
..Default::default()

tests/sqlparser_snowflake.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1365,7 +1365,7 @@ fn test_select_wildcard_with_exclude() {
13651365
let select = snowflake_and_generic()
13661366
.verified_only_select("SELECT name.* EXCLUDE department_id FROM employee_table");
13671367
let expected = SelectItem::QualifiedWildcard(
1368-
ObjectName(vec![Ident::new("name")]),
1368+
SelectItemQualifiedWildcardKind::ObjectName(ObjectName(vec![Ident::new("name")])),
13691369
WildcardAdditionalOptions {
13701370
opt_exclude: Some(ExcludeSelectItem::Single(Ident::new("department_id"))),
13711371
..Default::default()
@@ -1402,7 +1402,7 @@ fn test_select_wildcard_with_rename() {
14021402
"SELECT name.* RENAME (department_id AS new_dep, employee_id AS new_emp) FROM employee_table",
14031403
);
14041404
let expected = SelectItem::QualifiedWildcard(
1405-
ObjectName(vec![Ident::new("name")]),
1405+
SelectItemQualifiedWildcardKind::ObjectName(ObjectName(vec![Ident::new("name")])),
14061406
WildcardAdditionalOptions {
14071407
opt_rename: Some(RenameSelectItem::Multiple(vec![
14081408
IdentWithAlias {

0 commit comments

Comments
 (0)