Skip to content

Implement FROM-first selects #1713

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ pub use self::query::{
NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset, OffsetRows, OpenJsonTableColumn,
OrderBy, OrderByExpr, PivotValueSource, ProjectionSelect, Query, RenameSelectItem,
RepetitionQuantifier, ReplaceSelectElement, ReplaceSelectItem, RowsPerMatch, Select,
SelectInto, SelectItem, SelectItemQualifiedWildcardKind, SetExpr, SetOperator, SetQuantifier,
Setting, SymbolDefinition, Table, TableAlias, TableAliasColumnDef, TableFactor,
SelectFlavor, SelectInto, SelectItem, SelectItemQualifiedWildcardKind, SetExpr, SetOperator,
SetQuantifier, Setting, SymbolDefinition, Table, TableAlias, TableAliasColumnDef, TableFactor,
TableFunctionArgs, TableIndexHintForClause, TableIndexHintType, TableIndexHints,
TableIndexType, TableSample, TableSampleBucket, TableSampleKind, TableSampleMethod,
TableSampleModifier, TableSampleQuantity, TableSampleSeed, TableSampleSeedModifier,
Expand Down
29 changes: 27 additions & 2 deletions src/ast/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,19 @@ impl fmt::Display for Table {
}
}

/// What did this select look like?
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum SelectFlavor {
/// `SELECT *`
Standard,
/// `FROM ... SELECT *`
FromFirst,
/// `FROM *`
FromFirstNoSelect,
}

/// A restricted variant of `SELECT` (without CTEs/`ORDER BY`), which may
/// appear either as the only body item of a `Query`, or as an operand
/// to a set operation like `UNION`.
Expand Down Expand Up @@ -328,11 +341,23 @@ pub struct Select {
pub value_table_mode: Option<ValueTableMode>,
/// STARTING WITH .. CONNECT BY
pub connect_by: Option<ConnectBy>,
/// Was this a FROM-first query?
pub flavor: SelectFlavor,
}

impl fmt::Display for Select {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "SELECT")?;
match self.flavor {
SelectFlavor::Standard => {
write!(f, "SELECT")?;
}
SelectFlavor::FromFirst => {
write!(f, "FROM {} SELECT", display_comma_separated(&self.from))?;
}
SelectFlavor::FromFirstNoSelect => {
write!(f, "FROM {}", display_comma_separated(&self.from))?;
}
}

