|
| 1 | +--- |
| 2 | +'@objectstack/driver-sql': patch |
| 3 | +--- |
| 4 | + |
| 5 | +fix(driver-sql): `count` / `count_distinct` / `sum` / `avg` answer JS numbers on PostgreSQL and MySQL, as they do on SQLite and on the engine's rows path |
| 6 | + |
| 7 | +Clause-②: no |
| 8 | + |
| 9 | +`SqlDriver.aggregate` handed the SQL client's answer straight through. node-postgres parses |
| 10 | +`bigint` (`count`, and `sum` over an integer column) and `numeric` (`sum` / `avg` over the |
| 11 | +numeric family's exact-decimal column, `avg` over an integer column) to strings, and mysql2 |
| 12 | +does the same for `DECIMAL` (`SUM` / `AVG`). So one grouped query answered |
| 13 | + |
| 14 | + { "n": "2", "total": "500.000000000000000000000000000000" } |
| 15 | + |
| 16 | +on PostgreSQL's native path and `{ "n": 2, "total": 500 }` on SQLite and on the rows path of |
| 17 | +every dialect. The engine's `having` compares values as they |
| 18 | +arrive, so `having { n: { $in: [2] } }` kept no group on PostgreSQL alone, and |
| 19 | +`having { total: { $in: [500, 20] } }` kept no group on PostgreSQL or MySQL, while a string |
| 20 | +comparand such as `{ total: { $lt: 'not-a-date' } }` kept every group there and none anywhere |
| 21 | +else. |
| 22 | + |
| 23 | +Those four functions now answer a JS number on every dialect, through `SqlDriver.aggregate`, |
| 24 | +`engine.aggregate` and `POST /api/v1/data/:object/query`. The presentation is keyed on the |
| 25 | +aggregate function the query asked for; it only rewrites a string, so SQLite's answers are |
| 26 | +byte-identical to before. `min` / `max` are unchanged: they answer a value of the column and |
| 27 | +keep that column's presentation (a declared numeric field was already a number). |
| 28 | +Non-aggregate reads (`find()`, `distinct()`) are unchanged, and no connection-level type |
| 29 | +parser is touched. |
| 30 | + |
| 31 | +**Precision policy.** The answer is one JS number (an IEEE-754 double) on every dialect. A |
| 32 | +`sum` / `avg` whose exact value needs more than a double's 15 to 17 significant digits, or an |
| 33 | +integer total at or above 2^53, is rounded to the nearest double. That is the same bound |
| 34 | +`find()` already puts on a read of the same exact-decimal column, and the bound the rows path |
| 35 | +has always had. A total that fits keeps its exact value (`500`, `30.75`, `0.375`). The answer |
| 36 | +is never a string, including for large totals: an answer whose type depended on its size would |
| 37 | +break the same `having` or chart for exactly those totals. |
| 38 | + |
| 39 | +A consumer that read these values through `Number(...)` gets the same number it computed |
| 40 | +before. A consumer that compared them as strings, or checked `typeof value === 'string'`, |
| 41 | +now receives a number. |
0 commit comments