Skip to content

Commit 7c949e9

Browse files
committed
Postgres: enhance NUMERIC/DECIMAL parsing to support negative scale values
1 parent c1648e7 commit 7c949e9

File tree

2 files changed

+104
-3
lines changed

2 files changed

+104
-3
lines changed

src/ast/data_type.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -962,7 +962,7 @@ pub enum ExactNumberInfo {
962962
/// Only precision information, e.g. `DECIMAL(10)`
963963
Precision(u64),
964964
/// Precision and scale information, e.g. `DECIMAL(10,2)`
965-
PrecisionAndScale(u64, u64),
965+
PrecisionAndScale(u64, i64),
966966
}
967967

968968
impl fmt::Display for ExactNumberInfo {

src/parser/mod.rs

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11213,7 +11213,7 @@ impl<'a> Parser<'a> {
1121311213
if self.consume_token(&Token::LParen) {
1121411214
let precision = self.parse_literal_uint()?;
1121511215
let scale = if self.consume_token(&Token::Comma) {
11216-
Some(self.parse_literal_uint()?)
11216+
Some(self.parse_scale_value()?)
1121711217
} else {
1121811218
None
1121911219
};
@@ -11229,6 +11229,38 @@ impl<'a> Parser<'a> {
1122911229
}
1123011230
}
1123111231

11232+
/// Parse a scale value for NUMERIC/DECIMAL data types.
11233+
///
11234+
/// Supports positive, negative, and explicitly positive (with `+`) scale values.
11235+
/// Negative scale values are particularly useful for PostgreSQL, where they indicate
11236+
/// rounding to the left of the decimal point. For example:
11237+
/// - `NUMERIC(5, 2)` stores up to 5 digits with 2 decimal places (e.g., 123.45)
11238+
/// - `NUMERIC(5, -2)` stores up to 5 digits rounded to hundreds (e.g., 12300)
11239+
fn parse_scale_value(&mut self) -> Result<i64, ParserError> {
11240+
let next_token = self.next_token();
11241+
match next_token.token {
11242+
Token::Number(s, _) => Self::parse::<i64>(s, next_token.span.start),
11243+
Token::Minus => {
11244+
let next_token = self.next_token();
11245+
match next_token.token {
11246+
Token::Number(s, _) => {
11247+
let positive_value = Self::parse::<i64>(s, next_token.span.start)?;
11248+
Ok(-positive_value)
11249+
}
11250+
_ => self.expected("number after minus", next_token),
11251+
}
11252+
}
11253+
Token::Plus => {
11254+
let next_token = self.next_token();
11255+
match next_token.token {
11256+
Token::Number(s, _) => Self::parse::<i64>(s, next_token.span.start),
11257+
_ => self.expected("number after plus", next_token),
11258+
}
11259+
}
11260+
_ => self.expected("number", next_token),
11261+
}
11262+
}
11263+
1123211264
pub fn parse_optional_type_modifiers(&mut self) -> Result<Option<Vec<String>>, ParserError> {
1123311265
if self.consume_token(&Token::LParen) {
1123411266
let mut modifiers = Vec::new();
@@ -17069,7 +17101,7 @@ mod tests {
1706917101
use crate::ast::{
1707017102
CharLengthUnits, CharacterLength, DataType, ExactNumberInfo, ObjectName, TimezoneInfo,
1707117103
};
17072-
use crate::dialect::{AnsiDialect, GenericDialect};
17104+
use crate::dialect::{AnsiDialect, GenericDialect, PostgreSqlDialect};
1707317105
use crate::test_utils::TestedDialects;
1707417106

1707517107
macro_rules! test_parse_data_type {
@@ -17319,6 +17351,75 @@ mod tests {
1731917351
"DEC(2,10)",
1732017352
DataType::Dec(ExactNumberInfo::PrecisionAndScale(2, 10))
1732117353
);
17354+
17355+
// Test negative scale values (PostgreSQL supports scale from -1000 to 1000)
17356+
test_parse_data_type!(
17357+
dialect,
17358+
"NUMERIC(10,-2)",
17359+
DataType::Numeric(ExactNumberInfo::PrecisionAndScale(10, -2))
17360+
);
17361+
17362+
test_parse_data_type!(
17363+
dialect,
17364+
"DECIMAL(1000,-10)",
17365+
DataType::Decimal(ExactNumberInfo::PrecisionAndScale(1000, -10))
17366+
);
17367+
17368+
test_parse_data_type!(
17369+
dialect,
17370+
"DEC(5,-1000)",
17371+
DataType::Dec(ExactNumberInfo::PrecisionAndScale(5, -1000))
17372+
);
17373+
17374+
// Test positive scale with explicit plus sign
17375+
dialect.run_parser_method("NUMERIC(10,+5)", |parser| {
17376+
let data_type = parser.parse_data_type().unwrap();
17377+
assert_eq!(
17378+
DataType::Numeric(ExactNumberInfo::PrecisionAndScale(10, 5)),
17379+
data_type
17380+
);
17381+
// Note: Explicit '+' sign is not preserved in output, which is correct
17382+
assert_eq!("NUMERIC(10,5)", data_type.to_string());
17383+
});
17384+
}
17385+
17386+
#[test]
17387+
fn test_numeric_negative_scale() {
17388+
let dialect = TestedDialects::new(vec![
17389+
Box::new(PostgreSqlDialect {}),
17390+
Box::new(GenericDialect {}),
17391+
]);
17392+
17393+
// Test NUMERIC with negative scale
17394+
test_parse_data_type!(
17395+
dialect,
17396+
"NUMERIC(10,-5)",
17397+
DataType::Numeric(ExactNumberInfo::PrecisionAndScale(10, -5))
17398+
);
17399+
17400+
// Test DECIMAL with negative scale
17401+
test_parse_data_type!(
17402+
dialect,
17403+
"DECIMAL(20,-10)",
17404+
DataType::Decimal(ExactNumberInfo::PrecisionAndScale(20, -10))
17405+
);
17406+
17407+
// Test DEC with negative scale
17408+
test_parse_data_type!(
17409+
dialect,
17410+
"DEC(5,-2)",
17411+
DataType::Dec(ExactNumberInfo::PrecisionAndScale(5, -2))
17412+
);
17413+
17414+
// Test with explicit positive scale (note: +5 parses as 5, so display shows NUMERIC(10,5))
17415+
dialect.run_parser_method("NUMERIC(10,+5)", |parser| {
17416+
let data_type = parser.parse_data_type().unwrap();
17417+
assert_eq!(
17418+
DataType::Numeric(ExactNumberInfo::PrecisionAndScale(10, 5)),
17419+
data_type
17420+
);
17421+
assert_eq!("NUMERIC(10,5)", data_type.to_string());
17422+
});
1732217423
}
1732317424

1732417425
#[test]

0 commit comments

Comments
 (0)