Implement FROM-first selects (#1713)

This commit is contained in:
Armin Ronacher 2025-02-09 06:10:58 +01:00 committed by GitHub
parent cad49232c1
commit 46cfcfe8f7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 184 additions and 9 deletions

View file

@ -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,

View file

@ -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`.
@ -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}")?;
@ -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() {

View file

@ -2077,6 +2077,7 @@ impl Spanned for Select {
value_table_mode: _, // todo, BigQuery specific
connect_by,
top_before_distinct: _,
flavor: _,
} = self;
union_spans(

View file

@ -75,4 +75,8 @@ impl Dialect for ClickHouseDialect {
fn supports_lambda_functions(&self) -> bool {
true
}
fn supports_from_first_select(&self) -> bool {
true
}
}

View file

@ -85,4 +85,8 @@ impl Dialect for DuckDbDialect {
fn supports_array_typedef_size(&self) -> bool {
true
}
fn supports_from_first_select(&self) -> bool {
true
}
}

View file

@ -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

View file

@ -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)
}
@ -10218,7 +10218,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
@ -10317,6 +10319,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) {
@ -10371,10 +10406,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![];
@ -10506,6 +10543,11 @@ impl<'a> Parser<'a> {
qualify,
value_table_mode,
connect_by,
flavor: if from_first {
SelectFlavor::FromFirst
} else {
SelectFlavor::Standard
},
})
}