Skip to content

Commit 46cfcfe

Browse files
authored
Implement FROM-first selects (#1713)
1 parent cad4923 commit 46cfcfe

13 files changed

+184
-9
lines changed

src/ast/mod.rs

+2-2
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

+27-2
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

+1
Original file line numberDiff line numberDiff line change
@@ -2077,6 +2077,7 @@ impl Spanned for Select {
20772077
value_table_mode: _, // todo, BigQuery specific
20782078
connect_by,
20792079
top_before_distinct: _,
2080+
flavor: _,
20802081
} = self;
20812082

20822083
union_spans(

src/dialect/clickhouse.rs

+4
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

+4
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

+11
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

+47-5
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
}
@@ -10218,7 +10218,9 @@ impl<'a> Parser<'a> {
1021810218
pub fn parse_query_body(&mut self, precedence: u8) -> Result<Box<SetExpr>, ParserError> {
1021910219
// We parse the expression using a Pratt parser, as in `parse_expr()`.
1022010220
// Start by parsing a restricted SELECT or a `(subquery)`:
10221-
let expr = if self.peek_keyword(Keyword::SELECT) {
10221+
let expr = if self.peek_keyword(Keyword::SELECT)
10222+
|| (self.peek_keyword(Keyword::FROM) && self.dialect.supports_from_first_select())
10223+
{
1022210224
SetExpr::Select(self.parse_select().map(Box::new)?)
1022310225
} else if self.consume_token(&Token::LParen) {
1022410226
// CTEs are not allowed here, but the parser currently accepts them
@@ -10317,6 +10319,39 @@ impl<'a> Parser<'a> {
1031710319

1031810320
/// Parse a restricted `SELECT` statement (no CTEs / `UNION` / `ORDER BY`)
1031910321
pub fn parse_select(&mut self) -> Result<Select, ParserError> {
10322+
let mut from_first = None;
10323+
10324+
if self.dialect.supports_from_first_select() && self.peek_keyword(Keyword::FROM) {
10325+
let from_token = self.expect_keyword(Keyword::FROM)?;
10326+
let from = self.parse_table_with_joins()?;
10327+
if !self.peek_keyword(Keyword::SELECT) {
10328+
return Ok(Select {
10329+
select_token: AttachedToken(from_token),
10330+
distinct: None,
10331+
top: None,
10332+
top_before_distinct: false,
10333+
projection: vec![],
10334+
into: None,
10335+
from,
10336+
lateral_views: vec![],
10337+
prewhere: None,
10338+
selection: None,
10339+
group_by: GroupByExpr::Expressions(vec![], vec![]),
10340+
cluster_by: vec![],
10341+
distribute_by: vec![],
10342+
sort_by: vec![],
10343+
having: None,
10344+
named_window: vec![],
10345+
window_before_qualify: false,
10346+
qualify: None,
10347+
value_table_mode: None,
10348+
connect_by: None,
10349+
flavor: SelectFlavor::FromFirstNoSelect,
10350+
});
10351+
}
10352+
from_first = Some(from);
10353+
}
10354+
1032010355
let select_token = self.expect_keyword(Keyword::SELECT)?;
1032110356
let value_table_mode =
1032210357
if dialect_of!(self is BigQueryDialect) && self.parse_keyword(Keyword::AS) {
@@ -10371,10 +10406,12 @@ impl<'a> Parser<'a> {
1037110406
// otherwise they may be parsed as an alias as part of the `projection`
1037210407
// or `from`.
1037310408

10374-
let from = if self.parse_keyword(Keyword::FROM) {
10375-
self.parse_table_with_joins()?
10409+
let (from, from_first) = if let Some(from) = from_first.take() {
10410+
(from, true)
10411+
} else if self.parse_keyword(Keyword::FROM) {
10412+
(self.parse_table_with_joins()?, false)
1037610413
} else {
10377-
vec![]
10414+
(vec![], false)
1037810415
};
1037910416

1038010417
let mut lateral_views = vec![];
@@ -10506,6 +10543,11 @@ impl<'a> Parser<'a> {
1050610543
qualify,
1050710544
value_table_mode,
1050810545
connect_by,
10546+
flavor: if from_first {
10547+
SelectFlavor::FromFirst
10548+
} else {
10549+
SelectFlavor::Standard
10550+
},
1050910551
})
1051010552
}
1051110553

tests/sqlparser_clickhouse.rs

+1
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

+71
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,
@@ -5289,6 +5290,7 @@ fn test_parse_named_window() {
52895290
window_before_qualify: true,
52905291
value_table_mode: None,
52915292
connect_by: None,
5293+
flavor: SelectFlavor::Standard,
52925294
};
52935295
assert_eq!(actual_select_only, expected);
52945296
}
@@ -5915,6 +5917,7 @@ fn parse_interval_and_or_xor() {
59155917
window_before_qualify: false,
59165918
value_table_mode: None,
59175919
connect_by: None,
5920+
flavor: SelectFlavor::Standard,
59185921
}))),
59195922
order_by: None,
59205923
limit: None,
@@ -8022,6 +8025,7 @@ fn lateral_function() {
80228025
window_before_qualify: false,
80238026
value_table_mode: None,
80248027
connect_by: None,
8028+
flavor: SelectFlavor::Standard,
80258029
};
80268030
assert_eq!(actual_select_only, expected);
80278031
}
@@ -8919,6 +8923,7 @@ fn parse_merge() {
89198923
qualify: None,
89208924
value_table_mode: None,
89218925
connect_by: None,
8926+
flavor: SelectFlavor::Standard,
89228927
}))),
89238928
order_by: None,
89248929
limit: None,
@@ -10703,6 +10708,7 @@ fn parse_unload() {
1070310708
qualify: None,
1070410709
value_table_mode: None,
1070510710
connect_by: None,
10711+
flavor: SelectFlavor::Standard,
1070610712
}))),
1070710713
with: None,
1070810714
limit: None,
@@ -10913,6 +10919,7 @@ fn parse_connect_by() {
1091310919
))))),
1091410920
}],
1091510921
}),
10922+
flavor: SelectFlavor::Standard,
1091610923
};
1091710924

1091810925
let connect_by_1 = concat!(
@@ -10997,6 +11004,7 @@ fn parse_connect_by() {
1099711004
))))),
1099811005
}],
1099911006
}),
11007+
flavor: SelectFlavor::Standard,
1100011008
}
1100111009
);
1100211010

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

tests/sqlparser_duckdb.rs

+2
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

+3
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)