Skip to content
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

Parse true and false as identifiers in mssql #1510

Merged
merged 9 commits into from
Nov 13, 2024
6 changes: 6 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,12 @@ pub trait Dialect: Debug + Any {
fn supports_top_before_distinct(&self) -> bool {
false
}

/// Returns true if the dialect supports boolean literals (`true` and `false`).
/// For example, in MSSQL these are treated as identifiers rather than boolean literals.
fn supports_boolean_literals(&self) -> bool {
true
}
}

/// This represents the operators for which precedence must be defined
Expand Down
5 changes: 5 additions & 0 deletions src/dialect/mssql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,9 @@ impl Dialect for MsSqlDialect {
fn supports_try_convert(&self) -> bool {
true
}

/// In MSSQL, there is no boolean type, and `true` and `false` are valid column names
fn supports_boolean_literals(&self) -> bool {
false
}
}
14 changes: 11 additions & 3 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1014,7 +1014,11 @@ impl<'a> Parser<'a> {
let next_token = self.next_token();
let expr = match next_token.token {
Token::Word(w) => match w.keyword {
Keyword::TRUE | Keyword::FALSE | Keyword::NULL => {
Keyword::TRUE | Keyword::FALSE if self.dialect.supports_boolean_literals() => {
self.prev_token();
Ok(Expr::Value(self.parse_value()?))
}
Keyword::NULL => {
self.prev_token();
Ok(Expr::Value(self.parse_value()?))
}
Expand Down Expand Up @@ -7571,8 +7575,12 @@ impl<'a> Parser<'a> {
let location = next_token.location;
match next_token.token {
Token::Word(w) => match w.keyword {
Keyword::TRUE => Ok(Value::Boolean(true)),
Keyword::FALSE => Ok(Value::Boolean(false)),
Keyword::TRUE if self.dialect.supports_boolean_literals() => {
Ok(Value::Boolean(true))
}
Keyword::FALSE if self.dialect.supports_boolean_literals() => {
Ok(Value::Boolean(false))
}
Keyword::NULL => Ok(Value::Null),
Keyword::NoKeyword if w.quote_style.is_some() => match w.quote_style {
Some('"') => Ok(Value::DoubleQuotedString(w.value)),
Expand Down
81 changes: 24 additions & 57 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -820,7 +820,7 @@ fn parse_top_level() {
verified_stmt("(SELECT 1)");
verified_stmt("((SELECT 1))");
verified_stmt("VALUES (1)");
verified_stmt("VALUES ROW(1, true, 'a'), ROW(2, false, 'b')");
verified_stmt("VALUES ROW(1, NULL, 'a'), ROW(2, NULL, 'b')");
}

#[test]
Expand Down Expand Up @@ -1499,7 +1499,7 @@ fn parse_is_not_distinct_from() {
#[test]
fn parse_not_precedence() {
// NOT has higher precedence than OR/AND, so the following must parse as (NOT true) OR true
let sql = "NOT true OR true";
let sql = "NOT 1 OR 1";
assert_matches!(
verified_expr(sql),
Expr::BinaryOp {
Expand Down Expand Up @@ -1919,44 +1919,6 @@ fn parse_binary_all() {
);
}

#[test]
fn parse_logical_xor() {
let sql = "SELECT true XOR true, false XOR false, true XOR false, false XOR true";
let select = verified_only_select(sql);
assert_eq!(
SelectItem::UnnamedExpr(Expr::BinaryOp {
left: Box::new(Expr::Value(Value::Boolean(true))),
op: BinaryOperator::Xor,
right: Box::new(Expr::Value(Value::Boolean(true))),
}),
select.projection[0]
);
assert_eq!(
SelectItem::UnnamedExpr(Expr::BinaryOp {
left: Box::new(Expr::Value(Value::Boolean(false))),
op: BinaryOperator::Xor,
right: Box::new(Expr::Value(Value::Boolean(false))),
}),
select.projection[1]
);
assert_eq!(
SelectItem::UnnamedExpr(Expr::BinaryOp {
left: Box::new(Expr::Value(Value::Boolean(true))),
op: BinaryOperator::Xor,
right: Box::new(Expr::Value(Value::Boolean(false))),
}),
select.projection[2]
);
assert_eq!(
SelectItem::UnnamedExpr(Expr::BinaryOp {
left: Box::new(Expr::Value(Value::Boolean(false))),
op: BinaryOperator::Xor,
right: Box::new(Expr::Value(Value::Boolean(true))),
}),
select.projection[3]
);
}

#[test]
fn parse_between() {
fn chk(negated: bool) {
Expand Down Expand Up @@ -4113,14 +4075,14 @@ fn parse_alter_table_alter_column() {
);

match alter_table_op(verified_stmt(&format!(
"{alter_stmt} ALTER COLUMN is_active SET DEFAULT false"
"{alter_stmt} ALTER COLUMN is_active SET DEFAULT 0"
))) {
AlterTableOperation::AlterColumn { column_name, op } => {
assert_eq!("is_active", column_name.to_string());
assert_eq!(
op,
AlterColumnOperation::SetDefault {
value: Expr::Value(Value::Boolean(false))
value: Expr::Value(test_utils::number("0"))
}
);
}
Expand Down Expand Up @@ -6502,7 +6464,7 @@ fn parse_values() {
verified_stmt("SELECT * FROM (VALUES (1), (2), (3))");
verified_stmt("SELECT * FROM (VALUES (1), (2), (3)), (VALUES (1, 2, 3))");
verified_stmt("SELECT * FROM (VALUES (1)) UNION VALUES (1)");
verified_stmt("SELECT * FROM (VALUES ROW(1, true, 'a'), ROW(2, false, 'b')) AS t (a, b, c)");
verified_stmt("SELECT * FROM (VALUES ROW(1, NULL, 'a'), ROW(2, NULL, 'b')) AS t (a, b, c)");
}

#[test]
Expand Down Expand Up @@ -7321,15 +7283,15 @@ fn lateral_derived() {
let lateral_str = if lateral_in { "LATERAL " } else { "" };
let sql = format!(
"SELECT * FROM customer LEFT JOIN {lateral_str}\
(SELECT * FROM order WHERE order.customer = customer.id LIMIT 3) AS order ON true"
(SELECT * FROM orders WHERE orders.customer = customer.id LIMIT 3) AS orders ON 1"
);
let select = verified_only_select(&sql);
let from = only(select.from);
assert_eq!(from.joins.len(), 1);
let join = &from.joins[0];
assert_eq!(
join.join_operator,
JoinOperator::LeftOuter(JoinConstraint::On(Expr::Value(Value::Boolean(true))))
JoinOperator::LeftOuter(JoinConstraint::On(Expr::Value(test_utils::number("1"))))
);
if let TableFactor::Derived {
lateral,
Expand All @@ -7338,10 +7300,10 @@ fn lateral_derived() {
} = join.relation
{
assert_eq!(lateral_in, lateral);
assert_eq!(Ident::new("order"), alias.name);
assert_eq!(Ident::new("orders"), alias.name);
assert_eq!(
subquery.to_string(),
"SELECT * FROM order WHERE order.customer = customer.id LIMIT 3"
"SELECT * FROM orders WHERE orders.customer = customer.id LIMIT 3"
);
} else {
unreachable!()
Expand Down Expand Up @@ -8381,7 +8343,7 @@ fn parse_merge() {
_ => unreachable!(),
};

let sql = "MERGE INTO s.bar AS dest USING newArrivals AS S ON false WHEN NOT MATCHED THEN INSERT VALUES (stg.A, stg.B, stg.C)";
let sql = "MERGE INTO s.bar AS dest USING newArrivals AS S ON (1 > 1) WHEN NOT MATCHED THEN INSERT VALUES (stg.A, stg.B, stg.C)";
verified_stmt(sql);
}

Expand Down Expand Up @@ -11160,13 +11122,11 @@ fn parse_explain_with_option_list() {

#[test]
fn test_create_policy() {
let sql = concat!(
"CREATE POLICY my_policy ON my_table ",
"AS PERMISSIVE FOR SELECT ",
"TO my_role, CURRENT_USER ",
"USING (c0 = 1) ",
"WITH CHECK (true)"
);
let sql: &str = "CREATE POLICY my_policy ON my_table \
AS PERMISSIVE FOR SELECT \
TO my_role, CURRENT_USER \
USING (c0 = 1) \
WITH CHECK (1 = 1)";

match all_dialects().verified_stmt(sql) {
Statement::CreatePolicy {
Expand Down Expand Up @@ -11194,7 +11154,14 @@ fn test_create_policy() {
right: Box::new(Expr::Value(Value::Number("1".parse().unwrap(), false))),
})
);
assert_eq!(with_check, Some(Expr::Value(Value::Boolean(true))));
assert_eq!(
with_check,
Some(Expr::BinaryOp {
left: Box::new(Expr::Value(Value::Number("1".parse().unwrap(), false))),
op: BinaryOperator::Eq,
right: Box::new(Expr::Value(Value::Number("1".parse().unwrap(), false))),
})
);
}
_ => unreachable!(),
}
Expand All @@ -11205,7 +11172,7 @@ fn test_create_policy() {
"AS PERMISSIVE FOR SELECT ",
"TO my_role, CURRENT_USER ",
"USING (c0 IN (SELECT column FROM t0)) ",
"WITH CHECK (true)"
"WITH CHECK (1 = 1)"
));
// omit AS / FOR / TO / USING / WITH CHECK clauses is allowed
all_dialects().verified_stmt("CREATE POLICY my_policy ON my_table");
Expand Down
12 changes: 12 additions & 0 deletions tests/sqlparser_mssql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1031,6 +1031,18 @@ fn parse_create_table_with_identity_column() {
}
}

#[test]
fn parse_true_false_as_identifiers() {
assert_eq!(
ms().verified_expr("true"),
Expr::Identifier(Ident::new("true"))
);
assert_eq!(
ms().verified_expr("false"),
Expr::Identifier(Ident::new("false"))
);
}

fn ms() -> TestedDialects {
TestedDialects::new(vec![Box::new(MsSqlDialect {})])
}
Expand Down
39 changes: 39 additions & 0 deletions tests/sqlparser_mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2817,3 +2817,42 @@ fn test_group_concat() {
mysql_and_generic()
.verified_expr("GROUP_CONCAT(DISTINCT test_score ORDER BY test_score DESC SEPARATOR ' ')");
}

/// The XOR binary operator is only supported in MySQL
#[test]
fn parse_logical_xor() {
let sql = "SELECT true XOR true, false XOR false, true XOR false, false XOR true";
let select = mysql_and_generic().verified_only_select(sql);
assert_eq!(
SelectItem::UnnamedExpr(Expr::BinaryOp {
left: Box::new(Expr::Value(Value::Boolean(true))),
op: BinaryOperator::Xor,
right: Box::new(Expr::Value(Value::Boolean(true))),
}),
select.projection[0]
);
assert_eq!(
SelectItem::UnnamedExpr(Expr::BinaryOp {
left: Box::new(Expr::Value(Value::Boolean(false))),
op: BinaryOperator::Xor,
right: Box::new(Expr::Value(Value::Boolean(false))),
}),
select.projection[1]
);
assert_eq!(
SelectItem::UnnamedExpr(Expr::BinaryOp {
left: Box::new(Expr::Value(Value::Boolean(true))),
op: BinaryOperator::Xor,
right: Box::new(Expr::Value(Value::Boolean(false))),
}),
select.projection[2]
);
assert_eq!(
SelectItem::UnnamedExpr(Expr::BinaryOp {
left: Box::new(Expr::Value(Value::Boolean(false))),
op: BinaryOperator::Xor,
right: Box::new(Expr::Value(Value::Boolean(true))),
}),
select.projection[3]
);
}
Loading