if let Some(value_table_mode) = self.value_table_mode {
write!(f, " {value_table_mode}")?;
Expand Down Expand Up @@ -360,7 +385,7 @@ impl fmt::Display for Select {
write!(f, " {into}")?;
}

if !self.from.is_empty() {
if self.flavor == SelectFlavor::Standard && !self.from.is_empty() {
write!(f, " FROM {}", display_comma_separated(&self.from))?;
}
if !self.lateral_views.is_empty() {
Expand Down
1 change: 1 addition & 0 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2077,6 +2077,7 @@ impl Spanned for Select {
value_table_mode: _, // todo, BigQuery specific
connect_by,
top_before_distinct: _,
flavor: _,
} = self;

union_spans(
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/clickhouse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,8 @@ impl Dialect for ClickHouseDialect {
fn supports_lambda_functions(&self) -> bool {
true
}

fn supports_from_first_select(&self) -> bool {
true
}
}
4 changes: 4 additions & 0 deletions src/dialect/duckdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,8 @@ impl Dialect for DuckDbDialect {
fn supports_array_typedef_size(&self) -> bool {
true
}

fn supports_from_first_select(&self) -> bool {
true
}
}
11 changes: 11 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,17 @@ pub trait Dialect: Debug + Any {
false
}

/// Return true if the dialect supports "FROM-first" selects.
///
/// Example:
/// ```sql
/// FROM table
/// SELECT *
/// ```
fn supports_from_first_select(&self) -> bool {
false
}

/// Does the dialect support MySQL-style `'user'@'host'` grantee syntax?
fn supports_user_host_grantee(&self) -> bool {
false
Expand Down
52 changes: 47 additions & 5 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,7 @@ impl<'a> Parser<'a> {
Keyword::DESCRIBE => self.parse_explain(DescribeAlias::Describe),
Keyword::EXPLAIN => self.parse_explain(DescribeAlias::Explain),
Keyword::ANALYZE => self.parse_analyze(),
Keyword::SELECT | Keyword::WITH | Keyword::VALUES => {
Keyword::SELECT | Keyword::WITH | Keyword::VALUES | Keyword::FROM => {
self.prev_token();
self.parse_query().map(Statement::Query)
}
Expand Down Expand Up @@ -10217,7 +10217,9 @@ impl<'a> Parser<'a> {
pub fn parse_query_body(&mut self, precedence: u8) -> Result<Box<SetExpr>, ParserError> {
// We parse the expression using a Pratt parser, as in `parse_expr()`.
// Start by parsing a restricted SELECT or a `(subquery)`:
let expr = if self.peek_keyword(Keyword::SELECT) {
let expr = if self.peek_keyword(Keyword::SELECT)
|| (self.peek_keyword(Keyword::FROM) && self.dialect.supports_from_first_select())
{
SetExpr::Select(self.parse_select().map(Box::new)?)
} else if self.consume_token(&Token::LParen) {
// CTEs are not allowed here, but the parser currently accepts them
Expand Down Expand Up @@ -10316,6 +10318,39 @@ impl<'a> Parser<'a> {

/// Parse a restricted `SELECT` statement (no CTEs / `UNION` / `ORDER BY`)
pub fn parse_select(&mut self) -> Result<Select, ParserError> {
let mut from_first = None;

if self.dialect.supports_from_first_select() && self.peek_keyword(Keyword::FROM) {
let from_token = self.expect_keyword(Keyword::FROM)?;
let from = self.parse_table_with_joins()?;
if !self.peek_keyword(Keyword::SELECT) {
return Ok(Select {
select_token: AttachedToken(from_token),
distinct: None,
top: None,
top_before_distinct: false,
projection: vec![],
into: None,
from,
lateral_views: vec![],
prewhere: None,
selection: None,
group_by: GroupByExpr::Expressions(vec![], vec![]),
cluster_by: vec![],
distribute_by: vec![],
sort_by: vec![],
having: None,
named_window: vec![],
window_before_qualify: false,
qualify: None,
value_table_mode: None,
connect_by: None,
flavor: SelectFlavor::FromFirstNoSelect,
});
}
from_first = Some(from);
}

let select_token = self.expect_keyword(Keyword::SELECT)?;
let value_table_mode =
if dialect_of!(self is BigQueryDialect) && self.parse_keyword(Keyword::AS) {
Expand Down Expand Up @@ -10370,10 +10405,12 @@ impl<'a> Parser<'a> {
// otherwise they may be parsed as an alias as part of the `projection`
// or `from`.

let from = if self.parse_keyword(Keyword::FROM) {
self.parse_table_with_joins()?
let (from, from_first) = if let Some(from) = from_first.take() {
(from, true)
} else if self.parse_keyword(Keyword::FROM) {
(self.parse_table_with_joins()?, false)
} else {
vec![]
(vec![], false)
};

let mut lateral_views = vec![];
Expand Down Expand Up @@ -10505,6 +10542,11 @@ impl<'a> Parser<'a> {
qualify,
value_table_mode,
connect_by,
flavor: if from_first {
SelectFlavor::FromFirst
} else {
SelectFlavor::Standard
},
})
}

Expand Down
1 change: 1 addition & 0 deletions tests/sqlparser_clickhouse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ fn parse_map_access_expr() {
qualify: None,
value_table_mode: None,
connect_by: None,
flavor: SelectFlavor::Standard,
},
select
);
Expand Down
71 changes: 71 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@ fn parse_update_set_from() {
window_before_qualify: false,
value_table_mode: None,
connect_by: None,
flavor: SelectFlavor::Standard,
}))),
order_by: None,
limit: None,
Expand Down Expand Up @@ -5289,6 +5290,7 @@ fn test_parse_named_window() {
window_before_qualify: true,
value_table_mode: None,
connect_by: None,
flavor: SelectFlavor::Standard,
};
assert_eq!(actual_select_only, expected);
}
Expand Down Expand Up @@ -5915,6 +5917,7 @@ fn parse_interval_and_or_xor() {
window_before_qualify: false,
value_table_mode: None,
connect_by: None,
flavor: SelectFlavor::Standard,
}))),
order_by: None,
limit: None,
Expand Down Expand Up @@ -8022,6 +8025,7 @@ fn lateral_function() {
window_before_qualify: false,
value_table_mode: None,
connect_by: None,
flavor: SelectFlavor::Standard,
};
assert_eq!(actual_select_only, expected);
}
Expand Down Expand Up @@ -8919,6 +8923,7 @@ fn parse_merge() {
qualify: None,
value_table_mode: None,
connect_by: None,
flavor: SelectFlavor::Standard,
}))),
order_by: None,
limit: None,
Expand Down Expand Up @@ -10703,6 +10708,7 @@ fn parse_unload() {
qualify: None,
value_table_mode: None,
connect_by: None,
flavor: SelectFlavor::Standard,
}))),
with: None,
limit: None,
Expand Down Expand Up @@ -10913,6 +10919,7 @@ fn parse_connect_by() {
))))),
}],
}),
flavor: SelectFlavor::Standard,
};

