Skip to content

Commit e08aace

Browse files
mitsuhikoayman-sigma
authored andcommitted
Implement FROM-first selects (apache#1713)
1 parent 979000d commit e08aace

File tree

13 files changed

+184
-9
lines changed

13 files changed

+184
-9
lines changed

src/ast/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ 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, SelectItemQualifiedWildcardKind, SetExpr, SetOperator, SetQuantifier,
72-
Setting, SymbolDefinition, Table, TableAlias, TableAliasColumnDef, TableFactor,
71+
SelectFlavor, SelectInto, SelectItem, SelectItemQualifiedWildcardKind, SetExpr, SetOperator,
72+
SetQuantifier, Setting, SymbolDefinition, Table, TableAlias, TableAliasColumnDef, TableFactor,
7373
TableFunctionArgs, TableIndexHintForClause, TableIndexHintType, TableIndexHints,
7474
TableIndexType, TableSample, TableSampleBucket, TableSampleKind, TableSampleMethod,
7575
TableSampleModifier, TableSampleQuantity, TableSampleSeed, TableSampleSeedModifier,

src/ast/query.rs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,19 @@ impl fmt::Display for Table {
275275
}
276276
}
277277

278+
/// What did this select look like?
279+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
280+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
281+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
282+
pub enum SelectFlavor {
283+
/// `SELECT *`
284+
Standard,
285+
/// `FROM ... SELECT *`
286+
FromFirst,
287+
/// `FROM *`
288+
FromFirstNoSelect,
289+
}
290+
278291
/// A restricted variant of `SELECT` (without CTEs/`ORDER BY`), which may
279292
/// appear either as the only body item of a `Query`, or as an operand
280293
/// to a set operation like `UNION`.
@@ -328,11 +341,23 @@ pub struct Select {
328341
pub value_table_mode: Option<ValueTableMode>,
329342
/// STARTING WITH .. CONNECT BY
330343
pub connect_by: Option<ConnectBy>,
344+
/// Was this a FROM-first query?
345+
pub flavor: SelectFlavor,
331346
}
332347

