diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index fed81b60a..bbf7d5804 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -1047,6 +1047,12 @@ pub trait Dialect: Debug + Any { false } + /// Returns true if this dialect supports `$` as a prefix for money literals + /// e.g. `SELECT $123.45` (SQL Server) + fn supports_dollar_as_money_prefix(&self) -> bool { + false + } + /// Does the dialect support with clause in create index statement? /// e.g. `CREATE INDEX idx ON t WITH (key = value, key2)` fn supports_create_index_with_clause(&self) -> bool { diff --git a/src/dialect/mssql.rs b/src/dialect/mssql.rs index 8ad765dd3..980b63d28 100644 --- a/src/dialect/mssql.rs +++ b/src/dialect/mssql.rs @@ -65,6 +65,12 @@ impl Dialect for MsSqlDialect { true } + /// SQL Server supports `$` as a prefix for money literals + /// + fn supports_dollar_as_money_prefix(&self) -> bool { + true + } + fn supports_connect_by(&self) -> bool { true } diff --git a/src/tokenizer.rs b/src/tokenizer.rs index c055db8fe..d9f131f8f 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -1966,6 +1966,19 @@ impl<'a> Tokenizer<'a> { || matches!(ch, '$' if self.dialect.supports_dollar_placeholder()) })); + // If the dialect supports a dollar sign as a money prefix (e.g. SQL Server), + // and the value so far is all digits, check for a decimal part, e.g. `$123.45` + if matches!(chars.peek(), Some('.')) + && self.dialect.supports_dollar_as_money_prefix() + && !value.is_empty() + && value.chars().all(|c| c.is_ascii_digit()) + { + value.push('.'); + chars.next(); + value.push_str(&peeking_take_while(chars, |ch| ch.is_ascii_digit())); + return Ok(Token::Placeholder(format!("${value}"))); + } + // If the dialect does not support dollar-quoted strings, don't look for the end delimiter. if matches!(chars.peek(), Some('$')) && !self.dialect.supports_dollar_placeholder() { chars.next(); diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index e8ed79492..9f7b42eb1 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -2860,3 +2860,38 @@ fn parse_mssql_update_with_output_into() { "UPDATE employees SET salary = salary * 1.1 OUTPUT INSERTED.id, DELETED.salary, INSERTED.salary INTO @changes WHERE department = 'Engineering'", ); } + +#[test] +fn parse_mssql_money_constants() { + ms().verified_only_select("SELECT CEILING($123.45)"); + + let select = ms().verified_only_select("SELECT $123.45"); + assert_eq!( + &Expr::Value(Value::Placeholder("$123.45".to_string()).with_empty_span()), + expr_from_projection(only(&select.projection)), + ); + + let select = ms().verified_only_select("SELECT $0.99"); + assert_eq!( + &Expr::Value(Value::Placeholder("$0.99".to_string()).with_empty_span()), + expr_from_projection(only(&select.projection)), + ); + + let select = ms().verified_only_select("SELECT $0.0"); + assert_eq!( + &Expr::Value(Value::Placeholder("$0.0".to_string()).with_empty_span()), + expr_from_projection(only(&select.projection)), + ); + + let select = ms().verified_only_select("SELECT $123"); + assert_eq!( + &Expr::Value(Value::Placeholder("$123".to_string()).with_empty_span()), + expr_from_projection(only(&select.projection)), + ); + + let select = ms().verified_only_select("SELECT $0"); + assert_eq!( + &Expr::Value(Value::Placeholder("$0".to_string()).with_empty_span()), + expr_from_projection(only(&select.projection)), + ); +}