Reject mixed wildcard SQL projections without panicking
This commit is contained in:
parent
7111a682ff
commit
7c4cb70047
@ -22,6 +22,8 @@ pub enum PlannerError {
|
|||||||
DuplicateSourceName(String),
|
DuplicateSourceName(String),
|
||||||
/// The current `ORDER BY` subset only supports output column names.
|
/// The current `ORDER BY` subset only supports output column names.
|
||||||
UnsupportedOrderBy,
|
UnsupportedOrderBy,
|
||||||
|
/// The parser or AST contains a wildcard mixed with other projection items.
|
||||||
|
MixedWildcardProjection,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for PlannerError {
|
impl fmt::Display for PlannerError {
|
||||||
@ -35,6 +37,12 @@ impl fmt::Display for PlannerError {
|
|||||||
Self::UnsupportedOrderBy => {
|
Self::UnsupportedOrderBy => {
|
||||||
write!(f, "only output column names are supported in ORDER BY")
|
write!(f, "only output column names are supported in ORDER BY")
|
||||||
}
|
}
|
||||||
|
Self::MixedWildcardProjection => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"wildcard projections cannot be combined with other items"
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -43,9 +51,10 @@ impl Error for PlannerError {
|
|||||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||||
match self {
|
match self {
|
||||||
Self::Catalog(err) => Some(err),
|
Self::Catalog(err) => Some(err),
|
||||||
Self::UnknownColumn(_) | Self::DuplicateSourceName(_) | Self::UnsupportedOrderBy => {
|
Self::UnknownColumn(_)
|
||||||
None
|
| Self::DuplicateSourceName(_)
|
||||||
}
|
| Self::UnsupportedOrderBy
|
||||||
|
| Self::MixedWildcardProjection => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,7 +101,7 @@ pub fn plan_select(
|
|||||||
});
|
});
|
||||||
fields.push(Field::new(output_name, data_type, nullable));
|
fields.push(Field::new(output_name, data_type, nullable));
|
||||||
}
|
}
|
||||||
SelectItem::Wildcard => unreachable!("wildcard projections are handled earlier"),
|
SelectItem::Wildcard => return Err(PlannerError::MixedWildcardProjection),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,7 +296,7 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::catalog::PredicateCatalog;
|
use crate::catalog::PredicateCatalog;
|
||||||
use crate::chase::{Atom, Instance, Term};
|
use crate::chase::{Atom, Instance, Term};
|
||||||
use crate::sql::parser::parse_select;
|
use crate::sql::parser::{ParseError, parse_select};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn plans_projection_and_filter() {
|
fn plans_projection_and_filter() {
|
||||||
@ -466,4 +475,37 @@ mod tests {
|
|||||||
other => panic!("unexpected plan: {:?}", other),
|
other => panic!("unexpected plan: {:?}", other),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_mixed_wildcard_projection() {
|
||||||
|
let instance: Instance = vec![Atom::new(
|
||||||
|
"Parent",
|
||||||
|
vec![Term::constant("alice"), Term::constant("bob")],
|
||||||
|
)]
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
|
let catalog = PredicateCatalog::from_instance(&instance).unwrap();
|
||||||
|
let select = parse_select("SELECT *, c0 FROM Parent").unwrap_err();
|
||||||
|
assert_eq!(select, ParseError::MixedWildcardProjection);
|
||||||
|
let malformed = Select {
|
||||||
|
projection: vec![
|
||||||
|
SelectItem::Wildcard,
|
||||||
|
SelectItem::Expr {
|
||||||
|
expr: Expr::Identifier("c0".to_string()),
|
||||||
|
alias: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
from: vec![TableRef {
|
||||||
|
name: "Parent".to_string(),
|
||||||
|
alias: None,
|
||||||
|
}],
|
||||||
|
selection: None,
|
||||||
|
order_by: Vec::new(),
|
||||||
|
};
|
||||||
|
let error = plan_select(&malformed, &catalog).unwrap_err();
|
||||||
|
assert_eq!(
|
||||||
|
error.to_string(),
|
||||||
|
"wildcard projections cannot be combined with other items"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ pub enum ParseError {
|
|||||||
ExpectedToken(&'static str),
|
ExpectedToken(&'static str),
|
||||||
ExpectedIdentifier,
|
ExpectedIdentifier,
|
||||||
UnexpectedToken(String),
|
UnexpectedToken(String),
|
||||||
|
MixedWildcardProjection,
|
||||||
UnterminatedString,
|
UnterminatedString,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,6 +23,12 @@ impl fmt::Display for ParseError {
|
|||||||
Self::ExpectedToken(token) => write!(f, "expected `{}`", token),
|
Self::ExpectedToken(token) => write!(f, "expected `{}`", token),
|
||||||
Self::ExpectedIdentifier => write!(f, "expected identifier"),
|
Self::ExpectedIdentifier => write!(f, "expected identifier"),
|
||||||
Self::UnexpectedToken(token) => write!(f, "unexpected token `{}`", token),
|
Self::UnexpectedToken(token) => write!(f, "unexpected token `{}`", token),
|
||||||
|
Self::MixedWildcardProjection => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"wildcard projections cannot be combined with other items"
|
||||||
|
)
|
||||||
|
}
|
||||||
Self::UnterminatedString => write!(f, "unterminated string literal"),
|
Self::UnterminatedString => write!(f, "unterminated string literal"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -125,6 +132,14 @@ impl Parser {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if items.len() > 1
|
||||||
|
&& items
|
||||||
|
.iter()
|
||||||
|
.any(|item| matches!(item, SelectItem::Wildcard))
|
||||||
|
{
|
||||||
|
return Err(ParseError::MixedWildcardProjection);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(items)
|
Ok(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -515,4 +530,10 @@ mod tests {
|
|||||||
assert_eq!(select.from.len(), 1);
|
assert_eq!(select.from.len(), 1);
|
||||||
assert_eq!(select.from[0].name, "Employee-Records:2025");
|
assert_eq!(select.from[0].name, "Employee-Records:2025");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_mixed_wildcard_projection() {
|
||||||
|
let error = parse_select("SELECT *, c0 FROM Parent").unwrap_err();
|
||||||
|
assert_eq!(error, ParseError::MixedWildcardProjection);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user