diff --git a/sqlx-sqlite/src/types/mod.rs b/sqlx-sqlite/src/types/mod.rs index 907a1de089..fdc79d7fcb 100644 --- a/sqlx-sqlite/src/types/mod.rs +++ b/sqlx-sqlite/src/types/mod.rs @@ -79,6 +79,57 @@ //! In addition, `Option` is supported where `T` implements `Type`. An `Option` represents //! a potentially `NULL` value from SQLite. //! +//! # Non-feature: `NUMERIC` / `rust_decimal` / `bigdecimal` Support +//! Support for mapping `rust_decimal::Decimal` and `bigdecimal::BigDecimal` to SQLite has been +//! deliberately omitted because SQLite does not have native support for high- +//! or arbitrary-precision decimal arithmetic, and to pretend so otherwise would be a +//! significant misstep in API design. +//! +//! The in-tree [`decimal.c`] extension is unfortunately not included in the [amalgamation], +//! which is used to build the bundled version of SQLite3 for `libsqlite3-sys` (which we have +//! enabled by default for the simpler setup experience). +//! +//! The `NUMERIC` type affinity, while seemingly designed for storing decimal values, +//! stores non-integer real numbers as double-precision IEEE-754 floating point, +//! i.e. `REAL` in SQLite, `f64` in Rust, `double` in C/C++, etc. +//! +//! [Datatypes in SQLite: Type Affinity][type-affinity] (accessed 2023/11/20): +//! +//! > A column with NUMERIC affinity may contain values using all five storage classes. +//! When text data is inserted into a NUMERIC column, the storage class of the text is converted to +//! INTEGER or REAL (in order of preference) if the text is a well-formed integer or real literal, +//! respectively. If the TEXT value is a well-formed integer literal that is too large to fit in a +//! 64-bit signed integer, it is converted to REAL. For conversions between TEXT and REAL storage +//! classes, only the first 15 significant decimal digits of the number are preserved. +//! +//! With the SQLite3 interactive CLI, we can see that a higher-precision value +//! (20 digits in this case) is rounded off: +//! +//! ```text +//! sqlite> CREATE TABLE foo(bar NUMERIC); +//! sqlite> INSERT INTO foo(bar) VALUES('1.2345678901234567890'); +//! sqlite> SELECT * FROM foo; +//! 1.23456789012346 +//! ``` +//! +//! It appears the `TEXT` storage class is only used if the value contains invalid characters +//! or extra whitespace. +//! +//! Thus, the `NUMERIC` type affinity is **unsuitable** for storage of high-precision decimal values +//! and should be **avoided at all costs**. +//! +//! Support for `rust_decimal` and `bigdecimal` would only be a trap because users will naturally +//! want to use the `NUMERIC` type affinity, and might otherwise encounter serious bugs caused by +//! rounding errors that they were deliberately avoiding when they chose an arbitrary-precision type +//! over a floating-point type in the first place. +//! +//! Instead, you should only use a type affinity that SQLite will not attempt to convert implicitly, +//! such as `TEXT` or `BLOB`, and map values to/from SQLite as strings. +//! +//! [`decimal.c`]: https://www.sqlite.org/floatingpoint.html#the_decimal_c_extension +//! [amalgamation]: https://www.sqlite.org/amalgamation.html +//! [type-affinity]: https://www.sqlite.org/datatype3.html#type_affinity +//! pub(crate) use sqlx_core::types::*;