Repository navigation
fix(sqlite): treat % by a fractional literal under 1 as nullable - #124
fengqiaoqu wants to merge 1 commit into
Conversation
SQLite casts both operands of % to INTEGER before the remainder, so a literal such as 0.5 truncates to 0 and the result is NULL on every row. The engine shared / and %'s non-zero literal rule, which is correct for division but not for modulo. Fixes colinhacks#122
|
@fengqiaoqu is attempting to deploy a commit to the pullfrog Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
ℹ️ No issues with the engine rule — one docs wording nit inline.
Reviewed changes
Reviewed the whole PR: the SQLite engine's rule for the / and % divisor, its tests, the rebuilt artifact and the docs.
- Divisor rule split by operator: in
type_of,%now treats a literal divisor as non-zero only when it truncates to a non-zero integer./keeps the oldv != 0.0check. - Tests: a new engine unit test, plus the oracle case
sqlite_modulo_divisorwith six statements that cover% 0.5,% 0.99,% 1e-3,% 2.5,% 2and/ 0.5. - Artifact:
packages/sqlite/engine.wasmis rebuilt.tests/fingerprint.test.tspasses for SQLite on this head, so the artifact matches its sources. - Docs: the Expressions paragraph in
sqlite.mdxnow states the%case.
I checked the rule against SQLite itself using Node 24's node:sqlite. qty % 0.5, % 0.99, % 1e-3, % -0.5 and % 0x0 return NULL on every row. % 2.5 returns a non-null real, and so does % 1e400, which clamps to the largest int64. / 0.5 is non-null. In each case the engine gives the same answer. A negative literal parses as a neg node, so it already falls through to nullable. The other % sites, nulls_with and the parameter context at L2008, don't depend on what the divisor's value is.
claude-opus-5-5 | 𝕏
| **Expressions** | ||
|
|
||
| Reylo's table of SQLite's functions says which are non-null over non-null inputs: `length`, `coalesce`, `||` and arithmetic are; division by a column is not, since dividing by zero returns `NULL`. Arithmetic on a `REAL` with no answer is the one exception ([Limits](#limits)). | ||
| Reylo's table of SQLite's functions says which are non-null over non-null inputs: `length`, `coalesce`, `||` and arithmetic are; division by a column is not, since dividing by zero returns `NULL`. `%` by a fractional literal under 1 is not either: SQLite casts both operands to INTEGER first, so `0.5` truncates to `0` and the remainder is `NULL` on every row. Arithmetic on a `REAL` with no answer is the one exception ([Limits](#limits)). |
There was a problem hiding this comment.
The new sentence starts with inline code (`%` by a fractional literal…). AGENTS.md's writing conventions say docs prose never opens a sentence with inline code. Rewording it to start with "Nor is" keeps the meaning:
| Reylo's table of SQLite's functions says which are non-null over non-null inputs: `length`, `coalesce`, `||` and arithmetic are; division by a column is not, since dividing by zero returns `NULL`. `%` by a fractional literal under 1 is not either: SQLite casts both operands to INTEGER first, so `0.5` truncates to `0` and the remainder is `NULL` on every row. Arithmetic on a `REAL` with no answer is the one exception ([Limits](#limits)). | |
| Reylo's table of SQLite's functions says which are non-null over non-null inputs: `length`, `coalesce`, `||` and arithmetic are; division by a column is not, since dividing by zero returns `NULL`. Nor is `%` by a fractional literal under 1: SQLite casts both operands to INTEGER first, so `0.5` truncates to `0` and the remainder is `NULL` on every row. Arithmetic on a `REAL` with no answer is the one exception ([Limits](#limits)). |

Fixes #122.
SQLite casts both operands of
%to INTEGER before the remainder, so a literal such as0.5truncates to0and every row isNULL./and%shared one non-zero literal rule; that is right for/and wrong for%.select qty % 0.5 as r from itemsr: realr: real?select qty % 2.5 as r from itemsr: realr: realselect qty / 0.5 as r from itemsr: realr: realpackages/sqlite/engine.wasmis rebuilt. Oracle casesqlite_modulo_divisorand the engine unit test cover it; the SQLite docs page says so.