Repository navigation
Commit d7d5b4f
fix(service-analytics): the ObjectQL face echoes an offset with no limit as a statement the dialect runs (#21440)
Fixes #21365
Clause-②: no
## What this changes
This is the remainder of #21365 after PR #21399 (`6d67ad5ec`, which read
`Part of`). That PR narrowed the window contract and made the native
face render an offset-only window with the dialect's no-limit spelling.
The ObjectQL face's echo still wrote its own window.
`ObjectQLStrategy.generateSql`
(`packages/services/service-analytics/src/strategies/objectql-strategy.ts`)
wrote `LIMIT n` when a limit was set, then `OFFSET n` when an offset
was. An `offset` with no `limit` therefore echoed a bare `OFFSET`, and
SQLite's grammar has no bare `OFFSET`. Those two lines are now one call:
- `windowClauseSql(query.limit, query.offset, sqlDialectFor(ctx,
tableName))`
- `windowClauseSql` is the function the native face runs, already
exported from `native-sql-strategy.ts`. That file is not edited, and
there is no second spelling table.
The ObjectQL face's echoed `sql` and the `POST /api/v1/analytics/sql`
body (both are `generateSql`) now carry the same window clause the
native face executes on the same driver.
## Measured at the real route
The harness is a scratch copy of
`packages/runtime/src/analytics-query-window-validity.test.ts`. It uses
the real `dispatcher-plugin` mount over `AnalyticsServicePlugin` and a
real `ObjectQL` engine with `SqlDriver` (better-sqlite3). It boots the
default composition and one narrowed to the engine aggregate (the
ObjectQL face). The query is `order: { note: 'asc' }, offset: 1`, with
no limit. Each echoed statement was then run on the same SQLite database
through knex, to read SQLite's own diagnostic. The harness was deleted
and never committed.
| | `main` `bdd3654f2` | this branch |
|:--|:--|:--|
| ObjectQL face, `/query` rows | x y z | x y z |
| ObjectQL face, echoed `sql` = `/sql` body | `… ORDER BY "note" ASC
OFFSET 1` | `… ORDER BY "note" ASC LIMIT -1 OFFSET 1` |
| that statement on SQLite | `near "OFFSET": syntax error` | x y z |
| native face, executed = echoed = `/sql` | `… ASC LIMIT -1 OFFSET 1`, x
y z | unchanged |
| CONTROL `limit: 2, offset: 1`, ObjectQL echo | `… LIMIT 2 OFFSET 1`,
runs | unchanged |
On this query the ObjectQL echo now equals, byte for byte, the statement
the native face runs on SQLite.
## The dialect source (dispatch, Zone 2 item 2)
`generateSql` already has the strategy context in scope, and it already
reads `sqlDialectFor(ctx, tableName)` for its read-scope compile. That
is the same `sqlDialect` hook, read the same way, that
`NativeSQLStrategy.generateSql` uses with `sqlDialectFor(ctx,
this.extractObjectName(cube))`. `tableName` here is
`this.extractObjectName(cube)` too. `AnalyticsServicePlugin` wires that
hook from the data engine's driver whatever the query capabilities are,
so the narrowed composition above reads `sqlite`. No new hook.
## Pin
`packages/services/service-analytics/src/__tests__/objectql-echo-offset-only-window.test.ts`
has three tests per cell:
- **The card's row.** The ObjectQL face runs no raw statement and does
run the engine aggregate. It answers x y z. Its echo ends with the
dialect's window. `generateSql` (the `/analytics/sql` body) equals the
echo, with no params. From `ORDER BY` on, the echo equals the statement
the native face executes on the same driver. Run through the engine's
raw-SQL bridge, the echo answers the face's rows.
- **The `unknown` arm.** An `ObjectQLStrategy` whose context wires no
`sqlDialect` hook echoes `LIMIT 9223372036854775807 OFFSET 1`, and that
statement runs.
- **CONTROL.** `limit: 2, offset: 1` keeps its bytes, and the echo runs.
The cells:
- **sqlite**: executed on every run.
- **live postgres**: executed where `OS_TEST_POSTGRES_URL` is set, and a
named skip otherwise. No CI step provisions that variable for this
package, so CI runs only the SQLite cell. I ran it locally against
PostgreSQL 16.14. There the echo keeps `OFFSET 1` alone, byte-identical
to before. The comparison with the native statement starts at `ORDER BY`
because on PostgreSQL the native face also casts the summed column.
**Ablation** at committed `2cfcc44ee`, through `node
scripts/ablation-replace.mjs`. The anchor hit once, and the blob changed
`9ed31b50c50b` to `e8b910c3c13f`. The mutation restored the two old
window lines. The pin reads `src/` with no `dist/` leg (objectql is
aliased to source, and the strategy is imported from `src`). Result,
with live PostgreSQL: **3 failed, 3 passed**.
- Red: the SQLite card row (the echo ends with a bare `OFFSET 1`), and
the `unknown` arm on both engines.
- Green: both controls, and the PostgreSQL card row, whose bytes this
change does not move.
I predicted that direction before the run. Restore was proven: the blob
after restore equals HEAD `9ed31b50c50b`, and `git diff HEAD` is empty.
## Tests and gates
All of these ran at head `b9456bff3`, which is this branch with
`origin/main` `8b123c0ae` merged in. That merge brought PR #21424's
`native-sql-strategy.ts` change. `windowClauseSql` is unchanged by it.
- `pnpm --filter @objectstack/service-analytics test`, with
`OS_TEST_POSTGRES_URL` set to a local PostgreSQL 16.14: **Test Files 170
passed (170), Tests 3957 passed (3957)**. `os-verify-lock` printed
`VERDICT command-exit 0`.
- `pnpm --filter @objectstack/service-analytics typecheck`: `VERDICT
command-exit 0`. `tsc --noEmit --listFiles` lists the new pin once. The
package `tsconfig` includes `src` and does not exclude tests.
- Gates: `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` (no paths) derived 63 commands.
All 63 ran at `b9456bff3`. 62 exited 0 on the first run. `pnpm
check:dual-build-cjs-loads` first answered `PREREQUISITE NOT MET` (exit
3), because the workspace was not fully built. After a full `turbo run
build` (72 tasks, exit 0), it exited 0. `--ran` reconciles **63 derived,
63 run, 0 NOT-MEASURED, 0 UNRUN**. The list includes `check:nul-bytes`,
`check:changeset-gate-self-tests`, `check:adr-0087-registration`,
`check:changeset-no-major` and `check:test-source-alias`. No gate
touched `AGENTS.md`, and the tree stayed clean.
- eslint: `eslint --no-inline-config --format json` on the 2 touched
`.ts` files read 2 files, 0 errors, 0 warnings, none ignored.
`eslint.config.mjs` enables no type-aware linting (no
`parserOptions.project`), so an untouched file's verdict cannot move.
The repo-wide `pnpm lint` is left to CI.
## Docs
I grepped `content/docs/**` outside `releases/` for the analytics echo,
`/analytics/sql` and window rendering. `api/data-api.mdx` describes
`/analytics/sql` as a dry run returning `{ sql, params }`, and
`api/client-sdk.mdx` shows `client.analytics.explain`. Neither says how
a window renders. No sentence is made false, so there is no docs edit.
## Acceptance notes
- **The bucketed echo is a separate truth (dispatch, Zone 2 item 3), not
fixed here.** A month-bucketed dimension echoes `date_trunc('month',
closed_on)`. That holds on both faces, and in the default composition
too, because the native face declines granularity. SQLite refuses it
with `no such function: date_trunc`, measured on this branch after the
window fix. On `main` the same statement failed earlier, at the bare
`OFFSET`. The driver runs `strftime('%Y-%m', …)` there (`driver-sql`
`sql-driver.ts`). The comment over `dimExpr` says the echo renders "the
SQL shape the driver's own bucketing implements", and on SQLite that is
not so. This is `dimExpr`, not the two window lines, so it is reported
to the seat rather than fixed here.
- **`mysql`.** The window cell is `windowClauseSql`'s own (`LIMIT
18446744073709551615`). It is NOT MEASURED on either face here, because
no MySQL server is available. That is the same declared skip PR #21399
carries.
- **Unknown dialect on PostgreSQL.** A host whose `sqlDialect` hook
names no dialect now echoes `LIMIT 9223372036854775807 OFFSET n` where
it echoed `OFFSET n`. The native face already runs that statement for
such a host. It answers the same rows on PostgreSQL and SQLite, as the
pin's `unknown` arm measures.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent b206403 commit d7d5b4f
3 files changed
Lines changed: 263 additions & 3 deletions
File tree
- .changeset
- packages/services/service-analytics/src
- __tests__
- strategies
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
Lines changed: 240 additions & 0 deletions
| 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 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
Lines changed: 10 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
51 | | - | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
52 | 54 | | |
53 | 55 | | |
54 | 56 | | |
| |||
673 | 675 | | |
674 | 676 | | |
675 | 677 | | |
676 | | - | |
677 | | - | |
| 678 | + | |
| 679 | + | |
| 680 | + | |
| 681 | + | |
| 682 | + | |
| 683 | + | |
| 684 | + | |
678 | 685 | | |
679 | 686 | | |
680 | 687 | | |
| |||
0 commit comments