|
| 1 | +--- |
| 2 | +'@objectstack/driver-sql': patch |
| 3 | +--- |
| 4 | + |
| 5 | +fix(driver-sql): `sum` / `avg` accumulate in double on PostgreSQL and MySQL, as they do on SQLite and on the engine's rows path |
| 6 | + |
| 7 | +Clause-②: no |
| 8 | + |
| 9 | +`SqlDriver.aggregate` let PostgreSQL (`numeric`) and MySQL (`DECIMAL`) add exact decimals, |
| 10 | +while SQLite and the engine's rows path add JS doubles. So a `number` column holding `0.1` |
| 11 | +and `0.2` summed to `0.3` on the PostgreSQL and MySQL native paths and to |
| 12 | +`0.30000000000000004` everywhere else, and `having { s: { $eq: 0.3 } }` kept the group on |
| 13 | +those two faces only. `avg` over an integer column diverged too: MySQL rounds a decimal |
| 14 | +average to 4 places (`avg` of 1, 2, 2 answered `1.6667`), and PostgreSQL's `numeric` |
| 15 | +average rounds to 16 places before the answer becomes a double (`11 / 9` answered |
| 16 | +`1.2222222222222222`, where every other face answers `1.2222222222222223`). |
| 17 | + |
| 18 | +**The precision policy, applied to the arithmetic.** The policy already stated for the |
| 19 | +answer's type (one JS double on every dialect, the loss beyond a double's precision declared) |
| 20 | +now also decides how the answer is computed: |
| 21 | + |
| 22 | +- `avg` accumulates in double on PostgreSQL and MySQL, over every declared numeric or |
| 23 | + boolean column. |
| 24 | +- `sum` accumulates in double over a column that holds fractions: `number`, `currency`, |
| 25 | + `percent`, `slider`, `progress`, `summary`, and the driver's `float` alias. |
| 26 | +- `sum` over an integer-valued column (`rating`, the `integer` / `int` aliases, a boolean) |
| 27 | + keeps the database's exact integer total, rounded once to the double. |
| 28 | +- `count`, `count_distinct`, `min` and `max` are unchanged. SQLite is unchanged. |
| 29 | + |
| 30 | +Each value added is the column's text parsed as a double: the value the SQL client hands |
| 31 | +`find()`, and so the value the rows path adds. For the exact-decimal columns this equals a |
| 32 | +plain cast. For a binary `real` / `FLOAT` column, which a table created before the |
| 33 | +exact-decimal columns still has, a plain cast would add the widened binary value |
| 34 | +(`0.30000000447034836` for `0.1 + 0.2`). MySQL's `CAST(… AS DOUBLE)` needs MySQL 8.0.17 or |
| 35 | +later. |
| 36 | + |
| 37 | +Route chosen: (a), accumulate in double on the native faces. The other route, (b), was to make |
| 38 | +the rows path add exact decimals and round once. It was rejected because SQLite's native `sum` |
| 39 | +adds the stored doubles (`0.30000000000000004`), so the rows path would then disagree with SQLite |
| 40 | +for exactly `0.1 + 0.2`. |
| 41 | + |
| 42 | +**Residual, stated.** Double sums are added in row order, one after another, as the rows path |
| 43 | +adds them. SQLite 3.43 and later adds with compensated summation. So for a group of three or |
| 44 | +more fractions, SQLite's native answer can still differ from every other face in the last |
| 45 | +place (`0.1 + 0.2 + 0.3`: SQLite `0.6`, every other face `0.6000000000000001`). This was |
| 46 | +already true of SQLite's two paths before this change. Two addends cannot differ. |
| 47 | + |
| 48 | +A consumer that compared `sum` / `avg` over a fractional column with a decimal literal on |
| 49 | +PostgreSQL or MySQL (`$eq: 0.3`) now gets the answer SQLite and the rows path already gave: |
| 50 | +compare with a range, or with the double the arithmetic produces. |
0 commit comments