|
| 1 | +--- |
| 2 | +"@objectstack/objectql": patch |
| 3 | +--- |
| 4 | + |
| 5 | +fix(objectql): a cleared number, boolean, date, datetime or time field stores `null`, on every backend (#20308) |
| 6 | + |
| 7 | +`patch` — a bug fix in a released package. No exported symbol, no spec key and |
| 8 | +no API signature changes. |
| 9 | + |
| 10 | +## What was wrong |
| 11 | + |
| 12 | +The record validator reads a blank string — `''`, or whitespace only — as |
| 13 | +"missing", and returns before any type check. Nothing rewrote the value, so the |
| 14 | +driver received the blank exactly as sent. The same clear of one field therefore |
| 15 | +had three outcomes: |
| 16 | + |
| 17 | +- **memory and SQLite** stored `''` in a number, currency, percent, rating, |
| 18 | + slider, progress, summary, boolean, toggle, date, datetime or time column, on |
| 19 | + every write door (create, update, batch, `createMany`, `updateMany`). On SQLite |
| 20 | + a stored `''` on a boolean then read back as `false`. |
| 21 | +- **PostgreSQL** refused the statement (`invalid input syntax for type |
| 22 | + numeric` / `boolean` / `date`), which REST answered as `500 DATABASE_ERROR` — |
| 23 | + or as a failed row with `INTERNAL_ERROR` on the batch doors. |
| 24 | + |
| 25 | +objectui's edit form sends a cleared date, datetime or time box as `''`, so this |
| 26 | +is the ordinary "clear the field and save" gesture. |
| 27 | + |
| 28 | +Separately, `progress` and `summary` had no type check at all: a non-numeric |
| 29 | +string such as `'abc'` was stored verbatim on memory and SQLite. |
| 30 | + |
| 31 | +## What changes |
| 32 | + |
| 33 | +- **The write door reads a blank on a non-string-typed column as `null`.** Every |
| 34 | + field whose declared type is in the spec's `NON_TEXT_STORED_VALUE_TYPES` (the |
| 35 | + numeric types including `progress` and `summary`, `boolean`, `toggle`, |
| 36 | + `date`, `datetime`, `time`) has a blank string replaced by `null`. It happens |
| 37 | + at the start of `ObjectQL.insert()` and `ObjectQL.update()`, before the |
| 38 | + middleware, the hooks, the defaults and validation read the payload, and at |
| 39 | + the same point in `ObjectQL.validate()` (the dry run). Every REST, batch and |
| 40 | + import door writes through those methods. The caller's own objects are never |
| 41 | + mutated. |
| 42 | +- **What that means for a write:** the column stores `null` on every backend, |
| 43 | + and PostgreSQL no longer refuses the request. A blank on a `required` field is |
| 44 | + refused with `required`, exactly as `null` is. On create, a blank takes the |
| 45 | + field's `defaultValue` exactly as `null` does. |
| 46 | +- **String-stored columns are untouched.** A text, lookup or select `''` is still |
| 47 | + stored as `''`. |
| 48 | +- **`progress` and `summary` join the numeric type check.** A non-numeric |
| 49 | + string on either is refused with `invalid_number`, as on `number`. No `min`, |
| 50 | + `max` or `scale` is newly enforced on them. |
| 51 | +- **One consequence for roll-ups.** A `summary` whose `summaryOperations` takes |
| 52 | + `min` or `max` over a `date`, `datetime` or `time` field writes a date string |
| 53 | + into the numeric summary. PostgreSQL already refused that recompute |
| 54 | + (`ERR_SUMMARY_RECOMPUTE`). Memory and SQLite now refuse it the same way, |
| 55 | + instead of storing the string. |
| 56 | + |
| 57 | +## Rows already stored |
| 58 | + |
| 59 | +This fixes new writes only. Rows written earlier on SQLite (and on memory, |
| 60 | +MongoDB or libSQL) may still hold `''` in such a column; on SQLite a boolean |
| 61 | +holding `''` reads back as `false`, and one holding whitespace as `true`. |
| 62 | +PostgreSQL never stored one. To repair a |
| 63 | +SQLite table, run this once per non-string-typed column (a field of one of the |
| 64 | +types listed above): |
| 65 | + |
| 66 | +```sql |
| 67 | +UPDATE "<object>" SET "<field>" = NULL |
| 68 | + WHERE typeof("<field>") = 'text' AND trim("<field>", ' ' || char(9) || char(10) || char(13)) = ''; |
| 69 | +``` |
0 commit comments