333348
impl fmt::Display for Select {
334349
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
335-
write!(f, "SELECT")?;
350+
match self.flavor {
351+
SelectFlavor::Standard => {
352+
write!(f, "SELECT")?;
353+
}
354+
SelectFlavor::FromFirst => {
355+
write!(f, "FROM {} SELECT", display_comma_separated(&self.from))?;
356+
}
357+
SelectFlavor::FromFirstNoSelect => {
358+
write!(f, "FROM {}", display_comma_separated(&self.from))?;
359+
}
360+
}
336361

337362
if let Some(value_table_mode) = self.value_table_mode {
338363
write!(f, " {value_table_mode}")?;
@@ -360,7 +385,7 @@ impl fmt::Display for Select {
360385
write!(f, " {into}")?;
361386
}
362387

363-
if !self.from.is_empty() {
388+
if self.flavor == SelectFlavor::Standard && !self.from.is_empty() {
364389
write!(f, " FROM {}", display_comma_separated(&self.from))?;
365390
}
366391
if !self.lateral_views.is_empty() {

src/ast/spans.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2082,6 +2082,7 @@ impl Spanned for Select {
20822082
value_table_mode: _, // todo, BigQuery specific
20832083
connect_by,
20842084
top_before_distinct: _,
2085+
flavor: _,
20852086
} = self;
20862087

20872088
union_spans(

src/dialect/clickhouse.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,8 @@ impl Dialect for ClickHouseDialect {
7575
fn supports_lambda_functions(&self) -> bool {
7676
true
7777
}
78+
79+
fn supports_from_first_select(&self) -> bool {
80+
true
81+
}
7882
}

src/dialect/duckdb.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,8 @@ impl Dialect for DuckDbDialect {
8585
fn supports_array_typedef_size(&self) -> bool {
8686
true
8787
}
88+
89+
fn supports_from_first_select(&self) -> bool {
90+
true
91+
}
8892
}

src/dialect/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,17 @@ pub trait Dialect: Debug + Any {
463463
false
464464
}
465465

466+
/// Return true if the dialect supports "FROM-first" selects.
467+
///
468+
/// Example:
469+
/// ```sql
470+
/// FROM table
471+
/// SELECT *
472+
/// ```
473+
fn supports_from_first_select(&self) -> bool {
474+
false
475+
}
476+
466477
/// Does the dialect support MySQL-style `'user'@'host'` grantee syntax?
467478
fn supports_user_host_grantee(&self) -> bool {
468479
false

src/parser/mod.rs

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,7 @@ impl<'a> Parser<'a> {
528528
Keyword::DESCRIBE => self.parse_explain(DescribeAlias::Describe),
529529
Keyword::EXPLAIN => self.parse_explain(DescribeAlias::Explain),
530530
Keyword::ANALYZE => self.parse_analyze(),
531-
Keyword::SELECT | Keyword::WITH | Keyword::VALUES => {
531+
Keyword::SELECT | Keyword::WITH | Keyword::VALUES | Keyword::FROM => {
532532
self.prev_token();
533533
self.parse_query().map(Statement::Query)
534534
}
@@ -10228,7 +10228,9 @@ impl<'a> Parser<'a> {
1022810228
pub fn parse_query_body(&mut self, precedence: u8) -> Result<Box<SetExpr>, ParserError> {
1022910229
// We parse the expression using a Pratt parser, as in `parse_expr()`.
1023010230
// Start by parsing a restricted SELECT or a `(subquery)`:
10231-
let expr = if self.peek_keyword(Keyword::SELECT) {
10231+
let expr = if self.peek_keyword(Keyword::SELECT)
10232+
|| (self.peek_keyword(Keyword::FROM) && self.dialect.supports_from_first_select())
10233+
{
1023210234
SetExpr::Select(self.parse_select().map(Box::new)?)
1023310235
} else if self.consume_token(&Token::LParen) {
1023410236
// CTEs are not allowed here, but the parser currently accepts them
@@ -10327,6 +10329,39 @@ impl<'a> Parser<'a> {
1032710329

1032810330
/// Parse a restricted `SELECT` statement (no CTEs / `UNION` / `ORDER BY`)
1032910331
pub fn parse_select(&mut self) -> Result<Select, ParserError> {
10332+
let mut from_first = None;
10333+
10334+
if self.dialect.supports_from_first_select() && self.peek_keyword(Keyword::FROM) {
10335+
let from_token = self.expect_keyword(Keyword::FROM)?;
10336+
let from = self.parse_table_with_joins()?;
10337+
if !self.peek_keyword(Keyword::SELECT) {
10338+
return Ok(Select {
10339+
select_token: AttachedToken(from_token),
10340+
distinct: None,
10341+
top: None,
10342+
top_before_distinct: false,
10343+
projection: vec![],
10344+
into: None,
10345+
from,
10346+
lateral_views: vec![],
10347+
prewhere: None,
10348+
selection: None,
10349+
group_by: GroupByExpr::Expressions(vec![], vec![]),
10350+
cluster_by: vec![],
10351+
distribute_by: vec![],
10352+
sort_by: vec![],
10353+
having: None,
10354+
named_window: vec![],
10355+
window_before_qualify: false,
10356+
qualify: None,
10357+
value_table_mode: None,
10358+
connect_by: None,
10359+
flavor: SelectFlavor::FromFirstNoSelect,
10360+
});
10361+
}
10362+
from_first = Some(from);
10363+
}
10364+
1033010365
let select_token = self.expect_keyword(Keyword::SELECT)?;
1033110366
let value_table_mode =
1033210367
if dialect_of!(self is BigQueryDialect) && self.parse_keyword(Keyword::AS) {
@@ -10381,10 +10416,12 @@ impl<'a> Parser<'a> {
1038110416
// otherwise they may be parsed as an alias as part of the `projection`
1038210417
// or `from`.
1038310418

10384-
let from = if self.parse_keyword(Keyword::FROM) {
10385-
self.parse_table_with_joins()?
10419+
let (from, from_first) = if let Some(from) = from_first.take() {
10420+
(from, true)
10421+
} else if self.parse_keyword(Keyword::FROM) {
10422+
(self.parse_table_with_joins()?, false)
1038610423
} else {
10387-
vec![]
10424+
(vec![], false)
1038810425
};
1038910426

1039010427
let mut lateral_views = vec![];
@@ -10516,6 +10553,11 @@ impl<'a> Parser<'a> {
1051610553
qualify,
1051710554
value_table_mode,
1051810555
connect_by,
10556+
flavor: if from_first {
10557+
SelectFlavor::FromFirst
10558+
} else {
10559+
SelectFlavor::Standard
10560+
},
1051910561
})
1052010562
}
1052110563

tests/sqlparser_clickhouse.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ fn parse_map_access_expr() {
101101
qualify: None,
102102
value_table_mode: None,
103103
connect_by: None,
104+
flavor: SelectFlavor::Standard,
104105
},
105106
select
106107
);

tests/sqlparser_common.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,7 @@ fn parse_update_set_from() {
461461
window_before_qualify: false,
462462
value_table_mode: None,
463463
connect_by: None,
464+
flavor: SelectFlavor::Standard,
464465
}))),
465466
order_by: None,
466467
limit: None,
@@ -5290,6 +5291,7 @@ fn test_parse_named_window() {
52905291
window_before_qualify: true,
52915292
value_table_mode: None,
52925293
connect_by: None,
5294+
flavor: SelectFlavor::Standard,
52935295
};
52945296
assert_eq!(actual_select_only, expected);
52955297
}
@@ -5916,6 +5918,7 @@ fn parse_interval_and_or_xor() {
59165918
window_before_qualify: false,
59175919
value_table_mode: None,
59185920
connect_by: None,
5921+
flavor: SelectFlavor::Standard,
59195922
}))),
59205923
order_by: None,
59215924
limit: None,
@@ -8023,6 +8026,7 @@ fn lateral_function() {
80238026
window_before_qualify: false,
80248027
value_table_mode: None,
80258028
connect_by: None,
8029+
flavor: SelectFlavor::Standard,
80268030
};
80278031
assert_eq!(actual_select_only, expected);
80288032
}
@@ -8920,6 +8924,7 @@ fn parse_merge() {
89208924
qualify: None,
89218925
value_table_mode: None,
89228926
connect_by: None,
8927+
flavor: SelectFlavor::Standard,
89238928
}))),
89248929
order_by: None,
89258930
limit: None,
@@ -10706,6 +10711,7 @@ fn parse_unload() {
1070610711
qualify: None,
1070710712
value_table_mode: None,
1070810713
connect_by: None,
10714+
flavor: SelectFlavor::Standard,
1070910715
}))),
1071010716
with: None,
1071110717
limit: None,
@@ -10916,6 +10922,7 @@ fn parse_connect_by() {
1091610922
))))),
1091710923
}],
1091810924
}),
10925+
flavor: SelectFlavor::Standard,
1091910926
};
1092010927