let connect_by_1 = concat!(
Expand Down Expand Up @@ -10997,6 +11004,7 @@ fn parse_connect_by() {
))))),
}],
}),
flavor: SelectFlavor::Standard,
}
);

Expand Down Expand Up @@ -11860,6 +11868,7 @@ fn test_extract_seconds_ok() {
window_before_qualify: false,
value_table_mode: None,
connect_by: None,
flavor: SelectFlavor::Standard,
}))),
order_by: None,
limit: None,
Expand Down Expand Up @@ -13592,3 +13601,65 @@ fn test_lambdas() {
);
dialects.verified_expr("transform(array(1, 2, 3), x -> x + 1)");
}

#[test]
fn test_select_from_first() {
let dialects = all_dialects_where(|d| d.supports_from_first_select());
let q1 = "FROM capitals";
let q2 = "FROM capitals SELECT *";

for (q, flavor, projection) in [
(q1, SelectFlavor::FromFirstNoSelect, vec![]),
(
q2,
SelectFlavor::FromFirst,
vec![SelectItem::Wildcard(WildcardAdditionalOptions::default())],
),
] {
let ast = dialects.verified_query(q);
let expected = Query {
with: None,
body: Box::new(SetExpr::Select(Box::new(Select {
select_token: AttachedToken::empty(),
distinct: None,
top: None,
projection,
top_before_distinct: false,
into: None,
from: vec![TableWithJoins {
relation: table_from_name(ObjectName::from(vec![Ident {
value: "capitals".to_string(),
quote_style: None,
span: Span::empty(),
}])),
joins: vec![],
}],
lateral_views: vec![],
prewhere: None,
selection: None,
group_by: GroupByExpr::Expressions(vec![], vec![]),
cluster_by: vec![],
distribute_by: vec![],
sort_by: vec![],
having: None,
named_window: vec![],
window_before_qualify: false,
qualify: None,
value_table_mode: None,
connect_by: None,
flavor,
}))),
order_by: None,
limit: None,
offset: None,
fetch: None,
locks: vec![],
limit_by: vec![],
for_clause: None,
settings: None,
format_clause: None,
};
assert_eq!(expected, ast);
assert_eq!(ast.to_string(), q);
}
}
2 changes: 2 additions & 0 deletions tests/sqlparser_duckdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ fn test_select_union_by_name() {
qualify: None,
value_table_mode: None,
connect_by: None,
flavor: SelectFlavor::Standard,
}))),
right: Box::<SetExpr>::new(SetExpr::Select(Box::new(Select {
select_token: AttachedToken::empty(),
Expand Down Expand Up @@ -317,6 +318,7 @@ fn test_select_union_by_name() {
qualify: None,
value_table_mode: None,
connect_by: None,
flavor: SelectFlavor::Standard,
}))),
});
assert_eq!(ast.body, expected);
Expand Down
3 changes: 3 additions & 0 deletions tests/sqlparser_mssql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ fn parse_create_procedure() {
qualify: None,
value_table_mode: None,
connect_by: None,
flavor: SelectFlavor::Standard,
})))
}))],
params: Some(vec![
Expand Down Expand Up @@ -1114,6 +1115,7 @@ fn parse_substring_in_select() {
window_before_qualify: false,
value_table_mode: None,
connect_by: None,
flavor: SelectFlavor::Standard,
}))),
order_by: None,
limit: None,
Expand Down Expand Up @@ -1251,6 +1253,7 @@ fn parse_mssql_declare() {
qualify: None,
value_table_mode: None,
connect_by: None,
flavor: SelectFlavor::Standard,
})))
}))
],
Expand Down
Loading