use crate::ast::NoExt;
use crate::ast::dialect::FeatureSet;
use crate::parser::Dialect;
use crate::render::RenderDialect;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct Lenient;
impl Dialect for Lenient {
type Ext = NoExt;
fn features(&self) -> &FeatureSet {
&FeatureSet::LENIENT
}
}
impl RenderDialect for Lenient {
fn render_features(&self) -> FeatureSet {
FeatureSet::LENIENT
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{Expr, SelectItem, SetExpr, Statement};
use crate::dialect::Ansi;
use crate::dialect::test_support::assert_full_grammar;
use crate::parse_with;
#[test]
fn lenient_parses_the_full_m1_select_grammar() {
assert_full_grammar(Lenient);
}
#[test]
fn lenient_is_zero_sized_and_returns_its_preset() {
assert_eq!(std::mem::size_of::<Lenient>(), 0);
assert_eq!(Lenient.features(), &FeatureSet::LENIENT);
}
fn lenient_identifier_projection_count(sql: &str) -> Option<usize> {
let parsed = parse_with(sql, crate::ParseConfig::new(Lenient)).ok()?;
let Statement::Query { query, .. } = &parsed.statements()[0] else {
return None;
};
let SetExpr::Select { select, .. } = &query.body else {
return None;
};
select
.projection
.iter()
.all(|item| {
matches!(
item,
SelectItem::Expr {
expr: Expr::Column { .. },
..
}
)
})
.then_some(select.projection.len())
}
#[test]
fn lenient_accepts_all_three_identifier_quote_styles() {
assert_eq!(
lenient_identifier_projection_count(r#"SELECT "a", `b`, [c] FROM t"#),
Some(3),
"all three quote styles parse as identifier projections under Lenient",
);
}
#[test]
fn lenient_resolves_the_documented_lexical_conflicts() {
assert_eq!(
lenient_identifier_projection_count(r#"SELECT "x" FROM t"#),
Some(1),
"`\"x\"` is a quoted identifier, not a string",
);
assert!(
parse_with("SELECT $1", crate::ParseConfig::new(Lenient)).is_ok(),
"`$1` lexes as a positional parameter under Lenient",
);
let parsed = parse_with("SELECT a[1] FROM t", crate::ParseConfig::new(Lenient))
.expect("parses under Lenient");
let Statement::Query { query, .. } = &parsed.statements()[0] else {
panic!("expected a query statement");
};
let SetExpr::Select { select, .. } = &query.body else {
panic!("expected a select body");
};
assert!(
matches!(
select.projection.as_slice(),
[SelectItem::Expr {
expr: Expr::Column { .. },
alias: Some(_),
..
}]
),
"`a[1]` is column `a` aliased by the bracket identifier `[1]`, not a subscript: {:?}",
select.projection,
);
}
#[test]
fn lenient_accepts_a_spread_of_additive_permissive_forms() {
for sql in [
"SELECT a && b FROM t", "SELECT a FROM t LIMIT 5, 10", "SELECT `c` FROM t", "SELECT a # c\nFROM t", "SELECT $tag$body$tag$ FROM t", ] {
assert!(
parse_with(sql, crate::ParseConfig::new(Lenient)).is_ok(),
"Lenient accepts {sql:?}"
);
assert!(
parse_with(sql, crate::ParseConfig::new(Ansi)).is_err(),
"strict ANSI still rejects {sql:?} (no regression)",
);
}
}
#[test]
fn strict_ansi_still_rejects_non_ansi_quote_styles() {
for sql in [r#"SELECT `x` FROM t"#, "SELECT [x] FROM t"] {
assert!(
parse_with(sql, crate::ParseConfig::new(Ansi)).is_err(),
"ANSI rejects {sql:?}"
);
}
}
}