1092110928
let connect_by_1 = concat!(
@@ -11000,6 +11007,7 @@ fn parse_connect_by() {
1100011007
))))),
1100111008
}],
1100211009
}),
11010+
flavor: SelectFlavor::Standard,
1100311011
}
1100411012
);
1100511013

@@ -11863,6 +11871,7 @@ fn test_extract_seconds_ok() {
1186311871
window_before_qualify: false,
1186411872
value_table_mode: None,
1186511873
connect_by: None,
11874+
flavor: SelectFlavor::Standard,
1186611875
}))),
1186711876
order_by: None,
1186811877
limit: None,
@@ -13595,3 +13604,65 @@ fn test_lambdas() {
1359513604
);
1359613605
dialects.verified_expr("transform(array(1, 2, 3), x -> x + 1)");
1359713606
}
13607+
13608+
#[test]
13609+
fn test_select_from_first() {
13610+
let dialects = all_dialects_where(|d| d.supports_from_first_select());
13611+
let q1 = "FROM capitals";
13612+
let q2 = "FROM capitals SELECT *";
13613+
13614+
for (q, flavor, projection) in [
13615+
(q1, SelectFlavor::FromFirstNoSelect, vec![]),
13616+
(
13617+
q2,
13618+
SelectFlavor::FromFirst,
13619+
vec![SelectItem::Wildcard(WildcardAdditionalOptions::default())],
13620+
),
13621+
] {
13622+
let ast = dialects.verified_query(q);
13623+
let expected = Query {
13624+
with: None,
13625+
body: Box::new(SetExpr::Select(Box::new(Select {
13626+
select_token: AttachedToken::empty(),
13627+
distinct: None,
13628+
top: None,
13629+
projection,
13630+
top_before_distinct: false,
13631+
into: None,
13632+
from: vec![TableWithJoins {
13633+
relation: table_from_name(ObjectName::from(vec![Ident {
13634+
value: "capitals".to_string(),
13635+
quote_style: None,
13636+
span: Span::empty(),
13637+
}])),
13638+
joins: vec![],
13639+
}],
13640+
lateral_views: vec![],
13641+
prewhere: None,
13642+
selection: None,
13643+
group_by: GroupByExpr::Expressions(vec![], vec![]),
13644+
cluster_by: vec![],
13645+
distribute_by: vec![],
13646+
sort_by: vec![],
13647+
having: None,
13648+
named_window: vec![],
13649+
window_before_qualify: false,
13650+
qualify: None,
13651+
value_table_mode: None,
13652+
connect_by: None,
13653+
flavor,
13654+
}))),
13655+
order_by: None,
13656+
limit: None,
13657+
offset: None,
13658+
fetch: None,
13659+
locks: vec![],
13660+
limit_by: vec![],
13661+
for_clause: None,
13662+
settings: None,
13663+
format_clause: None,
13664+
};
13665+
assert_eq!(expected, ast);
13666+
assert_eq!(ast.to_string(), q);
13667+
}
13668+
}

