Commit 51efbf1
* feat(spec)!: type-gate a text operator over a temporal column
A text operator (`$contains` / `$startsWith` / `$endsWith` / `$icontains` /
`$notContains` / `$like` / `$ilike`) over a column whose DECLARED type is
`date` / `datetime` / `time` answered three ways across the SQL family:
SQLite matched the canonical ISO TEXT the column stores (ADR-0053), live
PostgreSQL 16.13 refused at query time with SQLSTATE 42883 and reached the
caller as a `DATABASE_ERROR` 500, and MySQL was never measured.
The maintainer ruled the cell on 2026-09-05: a text operator over a column
whose DECLARED type is temporal is type-gated exactly like the numeric and
boolean classes, and the SQLite ISO-text match is not a contract. So the
three temporal classes join `NON_TEXT_STORED_VALUE_TYPES`, and every face
that reads that set — `driver-sql`, `driver-turso`'s remote transport and
`service-analytics`' SQL lowerings — compiles the positive operators to the
FALSE constant and `$notContains` to the TRUE constant. Postgres's 500 is
now that declared answer; "records in 2026" is a range query.
`SqlDriver.isNonTextColumn` reads the `dateFields` / `datetimeFields` /
`timeFields` registries `initObjects` and `registerExternalObject` already
fill, through the existing `temporalFieldKind` — no new registry, no new
error code, no new spec export.
Claude-Session: https://claude.ai/code/session_01XTBcV7zZHmokdyQgXjbyEU
Co-authored-by: Claude <noreply@anthropic.com>
* fix(driver-sql): keep $contains working on a MULTI-VALUED temporal column
A `multiple: true` temporal field is stored as a JSON TEXT array, where
`$contains` is the MEMBERSHIP spelling — the one operator #7398 left working
on a JSON column after refusing the equality family there. The temporal limb
of the gate swallowed it, turning a working membership filter into "matches
nothing": the fail-CLOSED shape #7398's own table calls out. Caught by that
suite's live row (`$contains still works there too`, `ext_sprint.milestones`,
a `multiple: true` datetime) and pinned at the predicate too.
The numeric limb has the same carve-out already, spelled at the registry
(`numericFields` is filled `NUMERIC_SCALAR_TYPES.has(type) && !field.multiple`).
The temporal registries carry no such condition because they serve the
read-presentation seam, which DOES apply to a multi-valued column — so the
condition is spelled at `isNonTextColumn`, where the two questions differ,
rather than by narrowing a registry three other seams read.
Claude-Session: https://claude.ai/code/session_01XTBcV7zZHmokdyQgXjbyEU
Co-authored-by: Claude <noreply@anthropic.com>
* chore: changeset for the temporal text-operator declared-type gate
Claude-Session: https://claude.ai/code/session_01XTBcV7zZHmokdyQgXjbyEU
Co-authored-by: Claude <noreply@anthropic.com>
* test(driver-memory): re-anchor the temporal pins on the DECLARED type and name the divergence
The two rows that read as pinning the no-match answer over a declared
`datetime` column pass for a STRING reason, not a type gate: the fixture
stores `new Date('2026-01-01T00:00:00Z')`, the declared field kind
canonicalises it on write to `'2026-01-01T00:00:00.000Z'`, and the comparand
is spelled without the milliseconds — so it is simply not a substring. Their
names and a note now say so.
Measured on the live `find()` path: a declared temporal column stores ISO
TEXT for a `Date` input and a string input alike, so a positive text operator
MATCHES it — the exact complement of the answer the temporal type-gate
declares. Two characterisation rows record that divergence by name so it is
counted rather than hidden behind the millisecond pair, and so the day this
face is brought onto the declared answer they go red and point at themselves.
Pins only: no `driver-memory` production code is touched, and the driver ships
`dist` alone so nothing here reaches a published artifact. The repair is filed
separately against the standing investment freeze.
Claude-Session: https://claude.ai/code/session_01XTBcV7zZHmokdyQgXjbyEU
Co-authored-by: Claude <noreply@anthropic.com>
* docs(objectql): scope the "one thing on every driver" callout to the CASE axis
The callout's closing claim — "running your tests on the in-memory double no
longer returns rows a SQL or MongoDB deployment would not" — is falsified by
this branch for one axis, and by this branch's own measurement rather than by
inference: over a column whose DECLARED type is temporal the SQL family now
answers a declared no-match, while driver-memory canonicalises such a column
to ISO text on write and still matches the substring.
The callout's SUBJECT is the case-sensitivity alignment, and that half stays
true, so the sentence is scoped rather than the callout deleted. The new axis
is named with the card that tracks it, and driver-mongodb is stated as NOT
measured for it in either direction rather than swept into the claim. Why
FILTER_TEXT_CASES does not cover the axis is said in one line: its rows are
keyed on the stored value, the temporal rule on the declared type.
Claude-Session: https://claude.ai/code/session_01XTBcV7zZHmokdyQgXjbyEU
Co-authored-by: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 80aef80 commit 51efbf1
15 files changed
Lines changed: 720 additions & 86 deletions
File tree
- .changeset
- content/docs/protocol/objectql
- packages
- drivers
- driver-memory/src
- driver-sql/src
- driver-turso/src
- lint/src
- services/service-analytics/src
- __tests__
- spec/src/data
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1089 | 1089 | | |
1090 | 1090 | | |
1091 | 1091 | | |
1092 | | - | |
1093 | | - | |
1094 | | - | |
1095 | | - | |
| 1092 | + | |
| 1093 | + | |
| 1094 | + | |
| 1095 | + | |
| 1096 | + | |
| 1097 | + | |
| 1098 | + | |
| 1099 | + | |
| 1100 | + | |
| 1101 | + | |
| 1102 | + | |
| 1103 | + | |
| 1104 | + | |
| 1105 | + | |
| 1106 | + | |
| 1107 | + | |
1096 | 1108 | | |
1097 | 1109 | | |
1098 | 1110 | | |
| |||
Lines changed: 40 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
553 | 553 | | |
554 | 554 | | |
555 | 555 | | |
556 | | - | |
557 | | - | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
558 | 596 | | |
559 | 597 | | |
560 | 598 | | |
| |||
0 commit comments