Skip to content

Commit 6439120

Browse files
committed
Don't lose precision when parsing decimal fractions
The SQL standard requires that numeric literals with a decimal point, like 1.23, are represented exactly, up to some precision. That means that parsing these literals into f64s is invalid, as it is impossible to represent many decimal numbers exactly in binary floating point (for example, 0.3). This commit instead parses these literals into a big decimal type that is capable of representing every decimal fraction exactly. Downstream consumers can lower this representation into something more efficient but less precise (like the old f64) if desired.
1 parent 247cf66 commit 6439120

File tree

4 files changed

+26
-8
lines changed

4 files changed

+26
-8
lines changed

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ name = "sqlparser"
1919
path = "src/lib.rs"
2020

2121
[dependencies]
22+
bigdecimal = "0.1.0"
2223
log = "0.4.5"
23-
ordered-float = "1.0.2"
2424

2525
[dev-dependencies]
2626
simple_logger = "1.0.1"

src/ast/value.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,16 @@
1010
// See the License for the specific language governing permissions and
1111
// limitations under the License.
1212

13-
use ordered_float::OrderedFloat;
13+
use bigdecimal::BigDecimal;
1414
use std::fmt;
1515

1616
/// Primitive SQL values such as number and string
1717
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1818
pub enum Value {
1919
/// Unsigned integer value
2020
Long(u64),
21-
/// Unsigned floating point value
22-
Double(OrderedFloat<f64>),
21+
/// Unsigned decimal fraction
22+
Decimal(BigDecimal),
2323
/// 'string value'
2424
SingleQuotedString(String),
2525
/// N'string value'
@@ -61,7 +61,7 @@ impl fmt::Display for Value {
6161
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
6262
match self {
6363
Value::Long(v) => write!(f, "{}", v),
64-
Value::Double(v) => write!(f, "{}", v),
64+
Value::Decimal(v) => write!(f, "{}", v),
6565
Value::SingleQuotedString(v) => write!(f, "'{}'", escape_single_quote_string(v)),
6666
Value::NationalStringLiteral(v) => write!(f, "N'{}'", v),
6767
Value::HexStringLiteral(v) => write!(f, "X'{}'", v),

src/parser.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
//! SQL Parser
1414
15+
use bigdecimal::BigDecimal;
1516
use log::debug;
1617

1718
use super::ast::*;
@@ -1236,9 +1237,9 @@ impl Parser {
12361237
return parser_err!(format!("No value parser for keyword {}", k.keyword));
12371238
}
12381239
},
1239-
Token::Number(ref n) if n.contains('.') => match n.parse::<f64>() {
1240-
Ok(n) => Ok(Value::Double(n.into())),
1241-
Err(e) => parser_err!(format!("Could not parse '{}' as f64: {}", n, e)),
1240+
Token::Number(ref n) if n.contains('.') => match n.parse::<BigDecimal>() {
1241+
Ok(n) => Ok(Value::Decimal(n)),
1242+
Err(e) => parser_err!(format!("Could not parse '{}' as decimal: {}", n, e)),
12421243
},
12431244
Token::Number(ref n) => match n.parse::<u64>() {
12441245
Ok(n) => Ok(Value::Long(n)),

tests/sqlparser_common.rs

+17
Original file line numberDiff line numberDiff line change
@@ -1178,6 +1178,23 @@ fn parse_aggregate_with_group_by() {
11781178
//TODO: assertions
11791179
}
11801180

1181+
#[test]
1182+
fn parse_literal_decimal() {
1183+
// These numbers were explicitly chosen to not roundtrip if represented as
1184+
// f64s (i.e., as 64-bit binary floating point numbers).
1185+
let sql = "SELECT 0.300000000000000004, 9007199254740993.0";
1186+
let select = verified_only_select(sql);
1187+
assert_eq!(2, select.projection.len());
1188+
assert_eq!(
1189+
&Expr::Value(Value::Decimal("0.300000000000000004".parse().unwrap())),
1190+
expr_from_projection(&select.projection[0]),
1191+
);
1192+
assert_eq!(
1193+
&Expr::Value(Value::Decimal("9007199254740993.0".parse().unwrap())),
1194+
expr_from_projection(&select.projection[1]),
1195+
)
1196+
}
1197+
11811198
#[test]
11821199
fn parse_literal_string() {
11831200
let sql = "SELECT 'one', N'national string', X'deadBEEF'";

0 commit comments

Comments
 (0)