tests/sqlparser_duckdb.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ fn test_select_union_by_name() {
288288
qualify: None,
289289
value_table_mode: None,
290290
connect_by: None,
291+
flavor: SelectFlavor::Standard,
291292
}))),
292293
right: Box::<SetExpr>::new(SetExpr::Select(Box::new(Select {
293294
select_token: AttachedToken::empty(),
@@ -317,6 +318,7 @@ fn test_select_union_by_name() {
317318
qualify: None,
318319
value_table_mode: None,
319320
connect_by: None,
321+
flavor: SelectFlavor::Standard,
320322
}))),
321323
});
322324
assert_eq!(ast.body, expected);

tests/sqlparser_mssql.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ fn parse_create_procedure() {
137137
qualify: None,
138138
value_table_mode: None,
139139
connect_by: None,
140+
flavor: SelectFlavor::Standard,
140141
})))
141142
}))],
142143
params: Some(vec![
@@ -1114,6 +1115,7 @@ fn parse_substring_in_select() {
11141115
window_before_qualify: false,
11151116
value_table_mode: None,
11161117
connect_by: None,
1118+
flavor: SelectFlavor::Standard,
11171119
}))),
11181120
order_by: None,
11191121
limit: None,
@@ -1251,6 +1253,7 @@ fn parse_mssql_declare() {
12511253
qualify: None,
12521254
value_table_mode: None,
12531255
connect_by: None,
1256+
flavor: SelectFlavor::Standard,
12541257
})))
12551258
}))
12561259
],

0 commit comments

Comments
 (0)